/* Miscellaneous functions

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

   See doc/LICENSE for licensing details.
 */

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

static FILE *log_file;
static FILE *debug_file;

/* Open the logfile */
void open_log ()
{
    if (log_file)
	return;

    log_file = fopen (LOGFILE, "a");

    if (log_file)
	setbuf (log_file, NULL);
}

/* Open the debug log */
void open_debug ()
{
    if (debug_file)
	return;

    debug_file = fopen ("debug.log", "a");

    if (debug_file)
	setbuf (debug_file, NULL);
}

/* Log something */
void log (const char *fmt,...)
{
    va_list args;
    time_t t;
    struct tm tm;
    char buf[32];

    va_start (args, fmt);
    time (&t);
    tm = *localtime (&t);
    strftime (buf, sizeof (buf) - 1, "[%d/%m %H:%M:%S] ", &tm);

    if (!log_file)
	open_log ();

    if (log_file)
    {
	fputs (buf, log_file);
	vfprintf (log_file, fmt, args);
	fputc ('\n', log_file);
	fflush (log_file);
    }

    if (runflags & RUN_LIVE)
    {
	fputs (buf, stderr);
	vfprintf (stderr, fmt, args);
	fputc ('\n', stderr);
    }

#ifdef WIN32
    append_logger_v (-1, fmt, args);
#endif
}

/* Special log handling. If sameline is 1, the
   entry will have a timestamp, but no newline.
   Otherwise it'll have a newline, but no timestamp.

   If sameline is 2, it'll have no timestamp AND no newline.
 */
void log_sameline (uint8 sameline, const char *fmt,...)
{
    va_list args;
    time_t t;
    struct tm tm;
    char buf[32];

    va_start (args, fmt);

    if (sameline == 1)
    {
	time (&t);
	tm = *localtime (&t);
	strftime (buf, sizeof (buf) - 1, "[%d/%m %H:%M:%S] ", &tm);
    }

    if (!log_file)
	open_log ();

    if (log_file)
    {
	if (sameline)
	    fputs (buf, log_file);

	vfprintf (log_file, fmt, args);

	if (!sameline)
	    fputc ('\n', log_file);

	fflush (log_file);
    }

    if (runflags & RUN_LIVE)
    {
	if (sameline)
	    fputs (buf, stderr);

	vfprintf (stderr, fmt, args);

	if (!sameline)
	    fputc ('\n', stderr);
    }

#ifdef WIN32
    append_logger_v (sameline, fmt, args);
#endif
}

/* Write to our debug log */
void debug (const char *fmt,...)
{
    va_list args;
    time_t t;
    struct tm tm;
    char buf[32];

    va_start (args, fmt);
    time (&t);
    tm = *localtime (&t);
    strftime (buf, sizeof (buf) - 1, "[%d/%m %H:%M:%S] ", &tm);

    if (!debug_file)
	open_debug ();

    if (debug_file)
    {
	fputs (buf, debug_file);
	vfprintf (debug_file, fmt, args);
	fputc ('\n', debug_file);
	fflush (debug_file);
    }

    if (runflags & RUN_LIVE)
    {
	fputs (buf, stderr);
	vfprintf (stderr, fmt, args);
	fputc ('\n', stderr);
    }
}

/* Close the logfile */
void close_log ()
{
    if (!log_file)
	return;

    fclose (log_file);

    log_file = NULL;
}

/* Close the debug log */
void close_debug ()
{
    if (!debug_file)
	return;

    fclose (debug_file);

    debug_file = NULL;
}

/* Fatal error: Log it, globop it if we're still connected, then die. */
void fatal (uint8 sameline, const char *fmt,...)
{
    va_list args;
    char buf[BUFSIZE];

    va_start (args, fmt);    
    vsnprintf (buf, sizeof (buf), fmt, args);

    log_sameline (sameline, "FATAL: %s", buf);

    if (servsock > 0)
	globops (authserv (), "Fatal Error: %s", buf);

    exit (1);
}

void vsend_cmd (const char *source, const char *fmt, va_list args)
{
    char buf[2048];
    
    vsnprintf (buf, sizeof (buf), fmt, args);

    if (source)
    {
	sendtoserver (":%s %s\r\n", source, buf);
	if (debuglevel == 1 || debuglevel == 3)
	    debug ("<-- :%s %s", source, buf);
    }
    else
    {
	sendtoserver ("%s\r\n", buf);
	if (debuglevel == 1 || debuglevel == 3)
	    debug ("<-- %s", buf);
    }
}

/* Send a command to the server.  The two forms here are like
   printf()/vprintf() and friends.
 */
void send_cmd (const char *source, const char *fmt,...)
{
    va_list args;

    va_start (args, fmt);
    vsend_cmd (source, fmt, args);
    va_end (args);
}

/* Send a globops */
void globops (const char *sender, const char *fmt,...)
{
    va_list args;
    char buf[2048];
     
    va_start (args, fmt);
    vsnprintf (buf, sizeof (buf), fmt, args);

    send_cmd (sender ? sender : me.name, "%s :%s", me.token ? "]" : "GLOBOPS", buf);
}

/* PRIVMSG our snooping channel */
void snoop (const char *source, const char *fmt,...) 
{
    va_list args;
    char buf[2048];
    time_t t;
    struct tm tm;
    char timebuf[256];

    if (findchan (snoopchan))
    {
	time (&t);
	tm = *localtime (&t);
	strftime (timebuf, sizeof (timebuf) - 1, "[%H:%M:%S]", &tm);
	va_start (args, fmt);
	vsnprintf (buf, sizeof (buf), fmt, args);
	send_cmd (source, "%s %s :%s %s", me.token ? "!" :
	    "PRIVMSG", snoopchan, timebuf, buf);
    }
}

/* Send a notice to a user */
void notice (const char *source, const char *dest, const char *fmt,...)
{
    va_list args;
    char buf[2048];

    va_start (args, fmt);
    vsnprintf (buf, sizeof (buf), fmt, args);

    send_cmd (source, "%s %s :%s", me.token ? "B" : "NOTICE", dest, buf);
}

/* Reply to a CTCP command. */
void ctcpreply (const char *source, const char *dest, const char *fmt,...)
{
    va_list args;
    char buf[2048];

    va_start (args, fmt);
    vsnprintf (buf, sizeof (buf), fmt, args);

    send_cmd (source, "%s %s :\001%s\001", me.token ? "B" : "NOTICE", dest, buf);
}

/* Send a global notice to all users. */
void noticeall (const char *dest, const char *fmt,...)
{
    va_list args;
    char *tmp = strtok (tlds, " ");
    char buf[BUFSIZE];

    va_start (args, fmt);
    vsnprintf (buf, sizeof (buf), fmt, args);

    /* Is the GlobalNoticer on? Bail out if not.
       (We could easily use OperServ for this, but if people don't have
       the GlobalNoticer on, they probably use something else for notices.)
     */
    if (globalnoticer_on == FALSE)
	return;

    /* If a destination for the notice was specifically given, notice that. */
    if (dest)
    {
	send_cmd (s_GlobalNoticer, "%s $*.%s :%s", me.token ? "B" : "NOTICE", dest, buf);
	return;
    }

    /* Unreal lets us notice $*, so we'll do that. */
    if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	send_cmd (s_GlobalNoticer, "%s $* :%s", me.token ? "B" : "NOTICE", buf);
    else
    {
	/* Notice the first TLD */
	send_cmd (s_GlobalNoticer, "%s $*.%s :%s", me.token ? "B" : "NOTICE",
	    tmp, buf);
        
	/* Now run through the list and notice the remaining tlds.
	   This will terminate when we run out of tlds..
	 */
	for (;;)
	{
	    tmp = strtok (NULL, " ");

	    if (tmp)
		send_cmd (s_GlobalNoticer, "%s $*.%s :%s", me.token ? "B" :
		    "NOTICE", tmp, buf);
	    else
		break;
	}
    }
}       

/* strscpy:  Copy at most len-1 characters from a string to a buffer, and
             add a null terminator after the last character copied.
 */
char *strscpy (char *d, const char *s, size_t len)
{
    char *d_orig = d;

    if (!len)
	return d;
    while (--len && (*d++ = *s++))
	;
    *d = 0;
    return d_orig;
}

void *smalloc (uint32 size)
{
    void *buf;

    buf = malloc (size);
    if (!buf)
        raise (SIGUSR1);
    return buf;
}  

void *scalloc (uint32 elsize, uint32 els)  
{
    void *buf = calloc (elsize, els);
    if (!buf)
	raise (SIGUSR1);
    return buf;
}

void *srealloc (void *oldptr, uint32 newsize)
{   
    void *buf = realloc (oldptr, newsize);
    if (!buf)
        raise (SIGUSR1);
    return buf;
}

char *sstrdup (const char *s)
{
    char *t = smalloc (strlen (s) + 1);
    strcpy (t, s);
    return t;
}

/* strlcat: Not all systems (ie, linux) have strlcat, so here it is. */
size_t sstrlcat (char *dst, const char *src, size_t siz)
{
    char *d = dst;
    const char *s = src;
    size_t n = siz, dlen;

    while (*d != '\0' && n-- != 0)
	d++;

    dlen = d - dst;
    n = siz - dlen;

    if (!n)
	return (dlen + strlen (s));

    while (*s != '\0')
    {
	if (n != 1)
	{
	    *d++ = *s;
	    n--;
	}

	s++;
    }

    *d = '\0';

    return (dlen + (s - src));
}

/* strlcpy: Not all systems (ie, linux) have strlcpy, so here it is. */
size_t sstrlcpy (char *dst, const char *src, size_t siz)
{
    char *d = dst;
    const char *s = src;
    size_t n = siz;

    if (n != 0 && --n != 0)
    {
	do
	{
	    if (!(*d++ = *s++))
		break;
	} while (--n != 0);
    }

    if (!n)
    {
	if (siz != 0)
	    *d = '\0';

	while (*s++);
    }

    return (s - src - 1);
}

/* Hash table for command access */
Hash *get_hash (const char *whoami, User *u, const char *cmd, Hash *hash_table)
{
    for (; hash_table->accept; ++hash_table)
	if (match_wild_nocase (hash_table->accept, cmd))
	{
	    if ((hash_table->access == H_IRCOP && is_oper (u)) ||
		(hash_table->access == H_SRA && is_sra (u)) ||
		(hash_table->access == H_NONE))
		return hash_table;
	    else if ((hash_table->access == H_IRCOP && !is_oper (u)) ||
		     (hash_table->access == H_SRA && !is_sra (u)))
	    {
		notice (whoami, u->nick, RPL_ACCESS_DENIED);
		return NULL;
	    }
	}

    notice (whoami, u->nick, RPL_UNKNOWN_COMMAND, cmd, haveserv_on == TRUE ? "" : "MSG ",
	whoami, haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
	haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "");

    return NULL;
}

/* Subcommand hash - Similar to above, but no error if the command isn't
   found, the function that called this will handle that.
 */
Hash *get_sub_hash (const char *whoami, User *u, const char *cmd, Hash *hash_table)
{
    for (; hash_table->accept; ++hash_table)
	if (match_wild_nocase (hash_table->accept, cmd))
	{
	    if ((hash_table->access == H_IRCOP && is_oper (u)) ||
		(hash_table->access == H_SRA && is_sra (u)) ||
		(hash_table->access == H_NONE))
		return hash_table;
	    else if ((hash_table->access == H_IRCOP && !is_oper (u)) ||
		     (hash_table->access == H_SRA && !is_sra (u)))
		{
		    notice (whoami, u->nick, RPL_ACCESS_DENIED);
		    return NULL;
		}
	}
    return NULL;
}

/* Hash function for help tables */
Hash *get_help_hash (const char *whoami, User *u, const char *cmd, Hash *hash_table)
{
    for (; hash_table->accept; ++hash_table)
	if (match_wild_nocase (hash_table->accept, cmd))
	{
	    if ((hash_table->access == H_IRCOP && is_oper (u)) ||
		(hash_table->access == H_SRA && is_sra (u)) ||
		(hash_table->access == H_NONE))
		return hash_table;
	    else if ((hash_table->access == H_IRCOP && !is_oper (u)) ||
		     (hash_table->access == H_SRA && !is_sra (u)))
	    {
		notice (whoami, u->nick, RPL_ACCESS_DENIED);
		return NULL;
	    }
	}

    notice (whoami, u->nick, RPL_NO_HELP, cmd);
    return NULL;
}

/* Is the given nick an oper? */
uint8 is_oper (User *u)  
{
    return (u && (u->mode & UMODE_o));
}

/* Is the given nick an SRA? */
uint8 is_sra (User *u)
{
    return (u && (u->flags & UF_SRA));
}

/* Search case-insensitively for string s2 within string s1 */
char *stristr (char *s1, char *s2)
{ 
    register char *s = s1, *d = s2;

    while (*s1)
	if (tolower (*s1) == tolower (*d))
	{
	    s1++;
	    d++; 
	    if (*d == 0)
		return s;
	}
	else
	{
	    s = ++s1;
	    d = s2;  
	}
    return NULL;
}

/* Find a wildcard match */
static uint8 do_match_wild (const char *pattern, const char *str, uint8 docase)
{
    char c;
    const char *s;

    for (;;)
    {
	switch (c = *pattern++)
	{
	    case 0:
		if (!*str)
		    return 1;
		return 0;
	    case '?':
		if (!*str)
		    return 0;
		str++;
		break;
	    case '*':
		if (!*pattern)
		    return 1;
		s = str;
		while (*s)
		{
		    if ((docase ? (*s==*pattern) : (tolower (*s)==tolower
			(*pattern))) && do_match_wild (pattern, s, docase))
			return 1;
		    s++;
		}
		break;
	    default:
		if (docase ? (*str++ != c) : (tolower(*str++) != tolower(c)))
		    return 0;
		break;
	}
    }
}

/* Call do_match_wild case-sensitively */
uint8 match_wild (const char *pattern, const char *str)
{
    return do_match_wild (pattern, str, 1);
}

/* Call do_match_wild case-insensitively */
uint8 match_wild_nocase (const char *pattern, const char *str)
{
    return do_match_wild (pattern, str, 0);
}

/* strupper: Convert a string to upper case. */
char *strupper (char *s)
{
    char *t = s;

    while (*t)
    {
	*t = toupper (*t);
	t++;
    }

    return s;
}

/* strlower: Convert a string to lower case. */
char *strlower (char *s)
{
    char *t = s;

    while (*t)
    {
	*t = tolower (*t);
	t++;
    }

    return s;
}

/* Strip unwanted chars out of lines - Borrowed from GeoStats */
void strip (char *line)
{  
    char *c;

    if (line)
    {
	if ((c = strchr (line, '\n')))
	    *c = '\0';
	if ((c = strchr (line, '\r')))
	    *c = '\0';
	if ((c = strchr (line, '\1')))
	    *c = '\0';
    }
}

/* Is it one of ours? */
uint8 is_one_of_mine (const char *nick)
{
    if ((operserv_on == TRUE && !stricmp (nick, s_OperServ)) ||
        (statserv_on == TRUE && !stricmp (nick, s_StatServ)) ||
	(globalnoticer_on == TRUE && !stricmp (nick, s_GlobalNoticer)) ||
	(spamserv_on == TRUE && !stricmp (nick, sp.nick)))
        return 1;
 
    return 0;
}

/* Return the date an event happened
   If fulldate is:
	0:	Sun, Jan 01, 2000
	1:	Sun, Jan 01, 2000, 00:00:00 GMT
	2:	00:00:00 GMT
	3:	01/01/2000, 00:00:00 GMT
	4:	Jan 01, 2000, 00:00:00 GMT
 */
char *get_time (time_t event, uint8 fulldate)
{
    struct tm tm;
    static char timebuf[256];

    tm = *gmtime (&event);

    /* Date and time, or just date */
    if (fulldate == 1)
	strftime (timebuf, sizeof (timebuf), "%a, %b %d, %Y, %H:%M:%S GMT",
	    &tm);
    else if (fulldate == 2)
	strftime (timebuf, sizeof (timebuf), "%H:%M:%S GMT", &tm);
    else if (fulldate == 3)
	strftime (timebuf, sizeof (timebuf), "%d/%m/%Y %H:%M:%S GMT", &tm);
    else if (fulldate == 4)
	strftime (timebuf, sizeof (timebuf), "%b %d, %Y, %H:%M:%S GMT", &tm);
    else
	strftime (timebuf, sizeof (timebuf), "%a, %b %d, %Y", &tm);

    timebuf[sizeof(timebuf)-1] = 0;
    return timebuf;
}

/* Return the time with the given offset. */
char *zone_time (int32 offset)
{
    struct tm tm;
    static char timebuf[256];
    time_t event;

    event = time (NULL) + offset;

    tm = *gmtime (&event);

    strftime (timebuf, sizeof (timebuf), "%H:%M:%S, %b %d, %Y", &tm);

    timebuf[sizeof(timebuf)-1] = 0;
    return timebuf;
}

/* Return the time elapsed since an event */
char *time_ago (time_t event)
{
    static char ret[128];
    uint16 years, weeks, days, hours, minutes, seconds;

    event = time (NULL) - event;
    years = weeks = days = hours = minutes = seconds = 0;

    while (event >= 31536000)
    {
	event -= 31536000;
	years++;
    }
    while (event >= 604800)
    {
	event -= 604800;
	weeks++;
    }
    while (event >= 86400)
    {
	event -= 86400;
	days++; 
    }
    while (event >= 3600)
    {
	event -= 3600;
	hours++;
    }
    while (event >= 60)
    {
	event -= 60;
	minutes++;
    }

    seconds = event;

    if (years)
	snprintf (ret, sizeof (ret), "%d year%s, %d week%s, %d day%s, %02d:%02d:%02d",
	    years, years == 1 ? "" : "s", weeks, weeks == 1 ? "" : "s",
	    days, days == 1 ? "" : "s", hours, minutes, seconds);
    else if (weeks)
	snprintf (ret, sizeof (ret), "%d week%s, %d day%s, %02d:%02d:%02d",
	    weeks, weeks == 1 ? "" : "s", days, days == 1 ? "" : "s",
	    hours, minutes, seconds);
    else if (days)
	snprintf (ret, sizeof (ret), "%d day%s, %02d:%02d:%02d",
	    days, days == 1 ? "" : "s", hours, minutes, seconds);
    else if (hours)
	snprintf (ret, sizeof (ret), "%d hour%s, %d minute%s, %d second%s",
	    hours, hours == 1 ? "" : "s", minutes, minutes == 1 ? "" : "s",
	    seconds, seconds == 1 ? "" : "s");
    else if (minutes)
	snprintf (ret, sizeof (ret), "%d minute%s, %d second%s",
	    minutes, minutes == 1 ? "" : "s", seconds, seconds == 1 ? "" : "s");
    else
	snprintf (ret, sizeof (ret), "%d second%s", seconds, seconds == 1 ? "" : "s");

    return ret;
}

/* Duration: How long since an event happened.
   Depending on format, we return a time in one of two formats:
	1: #y#d#h#m#s
	2: # years, # days, # hours, # minutes, # seconds
 */
char *duration (time_t event, uint8 format)
{
    static char ret[128];
    uint16 years, days, hours, minutes, seconds;

    years = days = hours = minutes = seconds = 0;

    while (event >= 31536000)
    {
	event -= 31536000;
	years++;
    }
    while (event >= 86400)
    {
	event -= 86400;
	days++; 
    }
    while (event >= 3600)
    {
	event -= 3600;
	hours++;
    }
    while (event >= 60)
    {
	event -= 60;
	minutes++;
    }

    seconds = event;

    *ret = 0;

    if (years)
    {
	strcat (ret, itoa (years));

	if (format == 1)
	    strcat (ret, "y");
	else
	{
	    strcat (ret, " year");

	    if (years != 1)
		strcat (ret, "s");

	    if (days || hours || minutes || seconds)
		strcat (ret, ", ");
	}
    }

    if (days)
    {
	strcat (ret, itoa (days));

	if (format == 1)
	    strcat (ret, "d");
	else
	{
	    strcat (ret, " day");

	    if (days != 1)
		strcat (ret, "s");

	    if (hours || minutes || seconds)
		strcat (ret, ", ");
	}
    }

    if (hours)
    {
	strcat (ret, itoa (hours));

	if (format == 1)
	    strcat (ret, "h");
	else
	{
	    strcat (ret, " hour");

	    if (hours != 1)
		strcat (ret, "s");

	    if (minutes || seconds)
		strcat (ret, ", ");
	}
    }

    if (minutes)
    {
	strcat (ret, itoa (minutes));

	if (format == 1)
	    strcat (ret, "m");
	else
	{
	    strcat (ret, " minute");

	    if (minutes != 1)
		strcat (ret, "s");

	    if (seconds)
		strcat (ret, ", ");
	}
    }

    if (seconds)
    {
	strcat (ret, itoa (seconds));

	if (format == 1)
	    strcat (ret, "s");
	else
	{
	    strcat (ret, " second");

	    if (seconds != 1)
		strcat (ret, "s");
	}
    }

    /* We always want to return SOMETHING.. */
    if (!strlen (ret))
	strcat (ret, "0 seconds");

    return ret;
}

/* dotime:  Return the number of seconds corresponding to the given time
            string.  If the given string does not represent a valid time,
            return -1.
  
            A time string is either a plain integer (representing a number
            of seconds), or an integer followed by one of these characters:
            "s" (seconds), "m" (minutes), "h" (hours), "d" (days), "w"
            (weeks), or "y" (years).

   Borrowed from IRCServices, modified to accept stacking of times,
   weeks, and years. -skold
 */
uint32 dotime (const char *s)
{
    uint32 amount, seconds = 0;

    amount = strtol (s, (char **)&s, 10);

    while (*s)
    {
	switch (*s++)
	{
	    case 's':
		seconds += amount;
		amount = strtol (s, (char **)&s, 10);
		break;
	    case 'm':
		seconds += amount * 60;
		amount = strtol (s, (char **)&s, 10);
		break;
	    case 'h':
		seconds += amount * 3600;
		amount = strtol (s, (char **)&s, 10);
		break;
	    case 'd':
		seconds += amount * 86400;
		amount = strtol (s, (char **)&s, 10);
		break;
	    case 'w':
		seconds += amount * 604800;
		amount = strtol (s, (char **)&s, 10);
		break;
	    case 'y':
		seconds += amount * 31536000;
		amount = strtol (s, (char **)&s, 10);
		break;
	}
    }

    return seconds;
}

/* Send a NULL-terminated array of text as NOTICEs. */
void notice_list (const char *source, const char *dest, const char **text)
{
    while (*text)
    {
	if (**text)
	    notice (source, dest, *text);
	else
	    notice (source, dest, " ");
	text++;  
    }
}

/* Find an SRA by nick. Return NULL if sra could not be found. */
SRA *findsra (const char *nick)
{
    SRA *sra;

    if (!nick)  
	return NULL;

    for (sra = sralist; sra; sra = sra->next)
	if (!stricmp (nick, sra->nick))
	    return sra;

    return NULL;
}

/* Find a ULine by name. Return NULL if uline could not be found. */
ULine *finduline (const char *name)
{
    ULine *uline;

    if (!name)
	return NULL;

    for (uline = ulinelist; uline; uline = uline->next)
	if (!stricmp (name, uline->name))
	    return uline;

    return NULL;
}

/* Find an TLD. Return NULL if tld could not be found. */
TLD *findtld (const char *param)
{
    TLD *tld;

    if (!param)
	return NULL;

    for (tld = tldlist; tld; tld = tld->next)
	if (!stricmp (param, tld->name) || !stricmp (param, tld->desc))
	    return tld;

    return NULL;
}

/* Find a TimeZone. Return NULL if TimeZone could not be found. */
Zone *findzone (const char *param)
{
    Zone *zone;

    if (!param)
	return NULL;
 
    for (zone = zonelist; zone; zone = zone->next)
	if (!stricmp (param, zone->name))
	    return zone;
 
    return NULL;
}

/* Find a server for stats purposes. */
ServStat *findservstat (const char *server)
{
    ServStat *s;
    uint32 i;

    for (i = 0; i < HASHSIZE; i++)
	for (s = statlist[i]; s; s = s->next)
	    if (match_wild_nocase (server, s->name))
		return s;

    return NULL;
}

/* Is the given mask akilled? */
uint8 is_akilled (const char *mask)
{   
    uint16 i;
        
    for (i = 0; i < akillcnt; i++)
	if (match_wild_nocase (akills[i].mask, mask))
	    return 1;

    return 0;
}

/* Given a user, return a mask that will most likely match any address the
   user will have from that location.  For IP addresses, wildcards the
   appropriate subnet mask (e.g. 35.1.1.1 -> 35.*; 128.2.1.1 -> 128.2.*);
   for named addresses, wildcards the leftmost part of the name unless the
   name only contains two parts.  If the username begins with a ~, delete
   it.  The returned character string is malloc'd and should be free'd
   when done with.

   Borrowed from IRCServices.
 */
char *create_mask (User *u)
{
    char *mask, *s, *end;

    end = mask = smalloc (strlen (u->user) + strlen (u->host) + 2);
    end += sprintf (end, "*@");

    if (strspn (u->host, "0123456789.") == strlen (u->host)
	&& (s = strchr(u->host, '.')) && (s = strchr(s+1, '.'))
	&& (s = strchr(s+1, '.'))  && (   !strchr(s+1, '.')))
    {
	s = sstrdup (u->host);

	*strrchr (s, '.') = 0;

	if (atoi (u->host) < 192)
	    *strrchr (s, '.') = 0;

	if (atoi (u->host) < 128)
	    *strrchr (s, '.') = 0;

	sprintf (end, "%s.*", s);

	free(s);
    }
    else
    {
	if ((s = strchr (u->host, '.')) && strchr (s+1, '.'))
	{
	    s = sstrdup (strchr (u->host, '.') -1);
	    *s = '*';
	}
	else
	    s = sstrdup (u->host);

	strcpy (end, s);
	free (s);
    }
    return mask;
}

/* Remove control codes from text. Borrowed from UnrealIRCd,
   modified to strip bold, underline and reverse.
 */
char *stripcodes (char *buffer)
{
    static char in[BUFSIZE], out[BUFSIZE];
    uint16 i = 0, j = 0, hascomma = 0;

    /* Check if theres any codes in the text */
    if (!strchr (buffer, '\002') && !strchr (buffer, '\003') &&
	!strchr (buffer, '\026') && !strchr (buffer, '\037'))
	return buffer;

    bzero ((char *)out, sizeof (out));
    memcpy (in, buffer, BUFSIZE);

    while (in[i])
    {
	/* Remove codes */
	if (in[i] == '\002' || in[i] == '\003' ||
	    in[i] == '\026' || in[i] == '\037')
	{
	    hascomma = 0;
	    i++;

	    if (!isdigit (in[i]))
		continue;

	    while (isdigit (in[i]) || (isdigit (in[i - 1])
		   && in[i] == ',' && isdigit (in[i + 1])
		   && hascomma == 0))
	    {
		if (in[i] == ',' && hascomma == 1)
		    break;
		if (in[i] == ',' && hascomma == 0)
		    hascomma = 1;
		i++;
	    }
	    continue;
	}
	out[j] = in[i];
	i++;
	j++;
    }
    return out;
}

/* Is it midnight? - Borrowed from GeoStats. */
uint8 is_midnight ()
{
    time_t current = time (NULL);
    struct tm *ltm = gmtime (&current);

    if (ltm->tm_hour == 0)
	return 1;

    return 0;
}

/* Drop the current channel list to disk in an html format. 
   We get the format from an html file already on the disk.
   Theres several 'tokens' in this file that we look for, and
   insert information in place of them. The rest of the html
   we leave alone.
 */
void dumpchans ()
{
    FILE *in = fopen (CHANNELS_IN, "r");
    FILE *out = fopen (CHANNELS_OUT, "w");
    Channel *c;
    char last[64], end[64], topictmp[255], topic[255], dBuf[2048], ret[2048];
    char *tmp, links[512], tmpbuf[512], logoimgtmp[256];
    uint16 i;
    uint8 rowcolor = 0;

    if (!in)
    {
	log (RPL_CANT_OPEN, CHANNELS_IN);
	return;
    }

    if (!out)
    {
	log (RPL_CANT_OPEN, CHANNELS_OUT);
	return;
    }

    /* Set up the token replacements ahead of time. */
    sprintf (last, "%s", get_time (time (NULL), 1));
    sprintf (end, "%d user%s and %d channel%s", userstat,
	userstat == 1 ? "" : "s", countchans (0),
	countchans (0) == 1 ? "" : "s");

    if (strlen (logoimg))
	sprintf (logoimgtmp, "<img src=\"%s\">", logoimg);
    else
	sprintf (logoimgtmp, "Generated by OperStats v%s", version);

    while (fgets (dBuf, 2047, in))
    {
	/* Check if this line should be duplicated */
	if (strstr (dBuf, "$NA"))
	{
	    for (c = firstchan (); c; c = nextchan ())
	    {
		strscpy (ret, dBuf, sizeof (ret));

		if (!(c->mode & CMODE_s))
		{
		    *links = 0;
		    *tmpbuf = 0;

		    if (rowcolor == 1)
		    {
			rowcolor = 0;
			replace (ret, sizeof (ret), "$RC", "rowdark");
		    }
		    else
		    {
			rowcolor = 1;
			replace (ret, sizeof (ret), "$RC", "rowlight");
		    }

		    if (strlen (mirclink))
		    {
			/* mIRCLinks don't play nice with channels with
			   the # in them. So we have to strip it off.
			 */
			char *chantmp = sstrdup (c->name);
			tmp = strchr (chantmp, '#');
			*tmp++ = 0;

			snprintf (links, sizeof (links),
			    "<a href=\"irc://%s/%s\"><img src=\"%s\" border=\"0\" alt=\"mIRC Chat\"></a>",
			    mirclink, tmp, mirclinkimg);

			free (chantmp);
		    }

		    if (javaclient_on == TRUE)
		    {
			if (strlen (links))
			{
			    snprintf (tmpbuf, sizeof (tmpbuf), "%s", links);

			    snprintf (links, sizeof (links),
				"%s&nbsp;<a href=\"javascript:openChat('%s')\"><img src=\"%s\" border=\"0\" alt=\"Java Chat\"></a>",
				tmpbuf, c->name, javalinkimg);
			}
			else
			{
			    snprintf (links, sizeof (links),
				"<a href=\"javascript:openChat('%s')\"><img src=\"%s\" border=\"0\" alt=\"Java Chat\"></a>",
				c->name, javalinkimg);
			}
		    }

		    if (strlen (links))
			replace (ret, sizeof (ret), "$CL", links);
		    else
			replace (ret, sizeof (ret), "$CL", "N/A");

		    replace (ret, sizeof (ret), "$NA", c->name);

		    i = c->usercnt;

		    /* Count our clients for the JoinChan if needed */
		    if (!stricmp (c->name, joinchan))
			i += (servcount () - 1);

		    replace (ret, sizeof (ret), "$CC", itoa (i));

		    /* First, strip control codes from the topic. */
		    if (c->topic)
			tmp = stripcodes (c->topic);
		    else
			tmp = "";

		    /* Cut the topic down to 65 chars. */
                    /* Change by thirtysix, make this 255 chars instead. */
		    snprintf (topictmp, sizeof (topictmp), "%s", tmp);

		    /* Put ... at the end to denote a truncated topic */
		    snprintf (topic, sizeof (topic), "%s%s", topictmp,
			strlen (topictmp) == 255 ? "..." : "");

		    snprintf (tmpbuf, sizeof (tmpbuf), "%s", topic);

		    /* Certain things mess up the html, fix them.
		       We replace these certain things with html
		       equivilants like &nbsp and &lt. These don't
		       affect the visible length, so we don't have
		       to limit it to 65 again.
		     */
		    replace (tmpbuf, sizeof (tmpbuf), "<", "&lt;");
		    replace (tmpbuf, sizeof (tmpbuf), ">", "&gt;");
		    replace (tmpbuf, sizeof (tmpbuf), " ", "&nbsp;");

		    replace (ret, sizeof (ret), "$DE", tmpbuf);

		    fprintf (out, "%s", ret);
		}
	    }

	    continue;
	}

	replace (dBuf, sizeof (dBuf), "$LU", last);
	replace (dBuf, sizeof (dBuf), "$EN", end); 
	replace (dBuf, sizeof (dBuf), "$LI", logoimgtmp);
	replace (dBuf, sizeof (dBuf), "$NW", network_name);

	fprintf (out, "%s", dBuf);
    }

    fclose (in);
    fclose (out);
}

/* Drop tld TLD stats to disk in an html format. This is similar
   to the TLDMAP output.
 */
void dumptlds ()
{
    FILE *in = fopen (TLDSTATS_IN, "r");
    FILE *out = fopen (TLDSTATS_OUT, "w");
    TLD *tld;
    char last[64], end[64], dBuf[2048], ret[2048], logoimgtmp[256], *htmp;
    uint16 i = 0, j;

    if (!in)
    {
	log (RPL_CANT_OPEN, TLDSTATS_IN);
	return;
    }

    if (!out)
    {
	log (RPL_CANT_OPEN, TLDSTATS_OUT);
	return;
    }

    /* Set up the token replacements ahead of time. */
    for (tld = tldlist; tld; tld = tld->next)
	if (tld->hits)
	    i++;

    sprintf (last, "%s", get_time (time (NULL), 1));
    sprintf (end, "%d hit%s in %d TLD%s", dailyhitcnt,
	dailyhitcnt == 1 ? "" : "s", i, i == 1 ? "" : "s");

    if (strlen (logoimg))
	sprintf (logoimgtmp, "<img src=\"%s\">", logoimg);
    else
	sprintf (logoimgtmp, "Generated by OperStats v%s", version);

    while (fgets (dBuf, 2047, in))
    {
	/* Check if this line should be duplicated */
	if (strstr (dBuf, "$NA"))
	{
	    for (tld = tldlist; tld; tld = tld->next)
	    {
		strscpy (ret, dBuf, sizeof (ret));

		if (tld->hits)
		{
		    i = (tld->hits * 100) / dailyhitcnt;
		    j = 100 - i;

		    if (tld->hits == 1)
			htmp = "";
		    else
			htmp = "s";

		    replace (ret, sizeof (ret), "$NA", tld->desc);
		    replace (ret, sizeof (ret), "$DE", tld->name);
		    replace (ret, sizeof (ret), "$CC", itoa (tld->hits));
		    replace (ret, sizeof (ret), "$SS", htmp);
		    replace (ret, sizeof (ret), "$RP", itoa (j));
		    replace (ret, sizeof (ret), "$UP", itoa (i));

		    fprintf (out, "%s", ret);
		}
	    }

	    if (tldnoreshits)
	    {
		i = (tldnoreshits * 100) / dailyhitcnt;
		j = 100 - i;

		if (tldnoreshits == 1)
		    htmp = "";
		else
		    htmp = "s";

		replace (dBuf, sizeof (dBuf), "$NA", "Non Resolving");
		replace (dBuf, sizeof (dBuf), "$DE", "Unknown");
		replace (dBuf, sizeof (dBuf), "$CC", itoa (tldnoreshits));
		replace (dBuf, sizeof (dBuf), "$SS", htmp);
		replace (dBuf, sizeof (dBuf), "$RP", itoa (j));
		replace (dBuf, sizeof (dBuf), "$UP", itoa (i));

		fprintf (out, "%s", dBuf);
	    }

	    continue;
	}

	replace (dBuf, sizeof (dBuf), "$LU", last);
	replace (dBuf, sizeof (dBuf), "$EN", end);
	replace (dBuf, sizeof (dBuf), "$LI", logoimgtmp);

	fprintf (out, "%s", dBuf);
    }

    fclose (in);
    fclose (out);
}

/* Drop various statistics to a php file. */
void dumpstats ()
{
    FILE *f;
    uint16 i, servout = 1;
    ServStat *s;
    time_t now = time (NULL), uptime = (now - me.since);

    f = fopen (STATS_OUT, "w");

    if (!f)
    {
	log (RPL_CANT_OPEN, STATS_OUT);
	return;
    }

    /* I'd hope this is all self explanitory. */
    fprintf (f, "<?\n");
    fprintf (f, "    $userstat = %d;\n", userstat);
    fprintf (f, "    $chanstat = %d;\n", countchans (0));
    fprintf (f, "    $servstat = %d;\n", servstat);
    fprintf (f, "    $maxusers = %d;\n", maxusercnt);
    fprintf (f, "    $maxchans = %d;\n", maxchancnt);
    fprintf (f, "    $maxservs = %d;\n", maxservcnt);
    fprintf (f, "    $netspeed = \"%.2f %s/s\";\n", (float) SIZEREAL (speedsample), SIZENAME (speedsample));
    fprintf (f, "    $hitcount = %d;\n", dailyhitcnt);

    /* We don't want to link ourselves. We'll use this to figure out
       what server we are.
     */
    fprintf (f, "    $sn0 = \"%s\";\n", me.name);

    /* Drop the servers uptimes. Update them beforehand. */
    for (i = 0; i < HASHSIZE; i++)
    {
	for (s = statlist[i]; s; s = s->next)
	{
	    s->uptime += (now - s->uptimetime);
	    s->uptimetime = now;

	    if (s->uptime > s->maxuptime)
		s->maxuptime = s->uptime;

	    fprintf (f, "    $sn%d = \"%s\";\n", servout, s->name);
	    fprintf (f, "    $su%d = \"%d\";\n", servout, s->uptime);
	    fprintf (f, "    $sh%d = \"%d\";\n", servout, s->maxuptime);

	    servout++;
	}
    }

    /* If the current uptime is higher than the maxuptime, update! */
    if (uptime > maxuptime)
	maxuptime = uptime;

    /* Write ourselves, too */
    fprintf (f, "    $sn%d = \"%s\";\n", servout, me.name);
    fprintf (f, "    $su%d = \"%lu\";\n", servout, uptime);
    fprintf (f, "    $sh%d = \"%lu\";\n", servout, maxuptime);

    fprintf (f, "    $st = \"%d\";\n", servout);
    fprintf (f, "?>\n");

    fclose (f);
}

/* Drop the current server list to disk in an html format. 
   We get the format from an php file already on the disk.
   Theres several 'tokens' in this file that we look for, and
   insert information in place of them. The rest of the html
   we leave alone.
 */
void dumpservers ()
{
    FILE *in = fopen (SERVERS_IN, "r");
    FILE *out = fopen (SERVERS_OUT, "w");
    ServStat *s;
    uint8 rowcolor = 0;
    uint16 i;
    char tmp[64], last[64], end[64], dBuf[2048], ret[2048], logoimgtmp[256];
    time_t now = time (NULL);

    if (!in)
    {
	log (RPL_CANT_OPEN, SERVERS_IN);
	return;
    }

    if (!out)
    {
	log (RPL_CANT_OPEN, SERVERS_OUT);
	return;
    }

    /* Set up the token replacements ahead of time. */
    sprintf (last, "%s", get_time (time (NULL), 1));
    sprintf (end, "%d server%s", servstat, servstat == 1 ? "" : "s");

    if (strlen (logoimg))
	sprintf (logoimgtmp, "<img src=\"%s\">", logoimg);
    else
	sprintf (logoimgtmp, "Generated by OperStats v%s", version);

    while (fgets (dBuf, 2047, in))
    {
	/* Check if this line should be duplicated */
	if (strstr (dBuf, "$NA"))
	{
	    for (i = 0; i < HASHSIZE; i++)
	    {
		for (s = statlist[i]; s; s = s->next)
		{
		    strscpy (ret, dBuf, sizeof (ret));

		    if (finduline (s->name) && ignoreulines_on == TRUE)
			continue;

		    /* Update their max uptime */
		    s->uptime += (now - s->uptimetime);
		    s->uptimetime = now;

		    if (s->uptime > s->maxuptime)
			s->maxuptime = s->uptime;

		    replace (ret, sizeof (ret), "$NA", s->name);
		    replace (ret, sizeof (ret), "$SP", itoa (s->splits));
		    replace (ret, sizeof (ret), "$LQ", s->lastquit);
		    replace (ret, sizeof (ret), "$CC", itoa (s->clients));
		    replace (ret, sizeof (ret), "$MC", itoa (s->maxclients));

		    if (s->lag.tv_sec == 999)
			replace (ret, sizeof (ret), "$CL", "999ms");
		    else
		    {
			sprintf (tmp, "%dms", tv2ms (&(s->lag)));
			replace (ret, sizeof (ret), "$CL", tmp);
		    }

		    if (rowcolor == 1)
		    {
			rowcolor = 0;
			replace (ret, sizeof (ret), "$RC", "rowdark");
		    }
		    else
		    {
			rowcolor = 1;
			replace (ret, sizeof (ret), "$RC", "rowlight");
		    }

		    sprintf (tmp, "%dms", tv2ms (&(s->peaklag)));
		    replace (ret, sizeof (ret), "$PL", tmp);

		    sprintf (tmp, "%s (%s)", get_time (s->maxtime, 4), time_ago (s->maxtime));
		    replace (ret, sizeof (ret), "$MT", tmp);

		    sprintf (tmp, "%s", time_ago (time (NULL) - s->splittime));
		    replace (ret, sizeof (ret), "$TD", tmp);

		    sprintf (tmp, "%s", duration (s->lastdowntime, 2));
		    replace (ret, sizeof (ret), "$LD", tmp);

		    sprintf (tmp, "%d", s->uptime);
		    replace (ret, sizeof (ret), "$CU", tmp);

		    sprintf (tmp, "%d", s->maxuptime);
		    replace (ret, sizeof (ret), "$HU", tmp);

		    sprintf (tmp, "%s (%s)", get_time (s->statssince, 4), time_ago (s->statssince));
		    replace (ret, sizeof (ret), "$SS", tmp);

		    sprintf (tmp, "%s (%s)", get_time (s->lastsplit, 4), time_ago (s->lastsplit));
		    replace (ret, sizeof (ret), "$LS", tmp);

		    if (s->version)
			replace (ret, sizeof (ret), "$DE", s->version);
		    else
			replace (ret, sizeof (ret), "$DE", "Unknown");

		    if (s->flags)
			replace (ret, sizeof (ret), "$FL", s->flags);
		    else
			replace (ret, sizeof (ret), "$FL", "XX");

		    replace (ret, sizeof (ret), "$LU", last);

		    fprintf (out, "%s", ret);
		}
	    }

	    continue;
	}

	replace (dBuf, sizeof (dBuf), "$LU", last);
	replace (dBuf, sizeof (dBuf), "$EN", end); 
	replace (dBuf, sizeof (dBuf), "$LI", logoimgtmp);
	replace (dBuf, sizeof (dBuf), "$NW", network_name);

	fprintf (out, "%s", dBuf);
    }

    fclose (in);
    fclose (out);
}

/* Replace all occurances of 'old' with 'new'. - Borrowed from IRCServices */
char *replace (char *s, int32 size, const char *old, const char *new)
{
    char *ptr = s;
    int32 left = strlen (s);
    int32 avail = size - (left + 1);
    int32 oldlen = strlen (old);
    int32 newlen = strlen (new);
    int32 diff = newlen - oldlen;

    while (left >= oldlen)
    {
	if (strncmp (ptr, old, oldlen))
	{
	    left--;
	    ptr++;
	    continue;
	}

	if (diff > avail)
	    break;

	if (diff != 0)
	    memmove (ptr + oldlen + diff, ptr + oldlen, left + 1);

	strncpy (ptr, new, newlen);
	ptr += newlen;
	left -= oldlen;
    }

    return s;
}

/* Reverse of atoi (); */
char *itoa (uint32 num)
{
    static char ret[32];
    sprintf (ret, "%d", num);
    return ret;
}

/* Clear all SRAs, TLDs, and TimeZones. */
void flushlists ()
{
    SRA *sra;
    TLD *tld;
    Zone *zone;
    ULine *uline;

    for (sra = sralist; sra; sra = sra->next)
    {
	free (sra->nick);
	free (sra->pass);

	if (sra->prev)
	    sra->prev->next = sra->next;
	else
	    sralist = sra->next;

	if (sra->next)
	    sra->next->prev = sra->prev;
    }

    free (sra);

    for (tld = tldlist; tld; tld = tld->next)
    {
	free (tld->name);
	free (tld->desc);

	if (tld->prev)
	    tld->prev->next = tld->next;
	else
	    tldlist = tld->next;

	if (tld->next)
	    tld->next->prev = tld->prev;
    }

    free (tld);

    for (zone = zonelist; zone; zone = zone->next)
    {
	free (zone->name);
	free (zone->noffset);
	free (zone->desc);

	if (zone->prev)
	    zone->prev->next = zone->next;
	else
	    zonelist = zone->next;

	if (zone->next)
	    zone->next->prev = zone->prev;
    }

    free (zone);

    for (uline = ulinelist; uline; uline = uline->next)
    {
	free (uline->name);

	if (uline->prev)
	    uline->prev->next = uline->next;
	else
	    ulinelist = uline->next;

	if (uline->next)
	    uline->next->prev = uline->prev;
    }

    free (uline);
}


/* Check if the specified command is a password command. */
uint8 ispass (char *text)
{
    char *cmd = strtok (text, " ");

    /* Heres the idea. For half-security settings, we want
       to deny commands that deal with passwords. So this is
       where we check if the command is a password command.
     */
    if (!stricmp (cmd, "AUTH"))
	return 1;

    return 0;
}

/* Check if the command is a CTCP query. Return 1 if true, 0 if false. */
uint8 isctcp (char *command)
{
    /* We use stristr to check for PINGs, since we don't know exactly what
       they'll look like.
     */
    if (stristr (command, "\1PING") || !stricmp (command, "\1VERSION\1"))
	return 1;

    return 0;
}

/* Put all psuedoclients in the JoinChan. */
void joinchan_join ()
{
    if (!strlen (joinchan))
	return;

    if (operserv_on == TRUE)
    {
	if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
	    send_cmd (s_OperServ, "SJOIN %ld %ld %s + :%s", time (NULL), time (NULL),
		joinchan, s_OperServ);
	else
	    send_cmd (s_OperServ, "%s %s", me.token ? "C" : "JOIN", joinchan);
    }

    if (statserv_on == TRUE)
    {
	if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
	    send_cmd (s_StatServ, "SJOIN %ld %ld %s + :%s", time (NULL), time (NULL),
		joinchan, s_StatServ);
	else
	    send_cmd (s_StatServ, "%s %s", me.token ? "C" : "JOIN", joinchan);
    }

    if (globalnoticer_on == TRUE)
    {
	if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
	    send_cmd (s_GlobalNoticer, "SJOIN %ld %ld %s + :%s", time (NULL), time (NULL),
		joinchan, s_GlobalNoticer);
	else
	    send_cmd (s_GlobalNoticer, "%s %s", me.token ? "C" : "JOIN", joinchan);
    }

    send_cmd (authserv (), "%s %s +o%s%s %s %s %s", me.token ? "G" : "MODE", joinchan,
	statserv_on == TRUE ? "o" : "", globalnoticer_on == TRUE ? "o" : "", s_OperServ,
	statserv_on == TRUE ? s_StatServ : "", globalnoticer_on == TRUE ? s_GlobalNoticer : "");
}

/* Take all clients out of the JoinChan. */
void joinchan_part ()
{
    if (operserv_on == TRUE)
	send_cmd (s_OperServ, "%s %s", me.token ? "D" : "PART", joinchan);

    if (statserv_on == TRUE)
	send_cmd (s_StatServ, "%s %s", me.token ? "D" : "PART", joinchan);

    if (globalnoticer_on == TRUE)
	send_cmd (s_GlobalNoticer, "%s %s", me.token ? "D" : "PART", joinchan);
}

/* Return the number of IRCOps on the network */
uint8 countops ()
{
    uint32 i;
    uint8 found = 0;
    User *u;

    for (i = 0; i < HASHSIZE; i++)
	for (u = userlist[i]; u; u = u->next)
	    if (u->mode & UMODE_o)
		found++;

    /* Whups, don't forget our own clients! */
    if (strchr (os.mode, 'o'))
	found++;

    if (strchr (ss.mode, 'o'))
	found++;

    if (strchr (gn.mode, 'o'))
	found++;

    return found;
}

/* SpamServ routine. */
void spamserv (User *u, char *buf)
{
    char *cmd, *s;
    char orig[BUFSIZE];

    /* Make a copy of the original message. */
    strscpy (orig, buf, sizeof (orig));

    cmd = strtok (buf, " ");

    if (!cmd)
	return;

    if (!stricmp (cmd, "\1PING"))
    {
	if (!(s = strtok (NULL, "")))
	    s = "0";
	strip (s);
	ctcpreply (sp.nick, u->nick, "PING %s", s);
	return;
    }
    else if (!stricmp (cmd, "\1VERSION\1"))
    {
	ctcpreply (sp.nick, u->nick, "VERSION mIRC v6.02 Khaled Mardam-Bey");
	return;
    }

    /* A CTCP we don't recognize.. Ignore it. */
    else if (*cmd == '\1')
	return;

    /* Opers seem to find it funny when SpamServ globops their messages.
       NO FUN FOR YOU.
     */
    else if (is_oper (u))
	return;

    /* If we get here, we'll globop it. Theres no reason I can think of
       to message someone whos not in any channels and never messaged
       you first unless you're spamming them for something. So we consider
       it spam and send a globops about possible spam.
     */
    globops (authserv (), RPL_POSSIBLE_SPAM, u->nick, orig);
}

/* Send 'RPL_MORE_INFO' in the needed format. */
void errmoreinfo (const char *whoami, const char *source, char *text)
{
    notice (whoami, source, RPL_MORE_INFO, haveserv_on == TRUE ? "" : "MSG ",
	whoami, haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
	haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "", text);
}

#ifdef HAVE_GETTIMEOFDAY
/* Convert tv to ms */
uint32 tv2ms (struct timeval *tv)
{   
    return (tv->tv_sec * 1000) + (int) (tv->tv_usec / 1000);
}
#endif

/* Return 1 if the character is @, +, %, ~, or *. 0 otherwise. */
int32 prefix_to_flag (char c)
{
    if (c == '@' || c == '+' || c == '%' || c == '~' || c == '*')
	return 1;

    return 0;
}

/* After the burst, go through and do any needed actions. This
   consists of PING and VERSIONing servers.
 */
void check_eb ()
{
    Server *s;

    for (s = firstserv (); s; s = nextserv ())
    {
	gettimeofday (&(s->lastping), NULL);

	/* 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", s->name);

	if (statserv_on == TRUE)
	    send_cmd (me.name, "%s %s %s", me.token ? "8" : "PING", me.name, s->name);
    }
}

/* Generate a hash. */
uint32 shash (const unsigned char *text)
{
    uint32 h = 0, g;

    /* Generate a unique index */
    while (*text)
    {
	h = (h << 4) + tolower (*text++);

	if ((g = (h & 0xF0000000)))
	    h ^= g >> 24;

	h &= ~g;
    }

    return (h % HASHSIZE);
}

/* Process a buffer and output notices in columns for the help files. */
void helpcolumns (User *u, const char *whoami, const char *prefix, const char *commands)
{
    char *tmp, buf[BUFSIZE], cmdbuf[2048], prefixbuf[64];
    uint8 cmdcnt = 0, pad = 0, prefixed = 0, prefixpad, columns = 4;
    uint16 tmplen;

    *buf = 0;
    *prefixbuf = 0;

    /* We have more room on HelpIndex Short, so make it 5 columns. */
    if (helpindex == 1)
	columns = 5;

    if ((prefixpad = strlen (prefix)))
	while (prefixpad)
	{
	    strcat (prefixbuf, " ");
	    prefixpad--;
	}

    snprintf (cmdbuf, sizeof (cmdbuf), "%s", commands);
    tmp = strtok (cmdbuf, " ");

    /* Do the first one */
    strcat (buf, tmp);
    tmplen = strlen (tmp);
    pad = 10 - tmplen;

    while (pad)
    {
	strcat (buf, " ");
	pad--;
    }

    cmdcnt++;

    for (;;)
    {
	if (cmdcnt == 5)
	{
	    if (!prefixed && strlen (prefix))
	    {
		notice (whoami, u->nick, "%s\2%s\2", prefix, buf);
		prefixed = 1;
	    }
	    else
		notice (whoami, u->nick, "%s\2%s\2", prefixbuf, buf);

	    cmdcnt = 0;
	    *buf = 0;
	}

	tmp = strtok (NULL, " ");

	if (tmp)
	{
	    tmplen = strlen (tmp);
	    pad = 10 - tmplen;

	    strcat (buf, tmp);

	    cmdcnt++;

	    if (cmdcnt < 5)
		while (pad)
		{
		    strcat (buf, " ");
		    pad--;
		}
	}
	else
	{
	    if (!prefixed && strlen (prefix))
	    {
		notice (whoami, u->nick, "%s\2%s\2", prefix, buf);
		prefixed = 1;
	    }
	    else
		notice (whoami, u->nick, "%s\2%s\2", prefixbuf, buf);

	    break;
	}
    }
}

/* Count our clients so the counts we show are accurate. */
uint8 servcount ()
{
    uint8 tmp = 0;

    /* We have seperate counts for statistics purposes since we need
       the counts we use internally to be accurate, not including us.
     */
    if (operserv_on == TRUE)
	++tmp;
    if (statserv_on == TRUE)
	++tmp;
    if (globalnoticer_on == TRUE)
	++tmp;
    if (spamserv_on == TRUE)
	++tmp;

    return tmp;
}

/* Count how many of our clients are opered */
uint8 opcount ()
{
    uint8 tmp = 0;

    /* Since our modes aren't stored binary, we'll use strstr.. 
       SpamChecker doesn't have modes, so no, it's absence here isn't
       an oversight :P
     */
    if (strstr (os.mode, "o"))
	++tmp;
    if (strstr (ss.mode, "o"))
	++tmp;
    if (strstr (gn.mode, "o"))
	++tmp;

    return tmp;
}

/* Send a services message to the given server. This is for OperStats->Cygnus
   communication. Since IRCd drops unknown commands, we need to piggyback our
   little protocol here on top of something else. I chose numeric 351 (version
   reply). Every IRCd supports it, and doesn't place restrictions on the content
   of it. We could use PRIVMSG or NOTICE but that requires us knowing the nickname
   of a service on the remote server.

   For security, Cygnus and OperStats will ignore any services messages that
   don't come from a specific server defined in the conf.

   Currently theres 4 messages we send and receive. Rather than have nice long
   descriptive words for each command like IRCd, we give each a number:

   0 - SVSSEND command
   1 - AutoKill ADD (AKILL)
   2 - AutoKill DEL (RAKILL)
   3 - SRA Auth
   4 - SRA DeAuth

   So, if I were to auth, OperStats would send out something like
   351 services.habber.net 0 :3 skold

   This is fine as long as no IRCd's have their version string as '0' .. If they do
   they're edited anyhow and we don't support them so screw 'em :)
 */
void svssend (const char *fmt,...)
{
    va_list args;
    char buf[2048];

    va_start (args, fmt);
    vsnprintf (buf, sizeof (buf), fmt, args);

    send_cmd (me.name, "351 %s 0 :%s", cygnus_server, buf);
}

/* Return the current authoritive service, usually OperServ, maybe StatServ. */
char *authserv ()
{
    if (operserv_on == TRUE)
	return s_OperServ;
    else
	return s_StatServ;
}

/* Create or remove backups of OperStats databases. */
void db_backup (uint8 delete)
{
    char backup[2048];

    strcpy (backup, STATSERV_DB);
    strcat (backup, ".save");

    if (delete)
	remove (backup);
    else
	if (rename (STATSERV_DB, backup) < 0)
	    log ("SYNC: Can't back up %s!", STATSERV_DB);

    strcpy (backup, OPERSERV_DB);
    strcat (backup, ".save");

    if (delete)
	remove (backup);
    else
	if (rename (OPERSERV_DB, backup) < 0)
	    log ("SYNC: Can't back up %s!", OPERSERV_DB);
}

/* Return channel statistics - This is mainly so StatServ's counts
   match lusers, which doesn't include +s channels. This returns one
   of two things: The total amount of channels that aren't +s, or the
   total amount that are.
 */
uint32 countchans (uint8 secret)
{
    Channel *c;
    int i, total = 0;

    for (i = 0; i < HASHSIZE; i++)
    {
	for (c = chanlist[i]; c; c = c->next)
	{
	    if (secret && (c->mode & CMODE_s))
		total++;
	    else if (!secret && !(c->mode & CMODE_s))
		total++;
	}
    }

    /* Include the JoinChan as needed */
    if (strchr (joinchan, '#') && !findchan (joinchan))
	total++;

    return total;
}

/* Take the value a server gives for it's uptime and turn it into a seconds value. */
int parseuptime (char *string)
{
    char *days = strtok (string, "Server Up ");
    char *hms = strtok (NULL, "days, ");
    char *hours = strtok (hms, ":");
    char *minutes = strtok (NULL, ":");
    char *seconds = strtok (NULL, ":");
    int32 total = (atoi (days) * 86400) + (atoi (hours) * 3600) + (atoi (minutes) * 60) + atoi (seconds);

    return total;
}
