/* Processing code.

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

   See doc/LICENSE for licensing details.
 */

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

/* split_buf:  Split a buffer into arguments and store the arguments in an
               argument vector pointed to by argv (which will be malloc'd
               as necessary); return the argument count.
 */
int8 split_buf (char *buf, char ***argv)
{
    int8 argvsize = 8;
    int8 argc;
    char *s;

    *argv = smalloc (sizeof(char *) * argvsize);
    argc = 0;

    while (*buf)
    {
	if (argc == argvsize)
	{
	    argvsize += 8;
	    *argv = srealloc (*argv, sizeof (char *) * argvsize);
	}

	if (*buf == ':')
	{
	    (*argv)[argc++] = buf+1;
	    buf = "";
	}
	else
	{
	    s = strpbrk (buf, " ");

	    if (s)
	    {
		*s++ = 0;

		while (isspace (*s))
		    s++;
	    }
	    else
		s = buf + strlen (buf);

	    (*argv)[argc++] = buf;

	    buf = s;
	}
    }

    return argc;
}

/* process:  Main processing routine. */
void process ()
{
    char source[64];
    char cmd[64];
    char buf[512];
    char *s;
    int8 ac;
    char **av;
    Message *m;

    /* If debugging, log the buffer. */
    if (debuglevel == 1 || debuglevel == 3)
	debug ("--> %s", inbuf);

    /* First make a copy of the buffer so we have the original in case we
       crash - in that case, we want to know what we crashed on.
     */
    strscpy (buf, inbuf, sizeof (buf));

    /* Split the buffer into pieces. */
    if (*buf == ':')
    {
	s = strpbrk (buf, " ");

	if (!s)
	    return;

	*s = 0;

	while (isspace (*s++))
	    ;

	strscpy (source, buf + 1, sizeof (source));
	memmove (buf, s, strlen (s) + 1);
    }
    else
	*source = 0;

    if (!*buf)
	return;

    s = strpbrk (buf, " ");

    if (s)
    {
	*s = 0;

	while (isspace (*s++))
	    ;
    }
    else
	s = buf + strlen (buf);

    strscpy (cmd, buf, sizeof (cmd));
    ac = split_buf (s, &av);

    /* Do something with the message. */
    m = find_message (cmd);

    if (m)
	if (m->func)
	    m->func (source, ac, av);

    /* Free argument list we created */
    free (av);
}

/* Send our MOTD */
void m_motd (char *source, int8 ac, char **av)
{
    FILE *f;
    char buf[BUFSIZE];
    time_t uptime = time (NULL) - me.since;

    send_cmd (me.name, "375 %s :- %s Message of the Day -", source, me.name);
    send_cmd (me.name, "372 %s :- ", source);
    send_cmd (me.name,
	"372 %s :- OperStats v%s Copyright (c) 2000-2003 Darcy Grexton",
	source, version);
    send_cmd (me.name, "372 %s :- ", source);

    f = fopen (MOTDFILE, "r");

    if (f)
    {
	while (fgets (buf, sizeof (buf), f))
	{
	    buf[strlen (buf) - 1] = 0;
	    send_cmd (me.name, "372 %s :- %s", source, buf);
	}
	fclose (f);
    }

    send_cmd (me.name, "372 %s :- ", source);
    send_cmd (me.name, "372 %s :- Current uptime: %d days, %d:%02d:%02d", source, uptime/86400,
	(uptime/3600) % 24, (uptime/60) % 60, uptime % 60);
    send_cmd (me.name, "372 %s :- Highest uptime: %d days, %d:%02d:%02d", source, maxuptime/86400,
	(maxuptime/3600) % 24, (maxuptime/60) % 60, maxuptime % 60);

    send_cmd (me.name, "372 %s :- ", source);
    send_cmd (me.name, "376 %s :End of /MOTD command.", source);
}

/* Reply to a TIME query */
void m_time (char *source, int8 ac, char **av)
{
    time_t t;
    struct tm *tm;
    char buf[80];

    time (&t);
    tm = localtime (&t);
    strftime (buf, sizeof (buf), "%A %B %d %Y -- %H:%M %z", tm);

    send_cmd (me.name, "391 %s %s :%s", source, me.name, buf);
}

/* Send an ADMIN reply */
void m_admin (char *source, int8 ac, char **av)
{
    send_cmd (me.name, "256 %s :Administrative info about %s", source,
	me.name);
    send_cmd (me.name, "257 %s :OperStats v%s", source, version);
    send_cmd (me.name, "258 %s :Copyright (c) 2000-2003 Darcy Grexton",
	source);
    send_cmd (me.name,
	"259 %s :For more information E-Mail skold@habber.net.", source);
}

/* Handle an ERROR command */
void m_error (char *source, int8 ac, char **av)
{
    snprintf (quitmsg, BUFSIZE, "%s", inbuf);
    log (RPL_ERROR, inbuf);
    runflags |= RUN_SHUTDOWN;
}

/* Reply to a ping */
void m_ping (char *source, int8 ac, char **av)
{
    if (ac < 1)
	return;

    /* Reply to the ping with the servername the ping came for. This
       stops bad ping replies when we get PINGs for things like jupes.
       Bahamut seems to fuck up pings when bursting though, so we have
       to account for that too..
     */
    if (ac > 1)
	send_cmd (av[1], "%s %s %s", me.token ? "9" : "PONG",
	    ac > 1 ? av[1] : me.name, av[0]);
    else
	send_cmd (me.name, "%s %s %s", me.token ? "9" : "PONG",
	    ac > 1 ? av[1] : me.name, av[0]);

    if (ircdtype == BAHAMUT)
    {
	/* Bahamut sends two PINGs at the end of the burst. We'll use that to figure
	   out when it's over.
	 */
	if (bursting > 0)
	    bursting--;

	/* Now that the burst is done, run through every channel, and do the modechanges
	   and topic changes as needed.
	 */
	if (bursted && !bursting)
	{
	    globops (authserv (), RPL_SYNCHED, duration (time (NULL) - burststart, 1), userstat,
		chancnt, servstat);

#ifdef HAVE_GETTIMEOFDAY
	    gettimeofday (&burstnow, NULL);
	    timersub (&burstnow, &burstms, &bursttmp);

	    if (tv2ms (&bursttmp) > 1000)
		log_sameline (0, "completed in %s.", duration (tv2ms (&bursttmp) / 1000, 1));
	    else
		log_sameline (0, "completed in %d ms.", tv2ms (&bursttmp));
#endif

	    /* Set this so we don't bitch about the burst ;) */
	    lastwarnclients = userstat;

	    check_eb ();
	}

	/* Since Bahamut uses PINGs to show the end of the burst, we need somethign different
	   to track the end of burst. Otherwise we'll keep sending sync'd notices with each
	   ping.
	 */
	if (bursting == 0)
	    bursted = 0;
    }
}

/* A server replied to a PING, update its ping time */
void m_pong (char *source, int8 ac, char **av)
{
    Server *server;
    ServStat *serverstat, *uplink;

    serverstat = findservstat (source);
    server = findserv (source);

    if (ac < 1)
	return;

    if (server && servstat)
    {
	struct timeval now, tmp;

	uplink = findservstat (server->uplink);

	gettimeofday (&now, NULL);
	timersub (&now, &(server->lastping), &tmp);

	serverstat->lag.tv_sec = tmp.tv_sec;
	serverstat->lag.tv_usec = tmp.tv_usec;
	server->lastpong.tv_sec = now.tv_sec;
	server->lastpong.tv_usec = now.tv_usec;

	if (timercmp (&tmp, &(serverstat->peaklag), >))
	{
	    serverstat->peaklag.tv_sec = tmp.tv_sec;
	    serverstat->peaklag.tv_usec = tmp.tv_usec;
	}

	if (globalonlag_on == TRUE)
	{
	    /* If theres some pocket lag involved, we may get several
	       PONGs at once. To avoid flooding in globops about it,
	       we only report lag once every ping_delay seconds.
	     */
	    if ((tv2ms (&tmp) >= (lagthreshold * 1000)) && (time (NULL) - me.since > 60))
	    {
		if (now.tv_sec - serverstat->lastreport < ping_delay)
		    return;
		else
		{
		    /* ANNNNND... If a server's uplink is lagged, it's probably lagged
		       too. So don't report lag if the uplink is lagged.
		     */
		    if (uplink && (tv2ms (&(uplink->lag)) >= (lagthreshold * 1000)))
			return;

		    globops (s_StatServ, SS_SERVER_LAGGED, source, tv2ms (&tmp));
		    serverstat->lastreport = now.tv_sec;
		}
	    }
	}
    }
}

/* Handle a KICK command */
void m_kick (char *source, int8 ac, char **av)
{
    if (ac != 3)
	return; 

    do_kick (source, ac, av);
}

/* Handle a TOPIC command */
void m_topic (char *source, int8 ac, char **av)
{
    if (ac != 4)
	return;

    do_topic (source, ac, av);
}

/* Handle a JOIN command */
void m_join (char *source, int8 ac, char **av)
{
    if (ac != 1)
	return;

    do_join (source, ac, av);
}

/* Handle an SJOIN command. */
void m_sjoin (char *source, int8 ac, char **av)
{
    do_sjoin (source, ac, av);
}

/* Handle a PART command */
void m_part (char *source, int8 ac, char **av)
{
    if (ac < 1 || ac > 2)   
	return;

    do_part (source, ac, av);
}

/* Handle a NICK command */
void m_nick (char *source, int8 ac, char **av)
{
    do_nick (source, ac, av);
}

/* Handle a QUIT command */
void m_quit (char *source, int8 ac, char **av)
{
    if (ac != 1)
	return;

    if (!finduser (source))
    {
	if (debuglevel > 1)
	    debug ("do_quit(): Got QUIT for nonexistant user %s", source);

	return;
    }

    do_quit (source, ac, av);
}

/* Send our version reply */
void m_version (char *source, int8 ac, char **av)
{
    if (source)
    {
	/* Send version replies with the server name that was queried. If it's
           not the same as our name, theyre likely versioning a juped server,
           so we'll reply as such.
	 */
	if (!stricmp (me.name, av[0]))
	    send_cmd (me.name, "351 %s operstats-%s. %s :%s%s%s%s%s%s",
		source, version, me.name, (operserv_on == TRUE) ? "O" : "",
		(statserv_on == TRUE) ? "S" : "", (globalnoticer_on == TRUE) ? "g" : "",
		(spamserv_on == TRUE) ? "s" : "",
#ifdef WIN32
		"W",
#else
		"",
#endif
		debuglevel ? "d" : "");
	else
	    send_cmd (av[0], "351 %s juped-server. %s :XX", source, av[0]);
    }
}

/* A server replied to a version query, store its reply. */
void m_vreply (char *source, int8 ac, char **av)
{
    uint16 i;
    ServStat *serverstat;

    /* Intercept SVSSEND */
    if (!stricmp (av[1], "0"))
    {
	if (!stricmp (source, cygnus_server))
	{
	    char *cmd = strtok (av[2], " "), *param = strtok (NULL, "");

	    if (!cmd || !param)
		return;

	    /* AKILL */
	    if (!stricmp (cmd, "1"))
	    {
		AutoKill *akill;
		char *mask = strtok (param, " "), *setter = strtok (NULL, " ");
		char *realname = strtok (NULL, " "), *set = strtok (NULL, " ");
		char *expires = strtok (NULL, " "), *reason = strtok (NULL, "");

		if (!mask || !setter || !realname || !set || !expires || !reason)
		    return;

		/* Ignore it if we already have it. */
		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 (reason);
		akill->setter = sstrdup (setter);
		akill->realname = atoi (realname);
		akill->set = atol (set);
		akill->expires = atoi (expires);

		akillcnt++;

		return;
	    }

	    /* RAKILL */
	    if (!stricmp (cmd, "2"))
	    {
		char *mask = strtok (param, " ");

 		/* Make sure we have it */
		if (!is_akilled (mask))
		    return;

		for (i = 0; i < akillcnt && stricmp (akills[i].mask, mask); i++);

		if (i < akillcnt)
		{
		    free (akills[i].mask);
		    free (akills[i].reason);
		    free (akills[i].setter);

		    akillcnt--;

		    if (i < akillcnt)
			memmove (akills + i, akills + i + 1, sizeof (*akills) * (akillcnt - i));
		}

		return;
	    }

	    /* AUTH */
	    if (!stricmp (cmd, "3"))
	    {
		User *u = finduser (param);

		if (u)
		    u->flags |= UF_SRA;

		return;
	    }

	    /* DEAUTH */
	    if (!stricmp (cmd, "4"))
	    {
		User *u = finduser (param);

		if (u)
		    u->flags &= ~UF_SRA;

		return;
	    }
	}
	else
	    globops (authserv (), "Received SVSSEND from unexpected server %s, please check "
		"your configuration.", source);

	return;
    }

    for (i = 0; i < HASHSIZE; i++)
    {       
	for (serverstat = statlist[i]; serverstat; serverstat = serverstat->next)
	{
	    if (!stricmp (serverstat->name, source))
	    {
		/* Some jackasses edit their IRCd's version reply,
		   or run OperStats with unsupported servers. We need
		   to make sure av[1] and av[3] actually EXIST here,
		   otherwise we crash. If we DONT find them, they'll
		   stay as the defaults of 'Unknown' and 'XX'.
		 */
		if (av[1])
		    serverstat->version = sstrdup (av[1]);
		if (av[3])
		    serverstat->flags = sstrdup (av[3]);
	    }
	}
    }
}

/* A server replied to an uptime query, store its reply. */
void m_statsu (char *source, int8 ac, char **av)
{
    ServStat *s = findservstat (source);

    s->uptimetime = time (NULL);
    s->uptime = parseuptime (av[1]);
}

/* Handle an AWAY command */
void m_away (char *source, int8 ac, char **av)
{
    User *u = finduser (source);

    if (ac == 0 || *av[0] == 0)
    {
	if (u->isaway)
	{
	    awaystat--;
	    u->isaway = 0;
	}
    }
    else
    {
	if (!u->isaway)
	{
	    awaystat++;
	    u->isaway = 1;
	}
    }
}

/* Reply to an INFO command */
void m_info (char *source, int8 ac, char **av)
{
    uint8 i;

    for (i = 0; infotext[i]; i++)
	send_cmd (me.name, "371 %s :%s", source, infotext[i]);

    send_cmd (me.name, "371 %s :operstats-%s", source, version);
    send_cmd (me.name, "371 %s :Birth Date: %s, compile # %s", source,
	compile_time, build);
    send_cmd (me.name, "371 %s :On-line since %s", source, ctime (&me.since));
    send_cmd (me.name, "374 %s :End of /INFO list.", source);
}

/* Reply to a NETINFO command. */
void m_netinfo (char *source, int8 ac, char **av)
{
    send_cmd (NULL, "%s %d %lu 0 %s * * * :%s", me.token ? "AO" : "NETINFO",
	usercnt, time (NULL), av[3], av[7]);

    globops (authserv (), RPL_SYNCHED, duration (time (NULL) - burststart, 1), userstat,
	chancnt, servstat);

#ifdef HAVE_GETTIMEOFDAY
    gettimeofday (&burstnow, NULL);
    timersub (&burstnow, &burstms, &bursttmp);

    if (tv2ms (&bursttmp) > 1000)
	log_sameline (0, "completed in %s.", duration (tv2ms (&bursttmp) / 1000, 1));
    else
	log_sameline (0, "completed in %d ms.", tv2ms (&bursttmp));
#endif

    bursting = 0;

    /* Set this so we don't bitch about the burst ;) */
    lastwarnclients = userstat;

    /* Now that the burst is done, run through every channel, and do the modechanges
       and topic changes as needed.
     */
    check_eb ();
}

/* Reply to a STATS query */
void m_stats (char *source, int8 ac, char **av)
{
    if (ac < 1)
	return;

    switch (*av[0])
    {
	case 'u':
	{
	    time_t uptime = time (NULL) - me.since;
	    uint8 tmp = servcount ();

	    send_cmd (me.name, "242 %s :Server Up %d days, %d:%02d:%02d", source,
		uptime/86400, (uptime/3600) % 24, (uptime/60) % 60, uptime % 60);
	    send_cmd (me.name, "250 %s :Highest connection count: %d (%d clients)",
		source, tmp + 1, tmp);
	    send_cmd (me.name, "219 %s u :End of /STATS report.", source);
	    break;
	}
	case '?':
	{
	    time_t uptime = (time (NULL) - me.since);
	    uint64 curspeedsample = speedsample;

	    send_cmd (me.name, "249 %s :Transfer Total  : %.2f %s (%.2f %s/s)", source, (float) SIZEREAL (transfer),
		SIZENAME (transfer), (float) SIZEREAL (transfer / uptime), SIZENAME (transfer / uptime));
	    send_cmd (me.name, "249 %s :Current Transfer: %.2f %s (%.2f %s/s)", source, (float)
		SIZEREAL (curspeedsample), SIZENAME (curspeedsample), (float) SIZEREAL (speedstat),
		SIZENAME (speedstat));
	}
	default:
	    send_cmd (me.name, "219 %s * :End of /STATS report.", source);
    }
}

/* Reply to a WHOIS query */
void m_whois (char *source, int8 ac, char **av)
{   
    if (ac < 1)
	return;   

    if ((!stricmp (av[0], s_OperServ)) && operserv_on == TRUE)
    {
	send_cmd (me.name, "311 %s %s %s %s * :%s", source, s_OperServ,
	    os.user, os.host, os.real);
	send_cmd (me.name, "312 %s %s %s :%s", source, s_OperServ,
	    me.name, me.desc);
	if (strchr (os.mode, 'o'))
	    send_cmd (me.name, "313 %s %s :is an IRC Operator", source,
		s_OperServ);
	send_cmd (me.name, "317 %s %s 0 %d :seconds idle, signon time",
	    source, s_OperServ, me.since);
    }
    else if ((!stricmp (av[0], s_StatServ)) && statserv_on == TRUE)
    {
	send_cmd (me.name, "311 %s %s %s %s * :%s", source, s_StatServ,
	    ss.user, ss.host, ss.real);
	send_cmd (me.name, "312 %s %s %s :%s", source, s_StatServ,
	    me.name, me.desc);
	if (strchr (ss.mode, 'o'))
	    send_cmd (me.name, "313 %s %s :is an IRC Operator", source,
		s_StatServ);
	send_cmd (me.name, "317 %s %s 0 %d :seconds idle, signon time",
	    source, s_StatServ, me.since);
    }
    else if ((!stricmp (av[0], s_GlobalNoticer)) && globalnoticer_on == TRUE)
    {
	send_cmd (me.name, "311 %s %s %s %s * :%s", source, s_GlobalNoticer,
	    gn.user, gn.host, gn.real);
	send_cmd (me.name, "312 %s %s %s :%s", source, s_GlobalNoticer,
	    me.name, me.desc);
	if (strchr (gn.mode, 'o'))
	    send_cmd (me.name, "313 %s %s :is an IRC Operator", source,
		s_GlobalNoticer);
	send_cmd (me.name, "317 %s %s 0 %d :seconds idle, signon time",
	    source, s_GlobalNoticer, me.since);
    }
    else if ((!stricmp (av[0], sp.nick)) && spamserv_on == TRUE)
    {
	send_cmd (me.name, "311 %s %s %s %s * :%s", source, sp.nick,
	    sp.user, sp.host, sp.real);
	send_cmd (me.name, "312 %s %s %s :%s", source, sp.nick,
	    me.name, me.desc);
	if (strchr (ss.mode, 'o'))
	    send_cmd (me.name, "313 %s %s :is an IRC Operator", source,
		sp.nick);
	send_cmd (me.name, "317 %s %s 0 %d :seconds idle, signon time",
	    source, sp.nick, me.since);
    }
    else
	send_cmd (me.name, "401 %s %s :No such nick/channel", source, av[0]);
    send_cmd (me.name, "318 %s %s :End of /WHOIS list", source, av[0]);
}

/* Handle a SERVER command */
void m_server (char *source, int8 ac, char **av)
{
    Server *server;
    ServStat *serverstat, **list;

    /* Allocate Server structure and fill it in. */
    server = new_server (av[0]);

    server->uplink = sstrdup (source);
    server->hops = atoi (av[1]);
    server->connect = time (NULL);
    gettimeofday (&(server->lastping), NULL);

    /* Make note of our uplink for anti-jupe purposes */
    if (atoi (av[1]) == 1)
	memcpy (me.ruplink, av[1], sizeof (me.ruplink));

    /* We'll only ping if StatServ is on, because the results aren't
       viewable otherwise. The VERSION reply isn't viewable either, but
       StatServ may be enabled later on.
     */
    send_cmd (me.name, "%s %s", me.token ? "+" : "VERSION", av[0]);
    send_cmd (me.name, "%s u %s", me.token ? "2" : "STATS", av[0]);

    if (statserv_on == TRUE)
	send_cmd (me.name, "%s %s %s", me.token ? "8" : "PING", me.name, av[0]);

    if (!(finduline (av[0]) && ignoreulines_on == TRUE))
	servstat++;

    if (servstat > maxservcnt)
    {
	maxservcnt = servstat;
	maxservtime = time (NULL);
    }
    if (servstat > dailyservstat)
    {
	dailyservstat = servstat;
	dailyservtime = time (NULL);
    }

    /* We keep this in a seperate list because it's easier ;) */
    serverstat = findservstat (av[0]);

    if (!serverstat)
    {
	serverstat = scalloc (sizeof (ServStat), 1);

	serverstat->name = sstrdup (av[0]);
	serverstat->version = "Unknown";
	serverstat->flags = "XX";
	serverstat->lastquit = "N/A";
	serverstat->statssince = time (NULL);
	serverstat->lastsplit = time (NULL);
	serverstat->lag.tv_sec = 999;
	serverstat->lag.tv_usec = 0;
	serverstat->penalize = 0;
	serverstat->is_uline = finduline (serverstat->name) ? 1 : 0;
	serverstat->is_split = 0;
	serverstat->splittime = 0;
	serverstat->lastdowntime = 0;
	serverstat->splits = 0;
	serverstat->uptime = 0;
	serverstat->maxuptime = 0;

	list = &statlist[SSHASH(serverstat->name)];

	serverstat->next = *list;

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

	*list = serverstat;
    }
    else
    {
	/* We'll fill these in just in case this server doesn't reply. */
	serverstat->version = sstrdup ("Unknown");
	serverstat->flags = sstrdup ("XX");

	/* These aren't in the db, and need to be set regardless. */
	serverstat->lag.tv_sec = 999;
	serverstat->lag.tv_usec = 0;

	if (serverstat->is_split)
	{
	    if (serverstat->penalize)
	    {
		serverstat->splittime += time (NULL) - serverstat->lastsplit;
		serverstat->lastdowntime = time (NULL) - serverstat->lastsplit;
		serverstat->penalize = 0;
	    }

	    serverstat->is_split = 0;
	}
    }
}

/* Handle an SQUIT command */
void m_squit (char *source, int8 ac, char **av)
{
    User *u;
    Server *s;
    ServStat *serverstat;
    int32 i, delncnt = 0, delscnt = 0;
    char **delnicks = NULL, **delserv = NULL;

    s = servlist[SVHASH(av[0])];

    if (s)
    {
	/* Add all the users for that server into a buffer. */
	for (u = firstuser (); u; u = nextuser ())
	{
	    if (u && !stricmp (s->name, u->server))
	    {
		++delncnt;
		delnicks = srealloc (delnicks, sizeof (char *) *delncnt);
		delnicks[delncnt - 1] = sstrdup (u->nick);

		/* If they're away, drop awaystat */
		if (u->isaway)
		    awaystat--;
	    }
	}

	/* Add the splitting server into a buffer */
	++delscnt;
	delserv = srealloc (delserv, sizeof (char *) *delscnt);
	delserv[delscnt - 1] = sstrdup (s->name);

	serverstat = findservstat (av[0]);

	if (serverstat)
	{
	    serverstat->splits++;
	    serverstat->penalize = 1;
	    serverstat->lastquit = sstrdup (av[1]);
	    serverstat->lastsplit = time (NULL);
	    serverstat->is_split = 1;
	}
    }

    /* Bahamut, Prometheus and Unreal 3.2 only send SQUITs for the splitting server,
       not servers linked to it. So we have to free those servers and their users here.
     */
    if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS || ircdtype == UNREAL3_2)
    {
	for (s = firstserv (); s; s = nextserv ())
	{
	    if (s && !stricmp (s->uplink, av[0]))
	    {
		for (u = firstuser (); u; u = nextuser ())
		{
		    if (u && !stricmp (s->name, u->server))
		    {
			++delncnt;
			delnicks = srealloc (delnicks, sizeof (char *) *delncnt);
			delnicks[delncnt - 1] = sstrdup (u->nick);

			/* If they're away, drop awaystat */
			if (u->isaway)
			    awaystat--;
		    }
		}

		++delscnt;
		delserv = srealloc (delserv, sizeof (char *) *delscnt);
		delserv[delscnt - 1] = sstrdup (s->name);

		/* Update this server's stats */
		serverstat = findservstat (s->name);

		if (serverstat)
		{
		    serverstat->lastquit = sstrdup (av[1]);
		    serverstat->lastsplit = time (NULL);
		    serverstat->is_split = 1;
		}
	    }
	}
    }

    /* Now we have two buffers, one with the users to delete, and one with servers.
       While we delete the users, we'll check if we're waiting for one of their
       nicknames.
     */
    for (i = 0; i < delncnt; delnicks++, i++)
    {
	u = finduser (*delnicks);

	if (u)
	{
	    if (os.wantnick && !stricmp (u->nick, 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 && !stricmp (u->nick, 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 && !stricmp (u->nick, 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 (u);
	}

	free (*delnicks);
    }
            
    /* Now free all the affected servers. */
    for (i = 0; i < delscnt; delserv++, i++)
    {
	s = findserv (*delserv);

	if (s)
	{
	    delete_server (s);

	    if (!(finduline (s->name) && ignoreulines_on == TRUE))
		servstat--;
	}

	free (*delserv);
    }

    delserv = NULL;
}

/* Handle a KILL command. */
void m_kill (char *source, int8 ac, char **av)
{
    if (ac != 2)
	return;

    /* Before we reintroduce the user, we'll check to see if they've
       been collided more than 5 times in the last 5 seconds. This is
       a common problem with people who forget to rename/disable services
       with the same nicks as some of ours. If they HAVE been collided
       that many times, we'll give them a nick like OperServ5671 and
       send a globops about it. We'll then watch for a nickchange/quit from
       the client with our nick, and retake it if the opportunity presents
       itself.
     */
    if ((operserv_on == TRUE) && (!stricmp (av[0], s_OperServ)))
    {
	/* How many times we've been collided */
	os.collided++;

	/* If this is true, we give up trying to retake our nick.. */
	if (((os.lastcoll >= (time (NULL) - 5)) && (os.collided >= 5)))
	{
	    /* Check if it's a server killing us. If it's not, then it's
	       likely a regular kill, and we don't want to take a waiting
	       nick.
	     */
	    if (!strchr (source, '.'))
	    {
		/* It's not a server. We'll reintro with the regular
		   nick and leave it at that.
		 */
		intro_user (s_OperServ, os.user, os.host, os.real, os.mode);
		return;
	    }

	    if (!os.wantnick)
	    {
		/* Make note of the nick we want */
		strscpy (os.nick, s_OperServ, sizeof (os.nick));
	    }

	    /* Make note that we want it */
	    os.wantnick = 1;

	    /* Give ourselves a new nick */
	    snprintf (s_OperServ, sizeof (s_OperServ), "%s%d", os.nick,
		getpid ());

	    /* Introduce with the new nick */
	    intro_user (s_OperServ, os.user, os.host, os.real, os.mode);

	    /* Let the staff know about it */
	    globops (s_OperServ, RPL_COLLISIONS, os.nick);

	    os.collided = 0;

	    return;
	}
	else
	    os.lastcoll = time (NULL);

	/* Re-introduce our client */
	intro_user (s_OperServ, os.user, os.host, os.real, os.mode);
    }
    else if ((statserv_on == TRUE) && !stricmp (av[0], s_StatServ))
    {
	/* How many times we've been collided */
	ss.collided++;

	/* If this is true, we give up trying to retake our nick.. */
	if (((ss.lastcoll >= (time (NULL) - 5)) && (ss.collided >= 5)))
	{
	    /* Check if it's a server killing us. If it's not, then it's
	       likely a regular kill, and we don't want to take a waiting
	       nick.
	     */
	    if (!strchr (source, '.'))
	    {
		/* It's not a server. We'll reintro with the regular
		   nick and leave it at that.
		 */
		intro_user (s_StatServ, ss.user, ss.host, ss.real, ss.mode);
		return;
	    }

	    if (!ss.wantnick)
	    {
		/* Make note of the nick we want */
		strscpy (ss.nick, s_StatServ, sizeof (ss.nick));
	    }

	    /* Make note that we want it */
	    ss.wantnick = 1;

	    /* Give ourselves a new nick */
	    snprintf (s_StatServ, sizeof (s_StatServ), "%s%d", ss.nick,
		getpid ());

	    /* Introduce with the new nick */
	    intro_user (s_StatServ, ss.user, ss.host, ss.real, ss.mode);

	    /* Let the staff know about it */
	    globops (s_StatServ, RPL_COLLISIONS, ss.nick);

	    ss.collided = 0;

	    return;
	}
	else
	    ss.lastcoll = time (NULL);

	/* Re-introduce our client */
	intro_user (s_StatServ, ss.user, ss.host, ss.real, ss.mode);
    }
    else if ((globalnoticer_on == TRUE) && !stricmp (av[0], s_GlobalNoticer))
    {
	/* How many times we've been collided */
	gn.collided++;

	/* If this is true, we give up trying to retake our nick.. */
	if (((gn.lastcoll >= (time (NULL) - 5)) && (gn.collided >= 5)))
	{
	    /* Check if it's a server killing us. If it's not, then it's
	       likely a regular kill, and we don't want to take a waiting
	       nick.
	     */
	    if (!strchr (source, '.'))
	    {
		/* It's not a server. We'll reintro with the regular
		   nick and leave it at that.
		 */
		intro_user (s_GlobalNoticer, gn.user, gn.host, gn.real,
		    gn.mode);
		return;
	    }

	    if (!gn.wantnick)
	    {
		/* Make note of the nick we want */
		strscpy (gn.nick, s_GlobalNoticer, sizeof (gn.nick));
	    }

	    /* Make note that we want it */
	    gn.wantnick = 1;

	    /* Give ourselves a new nick */
	    snprintf (s_GlobalNoticer, sizeof (s_GlobalNoticer), "%s%d",
		gn.nick, getpid ());

	    /* Introduce with the new nick */
	    intro_user (s_GlobalNoticer, gn.user, gn.host, gn.real, gn.mode);

	    /* Let the staff know about it */
	    globops (s_GlobalNoticer, RPL_COLLISIONS, gn.nick);

	    gn.collided = 0;

	    return;
	}
	else
	    gn.lastcoll = time (NULL);

	/* Re-introduce our client */
	intro_user (s_GlobalNoticer, gn.user, gn.host, gn.real, gn.mode);
    }
    else if ((spamserv_on == TRUE) && !stricmp (av[0], sp.nick))
    {
	char guest[NICKLEN];
	uint32 guestnick;

	/* Unlike above, we don't care if we get collided.. If we do, we'll
	   just pick a new nick. So we don't do any of the fancy collision
	   counting here. We'll make ourselves a new nick and introduce with
	   it.
	 */
	guestnick = 1+(int) (99999.0*rand()/(RAND_MAX+10000.0));

	/* Sometimes rand() gives us a 4 digit number. If we get one,
	   we'll add 10000 to it, bringing it into the 5 digit range.
	 */
	if (guestnick < 10000)
	    guestnick = guestnick + 10000;

	snprintf (guest, sizeof (guest), "Guest%d", guestnick);

	/* Now replace the nick */
	strscpy (sp.nick, guest, sizeof (sp.nick));

	/* Finally, introduce the client with the new nick. */
	intro_user (sp.nick, sp.user, sp.host, sp.real, "+");
    }
    else
	do_kill (source, ac, av);
}

/* Handle a nick collision */
void m_collide (char *source, int8 ac, char **av)
{
    /* Before we reintroduce the user, we'll check to see if they've
       been collided more than 5 times in the last 5 seconds. This is
       a common problem with people who forget to rename/disable services
       with the same nicks as some of ours. If they HAVE been collided
       that many times, we'll give them a nick like OperServ986662577 and
       send a globops about it. We'll then watch for a nickchange/quit from
       the client with our nick, and retake it if the opportunity presents
       itself.
     */
    if ((operserv_on == TRUE) && (!stricmp (av[1], s_OperServ)))
    {
	/* How many times we've been collided */
	os.collided++;

	/* If this is true, we give up trying to retake our nick.. */
	if (((os.lastcoll >= (time (NULL) - 5)) && (os.collided >= 5)))
	{
	    if (!os.wantnick)
	    {
		/* Make note of the nick we want */
		strscpy (os.nick, s_OperServ, sizeof (os.nick));
	    }

	    /* Make note that we want it */
	    os.wantnick = 1;

	    /* Give ourselves a new nick */
	    snprintf (s_OperServ, sizeof (s_OperServ), "%s%d", os.nick,
		getpid ());

	    /* Introduce with the new nick */
	    intro_user (s_OperServ, os.user, os.host, os.real, os.mode);

	    /* Let the staff know about it */
	    globops (s_OperServ, RPL_COLLISIONS, os.nick);

	    os.collided = 0;

	    return;
	}
	else
	    os.lastcoll = time (NULL);

	/* Re-introduce our client */
	intro_user (s_OperServ, os.user, os.host, os.real, os.mode);
    }
    else if ((statserv_on == TRUE) && !stricmp (av[1], s_StatServ))
    {
	/* How many times we've been collided */
	ss.collided++;

	/* If this is true, we give up trying to retake our nick.. */
	if (((ss.lastcoll >= (time (NULL) - 5)) && (ss.collided >= 5)))
	{
	    if (!ss.wantnick)
	    {
		/* Make note of the nick we want */
		strscpy (ss.nick, s_StatServ, sizeof (ss.nick));
	    }

	    /* Make note that we want it */
	    ss.wantnick = 1;

	    /* Give ourselves a new nick */
	    snprintf (s_StatServ, sizeof (s_StatServ), "%s%d", ss.nick,
		getpid ());

	    /* Introduce with the new nick */
	    intro_user (s_StatServ, ss.user, ss.host, ss.real, ss.mode);

	    /* Let the staff know about it */
	    globops (s_StatServ, RPL_COLLISIONS, ss.nick);

	    ss.collided = 0;

	    return;
	}
	else
	    ss.lastcoll = time (NULL);

	/* Re-introduce our client */
	intro_user (s_StatServ, ss.user, ss.host, ss.real, ss.mode);
    }
    else if ((globalnoticer_on == TRUE) && !stricmp (av[1], s_GlobalNoticer))
    {
	/* How many times we've been collided */
	gn.collided++;

	/* If this is true, we give up trying to retake our nick.. */
	if (((gn.lastcoll >= (time (NULL) - 5)) && (gn.collided >= 5)))
	{
	    if (!gn.wantnick)
	    {
		/* Make note of the nick we want */
		strscpy (gn.nick, s_GlobalNoticer, sizeof (gn.nick));
	    }

	    /* Make note that we want it */
	    gn.wantnick = 1;

	    /* Give ourselves a new nick */
	    snprintf (s_GlobalNoticer, sizeof (s_GlobalNoticer), "%s%d",
		gn.nick, getpid());

	    /* Introduce with the new nick */
	    intro_user (s_GlobalNoticer, gn.user, gn.host, gn.real, gn.mode);

	    /* Let the staff know about it */
	    globops (s_GlobalNoticer, RPL_COLLISIONS, gn.nick);

	    gn.collided = 0;

	    return;
	}
	else
	    gn.lastcoll = time (NULL);

	/* Re-introduce our client */
	intro_user (s_GlobalNoticer, gn.user, gn.host, gn.real, gn.mode);
    }
    else if ((spamserv_on == TRUE) && !stricmp (av[0], sp.nick))
    {
	char guest[NICKLEN];
	uint32 guestnick;

	/* Unlike above, we don't care if we get collided.. If we do, we'll
	   just pick a new nick. So we don't do any of the fancy collision
	   counting here. We'll make ourselves a new nick and introduce with
	   it.
	 */
	guestnick = 1+(int) (99999.0*rand()/(RAND_MAX+10000.0));

	/* Sometimes rand() gives us a 4 digit number. If we get one,
	   we'll add 10000 to it, bringing it into the 5 digit range.
	 */
	if (guestnick < 10000)
	    guestnick = guestnick + 10000;

	snprintf (guest, sizeof (guest), "Guest%d", guestnick);

	/* Now replace the nick */
	strscpy (sp.nick, guest, sizeof (sp.nick));

	/* Finally, introduce the client with the new nick. */
	intro_user (sp.nick, sp.user, sp.host, sp.real, "+");
    }
}

/* Handle a PROTOCTL command */
void m_protoctl (char *source, int8 ac, char **av)
{
    uint16 i, token = 0;
    uint8 nickv2 = 0, umode2 = 0;

    for (i = 0; i < ac; i++)
    {
	if (!stricmp (av[i], "TOKEN"))
	    token = 1;

	if (!stricmp (av[i], "NICKv2"))
	    nickv2 = 1;

	if (!stricmp (av[i], "UMODE2"))
	    umode2 = 1;
    }

    if (token)
	me.token = 1;

    if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
    {
	if (!nickv2 || !umode2)
	{
	    send_cmd (NULL,
		"ERROR :Incompatible IRCd (I'm configured to support Unreal 3.1+)");
	    snprintf (quitmsg, BUFSIZE,
		"Incompatible IRCd (I'm configured to support Unreal 3.1+)");
	    runflags |= RUN_SHUTDOWN;
	}
    }
}

/* Handle a MODE command */
void m_mode (char *source, int8 ac, char **av)
{
    if (*av[0] == '#')
    {
	if (ac < 2)
	    return;

	do_cmode (source, ac, av);
    }
    else
    {
	if (ac != 2)
	    return;

	do_umode (source, ac, av);
    }
}

/* Handle an UnrealIRCd UMODE2 command */
void m_umode2 (char *source, int8 ac, char **av)
{
    av[1] = sstrdup (av[0]);
    strcpy (av[0], source);

    do_umode (source, ac, av);
}

/* Change the users fake hostname */
void m_sethost (char *source, int8 ac, char **av)
{
    User *u;

    if (ac != 1)
	return;

    u = finduser (source);

    if (!u)
	return;

    free (u->host);

    u->host = sstrdup (av[0]);
}

/* CHGHOST. Similar to SETHOST, use the same function. */
void m_chghost (char *source, int8 ac, char **av)
{
    if (ac == 2)
	m_sethost (av[0], ac-1, av+1);
}

/* Change a users ident (aka username) */
void m_setident (char *source, int8 ac, char **av)
{
    User *u;

    if (ac != 1)
	return;

    u = finduser (source);

    if (!u)
	return;

    free (u->user);

    u->user = sstrdup (av[0]);
}

/* CHGIDENT. Similar to SETIDENT, use the same function. */
void m_chgident (char *source, int8 ac, char **av)
{
    if (ac == 2)
	m_setident (av[0], ac-1, av+1);
}

/* Change a users realname. */
void m_setname (char *source, int8 ac, char **av)
{
    User *u;

    if (ac != 1)
	return;

    u = finduser (source);

    if (!u)
        return;

    free (u->real);

    u->real = sstrdup (av[0]);
}

/* CHGNAME. Similar to SETNAME, use the same function. */
void m_chgname (char *source, int8 ac, char **av)
{
    if (ac == 2)
	m_setname (av[0], ac-1, av+1);
}

/* Handle the ENDBURST command */
void m_endburst (char *source, int8 ac, char **av)
{
    globops (authserv (), RPL_SYNCHED, duration (time (NULL) - burststart, 1), userstat,
	chanstat, servstat);

    bursting = 0;

#ifdef HAVE_GETTIMEOFDAY
    gettimeofday (&burstnow, NULL);
    timersub (&burstnow, &burstms, &bursttmp);

    if (tv2ms (&bursttmp) > 1000)
	log_sameline (0, "completed in %s.", duration (tv2ms (&bursttmp) / 1000, 1));
    else
	log_sameline (0, "completed in %d ms.", tv2ms (&bursttmp));
#endif

    /* Set this so we don't bitch about the burst ;) */
    lastwarnclients = userstat;

    /* Send out any PINGs and VERSIONs for servers. */
    check_eb ();
}

/* Handle PRIVMSGs. */
void m_privmsg (char *source, int8 ac, char **av)
{
    User *u;
    char buf[BUFSIZE], text[BUFSIZE];

    if (ac != 2)
	return;

    u = finduser (source);

    if (!u)
    {
	log ("No user record found for %s!", source);
	notice (authserv (), source, RPL_YOU_DONT_EXIST);
	notice (authserv (), source, RPL_PLEASE_RECONNECT);
	return;
    }

    /* Run it through flood checks */
    if (floodmsgs)
    {
	time_t now = time (NULL);

	/* If it's a channel PRIVMSG, ignore it. We have nothing to do with
	   channels.
	 */
	if (av[0][0] == '#')
	    return;

	/* Check if they're being ignored. */
	if (u->offences > 10)
	{
	    /* They're being ignored. Check if the ignore expired. */
	    if (now - u->lastmsg > 30)
	    {
		/* They're not being ignored anymore. */
		u->offences = u->offences - 10;
		u->lastmsg = now;
		u->msgs = 0;
	    }
	    else
		return;
	}

	if (now - u->lastmsg > floodtime)
	{
	    u->lastmsg = now;
	    u->msgs = 0;
	}

	u->msgs++;

	if (u->msgs > floodmsgs)
	{
	    /* They set off the trigger. Take action. */
	    if (!u->offences)
	    {
		/* First ignore. */
		u->lastmsg = now;
		u->msgs = 0;

		/* So we know we're ignoring them */
		u->offences = 11;

		notice (authserv (), u->nick, OS_FLOOD_TRIGGERED);
		notice (authserv (), u->nick, OS_FLOOD_TRIGGER, floodmsgs,
		    duration (floodtime, 2));
		notice (authserv (), u->nick, OS_FLOOD_FIRST);

		/* Complain in GlobOps */
		if (noisyflood_on == TRUE)
		    globops (authserv (), OS_FLOOD_GLOBOPS, u->nick, "");

		return;
	    }

	    if (u->offences == 1)
	    {
		/* Second ignore. */
		u->lastmsg = now;
		u->msgs = 0;

		/* So we know we're ignoring them */
		u->offences = 12;

		notice (authserv (), u->nick, OS_FLOOD_TRIGGERED);
		notice (authserv (), u->nick, OS_FLOOD_TRIGGER, floodmsgs,
		    duration (floodtime, 2));
		notice (authserv (), u->nick, OS_FLOOD_SECOND, operserv_on == TRUE ? "AK" : "k");

		/* Complain in GlobOps */
		if (noisyflood_on == TRUE)
		    globops (authserv (), OS_FLOOD_GLOBOPS, u->nick, "");

		return;
	    }

	    if (u->offences == 2)
	    {
		AutoKill *akill;
		char mask[BUFSIZE], *username, *hostname, msgbuf[31];

		/* Third offence. AKill. */
		notice (authserv (), u->nick, OS_FLOOD_TRIGGERED);
		notice (authserv (), u->nick, OS_FLOOD_TRIGGER, floodmsgs,
		    duration (floodtime, 2));

		/* Complain in GlobOps */
		if (noisyflood_on == TRUE)
		    globops (authserv (), OS_FLOOD_GLOBOPS, u->nick, " (Killed)");

		snprintf (msgbuf, sizeof (msgbuf), "%s", av[1]);

		log ("OS:FLOOD:KILL: %s!%s@%s (%s%s)", u->nick, u->user, u->host, msgbuf,
		   strlen (msgbuf) == 30 ? "..." : "");

		u->offences = 13;

		snprintf (mask, sizeof (mask), "*@%s", u->host);

		/* If OperServ isn't on, we can't AKill.. Ah well. */
		if (operserv_on == FALSE)
		{
		    kill_user (s_StatServ, u->nick, OS_AKILL_FLOOD);
		    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->setter = sstrdup (s_OperServ);
		akill->realname = 0;
		akill->set = now;
		akill->expires = 1800;
		akill->reason = sstrdup (OS_AKILL_FLOOD);

		akillcnt++;

		username = sstrdup (akill->mask);
		hostname = strchr (username, '@');

		if (!hostname)
		    return;

		*hostname++ = 0;

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

		free (username);

		return;
	    }
	}
    }

    /* OperServ? */
    if (stristr (av[0], s_OperServ))
    {
	/* What a nick@server for OperServ looks like */
	snprintf (buf, sizeof (buf), "%s@%s", s_OperServ, me.name);

	/* Do our checks on a copy of the original */
	snprintf (text, sizeof (text), "%s", av[1]);

	/* Check if we should deny a message via security settings */
	if (stricmp (av[0], buf) && ((securitysetting == 1 ||
	    (securitysetting == 2 && ispass (text)))
	    && !isctcp (text)))
	{
	    /* Tell them to use nick@server and bail */
	    if (haveserv_on == TRUE)
		notice (s_OperServ, u->nick, RPL_SECURE_MESSAGES, "",
		    s_OperServ, "", "");
	    else
		notice (s_OperServ, u->nick, RPL_SECURE_MESSAGES, "MSG ",
		    s_OperServ, "@", me.name);
	    return;
	}
	else
	{
	    /* Passed security checks, throw it through OperServ */
	    operserv (u, av[1]);
	    return;
	}
    }

    /* StatServ? */
    if (stristr (av[0], s_StatServ))
    {
	/* What a nick@server for StatServ looks like */
	snprintf (buf, sizeof (buf), "%s@%s", s_StatServ, me.name);

	/* Do our checks on a copy of the original */
	snprintf (text, sizeof (text), "%s", av[1]);

	/* Check if we should deny a message via security settings */
	if (stricmp (av[0], buf) && ((securitysetting == 1 ||
	    (securitysetting == 2 && ispass (text)))
	    && !isctcp (text)))
	{
	    /* Tell them to use nick@server and bail */
	    if (haveserv_on == TRUE)
		notice (s_StatServ, u->nick, RPL_SECURE_MESSAGES, "",
		    s_StatServ, "", "");
	    else
		notice (s_StatServ, u->nick, RPL_SECURE_MESSAGES, "MSG ",
		    s_StatServ, "@", me.name);
	    return;
	}
	else
	{
	    /* Passed security checks, throw it through StatServ */
	    statserv (u, av[1]);
	    return;
	}
    }

    snprintf (buf, sizeof (buf), "%s@%s", s_GlobalNoticer, me.name);
    if (!stricmp (av[0], s_GlobalNoticer) || !stricmp (av[0], buf))
    {
	globalnoticer (u, av[1]);
	return;
    }

    snprintf (buf, sizeof (buf), "%s@%s", sp.nick, me.name);
    if (!stricmp (av[0], sp.nick) || !stricmp (av[0], buf))
    {
	spamserv (u, av[1]);
	return;
    }
}

Message messages[] = {

    /* These commands are ignored. */
    {"NOTICE",	NULL},
    {"GNOTICE",	NULL},
    {"PASS",	NULL},
    {"CAPAB",	NULL},
    {"SVINFO",	NULL},

    /* These commands are used by every IRCd we support. They're ordered
       by frequency of use to speed up processing.
     */
    {"PRIVMSG",	m_privmsg},
    {"NICK",	m_nick},
    {"MODE",	m_mode},
    {"SJOIN",	m_sjoin},
    {"JOIN",	m_join},
    {"PART",	m_part},
    {"KICK",	m_kick},
    {"AWAY",	m_away},
    {"TOPIC",	m_topic},
    {"QUIT",	m_quit},
    {"PING",	m_ping},
    {"PONG",	m_pong},
    {"KILL",	m_kill},
    {"UMODE2",	m_umode2},
    {"SETIDENT",m_setident},
    {"SETHOST",	m_sethost},
    {"SETNAME", m_setname},
    {"CHGHOST",	m_chghost},
    {"CHGIDENT",m_chgident},
    {"CHGNAME",	m_chgname},
    {"VERSION",	m_version},
    {"INFO",	m_info},
    {"WHOIS",	m_whois},
    {"STATS",	m_stats},
    {"MOTD",	m_motd},
    {"TIME",	m_time},
    {"351",	m_vreply},
    {"242",	m_statsu},
    {"ADMIN",	m_admin},
    {"436",	m_collide},
    {"SQUIT",	m_squit},
    {"NETINFO",	m_netinfo},
    {"SERVER",	m_server},
    {"PROTOCTL",m_protoctl},
    {"EB",	m_endburst},
    {"ERROR",	m_error},

    /* These commands are ignored. */
    {"B",	NULL},
    {"Z",	NULL},
    {"<",	NULL},

    /* Tokens - DreamForge and Unreal support these tokens. */
    {"!",	m_privmsg},
    {"&",	m_nick},
    {"G",	m_mode},
    {"~",	m_sjoin},
    {"C",	m_join},
    {"D",	m_part},
    {"H",	m_kick},
    {"6",	m_away},
    {")",	m_topic},
    {",",	m_quit},
    {"8",	m_ping},
    {"9",	m_pong},
    {".",	m_kill},
    {"|",	m_umode2},
    {"AD",	m_setident},
    {"AA",	m_sethost},
    {"AE",	m_setname},
    {"AL",	m_chghost},
    {"AZ",	m_chgident},
    {"BK",	m_chgname},
    {"+",	m_version},
    {"/",	m_info},
    {"#",	m_whois},
    {"2",	m_stats},
    {"F",	m_motd},
    {">",	m_time},
    {"@",	m_admin},
    {"-",	m_squit},
    {"AO",	m_netinfo},
    {"'",	m_server},
    {"_",	m_protoctl},
    {"5",	m_error},
    {NULL}
};

Message *find_message (const char *name)
{
    Message *m;

    for (m = messages; m->name; m++)
	if (!stricmp (name, m->name))
	    return m;

    return NULL;
}
