/*
* NeoIRCd: NeoStats Group. Based on Hybird7
* s_user.c: User related functions.
*
* Copyright (C) 2002 by the past and present ircd coders, and others.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
* $Id: s_user.c,v 1.46 2003/03/31 09:03:16 fishwaldo Exp $
*/
#include "stdinc.h"
#include "tools.h"
#include "s_user.h"
#include "channel.h"
#include "channel_mode.h"
#include "class.h"
#include "client.h"
#include "common.h"
#include "fdlist.h"
#include "hash.h"
#include "irc_string.h"
#include "ircd.h"
#include "list.h"
#include "listener.h"
#include "motd.h"
#include "ircd_handler.h"
#include "msg.h"
#include "numeric.h"
#include "s_bsd.h"
#include "s_conf.h"
#include "s_log.h"
#include "s_serv.h"
#include "s_stats.h"
#include "scache.h"
#include "send.h"
#include "supported.h"
#include "whowas.h"
#include "md5.h"
#include "memory.h"
#include "packet.h"
static int valid_hostname(const char* hostname);
static int valid_username(const char* username);
static void report_and_set_user_flags( struct Client *, struct ConfItem * );
static int check_X_line(struct Client *client_p, struct Client *source_p);
void user_welcome(struct Client *source_p);
static int introduce_client(struct Client *client_p, struct Client *source_p,
struct User *user, char *nick);
int oper_up( struct Client *source_p, struct ConfItem *aconf );
/* table of ascii char letters to corresponding bitmask */
struct flag_item
{
int mode;
char letter;
};
static struct flag_item user_modes[] =
{
{FLAGS_SERVICES, 'S'},
{FLAGS_ADMIN, 'A'},
{FLAGS_BOTS, 'b'},
{FLAGS_CCONN, 'c'},
{FLAGS_DEBUG, 'd'},
{FLAGS_FULL, 'f'},
{FLAGS_CALLERID, 'g'},
{FLAGS_INVISIBLE, 'i'},
{FLAGS_SKILL, 'k'},
{FLAGS_LOCOPS, 'l'},
{FLAGS_NCHANGE, 'n'},
{FLAGS_OPER, 'o'},
{FLAGS_REJ, 'R'},
{FLAGS_SERVNOTICE, 's'},
{FLAGS_UNAUTH, 'u'},
{FLAGS_WALLOP, 'w'},
{FLAGS_EXTERNAL, 'X'},
{FLAGS_SPY, 'y'},
{FLAGS_OPERWALL, 'z'},
{FLAGS_HIDDEN, 'x'},
{FLAGS_REGNICK, 'r'},
{FLAGS_SSL, 'Z'},
{0, 0}
};
/* memory is cheap. map 0-255 to equivalent mode */
int user_modes_from_c_to_bitmask[] =
{
/* 0x00 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x0F */
/* 0x10 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x1F */
/* 0x20 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x2F */
/* 0x30 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x3F */
0, /* @ */
FLAGS_ADMIN, /* A */
0, /* B */
0, /* C */
0, /* D */
0, /* E */
0, /* F */
0, /* G */
0, /* H */
0, /* I */
0, /* J */
0, /* K */
0, /* L */
0, /* M */
0, /* N */
0, /* O */
0, /* P */
0, /* Q */
FLAGS_REJ, /* R */
FLAGS_SERVICES, /* S */
0, /* T */
0, /* U */
0, /* V */
0, /* W */
FLAGS_EXTERNAL, /* X */
0, /* Y */
FLAGS_SSL, /* Z 0x5A */
0, 0, 0, 0, 0, /* 0x5F */
/* 0x60 */ 0,
0, /* a */
FLAGS_BOTS, /* b */
FLAGS_CCONN, /* c */
FLAGS_DEBUG, /* d */
0, /* e */
FLAGS_FULL, /* f */
FLAGS_CALLERID, /* g */
0, /* h */
FLAGS_INVISIBLE, /* i */
0, /* j */
FLAGS_SKILL, /* k */
FLAGS_LOCOPS, /* l */
0, /* m */
FLAGS_NCHANGE, /* n */
FLAGS_OPER, /* o */
0, /* p */
0, /* q */
FLAGS_REGNICK, /* r */
FLAGS_SERVNOTICE, /* s */
0, /* t */
FLAGS_UNAUTH, /* u */
0, /* v */
FLAGS_WALLOP, /* w */
FLAGS_HIDDEN, /* x */
FLAGS_SPY, /* y */
FLAGS_OPERWALL, /* z 0x7A */
0,0,0,0,0, /* 0x7B - 0x7F */
/* 0x80 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x9F */
/* 0x90 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0x9F */
/* 0xA0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xAF */
/* 0xB0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xBF */
/* 0xC0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xCF */
/* 0xD0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xDF */
/* 0xE0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* 0xEF */
/* 0xF0 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 /* 0xFF */
};
/*
* show_lusers -
*
* inputs - pointer to client
* output -
* side effects - display to client user counts etc.
*/
int
show_lusers(struct Client *source_p)
{
if(!ConfigServerHide.hide_servers || IsOper(source_p))
sendto_one(source_p, form_str(RPL_LUSERCLIENT), me.name, source_p->name,
(Count.total-Count.invisi), Count.invisi, Count.server);
else
sendto_one(source_p, form_str(RPL_LUSERCLIENT), me.name, source_p->name,
(Count.total-Count.invisi), Count.invisi, 1);
if (Count.oper > 0)
sendto_one(source_p, form_str(RPL_LUSEROP), me.name, source_p->name,
Count.oper);
if (Count.unknown > 0)
sendto_one(source_p, form_str(RPL_LUSERUNKNOWN), me.name, source_p->name,
Count.unknown);
if (Count.chan > 0)
sendto_one(source_p, form_str(RPL_LUSERCHANNELS),
me.name, source_p->name, Count.chan);
if(!ConfigServerHide.hide_servers || IsOper(source_p))
{
sendto_one(source_p, form_str(RPL_LUSERME),
me.name, source_p->name, Count.local, Count.myserver);
sendto_one(source_p, form_str(RPL_LOCALUSERS), me.name, source_p->name,
Count.local, Count.max_loc);
}
else
{
sendto_one(source_p, form_str(RPL_LUSERME),
me.name, source_p->name, Count.total, 0);
sendto_one(source_p, form_str(RPL_LOCALUSERS),
me.name, source_p->name, Count.total, Count.max_tot);
}
sendto_one(source_p, form_str(RPL_GLOBALUSERS), me.name, source_p->name,
Count.total, Count.max_tot);
if(!ConfigServerHide.hide_servers || IsOper(source_p))
sendto_one(source_p, form_str(RPL_STATSCONN), me.name, source_p->name,
MaxConnectionCount, MaxClientCount, Count.totalrestartcount);
if (Count.local > MaxClientCount)
MaxClientCount = Count.local;
if ((Count.local + Count.myserver) > MaxConnectionCount)
MaxConnectionCount = Count.local + Count.myserver;
return 0;
}
/*
* show_isupport
*
* inputs - pointer to client
* output -
* side effects - display to client what we support (for them)
*/
void
show_isupport(struct Client *source_p)
{
char isupportbuffer[512];
ircsprintf(isupportbuffer, FEATURES, FEATURESVALUES);
sendto_one(source_p, form_str(RPL_ISUPPORT), me.name, source_p->name,
isupportbuffer);
ircsprintf(isupportbuffer, FEATURES2, FEATURES2VALUES);
sendto_one(source_p, form_str(RPL_ISUPPORT), me.name, source_p->name,
isupportbuffer);
return;
}
/*
** register_local_user
** This function is called when both NICK and USER messages
** have been accepted for the client, in whatever order. Only
** after this, is the USER message propagated.
**
** NICK's must be propagated at once when received, although
** it would be better to delay them too until full info is
** available. Doing it is not so simple though, would have
** to implement the following:
**
** (actually it has been implemented already for a while) -orabidoo
**
** 1) user telnets in and gives only "NICK foobar" and waits
** 2) another user far away logs in normally with the nick
** "foobar" (quite legal, as this server didn't propagate
** it).
** 3) now this server gets nick "foobar" from outside, but
** has alread the same defined locally. Current server
** would just issue "KILL foobar" to clean out dups. But,
** this is not fair. It should actually request another
** nick from local user or kill him/her...
*/
int
register_local_user(struct Client *client_p, struct Client *source_p,
char *nick, char *username)
{
struct ConfItem* aconf;
struct User* user = source_p->user;
char tmpstr2[IRCD_BUFSIZE];
char ipaddr[HOSTIPLEN];
int status;
dlink_node *ptr;
dlink_node *m;
char *id;
assert(NULL != source_p);
assert(MyConnect(source_p));
assert(source_p->username != username);
if(source_p == NULL)
return -1;
if(!MyConnect(source_p))
return -1;
if(ConfigFileEntry.ping_cookie)
{
if(!IsPingSent(source_p) &&
source_p->localClient->random_ping == 0)
{
source_p->localClient->random_ping = (unsigned long)rand();
sendto_one(source_p,
"PING :%lu",
(unsigned long)source_p->localClient->random_ping);
SetPingSent(source_p);
return -1;
}
if(!(source_p->flags2 & FLAGS2_PING_COOKIE))
{
return -1;
}
}
user->last = CurrentTime;
/* Straight up the maximum rate of flooding... */
source_p->localClient->allow_read = MAX_FLOOD_BURST;
if( ( status = check_client(client_p, source_p, username )) < 0 )
return(CLIENT_EXITED);
if(!valid_hostname(source_p->host))
{
sendto_one(source_p,":%s NOTICE %s :*** Notice -- You have an illegal character in your hostname",
me.name, source_p->name );
strncpy(source_p->host,source_p->localClient->sockhost,HOSTIPLEN+1);
}
ptr = source_p->localClient->confs.head;
aconf = ptr->data;
if (aconf == NULL)
{
exit_client(client_p, source_p, &me, "*** Not Authorized");
return(CLIENT_EXITED);
}
if (!IsGotId(source_p))
{
char *p;
int i = 0;
if (IsNeedIdentd(aconf))
{
ServerStats->is_ref++;
sendto_one(source_p,
":%s NOTICE %s :*** Notice -- You need to install identd to use this server",
me.name, client_p->name);
exit_client(client_p, source_p, &me, "Install identd");
return(CLIENT_EXITED);
}
p = username;
if(!IsNoTilde(aconf))
source_p->username[i++] = '~';
while(*p && i < USERLEN)
{
if(*p != '[')
source_p->username[i++] = *p;
p++;
}
source_p->username[i] = '\0';
}
/* password check */
if (!BadPtr(aconf->passwd) &&
strcmp(source_p->localClient->passwd, aconf->passwd))
{
ServerStats->is_ref++;
sendto_one(source_p, form_str(ERR_PASSWDMISMATCH),
me.name, source_p->name);
exit_client(client_p, source_p, &me, "Bad Password");
return(CLIENT_EXITED);
}
memset(source_p->localClient->passwd,0, sizeof(source_p->localClient->passwd));
/* report if user has &^>= etc. and set flags as needed in source_p */
report_and_set_user_flags(source_p, aconf);
/* Limit clients */
/*
* We want to be able to have servers and F-line clients
* connect, so save room for "buffer" connections.
* Smaller servers may want to decrease this, and it should
* probably be just a percentage of the MAXCLIENTS...
* -Taner
*/
/* Except "F:" clients */
if ( ( (Count.local + 1) >= (GlobalSetOptions.maxclients+MAX_BUFFER)
||
(Count.local +1) >= (GlobalSetOptions.maxclients - 5) )
&&
!(IsExemptLimits(source_p)) )
{
sendto_realops_flags(FLAGS_FULL|FLAGS_REMOTE, L_ALL,
"Too many clients, rejecting %s[%s].",
nick, source_p->host);
ServerStats->is_ref++;
exit_client(client_p, source_p, &me,
"Sorry, server is full - try later");
return(CLIENT_EXITED);
}
/* valid user name check */
if (!valid_username(source_p->username))
{
sendto_realops_flags(FLAGS_REJ|FLAGS_REMOTE, L_ALL,
"Invalid username: %s (%s@%s)",
nick, source_p->username, source_p->host);
ServerStats->is_ref++;
ircsprintf(tmpstr2, "Invalid username [%s]", source_p->username);
exit_client(client_p, source_p, &me, tmpstr2);
return(CLIENT_EXITED);
}
/* end of valid user name check */
if ((status = check_X_line(client_p,source_p)) < 0)
return status;
if (IsDead(client_p))
return CLIENT_EXITED;
if (source_p->user->id[0] == '\0')
{
for (id = id_get(); find_id(id); id = id_get())
;
strcpy(source_p->user->id, id);
add_to_id_hash_table(id, source_p);
id = id_get();
strcpy(user->id_key, id);
}
inetntop(source_p->localClient->aftype, &IN_ADDR(source_p->localClient->ip),
ipaddr, HOSTIPLEN);
sendto_realops_flags(FLAGS_CCONN, L_ALL,
"Client connecting: %s (%s@%s) [%s] {%s} [%s]",
nick, source_p->username, source_p->host,
ipaddr,
get_client_class(source_p), source_p->info);
/* If they have died in send_* don't do anything. */
if (IsDead(source_p))
return CLIENT_EXITED;
source_p->umodes |= FLAGS_INVISIBLE;
/* set the services id to 0 */
source_p->svsid = 0;
SetHidden(source_p);
make_virthost(source_p->host, source_p->localClient->sockhost, source_p->vhost);
Count.invisi++;
if ((++Count.local) > Count.max_loc)
{
Count.max_loc = Count.local;
if (!(Count.max_loc % 10))
sendto_realops_flags(FLAGS_ALL, L_ALL,
"New Max Local Clients: %d",
Count.max_loc);
}
SetClient(source_p);
#ifdef USE_SSL
if (source_p->localClient->ssl) {
SetSSL(source_p);
source_p->umodes |= FLAGS_SSL;
}
#endif
/* XXX source_p->servptr is &me, since local client */
source_p->servptr = find_server(user->server);
add_client_to_llist(&(source_p->servptr->serv->users), source_p);
/* Increment our total user count here */
if (++Count.total > Count.max_tot)
Count.max_tot = Count.total;
source_p->localClient->allow_read = MAX_FLOOD_BURST;
Count.totalrestartcount++;
if ((m = dlinkFindDelete(&unknown_list, source_p)) != NULL)
{
free_dlink_node(m);
dlinkAdd(source_p, &source_p->localClient->lclient_node, &lclient_list);
}
else
{
sendto_realops_flags(FLAGS_ALL, L_ADMIN, "Tried to register %s (%s@%s) but I couldn't find it?!?",
nick, source_p->username, source_p->host);
exit_client(client_p, source_p, &me, "Client exited");
return CLIENT_EXITED;
}
user_welcome(source_p);
return (introduce_client(client_p, source_p, user, nick));
}
/*
* register_remote_user
*
* inputs
* output
* side effects - This function is called when a remote client
* is introduced by a server.
*/
int
register_remote_user(struct Client *client_p, struct Client *source_p,
char *nick, char *username)
{
struct User *user = source_p->user;
struct Client *target_p;
assert(NULL != source_p);
assert(source_p->username != username);
if(source_p == NULL)
return -1;
user->last = CurrentTime;
strlcpy(source_p->username, username, sizeof(source_p->username));
SetClient(source_p);
/* Increment our total user count here */
if (++Count.total > Count.max_tot)
Count.max_tot = Count.total;
source_p->servptr = find_server(user->server);
if (source_p->servptr == NULL)
{
sendto_realops_flags(FLAGS_ALL|FLAGS_REMOTE, L_ALL,"Ghost killed: %s on invalid server %s",
source_p->name, source_p->user->server);
kill_client(client_p, source_p, "%s (Server doesn't exist)",
me.name);
/* XXX */
SetKilled(source_p);
return exit_client(NULL, source_p, &me, "Ghosted Client");
}
add_client_to_llist(&(source_p->servptr->serv->users), source_p);
if ((target_p = find_server(user->server)) && target_p->from != source_p->from)
{
sendto_realops_flags(FLAGS_DEBUG, L_ALL,
"Bad User [%s] :%s USER %s@%s %s, != %s[%s]",
client_p->name, nick, source_p->username,
source_p->host, user->server,
target_p->name, target_p->from->name);
kill_client(client_p, source_p,
"%s (NICK from wrong direction (%s != %s))",
me.name,
user->server,
target_p->from->name);
SetKilled(source_p);
return exit_client(source_p, source_p, &me,
"USER server wrong direction");
}
/*
* Super GhostDetect:
* If we can't find the server the user is supposed to be on,
* then simply blow the user away. -Taner
*/
if (!target_p)
{
kill_client(client_p, source_p, "%s GHOST (no server found)",
me.name);
sendto_realops_flags(FLAGS_ALL|FLAGS_REMOTE, L_ALL, "No server %s for user %s[%s@%s] from %s",
user->server, source_p->name, source_p->username,
source_p->host, source_p->from->name);
SetKilled(source_p);
return exit_client(source_p, source_p, &me, "Ghosted Client");
}
return (introduce_client(client_p, source_p, user, nick));
}
/*
* introduce_clients
*
* inputs -
* output -
* side effects - This common function introduces a client to the rest
* of the net, either from a local client connect or
* from a remote connect.
*/
static int
introduce_client(struct Client *client_p, struct Client *source_p,
struct User *user, char *nick)
{
dlink_node *server_node;
struct Client *server;
static char ubuf[12];
if(MyClient(source_p))
send_umode(source_p, source_p, 0, SEND_UMODES, ubuf);
else
send_umode(NULL, source_p, 0, SEND_UMODES, ubuf);
if (!*ubuf)
{
ubuf[0] = '+';
ubuf[1] = '\0';
}
/* arghhh one could try not introducing new nicks to ll leafs
* but then you have to introduce them "on the fly" in SJOIN
* not fun.
* Its not going to cost much more bandwidth to simply let new
* nicks just ride on through.
*/
/*
* We now introduce nicks "on the fly" in SJOIN anyway --
* you _need_ to if you aren't going to burst everyone initially.
*
* Only send to non CAP_LL servers, unless we're a lazylink leaf,
* in that case just send it to the uplink.
* -davidt
* rewritten to cope with UIDs .. eww eww eww --is
*/
if (!ServerInfo.hub && uplink && IsCapable(uplink,CAP_LL)
&& client_p != uplink)
{
if (IsCapable(uplink, CAP_UID) && HasID(source_p))
{
sendto_one(uplink, "CLIENT %s %d %lu %s %s %s %s %s %s %lu :%s",
nick,
source_p->hopcount+1,
(unsigned long) source_p->tsinfo,
ubuf,
source_p->username, source_p->host,
IsHidden(source_p) ? sou