/* Miscellaneous functions

   Cygnus IRC Services - Copyright (c) 2001-2002 Darcy Grexton
   Contact: skold@habber.net, skold @ HabberNet

   See doc/LICENSE for licensing details.
 */

#include "../inc/services.h"

static FILE *log_file;
static FILE *debug_file;
static int ns_iterator_pos = HASHSIZE;
static NickInfo *ns_iterator_ptr = NULL;
static int cs_iterator_pos = HASHSIZE;
static ChanInfo *cs_iterator_ptr = NULL;

/* 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 (int 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 == 1)
	    fputs (buf, log_file);

	vfprintf (log_file, fmt, args);

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

	fflush (log_file);
    }

    if (runflags & RUN_LIVE)
    {
	if (sameline == 1)
	    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 (int 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 (s_RootServ, "Fatal Error: %s", buf);

    exit (1);
}

/* 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);
}

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 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,...)
{
    NickInfo *ni;
    User *u = finduser (dest);
    va_list args;
    char buf[2048];

    if (!u)
	return;

    ni = get_nick (u);

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

    if ((privmsg_on == FALSE && ni && (ni->flags & NF_PRIVMSG)) ||
	(privmsg_on == TRUE && ((ni && !(ni->flags & NF_NOTICE)) || !ni)))
	send_cmd (source, "%s %s :%s", me.token ? "!" : "PRIVMSG", dest, buf);
    else
	send_cmd (source, "%s %s :%s", me.token ? "B" : "NOTICE", dest, buf);
}

/* Send an op notice to a channel */
void opnotice (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 *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 RootServ for this, but if people don't have
       the GlobalNoticer on, they probably use something else for notices.)
     */
    if (globalnoticer_on == FALSE)
	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 (long size)
{
    void *buf;

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

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

void *srealloc (void *oldptr, long 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_CSOP && is_csop (u)) ||
		(hash_table->access == H_NONE))
		return hash_table;
	    else if ((hash_table->access == H_IRCOP && !is_oper (u)) ||
		     (hash_table->access == H_CSOP && !is_csop (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_CSOP && is_csop (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_CSOP && !is_csop (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_CSOP && is_csop (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_CSOP && !is_csop (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? */
int is_oper (User *u)
{
    return (u && (u->mode & UMODE_o));
}

/* Is the given nick on the given channel? */
int is_on_chan (const char *nick, const char *chan)
{
    Channel *c = findchan (chan);
    struct c_userlist *cu;

    if (!c)
	return 0;

    for (cu = c->users; cu; cu = cu->next)
	if (!stricmp (cu->user->nick, nick))
	   return 1;

    return 0;
}

/* Is the given nick a chanop on the given channel? */
int is_opped (const char *nick, const char *chan)
{
    Channel *c = findchan (chan);
    struct c_userlist *cu;

    if (!c)
	return 0;

    for (cu = c->users; cu; cu = cu->next)
	if (!stricmp (cu->user->nick, nick))
	    break;

    return (cu && cu->mode & CMODE_o);
}

/* Is the given nick a voice on the given channel? */
int is_voiced (const char *nick, const char *chan)
{
    Channel *c = findchan (chan);
    struct c_userlist *cu;

    if (!c)
	return 0;

    for (cu = c->users; cu; cu = cu->next)
	if (!stricmp (cu->user->nick, nick))
	    break;

    return (cu && cu->mode & CMODE_v);
}

/* Is the given nick a halfop on the given channel? */
int is_halfopped (const char *nick, const char *chan)
{
    Channel *c = findchan (chan);
    struct c_userlist *cu;

    if (!c)
	return 0;

    for (cu = c->users; cu; cu = cu->next)
	if (!stricmp (cu->user->nick, nick))
	    break;

    return (cu && cu->mode & CMODE_h);
}

/* Is the given nick a CSOp? */
int is_csop (User *u)
{
    NickInfo *ni;

    /* If they're not opered, they're not CSOp. */
    if (!is_oper (u))
	return 0;

    /* SRA > CSOp :P */
    if (is_sra (u))
	return 1;

    ni = get_nick (u);

    if (!ni)
	return 0;

    if (ni->flags & NF_CSOP)
	return 1;

    return 0;
}

/* Could this nick become a CSOp? */
int can_csop (const char *nick)
{
    NickInfo *ni = findnick (nick);

    /* SRA > CSOp .. :P */
    if (can_sra (nick))
	return 1;

    if (!ni)
	return 0;

    if (ni->flags & NF_CSOP)
	return 1;

    return 0;
}

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

/* Could this nick become an SRA? */
int can_sra (const char *nick)
{
    if (findsra (nick))
	return 1;

    return 0;
}

/* 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 int do_match_wild (const char *pattern, const char *str, int  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 */
int match_wild (const char *pattern, const char *str)
{
    return do_match_wild (pattern, str, 1);
}

/* Call do_match_wild case-insensitively */
int 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? */
int is_one_of_mine (const char *nick)
{
    if ((rootserv_on == TRUE && !stricmp (nick, s_RootServ)) ||
        (nickserv_on == TRUE && !stricmp (nick, s_NickServ)) ||
	(chanserv_on == TRUE && !stricmp (nick, s_ChanServ)) ||
	(memoserv_on == TRUE && !stricmp (nick, s_MemoServ)) ||
	(globalnoticer_on == TRUE && !stricmp (nick, s_GlobalNoticer)))
        return 1;
 
    return 0;
}

/* Call get_time with the appropriate offset and timezone. */
char *zone_time (NickInfo *ni, time_t event, int fulldate)
{
    Zone *zone;

    /* See if this nick has a zone setting, if not, default to GMT. */
    if (ni->zone)
    {
	zone = findzone (ni->zone);

	if (zone)
	    return get_time (event, fulldate, zone->offset, zone->name);
    }

    return get_time (event, fulldate, 0, "GMT");
}

/* 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, int fulldate, int offset, char *zone)
{
    struct tm tm;
    static char timebuf[256], tmpbuf[256];

    /* Apply the offset for this call */
    event += offset;

    tm = *gmtime (&event);

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

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

    if (!fulldate)
	snprintf (tmpbuf, sizeof (tmpbuf), "%s", timebuf);
    else
	snprintf (tmpbuf, sizeof (tmpbuf), "%s %s", timebuf, zone);

    return tmpbuf;
}

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

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

    while (event >= 60 * 60 * 24 * 365)
    {
	event -= 60 * 60 * 24 * 365;
	years++;
    }
    while (event >= 60 * 60 * 24 * 7)
    {
	event -= 60 * 60 * 24 * 7;
	weeks++;
    }
    while (event >= 60 * 60 * 24)
    {
	event -= 60 * 60 * 24;
	days++; 
    }
    while (event >= 60 * 60)
    {
	event -= 60 * 60;
	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: #d#h#m#s
	2: # days, # hours, # minutes, # seconds
   Like dotime, we only do days, hours, minutes and seconds here.
 */
char *duration (time_t event, int format)
{
    static char ret[128];
    int days, hours, minutes, seconds;

    days = hours = minutes = seconds = 0;

    while (event >= 60 * 60 * 24)
    {
	event -= 60 * 60 * 24;
	days++; 
    }
    while (event >= 60 * 60)
    {
	event -= 60 * 60;
	hours++;
    }
    while (event >= 60)
    {
	event -= 60;
	minutes++;
    }

    seconds = event;

    /* This is pretty big, but it works.. */
    if (format == 1)
    {
	if (days && hours && minutes && seconds)
	    snprintf (ret, sizeof (ret),
		"%dd%dh%dm%ds", days, hours, minutes, seconds);
	else if (days && hours && minutes)
	    snprintf (ret, sizeof (ret),
		"%dd%dh%dm", days, hours, minutes);
	else if (days && hours)
	    snprintf (ret, sizeof (ret),
		"%dd%dh", days, hours);
	else if (days)
	    snprintf (ret, sizeof (ret),
		"%dd", days);
	else if (hours && minutes && seconds)
	    snprintf (ret, sizeof (ret),
		"%dh%dm%ds", hours, minutes, seconds);
	else if (hours && minutes)
	    snprintf (ret, sizeof (ret),
		"%dh%dm", hours, minutes);
	else if (hours)
	    snprintf (ret, sizeof (ret),
		"%dh", hours);
	else if (minutes && seconds)
	    snprintf (ret, sizeof (ret),
		"%dm%ds", minutes, seconds);
	else if (minutes)
	    snprintf (ret, sizeof (ret), 
		"%dm", minutes);
	else
	    snprintf (ret, sizeof (ret),
		"%ds", seconds);
    }

    if (format == 2)
    {
	if (days && hours && minutes && seconds)
	    snprintf (ret, sizeof (ret),
		"%d day%s, %d hour%s, %d minute%s, %d second%s", days, days == 1 ? "" : "s",
		hours, hours == 1 ? "" : "s", minutes, minutes == 1 ? "" : "s", seconds,
		seconds == 1 ? "" : "s");
	else if (days && hours && minutes)
	    snprintf (ret, sizeof (ret),
		"%d day%s, %d hour%s, %d minute%s", days, days == 1 ? "" : "s",
		hours, hours == 1 ? "" : "s", minutes, minutes == 1 ? "" : "s");
	else if (days && hours)
	    snprintf (ret, sizeof (ret),
		"%d day%s, %d hour%s", days, days == 1 ? "" : "s",
		hours, hours == 1 ? "" : "s");
	else if (days)
	    snprintf (ret, sizeof (ret),
		"%d day%s", days, days == 1 ? "" : "s");
	else if (hours && minutes && seconds)
	    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 (hours && minutes)
	    snprintf (ret, sizeof (ret),
		"%d hour%s, %d minute%s", hours, hours == 1 ? "" : "s",
		minutes, minutes == 1 ? "" : "s");
	else if (hours)
	    snprintf (ret, sizeof (ret),
		"%d hour%s", hours, hours == 1 ? "" : "s");
	else if (minutes && seconds)
	    snprintf (ret, sizeof (ret),
		"%d minute%s %d second%s", minutes, minutes  == 1 ? "" : "s",
		seconds, seconds == 1 ? "" : "s");
	else if (minutes)
	    snprintf (ret, sizeof (ret), 
		"%d minute%s", minutes, minutes == 1 ? "" : "s");
	else
	    snprintf (ret, sizeof (ret),
		"%d second%s", seconds, seconds == 1 ? "" : "s");
    }

    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), or "d" (days).

   Borrowed from IRCServices, modified to accept stacking of times. -skold
 */
int dotime (const char *s)
{
    int 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;
	}
    }

    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 TimeZone. Return NULL if TimeZone could not be found. */
Zone *findzone (int zindex)
{
    Zone *zone;

    if (!zindex)
	return NULL;

    for (zone = zonelist; zone; zone = zone->next)
	if (zone->zindex == zindex)
	    return zone;

    return NULL;
}

/* Find a nickname. Return NULL if nickname could not be found. */
NickInfo *findnick (const char *nick)
{
    NickInfo *ni;

    for (ni = nicklist[NSHASH(nick)]; ni; ni = ni->next)
	if (!stricmp (nick, ni->nick))
	    return ni;

    return NULL;
}

/* Return the first nickname on record */
NickInfo *firstni ()
{
    ns_iterator_pos = -1;
    ns_iterator_ptr = NULL;

    return nextni ();
}

/* Return the next nickname on record */
NickInfo *nextni ()
{
    if (ns_iterator_ptr)
	ns_iterator_ptr = ns_iterator_ptr->next;

    while (!ns_iterator_ptr && ns_iterator_pos < HASHSIZE)
    {
	ns_iterator_pos++;

	if (ns_iterator_pos < HASHSIZE)
	    ns_iterator_ptr = nicklist[ns_iterator_pos];
    }

    return ns_iterator_ptr;
}

/* Find a registered channel. Return NULL if channel could not be found. */
ChanInfo *cs_findchan (const char *chan)
{
    ChanInfo *ci;

    if (!chan)
	return NULL;

    for (ci = cs_chanlist[CSHASH(chan)]; ci; ci = ci->next)
	if (!stricmp (chan, ci->name))
	    return ci;

    return NULL;
}

/* Return the first channel */
ChanInfo *firstci ()
{
    cs_iterator_pos = -1;
    cs_iterator_ptr = NULL;

    return nextci ();
}

/* Return the next channel */
ChanInfo *nextci ()
{
    if (cs_iterator_ptr)
	cs_iterator_ptr = cs_iterator_ptr->next;

    while (!cs_iterator_ptr && cs_iterator_pos < HASHSIZE)
    {
	cs_iterator_pos++;

	if (cs_iterator_pos < HASHSIZE)
	    cs_iterator_ptr = cs_chanlist[cs_iterator_pos];
    }

    return cs_iterator_ptr;
}

/* Find an E-Mail. Return NULL if not found. */
Mail *findemail (const char *email)
{
    Mail *em;

    if (!email)
	return NULL;

    for (em = emaillist[MHASH(email)]; em; em = em->next)
	if (!stricmp (email, em->email))
	    return em;

    return NULL;
}

/* Check how many times the given email has been used in the last 24 hours.
   If it's 5 or more, return 1. If not, incriment the count and return 0.
   If the email record doesnt exist, create it and return 0.
 */
int check_email (const char *email)
{
    Mail *em;

    /* Check if this email has been used more than 5 times in the last
       24 hours.
     */
    em = findemail (email);

    if (em)
    {
	if (em->times >= 5)
	    return 1;
	else
	{
	    em->times++;
	    return 0;
	}
    }

    /* The E-Mail isnt in the records, create a new record. */
    if (!em)
    {
	Mail **elist;

	em = scalloc (sizeof (Mail), 1);
	em->email = sstrdup (email);
	em->since = time (NULL);
	em->times = 1;

	elist = &emaillist[MHASH(em->email)];
	em->next = *elist;
	if (*elist)
	    (*elist)->prev = em;
	*elist = em;

	return 0;
    }

    return 0;
}

/* Remove any email records older than 24 hours */
void expire_emails ()
{
    Mail *em;
    int i, j = 0;

    if (logupdates_on == TRUE)
	log ("EM:SYNC: Checking expires...");

    for (i = 0; i < HASHSIZE; i++)
    {
	for (em = emaillist[i]; em; em = em->next)
	{
	    if (time (NULL) - em->since >= 86400)
	    {
		free (em->email);

		if (em->prev)
		    em->prev->next = em->next;
		else
		    emaillist[MHASH(em->email)] = em->next;

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

		free (em);
	    }
	}
    }

    if (logupdates_on == TRUE)
	log ("EM:SYNC:EXPIRE: %d e-mails expired.", j);
}

/* Is the given mask akilled? */
int is_akilled (const char *mask)
{   
    int 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, *host;

    host = u->host;

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

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

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

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

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

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

	free (s);
    }
    else
    {
	if ((s = strchr (host, '.')) && strchr (s + 1, '.'))
	{
	    s = sstrdup (strchr (host, '.') -1);
	    *s = '*';
	}
	else
	    s = sstrdup (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];
    int 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;
}

/* 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 (int num)
{
    static char ret[32];
    sprintf (ret, "%d", num);
    return ret;
}

/* Clear all SRAs and TimeZones. */
void flushlists ()
{
    SRA *sra;
    Zone *zone;

    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 (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);
}

/* Return the nickname the user last id'd for. NULL if they haven't.

   Here's how it works: When a user identifies for a nickname, that
   nickname is stored in their user info. This way, a user can change
   to any nickname they want, and identify from any nickname they want,
   they'll always be identified for that nickname until they disconnect,
   deauth, or identify for another nick.

   When we want to know if someones identified, we come here. If the
   nickname in their u->lastnick is a registered nickname, they're
   considered to be identified for it. It might not always be a registered
   nickname though, since it might get dropped by someone else who
   identified for it.

   Users are always considered to be wanting the nickname they last authed
   for. For example: I'm using the nick skold. I auth for skold2 and skold3.
   Then I /ns set noop off. NickServ assumes I want to set noop off for
   skold3, since I last id'd for it.
 */
NickInfo *get_nick (User *u)
{
    NickInfo *ni;

    if (!u)
    {
	log ("BUG: get_nick() called for null user");
	return NULL;
    }

    if (!u->lastnick)
	return NULL;

    ni = findnick (u->lastnick);

    if (ni)
	return ni;

    return NULL;
}

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

    /* 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") ||
	!stricmp (cmd, "REGISTER") ||
	!stricmp (cmd, "IDENTIFY") ||
	!stricmp (cmd, "ID") ||
	!stricmp (cmd, "RELEASE") ||
	!stricmp (cmd, "LINK") ||
	!stricmp (cmd, "DROP") ||
	(!stricmp (cmd, "SET") && option &&
	 !stricmp (option, "PASS")))
	return 1;

    return 0;
}

/* Check if the command is a CTCP query. Return 1 if true, 0 if false. */
int 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;
}

/* Is the given email valid? */
int validemail (char *email, const char *source)
{
    int i, valid = 1, chars = 0;

    /* Make sure it has @ and . */
    if (!strchr (email, '@') || !strchr (email, '.'))
	valid = 0;

    /* Check for other bad things */
    if (strchr (email, '$') || strchr (email, '/') ||
	strchr (email, ';') || strchr (email, '<') ||
	strchr (email, '>') || strchr (email, '&'))
    {
	globops (s_RootServ, RPL_HACK_EMAIL, source, email);
	valid = 0;
    }

    /* Make sure there are at least 6 characters besides the above
       mentioned @ and .
     */
    for (i = strlen (email) - 1; i > 0; i--)
	if (!(email[i] == '@' || email[i] == '.'))
	    chars++;

    if (chars < 6)
	valid = 0;

    return valid;
}

/* Generate a random number, for use as a key. */
unsigned long makekey ()
{
    unsigned long i, j, k;

    /* This needs to be an unguessable number. Since most of these numbers
       will always be different at any given time, this should be completely
       unguessable. This could probably be a lot easier.. :)
     */
    i = rand() % (time (NULL) / usercnt + 1);
    j = rand() % (me.since * chancnt + 1);

    if (i > j)
	k = (i - j) + strlen (inbuf);
    else
	k = (j - i) + strlen (inbuf);

    /* Shorten or pad it to 9 digits. */
    if (k > 1000000000)
	k = k - 1000000000;
    if (k < 100000000)
	k = k + 100000000;

    return k;
}

/* Send the specified type of E-Mail.

   What is what we're sending to, a nickname or a channel name.

   param is an extra parameter; E-Mail, memo number, etc.

   If type ==:
     1 - Nick REGISTER E-Mail
     2 - Nick SENDPASS E-Mail
     3 - Channel REGISTER E-Mail
     4 - Channel SENDPASS E-Mail
     5 - MemoMail E-Mail
     6 - Nick E-Mail change confirmation
 */
#ifndef WIN32
void sendemail (const char *what, const char *param, int type)
{
    NickInfo *ni, *hni;
    ChanInfo *ci = NULL;
    MemoInfo *mi;
    Memo *m = NULL;
    char *email, *date, *subject, *expire, *tmpex, *pass = NULL;
    char cmsg[256], nmsg[256], mmsg[256], wchan[CHANNELLEN];
    char dBuf[2048], cmdbuf[512], timebuf[256], to[128], from[128];
    FILE *in, *out;
    time_t t;
    struct tm tm;
    int i, number = 0;

    /* Check if the file exists. Open it if it does. */
    if (type == 1)
	in = fopen (NSREGISTER, "r");
    else if (type == 2)
	in = fopen (NSSENDPASS, "r");
    else if (type == 3)
	in = fopen (CSREGISTER, "r");
    else if (type == 4)
	in = fopen (CSSENDPASS, "r");
    else if (type == 5)
	in = fopen (MSMEMOMAIL, "r");
    else
	in = fopen (NSCHANGE, "r");

    if (!in)
    {
	globops (s_RootServ, "Warning! Can't find E-Mail file for %s. Aborting.",
	type == 1 ? "Nick REGISTER" : type == 2 ? "Nick SENDPASS" :
	type == 3 ? "Channel REGISTER" : type == 4 ? "Channel SENDPASS" :
	type == 5 ? "MemoMail" : type == 6 ? "E-Mail Change" : "Unknown");
	return;
    }

    if (type == 5)
	number = atoi (param);

    /* Get the nick we're talking to. */
    if (type == 1 || type == 2 || type == 5 || type == 6)
    {
	ni = findnick (what);

	if (!ni)
	    return;

	if (ni->host)
	    hni = findnick (ni->host);
	else
	    hni = ni;

	/* If this is a registration email, we want the key, not the
	   password.
	 */
	if ((nsregistertype == 1 || nsregistertype == 3 || nsregistertype == 6) && !(type == 2))
	    pass = itoa (hni->key);
	else
	    pass = hni->pass;

	expire = duration (nick_expire, 2);
    }
    else
    {
	char *chantmp, *tmp;

	ci = cs_findchan (what);
	ni = findnick (ci->founder);

	if (!ci || !ni)
	    return;

	if (ni->host)
	    hni = findnick (ni->host);
	else
	    hni = ni;

	if (type == 3)
	    pass = itoa (ci->key);
	else
	    pass = ci->pass;

	expire = duration (chan_expire, 2);

	chantmp = sstrdup (ci->name);

	tmp = strchr (chantmp, '#');

	*tmp++ = 0;

	strscpy (wchan, tmp, CHANNELLEN);

	free (chantmp);
    }

    if (type == 5)
    {
	/* Find their memolist */
	mi = &hni->memos;

	/* Now get the memo we're supposed to be E-Mailing */
	for (i = 0, m = mi->memos; i < mi->memocnt; i++, m++)
	    if (m->number == number)
		break;

	if (!m)
	{
	    log ("sendemail(): Couldn't find specified memo %s for %s!",
		param, what);
	    return;
	}

	if (m->number != number)
	{
	    log ("sendemail(): Got wrong memo for %s!", what);
	    return;
	}
    }

    if ((!type == 6 && !hni->email) || (type == 6 && !hni->temp))
    {
	log ("sendemail(): Couldn't find E-Mail for %s! Bailing out.", what);
	return;
    }

    if (type == 6)
	email = hni->temp;
    else
	email = hni->email;

    if (!validemail (email, hni->nick))
    {
	log ("sendemail(): Invalid E-Mail %s for %s. Bailing out.", email, what);
	return;
    }

    tmpex = duration (tempexpire, 2);

    /* Set up the E-Mail headers */
    time (&t);
    tm = *gmtime (&t);
    strftime (timebuf, sizeof (timebuf) - 1, "%a, %d %b %Y %H:%M:%S %z", &tm);

    date = timebuf;
 
    if (type == 1 || type == 2 || type == 6)
	sprintf (from, "%s <%s@%s>", s_NickServ, ns.user, ns.host);
    else if (type == 3 || type == 4)
	sprintf (from, "%s <%s@%s>", s_ChanServ, cs.user, cs.host);
    else
	sprintf (from, "%s <%s@%s>", s_MemoServ, ms.user, ms.host);

    sprintf (to, "%s <%s>", hni->nick, email);
 
    if (type == 1)
	subject = "Nickname Registration";
    else if (type == 2 || type == 4)
	subject = "Password Retrieval";
    else if (type == 3)
	subject = "Channel Registration";
    else if (type == 5)
	subject = "MemoMail";
    else
	subject = "Change E-Mail Confirmation";

    /* Now set up the E-Mail */
    sprintf (cmdbuf, "%s %s", sendmail_path, email);
    out = popen (cmdbuf, "w");

    /* Set up the preferred form of MSGing. */
    snprintf (mmsg, sizeof (mmsg), "%s%s%s%s", haveserv_on == TRUE ? "" :
	"MSG ", s_MemoServ, haveserv_on == TRUE ? "" : securitysetting == 1 ?
	"@" : "", haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name :
	"");

    snprintf (nmsg, sizeof (nmsg), "%s%s%s%s", haveserv_on == TRUE ? "" :
	"MSG ", s_NickServ, haveserv_on == TRUE ? "" : securitysetting == 1 ?
	"@" : "", haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name :
	"");

    snprintf (cmsg, sizeof (cmsg), "%s%s%s%s", haveserv_on == TRUE ? "" :
	"MSG ", s_ChanServ, haveserv_on == TRUE ? "" : securitysetting == 1 ?
	"@" : "", haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name :
	"");

    fprintf (out, "From: %s\n", from);
    fprintf (out, "To: %s\n", to);
    fprintf (out, "Subject: %s\n", subject);
    fprintf (out, "Date: %s\n\n", date);

    /* Not the most efficient thing in the world, but it works.. */
    while (fgets (dBuf, 2047, in))
    {
	replace (dBuf, sizeof (dBuf), "$NW", network_name);
	replace (dBuf, sizeof (dBuf), "$MM", mmsg);
	replace (dBuf, sizeof (dBuf), "$NM", nmsg);
	replace (dBuf, sizeof (dBuf), "$CM", cmsg);
	replace (dBuf, sizeof (dBuf), "$P", pass);
	replace (dBuf, sizeof (dBuf), "$EX", expire);
	replace (dBuf, sizeof (dBuf), "$E", email);
	replace (dBuf, sizeof (dBuf), "$NS", s_NickServ);
	replace (dBuf, sizeof (dBuf), "$CS", s_ChanServ);
	replace (dBuf, sizeof (dBuf), "$MS", s_MemoServ);
	replace (dBuf, sizeof (dBuf), "$TO", tmpex);
	replace (dBuf, sizeof (dBuf), "$N", hni->nick);

	if (hni->key)
	    replace (dBuf, sizeof (dBuf), "$K", itoa (hni->key));

	if (ci)
	{
	    replace (dBuf, sizeof (dBuf), "$C", ci->name);

	    if (ci->key)
	    {
		replace (dBuf, sizeof (dBuf), "$K", itoa (ci->key));
		replace (dBuf, sizeof (dBuf), "$WC", wchan);
	    }
	}

	if (m && type == 5)
	{
	    replace (dBuf, sizeof (dBuf), "$MD", ni ? zone_time (ni, m->time, 1) :
		get_time (m->time, 1, 0, "GMT"));
	    replace (dBuf, sizeof (dBuf), "$S", m->sender);
	    replace (dBuf, sizeof (dBuf), "$T", m->text);
	}

	/* Now write out the line. */
	fprintf (out, "%s", dBuf);
    }

    fclose (in);
    pclose (out);
}
#endif /* WIN32 */

/* Check if the given nick is fully registered. If it isn't, we'll send a
   notice to source saying that is isn't, then return 0, indicating to the
   function calling this that it isn't fully registered, which will cause
   it to simply return. If the nick is fully regustered, we say nothing and
   return 1.
 */
int check_ns_auth (const char *whoami, const char *source, NickInfo *ni)
{
    if (ni->flags & NF_WAITAUTH)
    {
	notice (whoami, source, NS_NOT_AUTHED, ni->nick);
        return 0;
    }

    return 1;
}

/* Check if the given channel is fully registered. If it isn't, we'll send
   a notice to source saying that it isn't, then return 0, indicating to the
   function calling this that it isn't fully registered, which will cause
   it to simply return. If the channel is fully registered, we say nothing and
   return 1.
 */
int check_cs_auth (const char *whoami, const char *source, ChanInfo *ci)
{
    if (ci->flags & (CF_WAITING | CF_WAITAUTH))
    {
	notice (whoami, source, CS_NOT_AUTHED, ci->name);
	return 0;
    }

    return 1;
}

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

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

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

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

    if (memoserv_on == TRUE)
    {
	if (ircdtype == BAHAMUT || ircdtype == PROMETHEUS)
	    send_cmd (s_MemoServ, "SJOIN %ld %ld %s + :%s", time (NULL), time (NULL),
		joinchan, s_MemoServ);
	else
	    send_cmd (s_MemoServ, "%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 (s_RootServ, "%s %s +o%s%s%s%s %s %s %s %s %s", me.token ? "G" : "MODE", joinchan,
	nickserv_on == TRUE ? "o" : "",
	chanserv_on == TRUE ? "o" : "",
	memoserv_on == TRUE ? "o" : "",
	globalnoticer_on == TRUE ? "o" : "", s_RootServ,
	nickserv_on == TRUE ? s_NickServ : "",
	chanserv_on == TRUE ? s_ChanServ : "",
	memoserv_on == TRUE ? s_MemoServ : "",
	globalnoticer_on == TRUE ? s_GlobalNoticer : "");
}

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

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

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

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

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

/* Notify all CSOps about a new object awaiting verification. */
void verify_notice (Verify *vf)
{
    User *u;

    /* Go through the user list, notifying csops as we find them. */
    for (u = firstuser (); u; u = nextuser ())
    {
	if (is_csop (u))
	{
	    notice (s_ChanServ, u->nick, CS_WAITING_VERIFY, vf->name);
	    notice (s_ChanServ, u->nick, CS_VERIFY_TOLIST, haveserv_on == TRUE ? "" :
		"MSG ", s_ChanServ, haveserv_on == TRUE ? "" : securitysetting == 1 ?
		"@" : "", haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "",
		"AUTH LIST");
	}
    }
}

/* Send a notice to all CSOps */
void noticecsops (const char *source, const char *fmt,...)
{
    User *u;
    va_list args;
    char buf[BUFSIZE];

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

    for (u = firstuser (); u; u = nextuser ())
	if (is_csop (u))
	    send_cmd (source, "%s %s :%s", me.token ? "B" : "NOTICE", u->nick, buf);
}

/* Check for any waiting verifications, and notify the csop if needed. */
void check_verifies (User *u)
{
    ChanInfo *ci;
    int i, j = 0;

    for (i = 0; i < verifycnt; i++)
    {
	ci = cs_findchan (verify[i].name);

	if (ci)
	    j++;
    }

    if (j)
    {
	notice (s_ChanServ, u->nick, CS_VERIFY_WAITING, j);
	notice (s_ChanServ, u->nick, CS_VERIFY_TOLIST, haveserv_on == TRUE ? "" : "MSG ",
	    s_ChanServ, haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
	    haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "", "AUTH LIST");
    }
}

/* Same as above, but notify *all* csops. */
void scan_verifies ()
{
    ChanInfo *ci;
    int i, j = 0;

    for (i = 0; i < verifycnt; i++)
    {
	ci = cs_findchan (verify[i].name);

	if (ci)
	    j++;
    }

    if (j)
    {
	noticecsops (s_ChanServ, CS_VERIFY_WAITING, j);
	noticecsops (s_ChanServ, CS_VERIFY_TOLIST, haveserv_on == TRUE ? "" : "MSG ",
	    s_ChanServ, haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
	    haveserv_on == TRUE ? "" : securitysetting == 1 ? me.name : "", "AUTH LIST");
    }
}

/* Convert a text mode string to flags */
int32 string_to_flags (char *modes)
{
    int32 flags = 0;

    while (*modes)
    {
	int i;

	for (i = 0; cmodes[i].mode != 0 && cmodes[i].mode != *modes; i++);

	if (!cmodes[i].mode)
	    return 0;

	flags |= cmodes[i].flag;
	*modes++ = 0;
    }

    return flags;
}

/* Convert mode flags to a text mode string */
char *flags_to_string (int32 flags)
{
    static char buf[32];
    char *s = buf;
    int i;

    for (i = 0; cmodes[i].mode != 0; i++)
	if (flags & cmodes[i].flag)
	    *s++ = cmodes[i].mode;

    *s = 0;

    return buf;
}

/* Convert a mode character to a flag. */
int32 mode_to_flag (char c)
{
    int i;

    for (i = 0; cmodes[i].mode != 0 && cmodes[i].mode != c; i++);

    return cmodes[i].flag;
}

/* Convert a mode flag to a character. */
char mode_to_char (int32 f)
{
    int i;

    for (i = 0; cmodes[i].flag != 0 && cmodes[i].flag != f; i++);

    return cmodes[i].mode;
}

int32 prefix_to_flag (char c)
{
    int i;

    for (i = 0; cumodes[i].prefix != 0 && cumodes[i].prefix != c; i++);

    return cumodes[i].flag;
}

/* 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);
}

/* Return the current time in milliseconds. */
uint32 time_msec ()
{
#ifdef HAVE_GETTIMEOFDAY
    struct timeval tv;
    gettimeofday (&tv, NULL);
    return tv.tv_sec*1000 + tv.tv_usec/1000;
#else
    return time (NULL) * 1000;
#endif
}

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

/* Go through every channel and nickname on the network.
   For nicknames, tell anyone who needs to, to identify.
   Also start collision timers.
   For each channel, do any modechanges and topic changes
   nessecary. Also kick any users as needed.
 */
void check_eb ()
{
    ChanInfo *ci;
    Channel *c;
    User *u;
    struct c_userlist *cu;
    int i, ulev = 0;

    /* Check every user and call validate_user(). */
    for (u = firstuser (); u; u = nextuser ())
	validate_user (u);

    /* Go through every channel on the network. */
    for (c = firstchan (); c; c = nextchan ())
    {
	int kickcnt = 0;
	char **kicknicks = NULL;

	ci = cs_findchan (c->name);

	/* If the channel isn't registered, we have nothing
	   to do with it. Continue.
	 */
	if (!ci)
	{
	    /* Does this channel have +r set? Unset it if so. */
	    if (c->mode & CMODE_r)
	    {
		c->mode &= ~CMODE_r;
		send_cmode (s_ChanServ, c->name, "-r");
	    }

	    continue;
	}

	/* Go through and kick anyone that needs it. Since cs_join will kick
	   the user, and remove it from the userlist, it'll fuck this up. So
	   we first make a copy of the userlist, and work off that.
	 */
	for (cu = c->users; cu; cu = cu->next)
	{
	    ++kickcnt;
	    kicknicks = srealloc (kicknicks, sizeof (char *) *kickcnt);
	    kicknicks[kickcnt - 1] = sstrdup (cu->user->nick);
	}

	/* Now go through and kick anyone who needs it. */
	for (i = 0; i < kickcnt; kicknicks++, i++)
	{
	    u = finduser (*kicknicks);

	    if (u)
		cs_join (u, ci->name, 1);

	    free (*kicknicks);
	}

	kicknicks = NULL;	

	/* Check if this channel still exists after kicking.. */
	if (!findchan (ci->name))
	    continue;

	/* Go through all the users on the channel, opping,
	   deopping, etc, as needed.
	 */
	for (cu = c->users; cu; cu = cu->next)
	{
	    int gavemode = 0;

	    u = cu->user;
	    ulev = get_access (u, ci);

	    /* VOP? */
	    if (ulev == VOP && !(cu->mode & CMODE_v))
	    {
		send_cmode (s_ChanServ, c->name, "+v", u->nick);
		cu->mode |= CMODE_v;
		gavemode = 1;
	    }

	    if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
	    {
		/* HOP? */
		if (ulev == HOP && !(cu->mode & CMODE_h))
		{
		    send_cmode (s_ChanServ, c->name, "+h", u->nick);
		    cu->mode |= CMODE_h;

		    gavemode = 1;
		}
	    }

	    /* AOP or higher? */
	    if (ulev >= AOP && !(cu->mode & CMODE_o))
	    {
		send_cmode (s_ChanServ, c->name, "+o", u->nick);
		cu->mode |= CMODE_o;

		gavemode = 1;
	    }

	    /* Update the last used timestamp */
	    if (ulev)
		ci->lastused = time (NULL);

	    /* If they don't have any access, but VOPALL is on, voice them. */
	    if (!gavemode && !ulev && (ci->flags & CF_VOPALL) && !(cu->mode & CMODE_v))
	    {
		send_cmode (s_ChanServ, c->name, "+v", u->nick);
		cu->mode |= CMODE_v;
	    }

	    /* Now check if anyone has a mode they SHOULDN'T have. Only
	       do this if CF_SECURE is on, otherwise we don't bitch about
	       unauthed ops/etc.
	     */
	    if (ci->flags & CF_SECURE)
	    {
		if (ulev < AOP && (cu->mode & CMODE_o))
		{
		    send_cmode (s_ChanServ, c->name, "-o", u->nick);
		    cu->mode &= ~CMODE_o;
		}

		if (ircdtype == UNREAL3 || ircdtype == UNREAL3_2)
		{
		    if (ulev < HOP && (cu->mode & CMODE_h))
		    {
			send_cmode (s_ChanServ, c->name, "-h", u->nick);
			cu->mode &= ~CMODE_h;
		    }
		}

		if (!(ci->flags & CF_VOPALL) && (ulev < VOP && (cu->mode & CMODE_v)))
		{
		    send_cmode (s_ChanServ, c->name, "-v", u->nick);
		    cu->mode &= ~CMODE_v;
		}
	    }
	}

	/* check_modes() will take care of any
	   needed modechanges.
	 */
	check_modes (c->name);

	/* Check the current channel topic against the stored one.
	   If it differs, check the access of the person who set it.
	   If topiclock is off, or their access is sufficient, store
	   the new topic, else revert it.
	 */
	if (stricmp (ci->topic, c->topic) && !(ci->flags & CF_FROZEN))
	{
	    u = finduser (c->topicsetter);

	    ulev = 0;

	    if (u)
		ulev = get_access (u, ci);

	    if (!ulev || ulev < ci->topiclock)
	    {
		/* Revert the topic. */
		strscpy (c->topicsetter, ci->topicsetter, NICKLEN);
		strscpy (c->topic, ci->topic, TOPICLEN);
		c->topictime = ci->topictime;

		send_cmd (s_ChanServ, "%s %s %s %lu :%s", me.token ? ")" : "TOPIC", c->name,
		    c->topicsetter, c->topictime, c->topic);
	    }
	    else
	    {
		/* No topiclock, sufficient access, etc. Store the new topic. */
		strscpy (ci->topicsetter, c->topicsetter, NICKLEN);
		strscpy (ci->topic, c->topic, TOPICLEN);
		ci->topictime = c->topictime;
	    }
	}
    }
}

/* Tell everyone identified to the given nickname that they
   have a new memo.
 */
void notify_new_memo (NickInfo *ni, Memo *m)
{
    User *u;

    for (u = firstuser (); u; u = nextuser ())
    {
	if (u->lastnick && !stricmp (ni->nick, u->lastnick))
	{
	    notice (s_MemoServ, u->nick, MS_NEW_MEMO, m->sender);
	    notice (s_MemoServ, u->nick, MS_TO_LIST,
		haveserv_on == TRUE ? "" : "MSG ", s_MemoServ,
		haveserv_on == TRUE ? "" : securitysetting == 1 ? "@" : "",
		haveserv_on == TRUE ? "" : securitysetting == 1 ?
		me.name : "");
	}
    }
}

/* Save the web.db to disk. Unlike other dbs, web.db is
   write only to services. It's only read by the web integration
   script. The transaction log (further down) is how we read
   changes in.
 */
void save_web_dbase ()
{
    FILE *f;
    NickInfo *ni, *hni;
    ChanInfo *ci;
    MemoInfo *mi;
    Memo *m;
    ChanAccess *ca;
    AKick *ak;
    int i, dolog = 0;
#ifdef HAVE_GETTIMEOFDAY
    struct timeval start, now, tmp;
#endif

    if (logupdates_on == TRUE)
#ifdef HAVE_GETTIMEOFDAY
	log_sameline (1, "WB:DB: Saving... ");
#else
	log ("WB:DB: Saving... ");
#endif

#if HAVE_UMASK
    /* web.db needs different permissions from other databases. */
    umask (022);
#endif

    f = fopen (WEB_DB, "w");

    if (!f)
    {
#ifdef HAVE_GETTIMEOFDAY
	log_sameline (0, "Can't write to %s! ", WEB_DB);
#else
	log ("WB:DB: Can't write to %s!", WEB_DB);
#endif
        dolog = 1;

	return;
    }

#ifdef HAVE_GETTIMEOFDAY
    gettimeofday (&(start), NULL);
#endif

    /* Start with nicks */
    for (ni = firstni (); ni; ni = nextni ())
    {
	/* Frozen nicks can't be used, so skip them. */
	if (ni->flags & NF_FROZEN)
	    continue;

	/* Find their memos here. This lets us write memo things to NickInfo. */
	mi = &ni->memos;

	if (ni->host)
	    hni = findnick (ni->host);
	else
	    hni = ni;

	/* One entry per nick. Linked nicks included. */
	fprintf (f, "NI %s %s %lu %lu %lu\n", ni->nick,
#ifdef HAVE_LCRYPT
	    strlen (salt) ? crypt (hni->pass, salt) : hni->pass,
#else
	    hni->pass,
#endif
	    hni->registered, hni->lastseen, hni->key);

	/* Write out any memos they have. */
	if (mi->memocnt)
	{
	    for (i = 0; i < mi->memocnt; i++)
	    {
		m = &mi->memos[i];

		/* Unlike services, we put the owner of the memo at the start
		   for ease of parsing.
		 */
		fprintf (f, "MO %s %s %lu %s %s\n", ni->nick,
		    (m->flags & MF_UNREAD) ? "UNREAD" : (m->flags & MF_EMAILED) ?
		    "EMAILED" : "READ", m->time, m->sender,
		    m->text);
	    }
        }   
    }

    /* Now do channels */
    for (ci = firstci (); ci; ci = nextci ())
    {
	/* Frozen channels can't be used, so skip them. */
	if (ci->flags & CF_FROZEN)
	    continue;

	fprintf (f, "CI %s %s %s %lu %lu %lu\n", ci->name,
#ifdef HAVE_LCRYPT
	    ci->founder, strlen (salt) ? crypt (ci->pass, salt) : ci->pass,
#else
	    ci->founder, ci->pass,
#endif
	    ci->registered, ci->lastused, ci->key);

	/* For ease of parsing, we drop the access and akick lists
	   in the same format. The levels are identical to services,
	   except akicks are level 0.
	 */
	for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
	{
	    if (!ca->level)
		continue;

	    fprintf (f, "CA %s %s %d\n", ci->name, ca->mask, ca->level);
	}

	for (ak = ci->akick, i = 0; i < ci->akickcnt; ak++, i++)
	{
	    if (!ak->level)
		continue;
    
	    fprintf (f, "CA %s %s 0\n", ci->name, ak->mask);
        }
    }

    fclose (f);

#if HAVE_UMASK
    /* Restore the umask for future db writes */
    umask (077);
#endif

#ifdef HAVE_GETTIMEOFDAY
    if (logupdates_on == TRUE && !dolog)
    {
	gettimeofday (&now, NULL);
	timersub (&now, &start, &tmp);
	log_sameline (0, "finished in %d ms.", tv2ms (&tmp));
    }
#endif
}

/* Open and parse the transaction log, and remove it afterward. */
void read_web_log ()
{
    FILE *f = fopen (WEB_LOG, "r");
    NickInfo *ni, *tni, **nlist;
    ChanInfo *ci, **clist;
    ChanAccess *ca;
    MemoInfo *mi;
    Memo *m;
    char *item, *s, dBuf[BUFSIZE];
    time_t now = time (NULL);
    int i;

    if (!f)
        return;

    /* Ok. Here's how it works. Changes made from the website are
       logged in web.log. Every minute, we attempt to read this log.
       Reading it this frequently has two benefits: One, there's not
       likely to be many changes in it. Two, any changes that are in
       it won't be sitting around for very long before being applied
       to services.

       Once we read an action from the log, we apply it.. Since it's
       not being done from IRC, we'll do it all here rather than
       calling other functions, unless that can be done without much
       effort. Making NickServ's do_register work from here is 'too
       much effort' .. so.

       The format of this log, and web.db, is rather 'loose' .. There's
       plenty of opportunities to save space in each, but it's done this
       way to make it easy for people to write interfaces for this.
     */
    while (fgets (dBuf, 2047, f))
    {
	item = strtok (dBuf, " ");
	strip (item);

	/* A nickname has been registered */
	if (!stricmp (item, "NR"))
	{
	    char nick[NICKLEN], host[HOSTLEN], pass[PASSWDLEN], *email;

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		strscpy (nick, s, sizeof (nick));
	    else
	    {
		log ("Invalid Nick Registration entry in web.log. No Nickname. Check your web integration script.");
		continue;
	    }

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		strscpy (host, s, sizeof (host));
	    else
	    {
		log ("Invalid Nick Registration entry in web.log. No Host. Check your web integration script.");
		continue;
	    }

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		strscpy (pass, s, sizeof (pass));
	    else
	    {
		log ("Invalid Nick Registration entry in web.log. No Password. Check your web integration script.");
		continue;
	    }

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		email = s;
	    else
	    {
		log ("Invalid Nick Registration entry in web.log. No Email. Check your web integration script.");
		continue;
	    }

	    /* If the nicknames already registered, bail */
	    if (findnick (nick))
		continue;

	    /* Now register the nickname. Unlike NS REGISTER, we can't deny new nicknames here if we
	       don't have all the info for that register type. We'll make an effort to honor the
	       register type (such as sending emails) but the nick is going to be registered.
	     */
	    ni = scalloc (sizeof (NickInfo), 1);
	    strscpy (ni->nick, nick, NICKLEN);
	    strscpy (ni->pass, pass, PASSWDLEN);
	    ni->registered = ni->lastseen = now;
	    ni->memos.memolimit = memobox_size;
	    ni->real = sstrdup ("Web Registration");
	    ni->usermask = smalloc (strlen (nick) + strlen (host) + 6);
	    sprintf (ni->usermask, "%s!web@%s", nick, host);
	    ni->accesscnt = 1;
	    ni->access = smalloc (sizeof (char *));
	    ni->access[0] = ni->usermask;
	    ni->email = sstrdup (email);

	    if (defnickflags)
		ni->flags = defnickflags;

	    nlist = &nicklist[NSHASH(ni->nick)];
	    ni->next = *nlist;
	    if (*nlist)
		(*nlist)->prev = ni;
	    *nlist = ni;

	    log ("NS:WEB:REGISTER: %s!web@%s", nick, host);
	    snoop (s_NickServ, "NS:WEB:REGISTER: %s!web@%s", nick, host);

	    continue;
	}

	/* A nickname has been dropped.. All checks are done on the site,
	   like everything else, we just accept these changes blindly.
	 */
	if (!stricmp (item, "ND"))
	{
	    char nick[NICKLEN];

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		strscpy (nick, s, sizeof (nick));
	    else
	    {
		log ("Invalid Nick Deletion entry in web.log. No Nickname. Check your web integration script.");
		continue;
	    }

	    /* Find the nick */
	    ni = findnick (nick);

	    /* If the nick isn't registered, there's nothing to do anyhow :) */
	    if (!ni)
		continue;

	    if (ni->linkcnt)
	    {
		log ("NS:WEB:DROP: %s (%d)", ni->nick, ni->linkcnt);
		snoop (s_NickServ, "NS:WEB:DROP: %s (%d)", ni->nick, ni->linkcnt);
	    }
	    else
	    {
		log ("NS:WEB:DROP: %s", ni->nick);
		snoop (s_NickServ, "NS:WEB:DROP: %s", ni->nick);
	    }

	    /* If this is a linked nick, we need to remove it from the hosts links
	       list here, since delnick doesn't do this.
	     */
	    if (ni->host)
	    {
		char **links;

		tni = findnick (ni->host);

		if (tni)
		{
		    for (links = tni->links, i = 0; i < tni->linkcnt; links++, i++)
			if (!stricmp (*links, ni->nick))
			    break;

		    if (*links)
			free (*links);

		    --tni->linkcnt;

		    if (i < tni->linkcnt)
			bcopy (links + 1, links, (tni->linkcnt - i) * sizeof (char *));

		    if (tni->linkcnt)
			tni->links = srealloc (tni->links, tni->linkcnt * sizeof (char *));
		    else
		    {
			if (tni->links)
			    free (tni->links);

			tni->links = NULL;
		    }
		}
	    }

	    delnick (ni);

	    continue;
	}

	/* A nickname has been validated */
	if (!stricmp (item, "NC"))
	{
	    char nick[NICKLEN];

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		strscpy (nick, s, sizeof (nick));
	    else
	    {
		log ("Invalid Nick Confirm entry in web.log. No Nickname. Check your web integration script.");
		continue;
	    }

	    ni = findnick (nick);

	    if (ni)
	    {
		ni->key = 0;
		ni->flags &= ~NF_WAITAUTH;
	    }
	}

	/* A channel has been registered */
	if (!stricmp (item, "CR"))
	{
	    char chan[CHANNELLEN], founder[NICKLEN], pass[PASSWDLEN];

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		strscpy (chan, s, sizeof (chan));
	    else
	    {
		log ("Invalid Channel Registration entry in web.log. No Channel. Check your web integration script.");
		continue;
	    }

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		strscpy (founder, s, sizeof (founder));
	    else
	    {
		log ("Invalid Channel Registration entry in web.log. No Founder. Check your web integration script.");
		continue;
	    }

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		strscpy (pass, s, sizeof (pass));
	    else
	    {
		log ("Invalid Channel Registration entry in web.log. No Password. Check your web integration script.");
		continue;
	    }

	    /* If the founders nick isn't regged, bail. */
	    if (!(ni = findnick (founder)))
		continue;

	    /* Channels are owned by the host. */
	    if (ni->host)
		tni = findnick (ni->host);
	    else
		tni = ni;

	    /* If the channel's already registered, bail. */
	    if (cs_findchan (chan))
		continue;

	    ci = scalloc (sizeof (ChanInfo), 1);
	    strscpy (ci->name, chan, CHANNELLEN);
	    strscpy (ci->founder, tni->nick, NICKLEN);
	    strscpy (ci->pass, pass, PASSWDLEN);
	    ci->registered = ci->lastused = time (NULL);

	    if (defchanflags)
		ci->flags = defchanflags;

	    /* Set the default modelock */
	    ci->mlock_on |= defmlock_on;

	    ci->memolevel = SOP;

	    if (defmlock_off)
		ci->mlock_off |= defmlock_off;
    
	    /* Add this channel to the founder's channel list */
	    tni->chancnt++;
	    tni->chans = srealloc (tni->chans, sizeof (char *) *tni->chancnt);
	    tni->chans[tni->chancnt - 1] = sstrdup (ci->name);

	    clist = &cs_chanlist[CSHASH(ci->name)];
	    ci->next = *clist;
	    if (*clist)
	        (*clist)->prev = ci;
	    *clist = ci;
     
	    /* Add them as founder */
	    for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
		if (ca->level)
		    break;

	    if (i == ci->accesscnt)
	    {
		ci->accesscnt++;
		ci->access = srealloc (ci->access, sizeof (ChanAccess) * ci->accesscnt);
		ca = &ci->access[ci->accesscnt - 1];
	    }

	    ca->mask = sstrdup (tni->nick);
	    ca->level = FOUNDER;

	    log ("CS:WEB:REGISTER: %s %s", chan, tni->nick);
	    snoop (s_ChanServ, "CS:WEB:REGISTER: %s %s", chan, tni->nick);

	    continue;
	}

	/* A channel has been dropped.. All checks are done on the site,
	   like everything else, we just accept these changes blindly.
	 */
	if (!stricmp (item, "CD"))
	{
	    char chan[CHANNELLEN];

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		strscpy (chan, s, sizeof (chan));
	    else
	    {
		log ("Invalid Channel Deletion entry in web.log. No Channel. Check your web integration script.");
		continue;
	    }

	    ci = cs_findchan (chan);

	    /* If the chan isn't registered, there's nothing to do anyhow :) */
	    if (!ci)
		continue;

	    log ("CS:WEB:DROP: %s", ci->name);
	    snoop (s_ChanServ, "CS:WEB:DROP: %s", ci->name);

	    delchan (ci, 0);

	    continue;
	}

	/* A channel has been validated */
	if (!stricmp (item, "CC"))
	{
	    char chan[CHANNELLEN];

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		strscpy (chan, s, sizeof (chan));
	    else
	    {
		log ("Invalid Channel Confirm entry in web.log. No Channel. Check your web integration script.");
		continue;
	    }

	    ci = cs_findchan (chan);

	    if (ci)
	    {
		ci->key = 0;
		ci->flags &= ~CF_WAITAUTH;
	    }
	}

	/* Channel access deletion */
	if (!stricmp (item, "AD"))
	{
	    char chan[CHANNELLEN], nick[NICKLEN];

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		strscpy (chan, s, sizeof (chan));
	    else
	    {
		log ("Invalid Channel Access Deletion entry in web.log. No Channel. Check your web integration script.");
		continue;
	    }

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		strscpy (nick, s, sizeof (nick));
	    else
	    {
		log ("Invalid Channel Access Deletion entry in web.log. No Nickname. Check your web integration script.");
		continue;
	    }

	    ci = cs_findchan (chan);
	    ni = findnick (nick);

	    if (ci && ni)
	    {
		for (ca = ci->access, i = 0; i < ci->accesscnt; ca++, i++)
		    if (ca->level)
			if (!stricmp (ni->nick, ca->mask))
			{
			    if (ca->mask)
				free (ca->mask);

			    ca->level = 0;

			    break;
			}

		continue;
	    }
	}

	/* A memo has been sent */
	if (!stricmp (item, "MS"))
	{
	    char to[NICKLEN], from[NICKLEN], text[256];

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		strscpy (to, s, sizeof (to));
	    else
	    {
		log ("Invalid Memo Send entry in web.log. No Recipient. Check your web integration script.");
		continue;
	    }

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		strscpy (from, s, sizeof (from));
	    else
	    {
		log ("Invalid Memo Send entry in web.log. No Sender. Check your web integration script.");
		continue;
	    }

	    s = strtok (NULL, "");
	    strip (s);

	    if (s)
		strscpy (text, s, sizeof (text));
	    else
	    {
		log ("Invalid Memo Send entry in web.log. No Text. Check your web integration script.");
		continue;
	    }

	    ni = findnick (to);
	    tni = findnick (from);

	    if (!ni | !tni)
		continue;

	    send_memo (ni, tni->nick, text);

	    continue;
	}

	/* A memo has been read. Note: since the memo index numbers could very likely change
	   in the lag time between the web.db being written and the web.log being read, we can't
	   rely on these numbers to know which memo has been read. So we compare the memo timestamps
	   until we find the one we're looking for. If we don't find it, no big deal.
	 */
	if (!stricmp (item, "MR"))
	{
	    char nick[NICKLEN];
	    time_t ts;

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		strscpy (nick, s, sizeof (nick));
	    else
	    {
		log ("Invalid Memo Read entry in web.log. No Nickname. Check your web integration script.");
		continue;
	    }

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		ts = atol (s);
	    else
	    {
		log ("Invalid Memo Read entry in web.log. No Timestamp. Check your web integration script.");
		continue;
	    }

	    if (!(ni = findnick (nick)))
		continue;

	    mi = &ni->memos;

	    for (i = 0; i < mi->memocnt; i++)
	    {
		m = &mi->memos[i];

		if (m->time == ts)
		    m->flags &= ~MF_UNREAD;
	    }

	    continue;
	}

	/* A memo has been deleted. Note: Like MR, the memo index numbers could change before
	   we get here. So we compare the memo timestamps.
	 */
	if (!stricmp (item, "MD"))
	{
	    char nick[NICKLEN];
	    time_t ts;

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		strscpy (nick, s, sizeof (nick));
	    else
	    {
		log ("Invalid Memo Delete entry in web.log. No Nickname. Check your web integration script.");
		continue;
	    }

	    s = strtok (NULL, " ");
	    strip (s);

	    if (s)
		ts = atol (s);
	    else
	    {
		log ("Invalid Memo Delete entry in web.log. No Timestamp. Check your web integration script.");
		continue;
	    }

	    if (!(ni = findnick (nick)))
		continue;

	    mi = &ni->memos;

	    for (i = 0; i < mi->memocnt; i++)
	    {
		m = &mi->memos[i];

		if (m->time == ts)
		{
		    free (mi->memos[i].text);
		    mi->memocnt--;

		    if (i < mi->memocnt)
			memmove (mi->memos + i, mi->memos + i + 1, sizeof (Memo) *
			    (mi->memocnt - i));

		    if (!mi->memocnt)
		    {
			free (mi->memos);
			mi->memos = NULL;
		    }
		}
	    }

	    continue;
	}
    }

    remove (WEB_LOG);
}

/* Increase the passfail count for the user, and kill them
   if they've exceeded the amount allowed.
 */
void passfail (const char *whoami, const char *what, const char *str, User *u)
{
    u->passfail++;

    notice (whoami, u->nick, RPL_WRONG_PASS, what);

    if (u->passfail >= passfails)
    {
	snoop (whoami, "%s %s %s!%s@%s (Killed)", str, what, u->nick,
	    u->user, u->host);
	log ("%s %s %s!%s@%s (Killed)", str, what, u->nick, u->user,
	    u->host);
	kill_user (whoami, u->nick, RPL_KILLED_PASSFAIL);
	return;
    }

    if (u->passfail >= (passfails - 1))
	notice (whoami, u->nick, RPL_LAST_TRY);
    snoop (whoami, "%s %s %s!%s@%s (%d/%d)", str, what, u->nick,
	u->user, u->host, u->passfail, passfails);
    log ("%s %s %s!%s@%s (%d/%d)", str, what, u->nick, u->user,
	u->host, u->passfail, passfails);
}

/* Add a new TimeZone into the timezones list. */
static Zone *new_zone (const char *zindex, const char *name, const char *noffset,
		       const char *offset, const char *desc)
{
    Zone *zone;

    zone = scalloc (sizeof (Zone), 1);

    zone->zindex = atoi (zindex);
    zone->name = sstrdup (name);
    zone->noffset = sstrdup (noffset);
    zone->offset = atoi (offset);
    zone->desc = sstrdup (desc);

    zone->next = zonelist;

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

    zonelist = zone;

    return zone;
}

/* Load the TimeZones into memory. */
void load_zones ()
{
    FILE *f = fopen (ZONEFILE, "r");
    char *s, dBuf[BUFSIZE], *zindex, *name, *noffset, *offset, *desc;
    int zin = 0;
#ifdef HAVE_GETTIMEOFDAY
    struct timeval start, now, tmp;
#endif

#ifdef HAVE_GETTIMEOFDAY
    log_sameline (1, "ZONE: Loading.... ");
#else
    log ("ZONE: Loading.... ");
#endif

    if (!f)
    {
#ifdef HAVE_GETTIMEOFDAY
	log_sameline (0, "No %s found! ", ZONEFILE);
#else
        log ("ZONE: No %s found!", ZONEFILE);
#endif
	return;
    }

#ifdef HAVE_GETTIMEOFDAY
    gettimeofday (&(start), NULL);
#endif

    while (fgets (dBuf, 2047, f))
    {
	s = strtok (dBuf, " ");
	strip (s);

	if (s)
	    zindex = s;
	else
	    zindex = "";

	s = strtok (NULL, " ");
	strip (s);

	if (s)
	    name = s;
	else
	    name = "";

	s = strtok (NULL, " ");
	strip (s);

	if (s)
	    noffset = s;
	else
	    noffset = "";

	s = strtok (NULL, " ");
	strip (s);

	if (s)
	    offset = s;
	else
	    offset = "";

	s = strtok (NULL, "");
	strip (s);

	if (s)
	    desc = s;
	else
	    desc = "";

	if (!desc)
	{
#if HAVE_GETTIMEOFDAY
	    log_sameline (0, "");
#endif
	    log ("Invalid TimeZone %s: Missing data!", name);
	}
	else
	{
	    new_zone (zindex, name, noffset, offset, desc);
	    zin++;
	}
    }

    fclose (f);

#ifdef HAVE_GETTIMEOFDAY
    gettimeofday (&now, NULL);
    timersub (&now, &start, &tmp);
    log_sameline (0, "finished in %d ms. (%d timezones)", tv2ms (&tmp), zin);
#endif
}

/* Generate a hash. */
unsigned long shash (const unsigned char *text)
{
    unsigned long 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);
}

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

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

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

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

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

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

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

/* Process a buffer and output notices in 4 columns 10 chars wide
   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];
    int tmplen, cmdcnt = 0, pad = 0, prefixed = 0, prefixpad, columns = 5;

    *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 == columns)
	{
	    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 < columns)
		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;
	}
    }
}

/* Send a services message to the given server. This is for Cygnus->OperStats
   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, Cygnus would send out something like
   351 operstats.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", operstats_server, buf);
}

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

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

    return tmp;
}
