/* User, channel and server routines.

   OperStats - Copyright (c) 2000-2003 Darcy Grexton
   Contact: skold@habber.net, skold @ HabberNet

   See doc/LICENSE for licensing details.
 */

#include "../inc/operstats.h"

Channel *chanlist[HASHSIZE];
User *userlist[HASHSIZE];
Server *servlist[HASHSIZE];
Clone *clonelist[HASHSIZE];

static User *current_u;
static uint32 next_index_u;
static Channel *current_c;
static uint32 next_index_c;
static Server *current_s;
static uint32 next_index_s;

/* Add a new user into memory. */
static User *new_user (const char *nick)
{
    User *user, **list;

    user = scalloc (sizeof (User), 1);

    if (!nick)
	nick = "";

    strscpy (user->nick, nick, NICKLEN);

    list = &userlist[NHASH(user->nick)];

    user->next = *list;

    if (*list)
	(*list)->prev = user;

    *list = user;

    usercnt++;

    return user;
}

/* Find a user by nick. Return NULL if user could not be found. */
User *finduser (const char *nick)
{
    User *user;

    user = userlist[NHASH(nick)];
    while (user && stricmp (user->nick, nick))
	user = user->next;
    return user;
}

/* Return the first user in the hash */
User *firstuser ()
{
    next_index_u = 0;

    while (next_index_u < HASHSIZE && current_u == NULL)
	current_u = userlist[next_index_u++];

    return current_u;
}

/* Return the next user in the hash */
User *nextuser ()
{
    if (current_u)
	current_u = current_u->next;

    if (!current_u && next_index_u < HASHSIZE)
	while (next_index_u < HASHSIZE && current_u == NULL)
	    current_u = userlist[next_index_u++];

    return current_u;
}

/* Add a new server into memory. */
Server *new_server (const char *name)
{
    Server *server, **list;

    server = scalloc (sizeof (Server), 1);

    if (!name)
	name = "";

    strscpy (server->name, name, HOSTLEN);

    list = &servlist[SVHASH(server->name)];

    server->next = *list;

    if (*list)
	(*list)->prev = server;

    *list = server;

    servcnt++;

    if (debuglevel > 1)
	debug ("new_server(): %s", server->name);

    return server;
}

/* Delete a server. */
void delete_server (Server *server)
{
    if (debuglevel > 1)
	debug ("delete_server(): %s", server->name);

    servcnt--;

    free (server->uplink);

    if (server->prev)
	server->prev->next = server->next;
    else
	servlist[SVHASH(server->name)] = server->next;

    if (server->next)
	server->next->prev = server->prev;

    free (server);
}

/* Find a server by name. Return NULL if user could not be found. */
Server *findserv (const char *name)
{
    Server *server;

    server = servlist[SVHASH(name)];

    if (!server)
	return NULL;

    return server;
}
    
/* Return the first server in the hash */
Server *firstserv ()
{
    next_index_s = 0;

    while (next_index_s < HASHSIZE && current_s == NULL)
	current_s = servlist[next_index_s++];

    return current_s;
}

/* Return the next server in the hash */
Server *nextserv ()
{
    if (current_s)
	current_s = current_s->next;

    if (!current_s && next_index_s < HASHSIZE)
	while (next_index_s < HASHSIZE && current_s == NULL)
	    current_s = servlist[next_index_s++];

    return current_s;
}

/* Add a new clone to the list */ 
static Clone *new_clone (const char *host)
{
    Clone *clone, **list;

    clone = scalloc (sizeof (Clone), 1);

    if (!host)
	host = "";

    strscpy (clone->host, host, HOSTLEN);
    list = &clonelist[CLHASH(clone->host)];
    clone->next = *list;

    if (*list)
	(*list)->prev = clone;

    *list = clone;
    return clone;
}

/* Find a Clone. Return NULL if clone could not be found. */
Clone *findclone (const char *host)
{
    Clone *clone;

    clone = clonelist[CLHASH(host)];
    while (clone && stricmp (clone->host, host))
	clone = clone->next;
    return clone;
}

/* Add a clone, creating the structure if needed. */
static void add_clone (User *u)
{
    Clone *clone;
    Trigger *trigger;
    Exception *exception;
    uint8 limit, excepted = 0;

    if (!(clone = findclone (u->host)))
	clone = new_clone (u->host);

    clone->cnt++;

    /* Check if this host is excepted */
    if ((exception = findexcept (u->user, u->host)))
	excepted = 1;

    /* Check for a trigger for this host */
    if (!excepted)
    {
	if ((trigger = findtrig (u->user, u->host)))
	    limit = trigger->limit;
	else
	    limit = maxclones;

	if (clone->cnt > limit)
	{
	    /* If they've broke the max clone limit, akill them */
	    if (maxclonekills && clone->kills >= maxclonekills)
	    {
		AutoKill *akill;
		char *mask;

		mask = smalloc (strlen (u->user) + strlen (u->host) + 2);
		snprintf (mask, strlen (u->user) + strlen (u->host) + 2,
		    "*@%s", u->host);

		/* Ignore it if we already have it. (which we shouldn't) */
		if (is_akilled (mask))
		    return;

		if (akillcnt >= akill_size)
		{
		    if (akill_size < 8)
			akill_size = 8;
		    else if (akill_size >= 16384)
			akill_size = 32767;
		    else
			akill_size *= 2;

		    akills = srealloc (akills, sizeof (*akills) * akill_size);
		}

		akill = &akills[akillcnt];
		akill->mask = sstrdup (mask);
		akill->reason = sstrdup (OS_MAX_CLONES);
		akill->setter = sstrdup (me.name);
		akill->realname = 0;
		akill->set = time (NULL);
		akill->expires = clonekill_time;

		akillcnt++;

		if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
		    send_cmd (me.name, "AKILL %s * 0 %s %ld :%s", u->host,
			s_OperServ, time (NULL), akill->reason);
		else
		    send_cmd (me.name, "%s %s * :%s", me.token ? "V" : "AKILL",
			u->host, akill->reason);

		free (mask);
	    }

	    kill_user (s_OperServ, u->nick, OS_MAX_CLONES);

	    if (maxclonekills)
		clone->kills++;
	}
    }
}

/* Remove a clone from a host, deleting it as needed */
static void del_clone (User *u)
{
    Clone *clone;

    if (!(clone = findclone (u->host)))
	return;

    clone->cnt--;

    if (clone->cnt <= 0)
    {
	if (clone->prev)
	    clone->prev->next = clone->next;
	else
	    clonelist[CLHASH(clone->host)] = clone->next;
	if (clone->next)
	    clone->next->prev = clone->prev;

	free (clone);
    }
}

/* Return the Channel structure corresponding to the named channel, or
   NULL if the channel was not found.
 */
Channel *findchan (const char *chan)
{
    Channel *c;

    c = chanlist[CHASH(chan)];

    while (c)
    {
	if (!stricmp (c->name, chan))
	    return c;

	c = c->next;
    }

    return NULL;
}

/* Return the first channel */
Channel *firstchan ()
{
    next_index_c = 0;

    while (next_index_c < HASHSIZE && current_c == NULL)
	current_c = chanlist[next_index_c++];

    return current_c;
}

/* Return the next channel */
Channel *nextchan ()
{
    if (current_c)
	current_c = current_c->next;

    if (!current_c && next_index_c < HASHSIZE)
	while (next_index_c < HASHSIZE && current_c == NULL)
	    current_c = chanlist[next_index_c++];

    return current_c;
}

/* Change a user's nick */
static void change_user_nick (User *user, const char *nick)
{
    User **list;

    if (user->prev)
	user->prev->next = user->next;
    else
	userlist[NHASH(user->nick)] = user->next;
    if (user->next)
	user->next->prev = user->prev;
    user->nick[1] = 0;
    strscpy (user->nick, nick, NICKLEN);
    list = &userlist[NHASH(user->nick)];
    user->next = *list;
    user->prev = NULL;
    if (*list)
	(*list)->prev = user;
    *list = user;
}

/* Handle a server NICK command.
	av[0] = nick
	If a new user:
	   av[1] = hop count
	   av[2] = signon time
	   DreamForge:
		av[3] = username
		av[4] = hostname
		av[5] = server
		av[6] = servicestamp
		av[7] = realname
	   Bahamut:
		av[3] = modes
		av[4] = username
		av[5] = hostname
		av[6] = server
		av[7] = servicestamp
		av[8] = realname
	   Unreal:
		av[3] = username
		av[4] = hostname
		av[5] = server
		av[6] = servicestamp
		av[7] = modes
		av[8] = vhost
		av[9] = realname
	Else:
	  av[1] = time of change
 */
void do_nick (const char *source, uint8 ac, char **av)
{
    User *user;
    char *tmp;
    time_t now = time (NULL);

    if (!*source)
    {
	MSG *msg;
	TLD *tld;

	/* This is a new user; create a User structure for it. */
	if (debuglevel > 1)
	    debug ("do_nick(): %s", av[0]);

	/* Check if this user matches any AKills */
	if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
	{
	    if (check_akill (av[0], av[4], av[5], av[8]))
		return;
	}
	else
	{
	    if (check_akill (av[0], av[3], av[4], av[7]))
		return;
	}

	/* Allocate User structure and fill it in. */
	user = new_user (av[0]);

	if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
	{
	    user->user = sstrdup (av[4]);
	    user->host = sstrdup (av[5]);
	    user->server = sstrdup (av[6]);
	    user->real = sstrdup (av[8]);

	    av[1] = av[3];
	    do_umode (av[0], 2, av);
	}
	else if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	{
	    user->user = sstrdup (av[3]);
	    user->host = sstrdup (av[4]);
	    user->server = sstrdup (av[5]);
	    user->real = sstrdup (av[9]);

	    av[1] = av[7];
	    do_umode (av[0], 2, av);
	}
	else
	{
	    user->user = sstrdup (av[3]);
	    user->host = sstrdup (av[4]);
	    user->server = sstrdup (av[5]);
	    user->real = sstrdup (av[7]);
	}

	/* Show them the LogonMSGs */
	if (globalnoticer_on == TRUE)
	    for (msg = msglist; msg; msg = msg->next)
		notice (s_GlobalNoticer, av[0], msg->text);

	if (!(finduline (user->server) && ignoreulines_on == TRUE))
	{
	    ServStat *serverstat;

	    userstat++;
	    dailyhitcnt++;

	    /* Update global stats */
	    if (userstat > maxusercnt)
	    {
		maxusercnt = userstat;
		maxusertime = now;
	    }

	    if (userstat > dailyuserstat)
	    {
		dailyuserstat = userstat;
		dailyusertime = now;
	    }

	    if (dailyhitcnt > maxhitcnt)
		maxhitcnt = dailyhitcnt;

	    /* Update server stats */
	    serverstat = findservstat (user->server);

	    if (serverstat)
	    {
		serverstat->clients++;

		if (serverstat->clients > serverstat->maxclients)
		{
		    serverstat->maxclients = serverstat->clients;
		    serverstat->maxtime = now;
		}
	    }

	    /* Increase the count for their TLD */
	    tmp = strrchr (user->host, '.');
	    tmp = strtok (tmp, ".");

	    if ((tld = findtld (tmp)))
	    {
		tld->cnt++;
		tld->hits++;
	    }
	    else
	    {
		tldnores++;
		tldnoreshits++;
	    }
	}

	/* Clone checking stuff */
	if (maxclones)
	    add_clone (user);
    }
    else
    {
	/* An old user changing nicks. */
	user = finduser (source);

	if (!user)
	{
	    if (debuglevel > 1)
		debug ("do_nick(): Got nickchange for nonexistant user %s->%s",
		    source, av[0]);
	    return;
	}

	if (debuglevel > 1)
	    debug ("do_nick(): %s changed nick to %s", source, av[0]);

	/* If this is a nickname we were waiting for, reclaim it. */
	if (os.wantnick)
	    if (!stricmp (source, os.nick))
	    {
		send_cmd (s_OperServ, "%s %s", me.token ? "&" : "NICK",
		    os.nick);
		strscpy (s_OperServ, os.nick, sizeof (s_OperServ));
		os.wantnick = 0;
	    }

	if (ss.wantnick)
	    if (!stricmp (source, ss.nick))
	    {
		send_cmd (s_StatServ, "%s %s", me.token ? "&" : "NICK",
		    ss.nick);
		strscpy (s_StatServ, ss.nick, sizeof (s_StatServ));
		ss.wantnick = 0;
	    }

	if (gn.wantnick)
	    if (!stricmp (source, gn.nick))
	    {
		send_cmd (s_GlobalNoticer, "%s %s", me.token ? "&" : "NICK",
		    gn.nick);
		strscpy (s_GlobalNoticer, gn.nick, sizeof (s_GlobalNoticer));
		gn.wantnick = 0;
	    }

	change_user_nick (user, av[0]);
    }
}

/* Handle a user modechange - This currently only cares about
   umode o, since we don't need any others.
 */
void do_umode (const char *source, uint8 ac, char **av)
{
    User *user;
    char *s;
    ServStat *serverstat;
    uint8 add = 1;

    if (stricmp (source, av[0]))
	return;

    user = finduser (source);

    if (!user)
    {
	if (debuglevel > 1)
	    debug ("MODE for nonexistant user %s: %s", source, av[1]);
	return;
    }

    if (debuglevel > 1)
	debug ("do_umode(): %s %s", av[0], av[1]);

    s = av[1];

    while (*s)
    {
	switch (*s++)
	{
	    case '+':
		add = 1;
		break;
	    case '-':
		add = 0;
		break;
	    case 'o':
		add ? (user->mode |= UMODE_o) : (user->mode &= ~UMODE_o);

		if (!add && (user->flags & UF_SRA))
		    user->flags &= ~UF_SRA;

		serverstat = findservstat (user->server);

		if (add)
		{
		    operstat++;

		    if (operstat > maxopers)
			maxopers = operstat;

		    if (serverstat)
			serverstat->opers++;
		}
		else
		{
		    operstat--;

		    if (serverstat)
			serverstat->opers--;
		}

		break;
	}
    }
}

/* Handle a channel modechange - Only cares about cmode s since it's
   the only one we need.
 */
void do_cmode (const char *source, uint8 ac, char **av)
{
    Channel *chan;
    char *s;
    uint8 add = 1;
    char *modestr = av[1];

    chan = findchan (av[0]);

    if (!chan)
    {
	if (debuglevel > 1)
	    debug ("MODE for nonexistant channel %s: %s", av[0], av[1]);
	return;
    }

    s = modestr;
    ac -= 2;
    av += 2;

    while (*s)
    {
	switch (*s++)
	{
	    case '+':
		add = 1;
		break;
	    case '-':
		add = 0;
		break;
	    case 's':
		add ? (chan->mode |= CMODE_s) : (chan->mode &= ~CMODE_s);
		break;
	}
    }
}

/* Add a user to a channel, creating the channel as necessary. */
Channel *chan_adduser (User *user, const char *chan)
{
    Channel *c = findchan (chan);
    Channel **list;
    struct c_userlist *u;
    uint8 newchan = !c;
    time_t now = time (NULL);

    if (newchan)
    {
	/* Allocate pre-cleared memory */
	c = scalloc (sizeof (Channel), 1);
	strscpy (c->name, chan, sizeof (c->name));
	list = &chanlist[CHASH(c->name)];
	c->next = *list;

	if (*list)
	    (*list)->prev = c;

	*list = c;

	if (debuglevel > 1)
	    debug ("chan_adduser(): Creating new channel %s", chan);

	chancnt++;
	chanstat++;

	if (chanstat > maxchancnt)
	{
	    maxchancnt = chanstat;
	    maxchantime = now;
	}

	if (chanstat > dailychanstat)
	{
	    dailychanstat = chanstat;
	    dailychantime = now;
	}
    }

    u = smalloc (sizeof (struct c_userlist));
    u->next = c->users;
    u->prev = NULL;

    if (c->users)
	c->users->prev = u;

    c->users = u;
    u->user = user;

    c->usercnt++;

    if (debuglevel > 1)
	debug ("chan_adduser(): %s added to %s", user->nick, c->name);

    return c;
}

/* Remove a user from a channel, deleting the channel as necessary. */
void chan_deluser (User *user, Channel *c)
{
    struct c_userlist *u;

    if (debuglevel > 1)
	debug ("chan_deluser(): %s %s", user->nick, c->name);

    for (u = c->users; u && u->user != user; u = u->next);

    if (!u)
    {
	if (debuglevel > 1)
	    debug ("chan_deluser(): %s not in %s!", user->nick, c->name);
    }
    else
    {
	if (u->next)
	    u->next->prev = u->prev;
	if (u->prev)
	    u->prev->next = u->next;
	else
	    c->users = u->next;

	free (u);

	c->usercnt--;
    }

    if (!c->users)
    {
	if (debuglevel > 1)
	    debug ("chan_deluser(): Deleting empty channel %s", c->name);

	chancnt--;
	chanstat--;

	if (c->next)
	    c->next->prev = c->prev;
	if (c->prev)
	    c->prev->next = c->next;
	else
	    chanlist[CHASH(c->name)] = c->next;

	free (c);

	return;
    }
}

/* Remove and free a User structure. */
void delete_user (User *user)
{
    ServStat *serverstat;
    TLD *tld;
    struct u_chanlist *uc, *nextuc;
    char *tmp;
    char buf[BUFSIZE];

    *buf = 0;

    if (debuglevel > 1)
	debug ("delete_user(): %s", user->nick);

    usercnt--;

    if (!(finduline (user->server) && ignoreulines_on == TRUE))
    {
	userstat--;

	/* Decrease the count for their TLD */
	tmp = strrchr (user->host, '.');
	tmp = strtok (tmp, ".");

	if ((tld = findtld (tmp)))
	    tld->cnt--;
	else
	    tldnores--;
    }

    /* Remove from server stats */
    serverstat = findservstat (user->server);

    if (serverstat)
	serverstat->clients--;

    if (is_oper (user))
	serverstat->opers--;

    /* Remove a clone from their record */
    del_clone (user);

    free (user->user);
    free (user->host);
    free (user->real);
    free (user->server);

    uc = user->chans;

    while (uc)
    {
	nextuc = uc->next;

	if (debuglevel > 1)
	{
	    strcat (buf, " ");
	    strcat (buf, uc->chan->name);
	}

	chan_deluser (user, uc->chan);

	free (uc);
	uc = nextuc;  
    }

    if (*buf && debuglevel > 1)
	debug ("delete_user(): %s left%s", user->nick, buf);

    if (user->prev)
	user->prev->next = user->next;
    else
	userlist[NHASH(user->nick)] = user->next;

    if (user->next)
	user->next->prev = user->prev;

    free (user);
}

/* Handle a JOIN command.
      av[0] = channels to join
 */
void do_join (const char *source, uint8 ac, char **av)
{
    User *user;
    Channel *c;
    char *s, *t;
    struct u_chanlist *uc, *nextuc;

    user = finduser (source);

    if (!user)
    {
	if (debuglevel > 1)
	    debug ("do_join(): Got JOIN to %s from nonexistant user %s",
		av[0], source);
	return;
    }

    t = av[0];

    while (*(s = t))
    {
	t = s + strcspn (s, ",");

	if (*t)
	    *t++ = 0;

        if (*s == '0')
        {
	    uc = user->chans;

	    while (uc)
	    {
		nextuc = uc->next;

		chan_deluser (user, uc->chan);

		free (uc);

		uc = nextuc;
	    }

	    if (debuglevel > 1)
		debug ("do_join(): %s parted all channels", source);

	    user->chans = NULL;
	    continue;
	}

	if (debuglevel > 1)
	    debug ("do_join(): %s joined %s", source, s);

	c = chan_adduser (user, s);
	uc = smalloc (sizeof (*c));
	uc->next = user->chans; 
	uc->prev = NULL;
	if (user->chans)
	    user->chans->prev = uc;
	user->chans = uc;
	uc->chan = c;
    }
}

/* Handle a PART command */
void do_part (const char *source, uint8 ac, char **av)
{
    User *user;
    char *s, *t;
    struct u_chanlist *c;
 
    user = finduser (source);

    if (!user)  
	return;

    t = av[0];

    while (*(s = t))
    {
	t = s + strcspn (s, ",");

	if (*t) 
	    *t++ = 0;

	if (debuglevel > 1)
	    debug ("do_part(): %s parted %s", source, s);

	for (c = user->chans; c && stricmp (s, c->chan->name) != 0; c = c->next);

	if (c)
	{
	    chan_deluser (user, c->chan);

	    if (c->next)
		c->next->prev = c->prev;
	    if (c->prev)
		c->prev->next = c->next;
	    else
		user->chans = c->next;

	    free (c);
	}
	else
	    if (debuglevel > 1)
		debug ("do_part(): Got PART %s %s, but %s isn't on %s!", source, s, source, s);
    }
}

/* Handle a KICK command */
void do_kick (const char *source, uint8 ac, char **av)
{
    User *user;
    char *s, *t;
    struct u_chanlist *c;   
        
    if (is_one_of_mine (av[1]) && stricmp (av[1], sp.nick))
    {
	/* Rejoin our channel if kicked */
	if (!stricmp (av[0], joinchan))
	{
	    if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
		send_cmd (av[1], "SJOIN %ld %ld %s + :%s", time (NULL), time (NULL),
		    joinchan, av[1]);
	    else
		send_cmd (av[1], "%s %s", me.token ? "C" : "JOIN", joinchan);

	    send_cmd (av[1], "%s %s +o %s", me.token ? "G" : "MODE", joinchan, av[1]);
	}

	return;
    }

    t = av[1];

    while (*(s = t))
    {
	t = s + strcspn (s, ",");

	if (*t)
	    *t++ = 0;

	user = finduser (s);

	if (!user)
	{
	    if (debuglevel > 1)
		debug ("do_kick(): Got KICK for nonexistant user %s from %s",
		    user->nick, av[0]);
	    continue;
	}

	if (debuglevel > 1)
	    debug ("do_kick(): %s was kicked from %s", s, av[0]);

	for (c = user->chans; c && stricmp (av[0], c->chan->name) != 0; c = c->next);

	if (c) 
	{
	    chan_deluser (user, c->chan);

	    if (c->next)
		c->next->prev = c->prev;
	    if (c->prev)
		c->prev->next = c->next;
	    else
		user->chans = c->next;

	    free (c);
	}
    }

    if (!source)
	return;
}

/* Handle a QUIT command */
void do_quit (const char *source, uint8 ac, char **av)
{    
    User *user;

    user = finduser (source);

    if (!user)
    {
	if (debuglevel > 1)
	    debug ("do_quit(): Got QUIT for nonexistant user %s", user->nick);
	return;
    }

    if (debuglevel > 1)
	debug ("do_quit(): %s QUIT", source);

    /* If this is a nickname we were waiting for, reclaim it. */
    if (os.wantnick)
	if (!stricmp (source, os.nick))
	{
	    send_cmd (s_OperServ, "%s %s", me.token ? "&" : "NICK", os.nick);
	    strscpy (s_OperServ, os.nick, sizeof (s_OperServ));
	    os.wantnick = 0;
	}

    if (ss.wantnick)
	if (!stricmp (source, ss.nick))
	{
	    send_cmd (s_StatServ, "%s %s", me.token ? "&" : "NICK", ss.nick);
	    strscpy (s_StatServ, ss.nick, sizeof (s_StatServ));
	    ss.wantnick = 0;
	}

    if (gn.wantnick)
	if (!stricmp (source, gn.nick))
	{
	    send_cmd (s_GlobalNoticer, "%s %s", me.token ? "&" : "NICK",
		gn.nick);
	    strscpy (s_GlobalNoticer, gn.nick, sizeof (s_GlobalNoticer));
	    gn.wantnick = 0;
	}

    delete_user (user);
}

/* Change a channel's stored topic. */
void do_topic (const char *source, uint8 ac, char **av)
{
    Channel *c = findchan (av[0]);

    if (!c)
	return;

    if (c->topic)
    {
	free (c->topic);
	c->topic = NULL;
    }

    if (ac > 3 && *av[3])
	c->topic = sstrdup (av[3]);
}

/* Handle a KILL command.
      av[0] = nick being killed
      av[1] = reason
 */
void do_kill (const char *source, uint8 ac, char **av)
{
    User *user;

    user = finduser (av[0]);

    if (!user)
    {
	if (debuglevel > 1)
	    debug ("do_kill(): Got kill for nonexistant user %s", av[0]);
	return;
    }

    if (debuglevel > 1)
	debug ("do_kill(): %s Killed", av[0]);

    delete_user (user);
}

/* Remove a user from the network. */
void kill_user (const char *source, const char *user, const char *reason)
{
    char *av[2];
    char buf[BUFSIZE];

    if (!user || !*user)
	return;

    if (!source || !*source)
	source = me.name;

    if (!reason)
	reason = "Killed";

    snprintf (buf, sizeof (buf), "%s (%s)", source, reason);

    av[0] = sstrdup (user);
    av[1] = buf;

    send_cmd (source, "%s %s :%s", me.token ? "." : "KILL", user, av[1]);
    do_kill (source, 2, av);
    free (av[0]);
}

/* Handle an SJOIN command. - Borrowed from IRCServices
        Bahamut no-SSJOIN format:
            av[0] = TS3 timestamp
            av[1] = TS3 timestamp
            av[2] = channel
            av[3] = channel modes
            av[4] = limit / key (depends on modes in av[3])
            av[5] = limit / key (depends on modes in av[3]) 
            av[ac-1] = nickname(s), with modes, joining channel
        Bahamut SSJOIN format (server source) / Unreal SJOIN2 format:
            av[0] = TS3 timestamp - channel creation time
            av[1] = channel
            av[2] = modes (limit/key in av[3]/av[4] as needed)
            av[ac-1] = users
            (Note that Unreal may omit the modes if there aren't any.)
        Bahamut SSJOIN format (client source):
            av[0] = TS3 timestamp
            av[1] = channel
 */
void do_sjoin (const char *source, uint8 ac, char **av)
{
    User *user;
    Channel *c = NULL;
    struct u_chanlist *uc;
    char *t, *nick, *channel;
    uint16 joins = 0;

    if (isdigit (av[1][0]))
    {
	memmove (&av[0], &av[1], sizeof (char *) * (ac-1));
	ac--;
    }

    channel = av[1];

    if (ac >= 3)
	t = av[ac-1];
    else
	t = (char *)source;

    while (*(nick=t))
    {
	int32 modes = 0, thismode;

        t = nick + strcspn (nick, " ");

	if (*t)
	    *t++ = 0;

	if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	{
	    if (*nick == '&' || *nick == '"')
	    {
		char *avtmp[3];

		avtmp[0] = channel;
		avtmp[1] = (*nick=='&') ? "+b" : "+e";
		avtmp[2] = nick + 1;

		do_cmode (source, 3, avtmp);
		continue;
	    }
	}

	do
	{
	    thismode = prefix_to_flag (*nick);
	    modes |= thismode;
	} while (thismode && nick++);

	user = finduser (nick);

	if (!user)
	{
	    if (debuglevel > 1)
		debug ("do_sjoin(): Got SJOIN to %s for nonexistant user %s",
		    channel, nick);
	    continue;
	}

	if (debuglevel > 1)
	    debug ("do_sjoin(): %s joined %s", nick, channel);

	c = chan_adduser (user, channel);
	joins++;

	uc = smalloc (sizeof (*uc));
	uc->next = user->chans;
	uc->prev = NULL;

	if (user->chans)
	    user->chans->prev = uc;

	user->chans = uc;
	uc->chan = c;
    }

    /* Did anyone actually join the channel? */
    if (c)
	if (ac > 3)
	    do_cmode (source, ac-2, av+1);
}
