/* OperServ functions.
 *
 * (C) 2003 Anope Team
 * Contact us at info@anope.org
 *
 * Please read COPYING and README for furhter details.
 *
 * Based on the original code of Epona by Lara.
 * Based on the original code of Services by Andy Church. 
 * 
 * $Id: operserv.c 420 2004-10-23 23:42:15Z trystan $
 *
 */

#include "services.h"
#include "pseudo.h"

/*
 * Disable the modules on OpenBSD (for now)
 * there is work in progress for this.
 */
#ifdef __OpenBSD__
#ifdef USE_MODULES
#undef USE_MODULES
#endif                          /* USE_MODULES */
#endif                          /* __OpenBSD__ */

extern Module *mod_current_module;
extern int mod_current_op;
extern User *mod_current_user;
extern ModuleHash *MODULE_HASH[MAX_CMD_HASH];
/*************************************************************************/

struct clone {
    char *host;
    long time;
};

/* List of most recent users - statically initialized to zeros */
static struct clone clonelist[CLONE_DETECT_SIZE];

/* Which hosts have we warned about, and when? This is used to keep us
 * from sending out notices over and over for clones from the same host. */
static struct clone warnings[CLONE_DETECT_SIZE];

/* List of Services administrators */
SList servadmins;
/* List of Services operators */
SList servopers;
/* AKILL, SGLINE, SQLINE and SZLINE lists */
SList akills, sglines, sqlines, szlines;

/*************************************************************************/

static void get_operserv_stats(long *nrec, long *memuse);

static int compare_adminlist_entries(SList * slist, void *item1,
                                     void *item2);
static int compare_operlist_entries(SList * slist, void *item1,
                                    void *item2);
static void free_adminlist_entry(SList * slist, void *item);
static void free_operlist_entry(SList * slist, void *item);

static int is_akill_entry_equal(SList * slist, void *item1, void *item2);
static void free_akill_entry(SList * slist, void *item);
#ifdef IRC_BAHAMUT
static int is_sgline_entry_equal(SList * slist, void *item1, void *item2);
static void free_sgline_entry(SList * slist, void *item);
#endif
static int is_sqline_entry_equal(SList * slist, void *item1, void *item2);
static void free_sqline_entry(SList * slist, void *item);
#ifdef IRC_BAHAMUT
static int is_szline_entry_equal(SList * slist, void *item1, void *item2);
static void free_szline_entry(SList * slist, void *item);
#endif

static int do_help(User * u);
static int do_global(User * u);
static int do_stats(User * u);
static int do_admin(User * u);
static int do_oper(User * u);
static int do_os_mode(User * u);
static int do_clearmodes(User * u);
static int do_os_kick(User * u);
static int do_akill(User * u);
static int do_sgline(User * u);
static int do_sqline(User * u);
static int do_szline(User * u);
static int do_set(User * u);
static int do_noop(User * u);
static int do_jupe(User * u);
static int do_raw(User * u);
static int do_update(User * u);
static int do_reload(User * u);
static int do_os_quit(User * u);
static int do_shutdown(User * u);
static int do_restart(User * u);
static int do_ignorelist(User * u);
static int do_clearignore(User * u);
static int do_killclones(User * u);
static int do_chanlist(User * u);
static int do_userlist(User * u);
static int do_ignoreuser(User * u);
static int do_staff(User * u);
static int do_defcon(User * u);
static int do_chankill(User * u);
static void defcon_sendlvls(User * u);
char *defconReverseModes(const char *modes);
int DefConModesSet = 0;
time_t DefContimer;
void runDefCon(void);
void resetDefCon(int level);
void oper_global(char *nick, char *fmt, ...);

#ifdef USE_MODULES
int do_modload(User * u);
int do_modunload(User * u);
int do_modlist(User * u);
int do_modinfo(User * u);
static int showModuleCmdLoaded(CommandHash * cmdList, char *mod_name,
                               User * u);
static int showModuleMsgLoaded(MessageHash * msgList, char *mod_name,
                               User * u);
#endif


#ifdef USE_OSSVS
#ifndef IRC_HYBRID
static int do_operumodes(User * u);
#endif
static int do_svsnick(User * u);
#endif

#if defined(IRC_UNREAL) && defined(USE_OSSVS)
static int do_operoline(User * u);
#endif

#ifdef DEBUG_COMMANDS
static void send_clone_lists(User * u);
static int do_matchwild(User * u);
#endif

/* OperServ restart needs access to this if were gonna avoid sending ourself a signal */
extern int do_restart_services(void);
void moduleAddOperServCmds(void);
/*************************************************************************/

/* Options for the lists */
SListOpts akopts = { 0, NULL, &is_akill_entry_equal, &free_akill_entry };
SListOpts saopts = { SLISTF_SORT, &compare_adminlist_entries, NULL,
    &free_adminlist_entry
};

#ifdef IRC_BAHAMUT
SListOpts sgopts = { 0, NULL, &is_sgline_entry_equal, &free_sgline_entry };
#endif
SListOpts soopts =
    { SLISTF_SORT, &compare_operlist_entries, NULL, &free_operlist_entry };
SListOpts sqopts =
    { SLISTF_SORT, NULL, &is_sqline_entry_equal, &free_sqline_entry };
#ifdef IRC_BAHAMUT
SListOpts szopts = { 0, NULL, &is_szline_entry_equal, &free_szline_entry };
#endif

/*************************************************************************/
/* *INDENT-OFF* */
void moduleAddOperServCmds(void) {
    Command *c;
    c = createCommand("HELP",       do_help,       NULL,  -1,                   -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("GLOBAL",     do_global,     NULL,  OPER_HELP_GLOBAL,     -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("STATS",      do_stats,      NULL,  OPER_HELP_STATS,      -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("UPTIME",     do_stats,      NULL,  OPER_HELP_STATS,      -1,-1,-1,-1); addCoreCommand(OPERSERV,c);

    /* Anyone can use the LIST option to the ADMIN and OPER commands; those
     * routines check privileges to ensure that only authorized users
     * modify the list. */
    c = createCommand("ADMIN",      do_admin,      NULL,  OPER_HELP_ADMIN,      -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("OPER",       do_oper,       NULL,  OPER_HELP_OPER,       -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("STAFF",      do_staff,      NULL,  OPER_HELP_STAFF,      -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    /* Similarly, anyone can use *NEWS LIST, but *NEWS {ADD,DEL} are
     * reserved for Services admins. */
    c = createCommand("LOGONNEWS",  do_logonnews,  NULL,  NEWS_HELP_LOGON,      -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("OPERNEWS",   do_opernews,   NULL,  NEWS_HELP_OPER,       -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("RANDOMNEWS", do_randomnews, NULL,  NEWS_HELP_RANDOM,     -1,-1,-1,-1); addCoreCommand(OPERSERV,c);

    /* Commands for Services opers: */
    c = createCommand("MODE",       do_os_mode,    is_services_oper,OPER_HELP_MODE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("CLEARMODES", do_clearmodes, is_services_oper,OPER_HELP_CLEARMODES, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("KICK",       do_os_kick,    is_services_oper,OPER_HELP_KICK, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("KILLCLONES", do_killclones, is_services_oper,OPER_HELP_KILLCLONES, -1,-1,-1, -1); addCoreCommand(OPERSERV,c);
    c = createCommand("AKILL",      do_akill,      is_services_oper,OPER_HELP_AKILL, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("SGLINE",     do_sgline,     is_services_oper,OPER_HELP_SGLINE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("SQLINE",     do_sqline,     is_services_oper,OPER_HELP_SQLINE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("SZLINE",     do_szline,     is_services_oper,OPER_HELP_SZLINE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);

    /* Commands for Services admins: */
    c = createCommand("SET",        do_set,        is_services_admin,OPER_HELP_SET, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("SET READONLY", NULL,         NULL,OPER_HELP_SET_READONLY, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("SET LOGCHAN",NULL,         NULL,OPER_HELP_SET_LOGCHAN, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("SET DEBUG",  NULL,          NULL,OPER_HELP_SET_DEBUG, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("SET NOEXPIRE",NULL,         NULL,OPER_HELP_SET_NOEXPIRE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("SET SUPERADMIN",NULL,         NULL,OPER_HELP_SET_SUPERADMIN, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
#ifdef USE_OSSVS
    c = createCommand("SVSNICK",       do_svsnick,    is_services_admin,OPER_HELP_SVSNICK, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
#ifndef IRC_HYBRID
    c = createCommand("UMODE",     do_operumodes,     is_services_admin,OPER_HELP_UMODE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
#endif
#endif
    c = createCommand("NOOP",       do_noop,       is_services_admin,OPER_HELP_NOOP, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("JUPE",       do_jupe,       is_services_admin,OPER_HELP_JUPE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("RAW",        do_raw,        is_services_admin,OPER_HELP_RAW, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("IGNORE",     do_ignoreuser,     is_services_admin,OPER_HELP_IGNORE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
#if defined(IRC_UNREAL) && defined(USE_OSSVS)
    c = createCommand("OLINE",     do_operoline,     is_services_admin,OPER_HELP_OLINE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
#endif
    c = createCommand("UPDATE",     do_update,     is_services_admin,OPER_HELP_UPDATE, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("RELOAD",     do_reload,     is_services_admin,OPER_HELP_RELOAD, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("QUIT",       do_os_quit,    is_services_admin,OPER_HELP_QUIT, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("SHUTDOWN",   do_shutdown,   is_services_admin,OPER_HELP_SHUTDOWN, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("RESTART",    do_restart,    is_services_admin,OPER_HELP_RESTART, -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
#ifndef STREAMLINED
    c = createCommand("SESSION",    do_session,    is_services_admin,OPER_HELP_SESSION, -1,-1,-1, -1); addCoreCommand(OPERSERV,c);
    c = createCommand("EXCEPTION",  do_exception,  is_services_admin,OPER_HELP_EXCEPTION, -1,-1,-1, -1); addCoreCommand(OPERSERV,c);
#endif
    c = createCommand("CHANLIST",   do_chanlist,   is_services_admin,OPER_HELP_CHANLIST,   -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("USERLIST",   do_userlist,   is_services_admin,OPER_HELP_USERLIST,   -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("CACHE",      do_cache,      is_services_admin,OPER_HELP_CACHE,      -1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("DEFCON", do_defcon,   is_services_admin, OPER_HELP_DEFCON,-1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("CHANKILL", do_chankill,   is_services_admin, OPER_HELP_CHANKILL,-1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    /* Commands for Services root: */
#ifdef USE_MODULES
    c = createCommand("MODLOAD", do_modload,   is_services_root, -1,-1,-1,-1,OPER_HELP_MODLOAD); addCoreCommand(OPERSERV,c);
    c = createCommand("MODUNLOAD", do_modunload,   is_services_root, -1,-1,-1,-1,OPER_HELP_MODUNLOAD); addCoreCommand(OPERSERV,c);
    c = createCommand("MODLIST", do_modlist,   is_services_root, -1,-1,-1,-1,OPER_HELP_MODLIST); addCoreCommand(OPERSERV,c);
    c = createCommand("MODINFO", do_modinfo,   is_services_root, -1,-1,-1,-1,OPER_HELP_MODINFO); addCoreCommand(OPERSERV,c);
#endif
#ifdef DEBUG_COMMANDS
    c = createCommand("LISTTIMERS", send_timeout_list,  is_services_root, -1,-1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("MATCHWILD",  do_matchwild,       is_services_root, -1,-1,-1,-1,-1); addCoreCommand(OPERSERV,c);
    c = createCommand("LISTCLONES", send_clone_lists,   is_services_root, -1,-1,-1,-1,-1); addCoreCommand(OPERSERV,c);
#endif
}

/* *INDENT-ON* */
/*************************************************************************/
/*************************************************************************/

/* OperServ initialization. */

void os_init(void)
{
    Command *cmd;
    moduleAddOperServCmds();
    cmd = findCommand(OPERSERV, "GLOBAL");
    if (cmd)
        cmd->help_param1 = s_GlobalNoticer;
    cmd = findCommand(OPERSERV, "ADMIN");
    if (cmd)
        cmd->help_param1 = s_NickServ;
    cmd = findCommand(OPERSERV, "OPER");
    if (cmd)
        cmd->help_param1 = s_NickServ;

    /* Initialization of the lists */
    slist_init(&servadmins);
    servadmins.opts = &saopts;
    slist_init(&servopers);
    servopers.opts = &soopts;

    slist_init(&akills);
    akills.opts = &akopts;
    slist_init(&sglines);
#ifdef IRC_BAHAMUT
    sglines.opts = &sgopts;
#endif
    slist_init(&sqlines);
    sqlines.opts = &sqopts;
    slist_init(&szlines);
#ifdef IRC_BAHAMUT
    szlines.opts = &szopts;
#endif
}

/*************************************************************************/

/* Main OperServ routine. */

void operserv(User * u, char *buf)
{
    char *cmd;
    char *s;

    alog("%s: %s: %s", s_OperServ, u->nick, buf);

    cmd = strtok(buf, " ");
    if (!cmd) {
        return;
    } else if (stricmp(cmd, "\1PING") == 0) {
        if (!(s = strtok(NULL, "")))
            s = "\1";
        notice(s_OperServ, u->nick, "\1PING %s", s);
    } else {
        mod_run_cmd(s_OperServ, u, OPERSERV, cmd);
    }
}

static void get_operserv_stats(long *nrec, long *memuse)
{
    int i;
    long mem = 0, count = 0, mem2 = 0, count2 = 0;
    Akill *ak;
    SXLine *sx;

    if (CheckClones) {
        mem = sizeof(struct clone) * CLONE_DETECT_SIZE * 2;
        for (i = 0; i < CLONE_DETECT_SIZE; i++) {
            if (clonelist[i].host) {
                count++;
                mem += strlen(clonelist[i].host) + 1;
            }
            if (warnings[i].host) {
                count++;
                mem += strlen(warnings[i].host) + 1;
            }
        }
    }

    count += akills.count;
    mem += akills.capacity * sizeof(void);
    mem += akills.count * sizeof(Akill);

    for (i = 0; i < akills.count; i++) {
        ak = akills.list[i];
        mem += strlen(ak->user) + 1;
        mem += strlen(ak->host) + 1;
        mem += strlen(ak->by) + 1;
        mem += strlen(ak->reason) + 1;
    }

#ifdef IRC_BAHAMUT

    count += sglines.count;
    mem += sglines.capacity * sizeof(void);
    mem += sglines.count * sizeof(SXLine);

    for (i = 0; i < sglines.count; i++) {
        sx = sglines.list[i];
        mem += strlen(sx->mask) + 1;
        mem += strlen(sx->by) + 1;
        mem += strlen(sx->reason) + 1;
    }

#endif

    count += sqlines.count;
    mem += sqlines.capacity * sizeof(void);
    mem += sqlines.count * sizeof(SXLine);

    for (i = 0; i < sqlines.count; i++) {
        sx = sqlines.list[i];
        mem += strlen(sx->mask) + 1;
        mem += strlen(sx->by) + 1;
        mem += strlen(sx->reason) + 1;
    }

#ifdef IRC_BAHAMUT

    count += szlines.count;
    mem += szlines.capacity * sizeof(void);
    mem += szlines.count * sizeof(SXLine);

    for (i = 0; i < szlines.count; i++) {
        sx = szlines.list[i];
        mem += strlen(sx->mask) + 1;
        mem += strlen(sx->by) + 1;
        mem += strlen(sx->reason) + 1;
    }

#endif

    get_news_stats(&count2, &mem2);
    count += count2;
    mem += mem2;
    get_exception_stats(&count2, &mem2);
    count += count2;
    mem += mem2;

    *nrec = count;
    *memuse = mem;
}

/*************************************************************************/
/**************************** Privilege checks ***************************/
/*************************************************************************/

/* Load old AKILL data. */

#define SAFE(x) do {					\
    if ((x) < 0) {					\
	if (!forceload)					\
	    fatal("Read error on %s", AutokillDBName);	\
	break;						\
    }							\
} while (0)

static void load_old_akill(void)
{
    dbFILE *f;
    int i, j;
    int16 tmp16;
    int32 tmp32;
    char buf[NICKMAX], mask2[BUFSIZE], *mask, *s;
    Akill *ak, *entry;

    if (!
        (f =
         open_db("AKILL", AutokillDBName ? AutokillDBName : "akill.db",
                 "r", 9)))
        return;

    get_file_version(f);

    read_int16(&tmp16, f);
    slist_setcapacity(&akills, tmp16);

    for (j = 0; j < akills.capacity; j++) {
        ak = scalloc(sizeof(Akill), 1);

        SAFE(read_string(&mask, f));
        s = strchr(mask, '@');
        *s = 0;
        s++;
        ak->user = sstrdup(mask);
        ak->host = sstrdup(s);
        SAFE(read_string(&ak->reason, f));
        SAFE(read_buffer(buf, f));
        if (!*buf)
            ak->by = sstrdup("<unknown>");
        else
            ak->by = sstrdup(buf);
        SAFE(read_int32(&tmp32, f));
        ak->seton = tmp32 ? tmp32 : time(NULL);
        SAFE(read_int32(&tmp32, f));
        ak->expires = tmp32;

        /* Sanity checks *sigh* */

        /* No nicknames allowed! */
        if (strchr(ak->user, '!')) {
            s_rakill(ak->user, ak->host);
            free(ak);
            continue;
        }

        snprintf(mask2, sizeof(mask2), "%s@%s", ak->user, ak->host);

        /* Is the mask already in the AKILL list? */
        if (slist_indexof(&akills, mask2) != -1) {
            free(ak);
            continue;
        }

        /* Checks whether there is an AKILL that already covers
         * the one we want to add, and whether there are AKILLs
         * that would be covered by this one. Expiry time
         * does *also* matter.
         */

        if (akills.count > 0) {

            for (i = akills.count - 1; i >= 0; i--) {

                char amask[BUFSIZE];

                entry = akills.list[i];

                if (!entry)
                    continue;

                snprintf(amask, sizeof(amask), "%s@%s", entry->user,
                         entry->host);

                if (match_wild_nocase(amask, mask2)
                    && (entry->expires >= ak->expires
                        || entry->expires == 0)) {
                    s_rakill(ak->user, ak->host);
                    free(ak);
                    ak = NULL;
                    break;
                }

                if (match_wild_nocase(mask2, amask)
                    && (entry->expires <= ak->expires || ak->expires == 0))
                    slist_delete(&akills, i);
            }

        }

        if (ak)
            slist_add(&akills, ak);
    }

    close_db(f);
}

#undef SAFE

/* Load OperServ data. */

#define SAFE(x) do {					\
    if ((x) < 0) {					\
	if (!forceload)					\
	    fatal("Read error on %s", OperDBName);	\
	failed = 1;					\
	break;						\
    }							\
} while (0)

void load_os_dbase(void)
{
    dbFILE *f;
    int16 i, n, ver, c;
    HostCache *hc, **hclast, *hcprev;
    int16 tmp16;
    int32 tmp32;
    char *s;
    int failed = 0;

    if (!(f = open_db(s_OperServ, OperDBName, "r", OPER_VERSION)))
        return;

    ver = get_file_version(f);

    if (ver <= 9) {
        NickAlias *na;

        SAFE(read_int16(&n, f));
        for (i = 0; i < n && !failed; i++) {
            SAFE(read_string(&s, f));
            if (s) {
                na = findnick(s);
                if (na) {
                    na->nc->flags |= NI_SERVICES_ADMIN;
                    if (slist_indexof(&servadmins, na) == -1)
                        slist_add(&servadmins, na);
                }
                free(s);
            }
        }
        if (!failed)
            SAFE(read_int16(&n, f));
        for (i = 0; i < n && !failed; i++) {
            SAFE(read_string(&s, f));
            if (s) {
                na = findnick(s);
                if (na) {
                    na->nc->flags |= NI_SERVICES_OPER;
                    if (slist_indexof(&servopers, na) == -1)
                        slist_add(&servopers, na);
                }
                free(s);
            }
        }
    }

    if (ver >= 7) {
        int32 tmp32;
        SAFE(read_int32(&maxusercnt, f));
        SAFE(read_int32(&tmp32, f));
        maxusertime = tmp32;
    }

    if (ver <= 10)
        load_old_akill();
    else {
        Akill *ak;

        read_int16(&tmp16, f);
        slist_setcapacity(&akills, tmp16);

        for (i = 0; i < akills.capacity; i++) {
            ak = scalloc(sizeof(Akill), 1);

            SAFE(read_string(&ak->user, f));
            SAFE(read_string(&ak->host, f));
            SAFE(read_string(&ak->by, f));
            SAFE(read_string(&ak->reason, f));
            SAFE(read_int32(&tmp32, f));
            ak->seton = tmp32;
            SAFE(read_int32(&tmp32, f));
            ak->expires = tmp32;

            slist_add(&akills, ak);
        }
    }

    if (ver >= 11) {
        SXLine *sx;

        read_int16(&tmp16, f);
        slist_setcapacity(&sglines, tmp16);

        for (i = 0; i < sglines.capacity; i++) {
            sx = scalloc(sizeof(SXLine), 1);

            SAFE(read_string(&sx->mask, f));
            SAFE(read_string(&sx->by, f));
            SAFE(read_string(&sx->reason, f));
            SAFE(read_int32(&tmp32, f));
            sx->seton = tmp32;
            SAFE(read_int32(&tmp32, f));
            sx->expires = tmp32;

            slist_add(&sglines, sx);
        }

        if (ver >= 13) {
            read_int16(&tmp16, f);
            slist_setcapacity(&sqlines, tmp16);

            for (i = 0; i < sqlines.capacity; i++) {
                sx = scalloc(sizeof(SXLine), 1);

                SAFE(read_string(&sx->mask, f));
                SAFE(read_string(&sx->by, f));
                SAFE(read_string(&sx->reason, f));
                SAFE(read_int32(&tmp32, f));
                sx->seton = tmp32;
                SAFE(read_int32(&tmp32, f));
                sx->expires = tmp32;

                slist_add(&sqlines, sx);
            }
        }

        read_int16(&tmp16, f);
        slist_setcapacity(&szlines, tmp16);

        for (i = 0; i < szlines.capacity; i++) {
            sx = scalloc(sizeof(SXLine), 1);

            SAFE(read_string(&sx->mask, f));
            SAFE(read_string(&sx->by, f));
            SAFE(read_string(&sx->reason, f));
            SAFE(read_int32(&tmp32, f));
            sx->seton = tmp32;
            SAFE(read_int32(&tmp32, f));
            sx->expires = tmp32;

            slist_add(&szlines, sx);
        }
    }

    if (ver >= 12) {
        for (i = 0; i < 1024 && !failed; i++) {
            hclast = &hcache[i];
            hcprev = NULL;

            while ((c = getc_db(f)) != 0) {
                if (c != 1)
                    fatal("Invalid format in %s", OperDBName);

                hc = scalloc(1, sizeof(HostCache));

                SAFE(read_string(&hc->host, f));
                SAFE(read_int16(&tmp16, f));
                hc->status = tmp16;
                SAFE(read_int32(&tmp32, f));
                hc->used = tmp32;

                *hclast = hc;
                hclast = &hc->next;
                hc->prev = hcprev;
                hcprev = hc;
            }                   /* while (getc_db(f) != 0) */

            *hclast = NULL;
        }                       /* for (i) */
    }

    close_db(f);

}

#undef SAFE

/*************************************************************************/

/* Save OperServ data. */

#define SAFE(x) do {						\
    if ((x) < 0) {						\
	restore_db(f);						\
	log_perror("Write error on %s", OperDBName);		\
	if (time(NULL) - lastwarn > WarningTimeout) {		\
	    wallops(NULL, "Write error on %s: %s", OperDBName,	\
			strerror(errno));			\
	    lastwarn = time(NULL);				\
	}							\
	return;							\
    }								\
} while (0)

void save_os_dbase(void)
{
    int i;
    dbFILE *f;
    static time_t lastwarn = 0;
    Akill *ak;
    SXLine *sx;
    HostCache *hc;

    if (!(f = open_db(s_OperServ, OperDBName, "w", OPER_VERSION)))
        return;
    SAFE(write_int32(maxusercnt, f));
    SAFE(write_int32(maxusertime, f));

    SAFE(write_int16(akills.count, f));
    for (i = 0; i < akills.count; i++) {
        ak = akills.list[i];

        SAFE(write_string(ak->user, f));
        SAFE(write_string(ak->host, f));
        SAFE(write_string(ak->by, f));
        SAFE(write_string(ak->reason, f));
        SAFE(write_int32(ak->seton, f));
        SAFE(write_int32(ak->expires, f));
    }

    SAFE(write_int16(sglines.count, f));
    for (i = 0; i < sglines.count; i++) {
        sx = sglines.list[i];

        SAFE(write_string(sx->mask, f));
        SAFE(write_string(sx->by, f));
        SAFE(write_string(sx->reason, f));
        SAFE(write_int32(sx->seton, f));
        SAFE(write_int32(sx->expires, f));
    }

    SAFE(write_int16(sqlines.count, f));
    for (i = 0; i < sqlines.count; i++) {
        sx = sqlines.list[i];

        SAFE(write_string(sx->mask, f));
        SAFE(write_string(sx->by, f));
        SAFE(write_string(sx->reason, f));
        SAFE(write_int32(sx->seton, f));
        SAFE(write_int32(sx->expires, f));
    }

    SAFE(write_int16(szlines.count, f));
    for (i = 0; i < szlines.count; i++) {
        sx = szlines.list[i];

        SAFE(write_string(sx->mask, f));
        SAFE(write_string(sx->by, f));
        SAFE(write_string(sx->reason, f));
        SAFE(write_int32(sx->seton, f));
        SAFE(write_int32(sx->expires, f));
    }

    for (i = 0; i < 1024; i++) {
        for (hc = hcache[i]; hc; hc = hc->next) {
            /* Don't save in-progress scans */
            if (hc->status < HC_NORMAL)
                continue;

            SAFE(write_int8(1, f));

            SAFE(write_string(hc->host, f));
            SAFE(write_int16(hc->status, f));
            SAFE(write_int32(hc->used, f));

        }                       /* for (hc) */
        SAFE(write_int8(0, f));
    }                           /* for (i) */

    close_db(f);

}

#undef SAFE

/*************************************************************************/

void save_os_rdb_dbase(void)
{
#ifdef USE_RDB
    if (!rdb_open())
        return;
    rdb_save_os_db(maxusercnt, maxusertime, &akills, &sglines, &sqlines,
                   &szlines, hcache[0]);
    rdb_close();
#endif
}

/*************************************************************************/

/* Removes the nick structure from OperServ lists. */

void os_remove_nick(NickCore * nc)
{
    slist_remove(&servadmins, nc);
    slist_remove(&servopers, nc);
}

/*************************************************************************/

/* Does the given user have Services root privileges?
   Now enhanced. */

int is_services_root(User * u)
{
    if ((NSStrictPrivileges && !is_oper(u))
        || (!skeleton && !nick_identified(u)))
        return 0;
    if (skeleton || (u->na->nc->flags & NI_SERVICES_ROOT))
        return 1;
    return 0;
}

/*************************************************************************/

/* Does the given user have Services admin privileges? */

int is_services_admin(User * u)
{
    if ((NSStrictPrivileges && !is_oper(u))
        || (!skeleton && !nick_identified(u)))
        return 0;
    if (skeleton
        || (u->na->nc->flags & (NI_SERVICES_ADMIN | NI_SERVICES_ROOT)))
        return 1;
    return 0;
}

/*************************************************************************/

/* Does the given user have Services oper privileges? */

int is_services_oper(User * u)
{
    if ((NSStrictPrivileges && !is_oper(u))
        || (!skeleton && !nick_identified(u)))
        return 0;
    if (skeleton
        || (u->na->nc->
            flags & (NI_SERVICES_OPER | NI_SERVICES_ADMIN |
                     NI_SERVICES_ROOT)))
        return 1;
    return 0;
}

/*************************************************************************/

/* Is the given nick a Services admin/root nick? */

int nick_is_services_admin(NickCore * nc)
{
    if (nc->flags & (NI_SERVICES_ADMIN | NI_SERVICES_ROOT))
        return 1;

    return 0;
}

/*************************************************************************/

/* Is the given nick a Services oper/admin/root nick? */

int nick_is_services_oper(NickCore * nc)
{
    if (nc->
        flags & (NI_SERVICES_OPER | NI_SERVICES_ADMIN | NI_SERVICES_ROOT))
        return 1;

    return 0;
}

/*************************************************************************/
/**************************** Clone detection ****************************/
/*************************************************************************/

/* We just got a new user; does it look like a clone?  If so, send out a
 * wallops.
 */

void check_clones(User * user)
{
#ifndef STREAMLINED
    int i, clone_count;
    long last_time;

    if (!CheckClones)
        return;

    if (clonelist[0].host)
        free(clonelist[0].host);
    i = CLONE_DETECT_SIZE - 1;
    memmove(clonelist, clonelist + 1, sizeof(struct clone) * i);
    clonelist[i].host = sstrdup(GetHost(user));
    last_time = clonelist[i].time = time(NULL);
    clone_count = 1;
    while (--i >= 0 && clonelist[i].host) {
        if (clonelist[i].time < last_time - CloneMaxDelay)
            break;
        if (stricmp(clonelist[i].host, GetHost(user)) == 0) {
            ++clone_count;
            last_time = clonelist[i].time;
            if (clone_count >= CloneMinUsers)
                break;
        }
    }
    if (clone_count >= CloneMinUsers) {
        /* Okay, we have clones.  Check first to see if we already know
         * about them. */
        for (i = CLONE_DETECT_SIZE - 1; i >= 0 && warnings[i].host; --i) {
            if (stricmp(warnings[i].host, GetHost(user)) == 0)
                break;
        }
        if (i < 0
            || warnings[i].time < user->my_signon - CloneWarningDelay) {
            /* Send out the warning, and note it. */
            wallops(s_OperServ,
                    "\2WARNING\2 - possible clones detected from %s",
                    GetHost(user));
            alog("%s: possible clones detected from %s", s_OperServ,
                 GetHost(user));
            i = CLONE_DETECT_SIZE - 1;
            if (warnings[0].host)
                free(warnings[0].host);
            memmove(warnings, warnings + 1, sizeof(struct clone) * i);
            warnings[i].host = sstrdup(GetHost(user));
            warnings[i].time = clonelist[i].time;
            if (KillClones)
                kill_user(s_OperServ, user->nick, "Clone kill");
        }
    }
#endif                          /* !STREAMLINED */
}

/*************************************************************************/

#ifdef DEBUG_COMMANDS

/* Send clone arrays to given nick. */

static void send_clone_lists(User * u)
{
    int i;

    if (!CheckClones) {
        notice(s_OperServ, u->nick, "CheckClones not enabled.");
        return;
    }

    notice(s_OperServ, u->nick, "clonelist[]");
    for (i = 0; i < CLONE_DETECT_SIZE; i++) {
        if (clonelist[i].host)
            notice(s_OperServ, u->nick, "    %10ld  %s", clonelist[i].time,
                   clonelist[i].host ? clonelist[i].host : "(null)");
    }
    notice(s_OperServ, u->nick, "warnings[]");
    for (i = 0; i < CLONE_DETECT_SIZE; i++) {
        if (clonelist[i].host)
            notice(s_OperServ, u->nick, "    %10ld  %s", warnings[i].time,
                   warnings[i].host ? warnings[i].host : "(null)");
    }
}

#endif                          /* DEBUG_COMMANDS */

/*************************************************************************/
/*********************** OperServ command functions **********************/
/*************************************************************************/

/* HELP command. */

static int do_help(User * u)
{
    const char *cmd = strtok(NULL, "");

    if (!cmd) {
        notice_help(s_OperServ, u, OPER_HELP);
        if (is_services_oper(u))
            notice_help(s_OperServ, u, OPER_HELP_OPER_CMD);
        if (is_services_admin(u))
            notice_help(s_OperServ, u, OPER_HELP_ADMIN_CMD);
#ifdef USE_MODULES
        if (is_services_root(u))
            notice_help(s_OperServ, u, OPER_HELP_ROOT_CMD);
#endif
        moduleDisplayHelp(5, u);
        notice_help(s_OperServ, u, OPER_HELP_LOGGED);
    } else {
        mod_help_cmd(s_OperServ, u, OPERSERV, cmd);
    }
    return MOD_CONT;
}

/*************************************************************************/

/* Global notice sending via GlobalNoticer. */
/* Added name tag for globalmsg -Certus */

static int do_global(User * u)
{
    char *msg = strtok(NULL, "");

    if (!msg) {
        syntax_error(s_OperServ, u, "GLOBAL", OPER_GLOBAL_SYNTAX);
        return MOD_CONT;
    }
    if (WallOSGlobal)
        wallops(s_OperServ, "\2%s\2 just used GLOBAL command.", u->nick);
    oper_global(u->nick, "%s", msg);
    return MOD_CONT;
}

void oper_global(char *nick, char *fmt, ...)
{
    va_list args;
    char msg[2048];             /* largest valid message is 512, this should cover any global */
    int i;

    va_start(args, fmt);
    vsnprintf(msg, sizeof(msg), fmt, args);
    va_end(args);

#ifdef IRC_HYBRID
    if (DomainNumber > 0) {
        for (i = 0; i < DomainNumber; i++) {
            if ((nick) && (!AnonymousGlobal)) {
                send_cmd(s_GlobalNoticer, "NOTICE $$*.%s :[%s] %s",
                         NetworkDomains[i], nick, msg);
            } else {
                send_cmd(s_GlobalNoticer, "NOTICE $$*.%s :%s",
                         NetworkDomains[i], msg);
            }
        }
    } else {
        /* Go through all common top-level domains.  If you have others,
         * add them here.
         */
        if ((nick) && (!AnonymousGlobal)) {
            notice(s_GlobalNoticer, "$$*.com", "[%s] %s", nick, msg);
            notice(s_GlobalNoticer, "$$*.net", "[%s] %s", nick, msg);
            notice(s_GlobalNoticer, "$$*.org", "[%s] %s", nick, msg);
            notice(s_GlobalNoticer, "$$*.edu", "[%s] %s", nick, msg);
        } else {
            notice(s_GlobalNoticer, "$$*.com", "%s", msg);
            notice(s_GlobalNoticer, "$$*.net", "%s", msg);
            notice(s_GlobalNoticer, "$$*.org", "%s", msg);
            notice(s_GlobalNoticer, "$$*.edu", "%s", msg);
        }
    }
#else
    if (DomainNumber > 0) {
        for (i = 0; i < DomainNumber; i++) {
            if ((nick) && (!AnonymousGlobal)) {
                send_cmd(s_GlobalNoticer, "NOTICE $*.%s :[%s] %s",
                         NetworkDomains[i], nick, msg);
            } else {
                send_cmd(s_GlobalNoticer, "NOTICE $*.%s :%s",
                         NetworkDomains[i], msg);
            }
        }
    } else {
        /* Go through all common top-level domains.  If you have others,
         * add them here.
         */
        if ((nick) && (!AnonymousGlobal)) {
            notice(s_GlobalNoticer, "$*.com", "[%s] %s", nick, msg);
            notice(s_GlobalNoticer, "$*.net", "[%s] %s", nick, msg);
            notice(s_GlobalNoticer, "$*.org", "[%s] %s", nick, msg);
            notice(s_GlobalNoticer, "$*.edu", "[%s] %s", nick, msg);
        } else {
            notice(s_GlobalNoticer, "$*.com", "%s", msg);
            notice(s_GlobalNoticer, "$*.net", "%s", msg);
            notice(s_GlobalNoticer, "$*.org", "%s", msg);
            notice(s_GlobalNoticer, "$*.edu", "%s", msg);
        }
    }
#endif
}

/*************************************************************************/

/* STATS command. */

static int do_stats(User * u)
{
    time_t uptime = time(NULL) - start_time;
    char *extra = strtok(NULL, "");
    int days = uptime / 86400, hours = (uptime / 3600) % 24,
        mins = (uptime / 60) % 60, secs = uptime % 60;
    struct tm *tm;
    char timebuf[64];

    if (extra && stricmp(extra, "ALL") != 0) {
        if (stricmp(extra, "AKILL") == 0) {
            int timeout;
            /* AKILLs */
            notice_lang(s_OperServ, u, OPER_STATS_AKILL_COUNT,
                        akills.count);
            timeout = AutokillExpiry + 59;
            if (timeout >= 172800)
                notice_lang(s_OperServ, u, OPER_STATS_AKILL_EXPIRE_DAYS,
                            timeout / 86400);
            else if (timeout >= 86400)
                notice_lang(s_OperServ, u, OPER_STATS_AKILL_EXPIRE_DAY);
            else if (timeout >= 7200)
                notice_lang(s_OperServ, u, OPER_STATS_AKILL_EXPIRE_HOURS,
                            timeout / 3600);
            else if (timeout >= 3600)
                notice_lang(s_OperServ, u, OPER_STATS_AKILL_EXPIRE_HOUR);
            else if (timeout >= 120)
                notice_lang(s_OperServ, u, OPER_STATS_AKILL_EXPIRE_MINS,
                            timeout / 60);
            else if (timeout >= 60)
                notice_lang(s_OperServ, u, OPER_STATS_AKILL_EXPIRE_MIN);
            else
                notice_lang(s_OperServ, u, OPER_STATS_AKILL_EXPIRE_NONE);
#ifdef IRC_BAHAMUT
            /* SGLINEs */
            notice_lang(s_OperServ, u, OPER_STATS_SGLINE_COUNT,
                        sglines.count);
            timeout = SGLineExpiry + 59;
            if (timeout >= 172800)
                notice_lang(s_OperServ, u, OPER_STATS_SGLINE_EXPIRE_DAYS,
                            timeout / 86400);
            else if (timeout >= 86400)
                notice_lang(s_OperServ, u, OPER_STATS_SGLINE_EXPIRE_DAY);
            else if (timeout >= 7200)
                notice_lang(s_OperServ, u, OPER_STATS_SGLINE_EXPIRE_HOURS,
                            timeout / 3600);
            else if (timeout >= 3600)
                notice_lang(s_OperServ, u, OPER_STATS_SGLINE_EXPIRE_HOUR);
            else if (timeout >= 120)
                notice_lang(s_OperServ, u, OPER_STATS_SGLINE_EXPIRE_MINS,
                            timeout / 60);
            else if (timeout >= 60)
                notice_lang(s_OperServ, u, OPER_STATS_SGLINE_EXPIRE_MIN);
            else
                notice_lang(s_OperServ, u, OPER_STATS_SGLINE_EXPIRE_NONE);
#endif
            /* SQLINEs */
            notice_lang(s_OperServ, u, OPER_STATS_SQLINE_COUNT,
                        sqlines.count);
            timeout = SQLineExpiry + 59;
            if (timeout >= 172800)
                notice_lang(s_OperServ, u, OPER_STATS_SQLINE_EXPIRE_DAYS,
                            timeout / 86400);
            else if (timeout >= 86400)
                notice_lang(s_OperServ, u, OPER_STATS_SQLINE_EXPIRE_DAY);
            else if (timeout >= 7200)
                notice_lang(s_OperServ, u, OPER_STATS_SQLINE_EXPIRE_HOURS,
                            timeout / 3600);
            else if (timeout >= 3600)
                notice_lang(s_OperServ, u, OPER_STATS_SQLINE_EXPIRE_HOUR);
            else if (timeout >= 120)
                notice_lang(s_OperServ, u, OPER_STATS_SQLINE_EXPIRE_MINS,
                            timeout / 60);
            else if (timeout >= 60)
                notice_lang(s_OperServ, u, OPER_STATS_SQLINE_EXPIRE_MIN);
            else
                notice_lang(s_OperServ, u, OPER_STATS_SQLINE_EXPIRE_NONE);
#ifdef IRC_BAHAMUT
            /* SZLINEs */
            notice_lang(s_OperServ, u, OPER_STATS_SZLINE_COUNT,
                        szlines.count);
            timeout = SZLineExpiry + 59;
            if (timeout >= 172800)
                notice_lang(s_OperServ, u, OPER_STATS_SZLINE_EXPIRE_DAYS,
                            timeout / 86400);
            else if (timeout >= 86400)
                notice_lang(s_OperServ, u, OPER_STATS_SZLINE_EXPIRE_DAY);
            else if (timeout >= 7200)
                notice_lang(s_OperServ, u, OPER_STATS_SZLINE_EXPIRE_HOURS,
                            timeout / 3600);
            else if (timeout >= 3600)
                notice_lang(s_OperServ, u, OPER_STATS_SZLINE_EXPIRE_HOUR);
            else if (timeout >= 120)
                notice_lang(s_OperServ, u, OPER_STATS_SZLINE_EXPIRE_MINS,
                            timeout / 60);
            else if (timeout >= 60)
                notice_lang(s_OperServ, u, OPER_STATS_SZLINE_EXPIRE_MIN);
            else
                notice_lang(s_OperServ, u, OPER_STATS_SZLINE_EXPIRE_NONE);
#endif
            return MOD_CONT;
        } else if (!stricmp(extra, "RESET")) {
            if (is_services_admin(u)) {
                maxusercnt = usercnt;
                notice_lang(s_OperServ, u, OPER_STATS_RESET);
            } else {
                notice_lang(s_OperServ, u, PERMISSION_DENIED);
            }
            return MOD_CONT;
        } else {
            notice_lang(s_OperServ, u, OPER_STATS_UNKNOWN_OPTION, extra);
        }
    }

    notice_lang(s_OperServ, u, OPER_STATS_CURRENT_USERS, usercnt, opcnt);
    tm = localtime(&maxusertime);
    strftime_lang(timebuf, sizeof(timebuf), u, STRFTIME_DATE_TIME_FORMAT,
                  tm);
    notice_lang(s_OperServ, u, OPER_STATS_MAX_USERS, maxusercnt, timebuf);
    if (days > 1) {
        notice_lang(s_OperServ, u, OPER_STATS_UPTIME_DHMS,
                    days, hours, mins, secs);
    } else if (days == 1) {
        notice_lang(s_OperServ, u, OPER_STATS_UPTIME_1DHMS,
                    days, hours, mins, secs);
    } else {
        if (hours > 1) {
            if (mins != 1) {
                if (secs != 1) {
                    notice_lang(s_OperServ, u, OPER_STATS_UPTIME_HMS,
                                hours, mins, secs);
                } else {
                    notice_lang(s_OperServ, u, OPER_STATS_UPTIME_HM1S,
                                hours, mins, secs);
                }
            } else {
                if (secs != 1) {
                    notice_lang(s_OperServ, u, OPER_STATS_UPTIME_H1MS,
                                hours, mins, secs);
                } else {
                    notice_lang(s_OperServ, u, OPER_STATS_UPTIME_H1M1S,
                                hours, mins, secs);
                }
            }
        } else if (hours == 1) {
            if (mins != 1) {
                if (secs != 1) {
                    notice_lang(s_OperServ, u, OPER_STATS_UPTIME_1HMS,
                                hours, mins, secs);
                } else {
                    notice_lang(s_OperServ, u, OPER_STATS_UPTIME_1HM1S,
                                hours, mins, secs);
                }
            } else {
                if (secs != 1) {
                    notice_lang(s_OperServ, u, OPER_STATS_UPTIME_1H1MS,
                                hours, mins, secs);
                } else {
                    notice_lang(s_OperServ, u, OPER_STATS_UPTIME_1H1M1S,
                                hours, mins, secs);
                }
            }
        } else {
            if (mins != 1) {
                if (secs != 1) {
                    notice_lang(s_OperServ, u, OPER_STATS_UPTIME_MS,
                                mins, secs);
                } else {
                    notice_lang(s_OperServ, u, OPER_STATS_UPTIME_M1S,
                                mins, secs);
                }
            } else {
                if (secs != 1) {
                    notice_lang(s_OperServ, u, OPER_STATS_UPTIME_1MS,
                                mins, secs);
                } else {
                    notice_lang(s_OperServ, u, OPER_STATS_UPTIME_1M1S,
                                mins, secs);
                }
            }
        }
    }

    if (extra && stricmp(extra, "ALL") == 0 && is_services_admin(u)) {
        long count, mem;

        notice_lang(s_OperServ, u, OPER_STATS_BYTES_READ,
                    total_read / 1024);
        notice_lang(s_OperServ, u, OPER_STATS_BYTES_WRITTEN,
                    total_written / 1024);

        get_user_stats(&count, &mem);
        notice_lang(s_OperServ, u, OPER_STATS_USER_MEM, count,
                    (mem + 512) / 1024);
        get_channel_stats(&count, &mem);
        notice_lang(s_OperServ, u, OPER_STATS_CHANNEL_MEM, count,
                    (mem + 512) / 1024);
        get_core_stats(&count, &mem);
        notice_lang(s_OperServ, u, OPER_STATS_GROUPS_MEM, count,
                    (mem + 512) / 1024);
        get_aliases_stats(&count, &mem);
        notice_lang(s_OperServ, u, OPER_STATS_ALIASES_MEM, count,
                    (mem + 512) / 1024);
        get_chanserv_stats(&count, &mem);
        notice_lang(s_OperServ, u, OPER_STATS_CHANSERV_MEM, count,
                    (mem + 512) / 1024);
        get_botserv_stats(&count, &mem);
        notice_lang(s_OperServ, u, OPER_STATS_BOTSERV_MEM, count,
                    (mem + 512) / 1024);
        get_operserv_stats(&count, &mem);
        notice_lang(s_OperServ, u, OPER_STATS_OPERSERV_MEM, count,
                    (mem + 512) / 1024);
        get_session_stats(&count, &mem);
        notice_lang(s_OperServ, u, OPER_STATS_SESSIONS_MEM, count,
                    (mem + 512) / 1024);
#ifdef USE_THREADS
        if (ProxyDetect) {
            get_proxy_stats(&count, &mem);
            notice_lang(s_OperServ, u, OPER_STATS_PROXY_MEM, count,
                        (mem + 512) / 1024);
        }
#endif
    }
    return MOD_CONT;
}

/*************************************************************************/

/* make Services ignore users for a certain time */

static int do_ignoreuser(User * u)
{
    char *cmd = strtok(NULL, " ");
    int t;

    if (!cmd) {
        notice_lang(s_OperServ, u, OPER_IGNORE_SYNTAX);
        return MOD_CONT;
    }

    if (!stricmp(cmd, "ADD")) {

        char *time = strtok(NULL, " ");
        char *nick = strtok(NULL, " ");
        char *rest = strtok(NULL, "");

        if (!nick) {
            notice_lang(s_OperServ, u, OPER_IGNORE_SYNTAX);
            return MOD_CONT;
        } else if (!time) {
            notice_lang(s_OperServ, u, OPER_IGNORE_SYNTAX);
            return MOD_CONT;
        } else {
            t = dotime(time);
            rest = NULL;

            if (t <= -1) {
                notice_lang(s_OperServ, u, OPER_IGNORE_VALID_TIME);
                return MOD_CONT;
            } else if (t == 0) {
                t = 157248000;  /* if 0 is given, we set time to 157248000 seconds == 5 years (let's hope the next restart will  be before that time ;-)) */
                add_ignore(nick, t);
                notice_lang(s_OperServ, u, OPER_IGNORE_PERM_DONE, nick);
            } else {
                add_ignore(nick, t);
                notice_lang(s_OperServ, u, OPER_IGNORE_TIME_DONE, nick,
                            time);
            }
        }
    } else if (!stricmp(cmd, "LIST")) {
        do_ignorelist(u);
    }

    else if (!stricmp(cmd, "DEL")) {
        char *nick = strtok(NULL, " ");
        if (!nick) {
            notice_lang(s_OperServ, u, OPER_IGNORE_SYNTAX);
        } else {
            if (get_ignore(nick) == 0) {
                notice_lang(s_OperServ, u, OPER_IGNORE_LIST_NOMATCH, nick);
                return MOD_CONT;
            } else {
                delete_ignore(nick);
                notice_lang(s_OperServ, u, OPER_IGNORE_DEL_DONE, nick);
            }
        }
    } else if (!stricmp(cmd, "CLEAR")) {
        do_clearignore(u);

    } else
        notice_lang(s_OperServ, u, OPER_IGNORE_SYNTAX);
    return MOD_CONT;
}

/*************************************************************************/

/* deletes a nick from the ignore list  */

void delete_ignore(const char *nick)
{
    IgnoreData *ign, *prev;
    IgnoreData **whichlist = &ignore[tolower(nick[0])];

    for (ign = *whichlist, prev = NULL; ign; prev = ign, ign = ign->next) {
        if (stricmp(ign->who, nick) == 0)
            break;
    }
    if (prev)
        prev->next = ign->next;
    else
        *whichlist = ign->next;
    free(ign);
    ign = NULL;
}

/*************************************************************************/

/* shows the Services ignore list */

static int do_ignorelist(User * u)
{
    int sent_header = 0;
    IgnoreData *id;
    int i;

    for (i = 0; i < 256; i++) {
        for (id = ignore[i]; id; id = id->next) {
            if (!sent_header) {
                notice_lang(s_OperServ, u, OPER_IGNORE_LIST);
                sent_header = 1;
            }
            notice(s_OperServ, u->nick, "%s", id->who);
        }
    }
    if (!sent_header)
        notice_lang(s_OperServ, u, OPER_IGNORE_LIST_EMPTY);
    return MOD_CONT;
}

/**************************************************************************/
/* Cleares the Services ignore list */

static int do_clearignore(User * u)
{
    IgnoreData *id = NULL, *next = NULL;
    int i;
    for (i = 0; i < 256; i++) {
        for (id = ignore[i]; id; id = next) {
            next = id->next;
            free(id);
            if (!next) {
                ignore[i] = NULL;
            }
        }
    }
    notice_lang(s_OperServ, u, OPER_IGNORE_LIST_CLEARED);
    return MOD_CONT;
}

/**************************************************************************/

/* Channel mode changing (MODE command). */

static int do_os_mode(User * u)
{
    int ac;
    char **av;
    char *chan = strtok(NULL, " "), *modes = strtok(NULL, "");
    Channel *c;

    if (!chan || !modes) {
        syntax_error(s_OperServ, u, "MODE", OPER_MODE_SYNTAX);
        return MOD_CONT;
    }

    if (!(c = findchan(chan))) {
        notice_lang(s_OperServ, u, CHAN_X_NOT_IN_USE, chan);
    } else if (c->bouncy_modes) {
        notice_lang(s_OperServ, u, OPER_BOUNCY_MODES_U_LINE);
        return MOD_CONT;
#ifdef CMODE_A
    } else if ((!is_services_admin(u)) && (c->mode & CMODE_A)) {
        notice_lang(s_OperServ, u, PERMISSION_DENIED);
        return MOD_CONT;
#endif
    } else {
        send_cmd(s_OperServ, "MODE %s %s", chan, modes);

        ac = split_buf(modes, &av, 1);
        chan_set_modes(s_OperServ, c, ac, av, 0);

        if (WallOSMode)
            wallops(s_OperServ, "%s used MODE %s on %s", u->nick, modes,
                    chan);
    }
    return MOD_CONT;
}

/**************************************************************************/

/**
 * Change any user's UMODES
 *
 * modified to be part of the SuperAdmin directive -jester
 * check user flag for SuperAdmin -rob
 */
#ifdef USE_OSSVS
#ifndef IRC_HYBRID
static int do_operumodes(User * u)
{
    char *nick = strtok(NULL, " ");
    char *modes = strtok(NULL, "");

    User *u2;

    /* Only allow this if SuperAdmin is enabled */
    if (!u->isSuperAdmin) {
        notice_lang(s_OperServ, u, OPER_SUPER_ADMIN_ONLY);
        return MOD_CONT;
    }

    if (!nick || !modes) {
        syntax_error(s_OperServ, u, "UMODE", OPER_UMODE_SYNTAX);
        return MOD_CONT;
    }

    /**
     * Only accept a +/- mode string
     *-rob
     **/
    if ((modes[0] != '+') && (modes[0] != '-')) {
        syntax_error(s_OperServ, u, "UMODE", OPER_UMODE_SYNTAX);
        return MOD_CONT;
    }
    if (!(u2 = finduser(nick))) {
        notice_lang(s_OperServ, u, NICK_X_NOT_IN_USE, nick);
    } else {
        send_cmd(s_OperServ, "MODE %s %s", nick, modes);

        change_user_mode(u2, modes, NULL);

        notice_lang(s_OperServ, u, OPER_UMODE_SUCCESS, nick);
        notice_lang(s_OperServ, u2, OPER_UMODE_CHANGED, u->nick);

        if (WallOSMode)
            wallops(s_OperServ, "\2%s\2 used UMODE on %s", u->nick, nick);
    }
    return MOD_CONT;
}
#endif
#endif
/**************************************************************************/

/**
 * give Operflags to any user
 *
 * modified to be part of the SuperAdmin directive -jester
 * check u-> for SuperAdmin -rob
 */
#if defined (IRC_UNREAL) && defined (USE_OSSVS)

static int do_operoline(User * u)
{
    char *nick = strtok(NULL, " ");
    char *flags = strtok(NULL, "");
    User *u2 = NULL;

    /* Only allow this if SuperAdmin is enabled */
    if (!u->isSuperAdmin) {
        notice_lang(s_OperServ, u, OPER_SUPER_ADMIN_ONLY);
        return MOD_CONT;
    }

    if (!nick || !flags) {
        syntax_error(s_OperServ, u, "OLINE", OPER_OLINE_SYNTAX);
        return MOD_CONT;
    } else {
        u2 = finduser(nick);

/* let's check whether the user is online */

        if (!finduser(nick)) {
            notice_lang(s_OperServ, u, NICK_X_NOT_IN_USE, nick);
        } else if (u2 && flags[0] == '+') {
            send_cmd(s_OperServ, "SVSO %s %s", nick, flags);
            send_cmd(s_OperServ, "MODE %s +o", nick);
            change_user_mode(u2, "+o", NULL);
            notice_lang(s_OperServ, u2, OPER_OLINE_IRCOP);
            notice_lang(s_OperServ, u, OPER_OLINE_SUCCESS, flags, nick);
            wallops(s_OperServ, "\2%s\2 used OLINE for %s", u->nick, nick);
        } else if (u2 && flags[0] == '-') {
            send_cmd(s_OperServ, "SVSO %s %s", nick, flags);
            notice_lang(s_OperServ, u, OPER_OLINE_SUCCESS, flags, nick);
            wallops(s_OperServ, "\2%s\2 used OLINE for %s", u->nick, nick);
        } else
            syntax_error(s_OperServ, u, "OLINE", OPER_OLINE_SYNTAX);
    }
    return MOD_CONT;
}
#endif
/*************************************************************************/

/* Clear all modes from a channel. */

static int do_clearmodes(User * u)
{
    char *s;
    int i;
    char *argv[2];
    char *chan = strtok(NULL, " ");
    Channel *c;
    int all = 0;
    int count;                  /* For saving ban info */
    char **bans;                /* For saving ban info */
#if defined (IRC_ULTIMATE) || defined (IRC_UNREAL) || defined (IRC_ULTIMATE3) ||defined (IRC_VIAGRA)
    int exceptcount;            /* For saving except info */
    char **excepts;             /* For saving except info */
#endif
    struct c_userlist *cu, *next;

    if (!chan) {
        syntax_error(s_OperServ, u, "CLEARMODES", OPER_CLEARMODES_SYNTAX);
    } else if (!(c = findchan(chan))) {
        notice_lang(s_OperServ, u, CHAN_X_NOT_IN_USE, chan);
    } else if (c->bouncy_modes) {
        notice_lang(s_OperServ, u, OPER_BOUNCY_MODES_U_LINE);
        return MOD_CONT;
    } else {
        s = strtok(NULL, " ");
        if (s) {
            if (stricmp(s, "ALL") == 0) {
                all = 1;
            } else {
                syntax_error(s_OperServ, u, "CLEARMODES",
                             OPER_CLEARMODES_SYNTAX);
                return MOD_CONT;
            }
        }

        if (WallOSClearmodes)
            wallops(s_OperServ, "%s used CLEARMODES%s on %s", u->nick,
                    all ? " ALL" : "", chan);

        if (all) {
            /* Clear mode +o */
            for (cu = c->users; cu; cu = next) {
                next = cu->next;

                if (!chan_has_user_status(c, cu->user, CUS_OP))
                    continue;

                argv[0] = sstrdup("-o");
                argv[1] = cu->user->nick;

                send_cmd(s_OperServ, "MODE %s -o %s", c->name,
                         cu->user->nick);
                chan_set_modes(s_OperServ, c, 2, argv, 0);

                free(argv[0]);
            }

            /* Clear mode +v */
            for (cu = c->users; cu; cu = next) {
                next = cu->next;

                if (!chan_has_user_status(c, cu->user, CUS_VOICE))
                    continue;

                argv[0] = sstrdup("-v");
                argv[1] = sstrdup(cu->user->nick);

                send_cmd(s_OperServ, "MODE %s -v %s", c->name,
                         cu->user->nick);
                chan_set_modes(s_OperServ, c, 2, argv, 0);

                free(argv[0]);
            }
#ifdef HAS_HALFOP
            /* Clear mode +h */
            for (cu = c->users; cu; cu = next) {
                next = cu->next;

                if (!chan_has_user_status(c, cu->user, CUS_HALFOP))
                    continue;

                argv[0] = sstrdup("-h");
                argv[1] = sstrdup(cu->user->nick);

                send_cmd(s_OperServ, "MODE %s -h %s", c->name,
                         cu->user->nick);
                chan_set_modes(s_OperServ, c, 2, argv, 0);

                free(argv[0]);
            }
#endif
        }

        /* Clear modes */
        send_cmd(s_OperServ, "MODE %s %s %s", c->name, MODESTOREMOVE,
                 c->key ? c->key : "");
        argv[0] = sstrdup(MODESTOREMOVE);
        argv[1] = c->key ? c->key : NULL;
        chan_set_modes(s_OperServ, c, c->key ? 2 : 1, argv, 0);
        free(argv[0]);

        /* Clear bans */
        count = c->bancount;
        bans = scalloc(sizeof(char *) * count, 1);

        for (i = 0; i < count; i++)
            bans[i] = sstrdup(c->bans[i]);

        for (i = 0; i < count; i++) {
            argv[0] = sstrdup("-b");
            argv[1] = bans[i];
            send_cmd(s_OperServ, "MODE %s -b %s", c->name, argv[1]);
            chan_set_modes(s_OperServ, c, 2, argv, 0);
            free(argv[1]);
            free(argv[0]);
        }

        free(bans);

#if defined (IRC_ULTIMATE) || defined (IRC_UNREAL) || defined (IRC_ULTIMATE3) || defined (IRC_VIAGRA)
        /* Clear excepts */
        exceptcount = c->exceptcount;
        excepts = scalloc(sizeof(char *) * exceptcount, 1);

        for (i = 0; i < exceptcount; i++)
            excepts[i] = sstrdup(c->excepts[i]);

        for (i = 0; i < exceptcount; i++) {
            argv[0] = sstrdup("-e");
            argv[1] = excepts[i];
            send_cmd(s_OperServ, "MODE %s -e %s", c->name, argv[1]);
            chan_set_modes(s_OperServ, c, 2, argv, 0);
            free(argv[1]);
            free(argv[0]);
        }

        free(excepts);
#endif
    }

    notice_lang(s_OperServ, u, OPER_CLEARMODES_ALL_DONE, chan);
    return MOD_CONT;
}

/*************************************************************************/

/* Kick a user from a channel (KICK command). */

static int do_os_kick(User * u)
{
    char *argv[3];
    char *chan, *nick, *s;
    Channel *c;

    chan = strtok(NULL, " ");
    nick = strtok(NULL, " ");
    s = strtok(NULL, "");
    if (!chan || !nick || !s) {
        syntax_error(s_OperServ, u, "KICK", OPER_KICK_SYNTAX);
        return MOD_CONT;
    }
    if (!(c = findchan(chan))) {
        notice_lang(s_OperServ, u, CHAN_X_NOT_IN_USE, chan);
    } else if (c->bouncy_modes) {
        notice_lang(s_OperServ, u, OPER_BOUNCY_MODES_U_LINE);
        return MOD_CONT;
    }
    send_cmd(s_OperServ, "KICK %s %s :%s (%s)", chan, nick, u->nick, s);
    if (WallOSKick)
        wallops(s_OperServ, "%s used KICK on %s/%s", u->nick, nick, chan);
    argv[0] = sstrdup(chan);
    argv[1] = sstrdup(nick);
    argv[2] = sstrdup(s);
    do_kick(s_OperServ, 3, argv);
    free(argv[2]);
    free(argv[1]);
    free(argv[0]);
    return MOD_CONT;
}

/*************************************************************************/

/* Forcefully change a user's nickname */
#ifdef USE_OSSVS

static int do_svsnick(User * u)
{
    char *nick = strtok(NULL, " ");
    char *newnick = strtok(NULL, " ");

    NickAlias *na;
    char *c;

    /* Only allow this if SuperAdmin is enabled */
    if (!u->isSuperAdmin) {
        notice_lang(s_OperServ, u, OPER_SUPER_ADMIN_ONLY);
        return MOD_CONT;
    }

    if (!nick || !newnick) {
        syntax_error(s_OperServ, u, "SVSNICK", OPER_SVSNICK_SYNTAX);
        return MOD_CONT;
    }

    /* Truncate long nicknames to NICKMAX-2 characters */
    if (strlen(newnick) > (NICKMAX - 2)) {
        notice_lang(s_NickServ, u, NICK_X_TRUNCATED,
                    newnick, NICKMAX - 2, newnick);
        newnick[NICKMAX - 2] = '\0';
    }

    /* Check for valid characters */
    if (*newnick == '-' || isdigit(*newnick)) {
        notice_lang(s_OperServ, u, NICK_X_ILLEGAL, newnick);
        return MOD_CONT;
    }
#define isvalid(c) (((c) >= 'A' && (c) <= '~') || isdigit(c) || (c) == '-')
    for (c = newnick; *c && (c - newnick) < NICKMAX; c++) {
        if (!isvalid(*c) || isspace(*c)) {
            notice_lang(s_OperServ, u, NICK_X_ILLEGAL, nick);
            return MOD_CONT;
        }
    }

    /* Check for a nick in use or a forbidden/suspended nick */
    if (!finduser(nick)) {
        notice_lang(s_OperServ, u, NICK_X_NOT_IN_USE, nick);
    } else if (finduser(newnick)) {
        notice_lang(s_NickServ, u, NICK_X_IN_USE, newnick);
    } else if ((na = findnick(newnick)) && (na->status & NS_VERBOTEN)) {
        notice_lang(s_NickServ, u, NICK_X_FORBIDDEN, newnick);
    } else {
        notice_lang(s_OperServ, u, OPER_SVSNICK_NEWNICK, nick, newnick);
        wallops(s_OperServ, "%s used SVSNICK to change %s to %s",
                u->nick, nick, newnick);
        send_cmd(NULL, "SVSNICK %s %s :%ld", nick, newnick,
                 (long int) time(NULL));
    }
    return MOD_CONT;
}
#endif
/*************************************************************************/

/* Adds an AKILL to the list. Returns >= 0 on success, -1 if it fails, -2
 * if only the expiry time was changed.
 * The success result is the number of AKILLs that were deleted to successfully add one.
 */

int add_akill(User * u, char *mask, const char *by, const time_t expires,
              const char *reason)
{
    int deleted = 0, i;
    char *user, *mask2, *host;
    Akill *entry;

    /* Checks whether there is an AKILL that already covers
     * the one we want to add, and whether there are AKILLs
     * that would be covered by this one. The masks AND the
     * expiry times are used to determine this, because some
     * AKILLs may become useful when another one expires.
     * If so, warn the user in the first case and cleanup
     * the useless AKILLs in the second.
     */

    if (akills.count > 0) {

        for (i = akills.count - 1; i >= 0; i--) {
            char amask[BUFSIZE];

            entry = akills.list[i];

            if (!entry)
                continue;

            snprintf(amask, sizeof(amask), "%s@%s", entry->user,
                     entry->host);

            if (!stricmp(amask, mask)) {
                /* We change the AKILL expiry time if its current one is less than the new.
                 * This is preferable to be sure we don't change an important AKILL
                 * accidentely.
                 */
                if (entry->expires >= expires || entry->expires == 0) {
                    if (u)
                        notice_lang(s_OperServ, u, OPER_AKILL_EXISTS,
                                    mask);
                    return -1;
                } else {
                    entry->expires = expires;
                    if (u)
                        notice_lang(s_OperServ, u, OPER_AKILL_CHANGED,
                                    amask);
                    return -2;
                }
            }

            if (match_wild_nocase(amask, mask)
                && (entry->expires >= expires || entry->expires == 0)) {
                if (u)
                    notice_lang(s_OperServ, u, OPER_AKILL_ALREADY_COVERED,
                                mask, amask);
                return -1;
            }

            if (match_wild_nocase(mask, amask)
                && (entry->expires <= expires || expires == 0)) {
                slist_delete(&akills, i);
                deleted++;
            }
        }

    }

    /* We can now check whether the list is full or not. */
    if (slist_full(&akills)) {
        if (u)
            notice_lang(s_OperServ, u, OPER_AKILL_REACHED_LIMIT,
                        akills.limit);
        return -1;
    }

    /* We can now (really) add the AKILL. */
    mask2 = sstrdup(mask);
    host = strchr(mask2, '@');
    if (!host)
        return -1;
    user = mask2;
    *host = 0;
    host++;

    entry = scalloc(sizeof(Akill), 1);
    if (!entry)
        return -1;

    entry->user = sstrdup(user);
    entry->host = sstrdup(host);
    entry->by = sstrdup(by);
    entry->reason = sstrdup(reason);
    entry->seton = time(NULL);
    entry->expires = expires;

    slist_add(&akills, entry);

    if (AkillOnAdd)
        s_akill(entry->user, entry->host, entry->by, entry->seton,
                entry->expires, entry->reason);

    free(mask2);

    return deleted;
}

/* Does the user match any AKILLs? */

int check_akill(const char *nick, const char *username, const char *host,
                const char *vhost, const char *ip)
{
    int i;
    Akill *ak;

    /**
     * If DefCon is set to NO new users - kill the user ;).
     **/
    if (checkDefCon(DEFCON_NO_NEW_CLIENTS)) {
        kill_user(s_OperServ, nick, DefConAkillReason);
        return 1;
    }

    if (akills.count == 0)
        return 0;

    for (i = 0; i < akills.count; i++) {
        ak = akills.list[i];
        if (!ak)
            continue;
        if (match_wild_nocase(ak->user, username)
            && (match_wild_nocase(ak->host, host)
                || (vhost && match_wild_nocase(ak->host, vhost)))) {
            s_akill(ak->user, ak->host, ak->by, ak->seton, ak->expires,
                    ak->reason);
            return 1;
        }
#ifdef HAS_NICKIP
        if (ip)
            if (match_wild_nocase(ak->user, username)
                && match_wild_nocase(ak->host, ip)) {
                s_akill(ak->user, ak->host, ak->by, ak->seton, ak->expires,
                        ak->reason);
                return 1;
            }
#endif

    }

    return 0;
}

/* Delete any expired autokills. */

void expire_akills(void)
{
    int i;
    time_t now = time(NULL);
    Akill *ak;

    for (i = akills.count - 1; i >= 0; i--) {
        ak = akills.list[i];

        if (!ak->expires || ak->expires > now)
            continue;

        if (WallAkillExpire)
            wallops(s_OperServ, "AKILL on %s@%s has expired", ak->user,
                    ak->host);
        slist_delete(&akills, i);
    }
}

static void free_akill_entry(SList * slist, void *item)
{
    Akill *ak = item;

    /* Remove the AKILLs from all the servers */
    s_rakill(ak->user, ak->host);

    /* Free the structure */
    free(ak->user);
    free(ak->host);
    free(ak->by);
    free(ak->reason);
    free(ak);
}

/* item1 is not an Akill pointer, but a char
 */

static int is_akill_entry_equal(SList * slist, void *item1, void *item2)
{
    char *ak1 = item1, buf[BUFSIZE];
    Akill *ak2 = item2;

    if (!ak1 || !ak2)
        return 0;

    snprintf(buf, sizeof(buf), "%s@%s", ak2->user, ak2->host);

    if (!stricmp(ak1, buf))
        return 1;
    else
        return 0;
}

/* Lists an AKILL entry, prefixing it with the header if needed */

static int akill_list(int number, Akill * ak, User * u, int *sent_header)
{
    char mask[BUFSIZE];

    if (!ak)
        return 0;

    if (!*sent_header) {
        notice_lang(s_OperServ, u, OPER_AKILL_LIST_HEADER);
        *sent_header = 1;
    }

    snprintf(mask, sizeof(mask), "%s@%s", ak->user, ak->host);
    notice_lang(s_OperServ, u, OPER_AKILL_LIST_FORMAT, number, mask,
                ak->reason);

    return 1;
}

/* Callback for enumeration purposes */

static int akill_list_callback(SList * slist, int number, void *item,
                               va_list args)
{
    User *u = va_arg(args, User *);
    int *sent_header = va_arg(args, int *);

    return akill_list(number, item, u, sent_header);
}

/* Lists an AKILL entry, prefixing it with the header if needed */

static int akill_view(int number, Akill * ak, User * u, int *sent_header)
{
    char mask[BUFSIZE];
    char timebuf[32], expirebuf[256];
    struct tm tm;

    if (!ak)
        return 0;

    if (!*sent_header) {
        notice_lang(s_OperServ, u, OPER_AKILL_VIEW_HEADER);
        *sent_header = 1;
    }

    snprintf(mask, sizeof(mask), "%s@%s", ak->user, ak->host);
    tm = *localtime(&ak->seton);
    strftime_lang(timebuf, sizeof(timebuf), u, STRFTIME_SHORT_DATE_FORMAT,
                  &tm);
    expire_left(u->na, expirebuf, sizeof(expirebuf), ak->expires);
    notice_lang(s_OperServ, u, OPER_AKILL_VIEW_FORMAT, number, mask,
                ak->by, timebuf, expirebuf, ak->reason);

    return 1;
}

/* Callback for enumeration purposes */

static int akill_view_callback(SList * slist, int number, void *item,
                               va_list args)
{
    User *u = va_arg(args, User *);
    int *sent_header = va_arg(args, int *);

    return akill_view(number, item, u, sent_header);
}

/* Manage the AKILL list. */

static int do_akill(User * u)
{
    char *cmd = strtok(NULL, " ");
    char breason[BUFSIZE];

    if (!cmd)
        cmd = "";

    if (!stricmp(cmd, "ADD")) {
        int deleted = 0;
        char *expiry, *mask, *reason;
        time_t expires;

        mask = strtok(NULL, " ");
        if (mask && *mask == '+') {
            expiry = mask;
            mask = strtok(NULL, " ");
        } else {
            expiry = NULL;
        }

        expires = expiry ? dotime(expiry) : AutokillExpiry;
        /* If the expiry given does not contain a final letter, it's in days,
         * said the doc. Ah well.
         */
        if (expiry && isdigit(expiry[strlen(expiry) - 1]))
            expires *= 86400;
        /* Do not allow less than a minute expiry time */
        if (expires != 0 && expires < 60) {
            notice_lang(s_OperServ, u, BAD_EXPIRY_TIME);
            return MOD_CONT;
        } else if (expires > 0) {
            expires += time(NULL);
        }

        if (mask && (reason = strtok(NULL, ""))) {
            /* We first do some sanity check on the proposed mask. */
            if (strchr(mask, '!')) {
                notice_lang(s_OperServ, u, OPER_AKILL_NO_NICK);
                return MOD_CONT;
            }

            if (!strchr(mask, '@')) {
                notice_lang(s_OperServ, u, BAD_USERHOST_MASK);
                return MOD_CONT;
            }

            if (mask && strspn(mask, "~@.*?") == strlen(mask)) {
                notice_lang(s_OperServ, u, USERHOST_MASK_TOO_WIDE, mask);
                return MOD_CONT;
            }

            /**
             * Changed sprintf() to snprintf()and increased the size of
             * breason to match bufsize
             * -Rob
             **/
            if (AddAkiller) {
                snprintf(breason, sizeof(breason), "[%s] %s", u->nick,
                         reason);
                reason = sstrdup(breason);
            }

            deleted = add_akill(u, mask, u->nick, expires, reason);
            if (deleted < 0)
                return MOD_CONT;
            else if (deleted)
                notice_lang(s_OperServ, u, OPER_AKILL_DELETED_SEVERAL,
                            deleted);
            notice_lang(s_OperServ, u, OPER_AKILL_ADDED, mask);

            if (WallOSAkill) {
                char buf[128];

                if (!expires) {
                    strcpy(buf, "does not expire");
                } else {
                    int wall_expiry = expires - time(NULL);
                    char *s = NULL;

                    if (wall_expiry >= 86400) {
                        wall_expiry /= 86400;
                        s = "day";
                    } else if (wall_expiry >= 3600) {
                        wall_expiry /= 3600;
                        s = "hour";
                    } else if (wall_expiry >= 60) {
                        wall_expiry /= 60;
                        s = "minute";
                    }

                    snprintf(buf, sizeof(buf), "expires in %d %s%s",
                             wall_expiry, s,
                             (wall_expiry == 1) ? "" : "s");
                }

                wallops(s_OperServ, "%s added an AKILL for %s (%s) (%s)",
                        u->nick, mask, reason, buf);
            }

            if (readonly)
                notice_lang(s_OperServ, u, READ_ONLY_MODE);

        } else {
            syntax_error(s_OperServ, u, "AKILL", OPER_AKILL_SYNTAX);
        }

    } else if (!stricmp(cmd, "DEL")) {

        char *mask;
        int res = 0;

        mask = strtok(NULL, " ");

        if (!mask) {
            syntax_error(s_OperServ, u, "AKILL", OPER_AKILL_SYNTAX);
            return MOD_CONT;
        }

        if (akills.count == 0) {
            notice_lang(s_OperServ, u, OPER_AKILL_LIST_EMPTY);
            return MOD_CONT;
        }

        if (isdigit(*mask) && strspn(mask, "1234567890,-") == strlen(mask)) {
            /* Deleting a range */
            res = slist_delete_range(&akills, mask, NULL);
            if (res == 0) {
                notice_lang(s_OperServ, u, OPER_AKILL_NO_MATCH);
                return MOD_CONT;
            } else if (res == 1) {
                notice_lang(s_OperServ, u, OPER_AKILL_DELETED_ONE);
            } else {
                notice_lang(s_OperServ, u, OPER_AKILL_DELETED_SEVERAL,
                            res);
            }
        } else {
            if ((res = slist_indexof(&akills, mask)) == -1) {
                notice_lang(s_OperServ, u, OPER_AKILL_NOT_FOUND, mask);
                return MOD_CONT;
            }

            slist_delete(&akills, res);
            notice_lang(s_OperServ, u, OPER_AKILL_DELETED, mask);
        }

        if (readonly)
            notice_lang(s_OperServ, u, READ_ONLY_MODE);

    } else if (!stricmp(cmd, "LIST")) {
        char *mask;
        int res, sent_header = 0;

        if (akills.count == 0) {
            notice_lang(s_OperServ, u, OPER_AKILL_LIST_EMPTY);
            return MOD_CONT;
        }

        mask = strtok(NULL, " ");

        if (!mask || (isdigit(*mask)
                      && strspn(mask, "1234567890,-") == strlen(mask))) {
            res =
                slist_enum(&akills, mask, &akill_list_callback, u,
                           &sent_header);
            if (res == 0) {
                notice_lang(s_OperServ, u, OPER_AKILL_NO_MATCH);
                return MOD_CONT;
            } else {
                notice_lang(s_OperServ, u, END_OF_ANY_LIST, "Akill");
            }
        } else {
            int i;
            char amask[BUFSIZE];

            for (i = 0; i < akills.count; i++) {
                snprintf(amask, sizeof(amask), "%s@%s",
                         ((Akill *) akills.list[i])->user,
                         ((Akill *) akills.list[i])->host);
                if (!stricmp(mask, amask)
                    || match_wild_nocase(mask, amask))
                    akill_list(i + 1, akills.list[i], u, &sent_header);
            }

            if (!sent_header)
                notice_lang(s_OperServ, u, OPER_AKILL_NO_MATCH);
            else {
                notice_lang(s_OperServ, u, END_OF_ANY_LIST, "Akill");
            }
        }
    } else if (!stricmp(cmd, "VIEW")) {
        char *mask;
        int res, sent_header = 0;

        if (akills.count == 0) {
            notice_lang(s_OperServ, u, OPER_AKILL_LIST_EMPTY);
            return MOD_CONT;
        }

        mask = strtok(NULL, " ");

        if (!mask || (isdigit(*mask)
                      && strspn(mask, "1234567890,-") == strlen(mask))) {
            res =
                slist_enum(&akills, mask, &akill_view_callback, u,
                           &sent_header);
            if (res == 0) {
                notice_lang(s_OperServ, u, OPER_AKILL_NO_MATCH);
                return MOD_CONT;
            }
        } else {
            int i;
            char amask[BUFSIZE];

            for (i = 0; i < akills.count; i++) {
                snprintf(amask, sizeof(amask), "%s@%s",
                         ((Akill *) akills.list[i])->user,
                         ((Akill *) akills.list[i])->host);
                if (!stricmp(mask, amask)
                    || match_wild_nocase(mask, amask))
                    akill_view(i + 1, akills.list[i], u, &sent_header);
            }

            if (!sent_header)
                notice_lang(s_OperServ, u, OPER_AKILL_NO_MATCH);
        }
    } else if (!stricmp(cmd, "CLEAR")) {
        slist_clear(&akills, 1);
        notice_lang(s_OperServ, u, OPER_AKILL_CLEAR);
    } else {
        syntax_error(s_OperServ, u, "AKILL", OPER_AKILL_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

#ifdef IRC_BAHAMUT

/* Adds an SGLINE to the list. Returns >= 0 on success, -1 if it failed, -2 if
 * only the expiry time changed.
 * The success result is the number of SGLINEs that were deleted to successfully add one.
 */

int add_sgline(User * u, char *mask, const char *by, const time_t expires,
               const char *reason)
{
    int deleted = 0, i;
    SXLine *entry;

    /* Checks whether there is an SGLINE that already covers
     * the one we want to add, and whether there are SGLINEs
     * that would be covered by this one.
     * If so, warn the user in the first case and cleanup
     * the useless SGLINEs in the second.
     */

    if (sglines.count > 0) {

        for (i = sglines.count - 1; i >= 0; i--) {
            entry = sglines.list[i];

            if (!entry)
                continue;

            if (!stricmp(entry->mask, mask)) {
                if (entry->expires >= expires || entry->expires == 0) {
                    if (u)
                        notice_lang(s_OperServ, u, OPER_SGLINE_EXISTS,
                                    mask);
                    return -1;
                } else {
                    entry->expires = expires;
                    if (u)
                        notice_lang(s_OperServ, u, OPER_SGLINE_CHANGED,
                                    entry->mask);
                    return -2;
                }
            }

            if (match_wild_nocase(entry->mask, mask)
                && (entry->expires >= expires || entry->expires == 0)) {
                if (u)
                    notice_lang(s_OperServ, u, OPER_SGLINE_ALREADY_COVERED,
                                mask, entry->mask);
                return -1;
            }

            if (match_wild_nocase(mask, entry->mask)
                && (entry->expires <= expires || expires == 0)) {
                slist_delete(&sglines, i);
                deleted++;
            }
        }

    }

    /* We can now check whether the list is full or not. */
    if (slist_full(&sglines)) {
        if (u)
            notice_lang(s_OperServ, u, OPER_SGLINE_REACHED_LIMIT,
                        sglines.limit);
        return -1;
    }

    /* We can now (really) add the SGLINE. */
    entry = scalloc(sizeof(SXLine), 1);
    if (!entry)
        return -1;

    entry->mask = sstrdup(mask);
    entry->by = sstrdup(by);
    entry->reason = sstrdup(reason);
    entry->seton = time(NULL);
    entry->expires = expires;

    slist_add(&sglines, entry);

    s_sgline(entry->mask, entry->reason);

    return deleted;
}

/* Does the user match any SGLINEs? */

int check_sgline(const char *nick, const char *realname)
{
    int i;
    SXLine *sx;

    if (sglines.count == 0)
        return 0;

    for (i = 0; i < sglines.count; i++) {
        sx = sglines.list[i];
        if (!sx)
            continue;

        if (match_wild_nocase(sx->mask, realname)) {
            s_sgline(sx->mask, sx->reason);
            /* We kill nick since s_sgline can't */
            send_cmd(ServerName, "SVSKILL %s :G-Lined: %s", nick,
                     sx->reason);
            return 1;
        }
    }

    return 0;
}

/* Delete any expired SGLINEs. */

void expire_sglines(void)
{
    int i;
    time_t now = time(NULL);
    SXLine *sx;

    for (i = sglines.count - 1; i >= 0; i--) {
        sx = sglines.list[i];

        if (!sx->expires || sx->expires > now)
            continue;

        if (WallSGLineExpire)
            wallops(s_OperServ, "SGLINE on \2%s\2 has expired", sx->mask);
        slist_delete(&sglines, i);
    }
}

static void free_sgline_entry(SList * slist, void *item)
{
    SXLine *sx = item;

    /* Remove the SGLINE from all the servers */
    s_unsgline(sx->mask);

    /* Free the structure */
    free(sx->mask);
    free(sx->by);
    free(sx->reason);
    free(sx);
}

/* item1 is not an SXLine pointer, but a char */

static int is_sgline_entry_equal(SList * slist, void *item1, void *item2)
{
    char *sx1 = item1;
    SXLine *sx2 = item2;

    if (!sx1 || !sx2)
        return 0;

    if (!stricmp(sx1, sx2->mask))
        return 1;
    else
        return 0;
}

/* Lists an SGLINE entry, prefixing it with the header if needed */

static int sgline_list(int number, SXLine * sx, User * u, int *sent_header)
{
    if (!sx)
        return 0;

    if (!*sent_header) {
        notice_lang(s_OperServ, u, OPER_SGLINE_LIST_HEADER);
        *sent_header = 1;
    }

    notice_lang(s_OperServ, u, OPER_SGLINE_LIST_FORMAT, number, sx->mask,
                sx->reason);

    return 1;
}

/* Callback for enumeration purposes */

static int sgline_list_callback(SList * slist, int number, void *item,
                                va_list args)
{
    User *u = va_arg(args, User *);
    int *sent_header = va_arg(args, int *);

    return sgline_list(number, item, u, sent_header);
}

/* Lists an SGLINE entry, prefixing it with the header if needed */

static int sgline_view(int number, SXLine * sx, User * u, int *sent_header)
{
    char timebuf[32], expirebuf[256];
    struct tm tm;

    if (!sx)
        return 0;

    if (!*sent_header) {
        notice_lang(s_OperServ, u, OPER_SGLINE_VIEW_HEADER);
        *sent_header = 1;
    }

    tm = *localtime(&sx->seton);
    strftime_lang(timebuf, sizeof(timebuf), u, STRFTIME_SHORT_DATE_FORMAT,
                  &tm);
    expire_left(u->na, expirebuf, sizeof(expirebuf), sx->expires);
    notice_lang(s_OperServ, u, OPER_SGLINE_VIEW_FORMAT, number, sx->mask,
                sx->by, timebuf, expirebuf, sx->reason);

    return 1;
}

/* Callback for enumeration purposes */

static int sgline_view_callback(SList * slist, int number, void *item,
                                va_list args)
{
    User *u = va_arg(args, User *);
    int *sent_header = va_arg(args, int *);

    return sgline_view(number, item, u, sent_header);
}

#endif

/* Manage the SGLINE list. */

static int do_sgline(User * u)
{
#ifdef IRC_BAHAMUT
    char *cmd = strtok(NULL, " ");

    if (!cmd)
        cmd = "";

    if (!stricmp(cmd, "ADD")) {
        int deleted = 0;
        char *expiry, *mask, *reason;
        time_t expires;

        mask = strtok(NULL, ":");
        if (mask && *mask == '+') {
            expiry = mask;
            mask = strchr(expiry, ' ');
            if (mask) {
                *mask = 0;
                mask++;
            }
        } else {
            expiry = NULL;
        }

        expires = expiry ? dotime(expiry) : SGLineExpiry;
        /* If the expiry given does not contain a final letter, it's in days,
         * said the doc. Ah well.
         */
        if (expiry && isdigit(expiry[strlen(expiry) - 1]))
            expires *= 86400;
        /* Do not allow less than a minute expiry time */
        if (expires != 0 && expires < 60) {
            notice_lang(s_OperServ, u, BAD_EXPIRY_TIME);
            return MOD_CONT;
        } else if (expires > 0) {
            expires += time(NULL);
        }

        if (mask && (reason = strtok(NULL, ""))) {
            /* We first do some sanity check on the proposed mask. */

            if (mask && strspn(mask, "*?") == strlen(mask)) {
                notice_lang(s_OperServ, u, USERHOST_MASK_TOO_WIDE, mask);
                return MOD_CONT;
            }

            deleted = add_sgline(u, mask, u->nick, expires, reason);
            if (deleted < 0)
                return MOD_CONT;
            else if (deleted)
                notice_lang(s_OperServ, u, OPER_SGLINE_DELETED_SEVERAL,
                            deleted);
            notice_lang(s_OperServ, u, OPER_SGLINE_ADDED, mask);

            if (WallOSSGLine) {
                char buf[128];

                if (!expires) {
                    strcpy(buf, "does not expire");
                } else {
                    int wall_expiry = expires - time(NULL);
                    char *s = NULL;

                    if (wall_expiry >= 86400) {
                        wall_expiry /= 86400;
                        s = "day";
                    } else if (wall_expiry >= 3600) {
                        wall_expiry /= 3600;
                        s = "hour";
                    } else if (wall_expiry >= 60) {
                        wall_expiry /= 60;
                        s = "minute";
                    }

                    snprintf(buf, sizeof(buf), "expires in %d %s%s",
                             wall_expiry, s,
                             (wall_expiry == 1) ? "" : "s");
                }

                wallops(s_OperServ, "%s added an SGLINE for %s (%s)",
                        u->nick, mask, buf);
            }

            if (readonly)
                notice_lang(s_OperServ, u, READ_ONLY_MODE);

        } else {
            syntax_error(s_OperServ, u, "SGLINE", OPER_SGLINE_SYNTAX);
        }

    } else if (!stricmp(cmd, "DEL")) {

        char *mask;
        int res = 0;

        mask = strtok(NULL, "");

        if (!mask) {
            syntax_error(s_OperServ, u, "SGLINE", OPER_SGLINE_SYNTAX);
            return MOD_CONT;
        }

        if (sglines.count == 0) {
            notice_lang(s_OperServ, u, OPER_SGLINE_LIST_EMPTY);
            return MOD_CONT;
        }

        if (isdigit(*mask) && strspn(mask, "1234567890,-") == strlen(mask)) {
            /* Deleting a range */
            res = slist_delete_range(&sglines, mask, NULL);
            if (res == 0) {
                notice_lang(s_OperServ, u, OPER_SGLINE_NO_MATCH);
                return MOD_CONT;
            } else if (res == 1) {
                notice_lang(s_OperServ, u, OPER_SGLINE_DELETED_ONE);
            } else {
                notice_lang(s_OperServ, u, OPER_SGLINE_DELETED_SEVERAL,
                            res);
            }
        } else {
            if ((res = slist_indexof(&sglines, mask)) == -1) {
                notice_lang(s_OperServ, u, OPER_SGLINE_NOT_FOUND, mask);
                return MOD_CONT;
            }

            slist_delete(&sglines, res);
            notice_lang(s_OperServ, u, OPER_SGLINE_DELETED, mask);
        }

        if (readonly)
            notice_lang(s_OperServ, u, READ_ONLY_MODE);

    } else if (!stricmp(cmd, "LIST")) {
        char *mask;
        int res, sent_header = 0;

        if (sglines.count == 0) {
            notice_lang(s_OperServ, u, OPER_SGLINE_LIST_EMPTY);
            return MOD_CONT;
        }

        mask = strtok(NULL, "");

        if (!mask || (isdigit(*mask)
                      && strspn(mask, "1234567890,-") == strlen(mask))) {
            res =
                slist_enum(&sglines, mask, &sgline_list_callback, u,
                           &sent_header);
            if (res == 0) {
                notice_lang(s_OperServ, u, OPER_SGLINE_NO_MATCH);
                return MOD_CONT;
            }
        } else {
            int i;
            char *amask;

            for (i = 0; i < sglines.count; i++) {
                amask = ((SXLine *) sglines.list[i])->mask;
                if (!stricmp(mask, amask)
                    || match_wild_nocase(mask, amask))
                    sgline_list(i + 1, sglines.list[i], u, &sent_header);
            }

            if (!sent_header)
                notice_lang(s_OperServ, u, OPER_SGLINE_NO_MATCH);
            else {
                notice_lang(s_OperServ, u, END_OF_ANY_LIST, "SGLine");
            }
        }
    } else if (!stricmp(cmd, "VIEW")) {
        char *mask;
        int res, sent_header = 0;

        if (sglines.count == 0) {
            notice_lang(s_OperServ, u, OPER_SGLINE_LIST_EMPTY);
            return MOD_CONT;
        }

        mask = strtok(NULL, "");

        if (!mask || (isdigit(*mask)
                      && strspn(mask, "1234567890,-") == strlen(mask))) {
            res =
                slist_enum(&sglines, mask, &sgline_view_callback, u,
                           &sent_header);
            if (res == 0) {
                notice_lang(s_OperServ, u, OPER_SGLINE_NO_MATCH);
                return MOD_CONT;
            }
        } else {
            int i;
            char *amask;

            for (i = 0; i < sglines.count; i++) {
                amask = ((SXLine *) sglines.list[i])->mask;
                if (!stricmp(mask, amask)
                    || match_wild_nocase(mask, amask))
                    sgline_view(i + 1, sglines.list[i], u, &sent_header);
            }

            if (!sent_header)
                notice_lang(s_OperServ, u, OPER_SGLINE_NO_MATCH);
        }
    } else if (!stricmp(cmd, "CLEAR")) {
        slist_clear(&sglines, 1);
        notice_lang(s_OperServ, u, OPER_SGLINE_CLEAR);
    } else {
        syntax_error(s_OperServ, u, "SGLINE", OPER_SGLINE_SYNTAX);
    }
#else
    notice_lang(s_OperServ, u, OPER_SGLINE_UNSUPPORTED);
#endif
    return MOD_CONT;
}

/*************************************************************************/

/* Adds an SQLINE to the list. Returns >= 0 on success, -1 if it failed, -2 if
 * only the expiry time changed.
 * The success result is the number of SQLINEs that were deleted to successfully add one.
 */

int add_sqline(User * u, char *mask, const char *by, const time_t expires,
               const char *reason)
{
    int deleted = 0, i;
    SXLine *entry;

    /* Checks whether there is an SQLINE that already covers
     * the one we want to add, and whether there are SQLINEs
     * that would be covered by this one.
     * If so, warn the user in the first case and cleanup
     * the useless SQLINEs in the second.
     */

    if (sqlines.count > 0) {

        for (i = sqlines.count - 1; i >= 0; i--) {
            entry = sqlines.list[i];

            if (!entry)
                continue;

            if ((*mask == '#' && *entry->mask != '#') ||
                (*mask != '#' && *entry->mask == '#'))
                continue;

            if (!stricmp(entry->mask, mask)) {
                if (entry->expires >= expires || entry->expires == 0) {
                    if (u)
                        notice_lang(s_OperServ, u, OPER_SQLINE_EXISTS,
                                    mask);
                    return -1;
                } else {
                    entry->expires = expires;
                    if (u)
                        notice_lang(s_OperServ, u, OPER_SQLINE_CHANGED,
                                    entry->mask);
                    return -2;
                }
            }

            if (match_wild_nocase(entry->mask, mask)
                && (entry->expires >= expires || entry->expires == 0)) {
                if (u)
                    notice_lang(s_OperServ, u, OPER_SQLINE_ALREADY_COVERED,
                                mask, entry->mask);
                return -1;
            }

            if (match_wild_nocase(mask, entry->mask)
                && (entry->expires <= expires || expires == 0)) {
                slist_delete(&sqlines, i);
                deleted++;
            }
        }

    }

    /* We can now check whether the list is full or not. */
    if (slist_full(&sqlines)) {
        if (u)
            notice_lang(s_OperServ, u, OPER_SQLINE_REACHED_LIMIT,
                        sqlines.limit);
        return -1;
    }

    /* We can now (really) add the SQLINE. */
    entry = scalloc(sizeof(SXLine), 1);
    if (!entry)
        return -1;

    entry->mask = sstrdup(mask);
    entry->by = sstrdup(by);
    entry->reason = sstrdup(reason);
    entry->seton = time(NULL);
    entry->expires = expires;

    slist_add(&sqlines, entry);

    s_sqline(entry->mask, entry->reason);

    return deleted;
}

/* Does the user match any SQLINEs? */

int check_sqline(const char *nick, int nick_change)
{
    int i;
    SXLine *sx;

    if (sqlines.count == 0)
        return 0;

    for (i = 0; i < sqlines.count; i++) {
        sx = sqlines.list[i];
        if (!sx)
            continue;

#ifdef IRC_BAHAMUT
        if (*sx->mask == '#')
            continue;
#endif

        if (match_wild_nocase(sx->mask, nick)) {
            s_sqline(sx->mask, sx->reason);
            /* We kill nick since s_sqline can't */
#ifdef IRC_BAHAMUT
            send_cmd(ServerName, "SVSKILL %s :Q-Lined: %s", nick,
                     sx->reason);
#else
            if (!nick_change) {
                send_cmd(s_OperServ, "KILL %s :Q-Lined: %s", nick,
                         sx->reason);
            } else {
                char reason[300];
                snprintf(reason, sizeof(reason), "Q-Lined: %s",
                         sx->reason);
                kill_user(s_OperServ, nick, reason);
            }
#endif
            return 1;
        }
    }

    return 0;
}

#ifdef IRC_BAHAMUT
int check_chan_sqline(const char *chan)
{
    int i;
    SXLine *sx;

    if (sqlines.count == 0)
        return 0;

    for (i = 0; i < sqlines.count; i++) {
        sx = sqlines.list[i];
        if (!sx)
            continue;

        if (*sx->mask != '#')
            continue;

        if (match_wild_nocase(sx->mask, chan)) {
            s_sqline(sx->mask, sx->reason);
            return 1;
        }
    }

    return 0;
}
#endif

/* Delete any expired SQLINEs. */

void expire_sqlines(void)
{
    int i;
    time_t now = time(NULL);
    SXLine *sx;

    for (i = sqlines.count - 1; i >= 0; i--) {
        sx = sqlines.list[i];

        if (!sx->expires || sx->expires > now)
            continue;

        if (WallSQLineExpire)
            wallops(s_OperServ, "SQLINE on \2%s\2 has expired", sx->mask);

        slist_delete(&sqlines, i);
    }
}

static void free_sqline_entry(SList * slist, void *item)
{
    SXLine *sx = item;

    /* Remove the SQLINE from all the servers */
    s_unsqline(sx->mask);

    /* Free the structure */
    free(sx->mask);
    free(sx->by);
    free(sx->reason);
    free(sx);
}

/* item1 is not an SXLine pointer, but a char */

static int is_sqline_entry_equal(SList * slist, void *item1, void *item2)
{
    char *sx1 = item1;
    SXLine *sx2 = item2;

    if (!sx1 || !sx2)
        return 0;

    if (!stricmp(sx1, sx2->mask))
        return 1;
    else
        return 0;
}

/* Lists an SQLINE entry, prefixing it with the header if needed */

static int sqline_list(int number, SXLine * sx, User * u, int *sent_header)
{
    if (!sx)
        return 0;

    if (!*sent_header) {
        notice_lang(s_OperServ, u, OPER_SQLINE_LIST_HEADER);
        *sent_header = 1;
    }

    notice_lang(s_OperServ, u, OPER_SQLINE_LIST_FORMAT, number, sx->mask,
                sx->reason);

    return 1;
}

/* Callback for enumeration purposes */

static int sqline_list_callback(SList * slist, int number, void *item,
                                va_list args)
{
    User *u = va_arg(args, User *);
    int *sent_header = va_arg(args, int *);

    return sqline_list(number, item, u, sent_header);
}

/* Lists an SQLINE entry, prefixing it with the header if needed */

static int sqline_view(int number, SXLine * sx, User * u, int *sent_header)
{
    char timebuf[32], expirebuf[256];
    struct tm tm;

    if (!sx)
        return 0;

    if (!*sent_header) {
        notice_lang(s_OperServ, u, OPER_SQLINE_VIEW_HEADER);
        *sent_header = 1;
    }

    tm = *localtime(&sx->seton);
    strftime_lang(timebuf, sizeof(timebuf), u, STRFTIME_SHORT_DATE_FORMAT,
                  &tm);
    expire_left(u->na, expirebuf, sizeof(expirebuf), sx->expires);
    notice_lang(s_OperServ, u, OPER_SQLINE_VIEW_FORMAT, number, sx->mask,
                sx->by, timebuf, expirebuf, sx->reason);

    return 1;
}

/* Callback for enumeration purposes */

static int sqline_view_callback(SList * slist, int number, void *item,
                                va_list args)
{
    User *u = va_arg(args, User *);
    int *sent_header = va_arg(args, int *);

    return sqline_view(number, item, u, sent_header);
}

/* Manage the SQLINE list. */

static int do_sqline(User * u)
{
    char *cmd = strtok(NULL, " ");

    if (!cmd)
        cmd = "";

    if (!stricmp(cmd, "ADD")) {
        int deleted = 0;
        char *expiry, *mask, *reason;
        time_t expires;

        mask = strtok(NULL, " ");
        if (mask && *mask == '+') {
            expiry = mask;
            mask = strtok(NULL, " ");
        } else {
            expiry = NULL;
        }

        expires = expiry ? dotime(expiry) : SQLineExpiry;
        /* If the expiry given does not contain a final letter, it's in days,
         * said the doc. Ah well.
         */
        if (expiry && isdigit(expiry[strlen(expiry) - 1]))
            expires *= 86400;
        /* Do not allow less than a minute expiry time */
        if (expires != 0 && expires < 60) {
            notice_lang(s_OperServ, u, BAD_EXPIRY_TIME);
            return MOD_CONT;
        } else if (expires > 0) {
            expires += time(NULL);
        }

        if (mask && (reason = strtok(NULL, ""))) {

            /* We first do some sanity check on the proposed mask. */
            if (strspn(mask, "*?") == strlen(mask)) {
                notice_lang(s_OperServ, u, USERHOST_MASK_TOO_WIDE, mask);
                return MOD_CONT;
            }
#ifndef IRC_BAHAMUT
            /* Channel SQLINEs are only supported on Bahamut servers */
            if (*mask == '#') {
                notice_lang(s_OperServ, u,
                            OPER_SQLINE_CHANNELS_UNSUPPORTED);
                return MOD_CONT;
            }
#endif

            deleted = add_sqline(u, mask, u->nick, expires, reason);
            if (deleted < 0)
                return MOD_CONT;
            else if (deleted)
                notice_lang(s_OperServ, u, OPER_SQLINE_DELETED_SEVERAL,
                            deleted);
            notice_lang(s_OperServ, u, OPER_SQLINE_ADDED, mask);

            if (WallOSSQLine) {
                char buf[128];

                if (!expires) {
                    strcpy(buf, "does not expire");
                } else {
                    int wall_expiry = expires - time(NULL);
                    char *s = NULL;

                    if (wall_expiry >= 86400) {
                        wall_expiry /= 86400;
                        s = "day";
                    } else if (wall_expiry >= 3600) {
                        wall_expiry /= 3600;
                        s = "hour";
                    } else if (wall_expiry >= 60) {
                        wall_expiry /= 60;
                        s = "minute";
                    }

                    snprintf(buf, sizeof(buf), "expires in %d %s%s",
                             wall_expiry, s,
                             (wall_expiry == 1) ? "" : "s");
                }

                wallops(s_OperServ, "%s added an SQLINE for %s (%s)",
                        u->nick, mask, buf);
            }

            if (readonly)
                notice_lang(s_OperServ, u, READ_ONLY_MODE);

        } else {
            syntax_error(s_OperServ, u, "SQLINE", OPER_SQLINE_SYNTAX);
        }

    } else if (!stricmp(cmd, "DEL")) {

        char *mask;
        int res = 0;

        mask = strtok(NULL, "");

        if (!mask) {
            syntax_error(s_OperServ, u, "SQLINE", OPER_SQLINE_SYNTAX);
            return MOD_CONT;
        }

        if (sqlines.count == 0) {
            notice_lang(s_OperServ, u, OPER_SQLINE_LIST_EMPTY);
            return MOD_CONT;
        }

        if (isdigit(*mask) && strspn(mask, "1234567890,-") == strlen(mask)) {
            /* Deleting a range */
            res = slist_delete_range(&sqlines, mask, NULL);
            if (res == 0) {
                notice_lang(s_OperServ, u, OPER_SQLINE_NO_MATCH);
                return MOD_CONT;
            } else if (res == 1) {
                notice_lang(s_OperServ, u, OPER_SQLINE_DELETED_ONE);
            } else {
                notice_lang(s_OperServ, u, OPER_SQLINE_DELETED_SEVERAL,
                            res);
            }
        } else {
            if ((res = slist_indexof(&sqlines, mask)) == -1) {
                notice_lang(s_OperServ, u, OPER_SQLINE_NOT_FOUND, mask);
                return MOD_CONT;
            }

            slist_delete(&sqlines, res);
            notice_lang(s_OperServ, u, OPER_SQLINE_DELETED, mask);
        }

        if (readonly)
            notice_lang(s_OperServ, u, READ_ONLY_MODE);

    } else if (!stricmp(cmd, "LIST")) {
        char *mask;
        int res, sent_header = 0;

        if (sqlines.count == 0) {
            notice_lang(s_OperServ, u, OPER_SQLINE_LIST_EMPTY);
            return MOD_CONT;
        }

        mask = strtok(NULL, "");

        if (!mask || (isdigit(*mask)
                      && strspn(mask, "1234567890,-") == strlen(mask))) {
            res =
                slist_enum(&sqlines, mask, &sqline_list_callback, u,
                           &sent_header);
            if (res == 0) {
                notice_lang(s_OperServ, u, OPER_SQLINE_NO_MATCH);
                return MOD_CONT;
            }
        } else {
            int i;
            char *amask;

            for (i = 0; i < sqlines.count; i++) {
                amask = ((SXLine *) sqlines.list[i])->mask;
                if (!stricmp(mask, amask)
                    || match_wild_nocase(mask, amask))
                    sqline_list(i + 1, sqlines.list[i], u, &sent_header);
            }

            if (!sent_header)
                notice_lang(s_OperServ, u, OPER_SQLINE_NO_MATCH);
            else {
                notice_lang(s_OperServ, u, END_OF_ANY_LIST, "SQLine");
            }
        }
    } else if (!stricmp(cmd, "VIEW")) {
        char *mask;
        int res, sent_header = 0;

        if (sqlines.count == 0) {
            notice_lang(s_OperServ, u, OPER_SQLINE_LIST_EMPTY);
            return MOD_CONT;
        }

        mask = strtok(NULL, "");

        if (!mask || (isdigit(*mask)
                      && strspn(mask, "1234567890,-") == strlen(mask))) {
            res =
                slist_enum(&sqlines, mask, &sqline_view_callback, u,
                           &sent_header);
            if (res == 0) {
                notice_lang(s_OperServ, u, OPER_SQLINE_NO_MATCH);
                return MOD_CONT;
            }
        } else {
            int i;
            char *amask;

            for (i = 0; i < sqlines.count; i++) {
                amask = ((SXLine *) sqlines.list[i])->mask;
                if (!stricmp(mask, amask)
                    || match_wild_nocase(mask, amask))
                    sqline_view(i + 1, sqlines.list[i], u, &sent_header);
            }

            if (!sent_header)
                notice_lang(s_OperServ, u, OPER_SQLINE_NO_MATCH);
        }
    } else if (!stricmp(cmd, "CLEAR")) {
        slist_clear(&sqlines, 1);
        notice_lang(s_OperServ, u, OPER_SQLINE_CLEAR);
    } else {
        syntax_error(s_OperServ, u, "SQLINE", OPER_SQLINE_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

#ifdef IRC_BAHAMUT

/* Adds an SZLINE to the list. Returns >= 0 on success, -1 on error, -2 if
 * only the expiry time changed.
 * The success result is the number of SZLINEs that were deleted to successfully add one.
 */

int add_szline(User * u, char *mask, const char *by, const time_t expires,
               const char *reason)
{
    int deleted = 0, i;
    SXLine *entry;

    /* Checks whether there is an SZLINE that already covers
     * the one we want to add, and whether there are SZLINEs
     * that would be covered by this one.
     * If so, warn the user in the first case and cleanup
     * the useless SZLINEs in the second.
     */

    if (szlines.count > 0) {

        for (i = szlines.count - 1; i >= 0; i--) {
            entry = szlines.list[i];

            if (!entry)
                continue;

            if (!stricmp(entry->mask, mask)) {
                if (entry->expires >= expires || entry->expires == 0) {
                    if (u)
                        notice_lang(s_OperServ, u, OPER_SZLINE_EXISTS,
                                    mask);
                    return -1;
                } else {
                    entry->expires = expires;
                    if (u)
                        notice_lang(s_OperServ, u, OPER_SZLINE_EXISTS,
                                    mask);
                    return -2;
                }
            }

            if (match_wild_nocase(entry->mask, mask)) {
                if (u)
                    notice_lang(s_OperServ, u, OPER_SZLINE_ALREADY_COVERED,
                                mask, entry->mask);
                return -1;
            }

            if (match_wild_nocase(mask, entry->mask)) {
                slist_delete(&szlines, i);
                deleted++;
            }
        }

    }

    /* We can now check whether the list is full or not. */
    if (slist_full(&szlines)) {
        if (u)
            notice_lang(s_OperServ, u, OPER_SZLINE_REACHED_LIMIT,
                        szlines.limit);
        return -1;
    }

    /* We can now (really) add the SZLINE. */
    entry = scalloc(sizeof(SXLine), 1);
    if (!entry)
        return -1;

    entry->mask = sstrdup(mask);
    entry->by = sstrdup(by);
    entry->reason = sstrdup(reason);
    entry->seton = time(NULL);
    entry->expires = expires;

    slist_add(&szlines, entry);
    s_szline(entry->mask, entry->reason);

    return deleted;
}

/* Delete any expired SZLINEs. */

void expire_szlines(void)
{
    int i;
    time_t now = time(NULL);
    SXLine *sx;

    for (i = szlines.count - 1; i >= 0; i--) {
        sx = szlines.list[i];

        if (!sx->expires || sx->expires > now)
            continue;

        if (WallSZLineExpire)
            wallops(s_OperServ, "SZLINE on \2%s\2 has expired", sx->mask);
        slist_delete(&szlines, i);
    }
}

static void free_szline_entry(SList * slist, void *item)
{
    SXLine *sx = item;

    /* Remove the SZLINE from all the servers */
    s_unszline(sx->mask);

    /* Free the structure */
    free(sx->mask);
    free(sx->by);
    free(sx->reason);
    free(sx);
}

/* item1 is not an SXLine pointer, but a char
 */

static int is_szline_entry_equal(SList * slist, void *item1, void *item2)
{
    char *sx1 = item1;
    SXLine *sx2 = item2;

    if (!sx1 || !sx2)
        return 0;

    if (!stricmp(sx1, sx2->mask))
        return 1;
    else
        return 0;
}

/* Lists an SZLINE entry, prefixing it with the header if needed */

static int szline_list(int number, SXLine * sx, User * u, int *sent_header)
{
    if (!sx)
        return 0;

    if (!*sent_header) {
        notice_lang(s_OperServ, u, OPER_SZLINE_LIST_HEADER);
        *sent_header = 1;
    }

    notice_lang(s_OperServ, u, OPER_SZLINE_LIST_FORMAT, number, sx->mask,
                sx->reason);

    return 1;
}

/* Callback for enumeration purposes */

static int szline_list_callback(SList * slist, int number, void *item,
                                va_list args)
{
    User *u = va_arg(args, User *);
    int *sent_header = va_arg(args, int *);

    return szline_list(number, item, u, sent_header);
}

/* Lists an SZLINE entry, prefixing it with the header if needed */

static int szline_view(int number, SXLine * sx, User * u, int *sent_header)
{
    char timebuf[32], expirebuf[256];
    struct tm tm;

    if (!sx)
        return 0;

    if (!*sent_header) {
        notice_lang(s_OperServ, u, OPER_SZLINE_VIEW_HEADER);
        *sent_header = 1;
    }

    tm = *localtime(&sx->seton);
    strftime_lang(timebuf, sizeof(timebuf), u, STRFTIME_SHORT_DATE_FORMAT,
                  &tm);
    expire_left(u->na, expirebuf, sizeof(expirebuf), sx->expires);
    notice_lang(s_OperServ, u, OPER_SZLINE_VIEW_FORMAT, number, sx->mask,
                sx->by, timebuf, expirebuf, sx->reason);

    return 1;
}

/* Callback for enumeration purposes */

static int szline_view_callback(SList * slist, int number, void *item,
                                va_list args)
{
    User *u = va_arg(args, User *);
    int *sent_header = va_arg(args, int *);

    return szline_view(number, item, u, sent_header);
}

#endif

/* Manage the SZLINE list. */

static int do_szline(User * u)
{
#ifdef IRC_BAHAMUT
    char *cmd = strtok(NULL, " ");

    if (!cmd)
        cmd = "";

    if (!stricmp(cmd, "ADD")) {
        int deleted = 0;
        char *expiry, *mask, *reason;
        time_t expires;

        mask = strtok(NULL, " ");
        if (mask && *mask == '+') {
            expiry = mask;
            mask = strtok(NULL, " ");
        } else {
            expiry = NULL;
        }

        expires = expiry ? dotime(expiry) : SZLineExpiry;
        /* If the expiry given does not contain a final letter, it's in days,
         * said the doc. Ah well.
         */
        if (expiry && isdigit(expiry[strlen(expiry) - 1]))
            expires *= 86400;
        /* Do not allow less than a minute expiry time */
        if (expires != 0 && expires < 60) {
            notice_lang(s_OperServ, u, BAD_EXPIRY_TIME);
            return MOD_CONT;
        } else if (expires > 0) {
            expires += time(NULL);
        }

        if (mask && (reason = strtok(NULL, ""))) {
            /* We first do some sanity check on the proposed mask. */

            if (strchr(mask, '!') || strchr(mask, '@')) {
                notice_lang(s_OperServ, u, OPER_SZLINE_ONLY_IPS);
                return MOD_CONT;
            }

            if (strspn(mask, "*?") == strlen(mask)) {
                notice_lang(s_OperServ, u, USERHOST_MASK_TOO_WIDE, mask);
                return MOD_CONT;
            }

            deleted = add_szline(u, mask, u->nick, expires, reason);
            if (deleted < 0)
                return MOD_CONT;
            else if (deleted)
                notice_lang(s_OperServ, u, OPER_SZLINE_DELETED_SEVERAL,
                            deleted);
            notice_lang(s_OperServ, u, OPER_SZLINE_ADDED, mask);

            if (WallOSSZLine) {
                char buf[128];

                if (!expires) {
                    strcpy(buf, "does not expire");
                } else {
                    int wall_expiry = expires - time(NULL);
                    char *s = NULL;

                    if (wall_expiry >= 86400) {
                        wall_expiry /= 86400;
                        s = "day";
                    } else if (wall_expiry >= 3600) {
                        wall_expiry /= 3600;
                        s = "hour";
                    } else if (wall_expiry >= 60) {
                        wall_expiry /= 60;
                        s = "minute";
                    }

                    snprintf(buf, sizeof(buf), "expires in %d %s%s",
                             wall_expiry, s,
                             (wall_expiry == 1) ? "" : "s");
                }

                wallops(s_OperServ, "%s added an SZLINE for %s (%s)",
                        u->nick, mask, buf);
            }

            if (readonly)
                notice_lang(s_OperServ, u, READ_ONLY_MODE);

        } else {
            syntax_error(s_OperServ, u, "SZLINE", OPER_SZLINE_SYNTAX);
        }

    } else if (!stricmp(cmd, "DEL")) {

        char *mask;
        int res = 0;

        mask = strtok(NULL, " ");

        if (!mask) {
            syntax_error(s_OperServ, u, "SZLINE", OPER_SZLINE_SYNTAX);
            return MOD_CONT;
        }

        if (szlines.count == 0) {
            notice_lang(s_OperServ, u, OPER_SZLINE_LIST_EMPTY);
            return MOD_CONT;
        }

        if (isdigit(*mask) && strspn(mask, "1234567890,-") == strlen(mask)) {
            /* Deleting a range */
            res = slist_delete_range(&szlines, mask, NULL);
            if (res == 0) {
                notice_lang(s_OperServ, u, OPER_SZLINE_NO_MATCH);
                return MOD_CONT;
            } else if (res == 1) {
                notice_lang(s_OperServ, u, OPER_SZLINE_DELETED_ONE);
            } else {
                notice_lang(s_OperServ, u, OPER_SZLINE_DELETED_SEVERAL,
                            res);
            }
        } else {
            if ((res = slist_indexof(&szlines, mask)) == -1) {
                notice_lang(s_OperServ, u, OPER_SZLINE_NOT_FOUND, mask);
                return MOD_CONT;
            }

            slist_delete(&szlines, res);
            notice_lang(s_OperServ, u, OPER_SZLINE_DELETED, mask);
        }

        if (readonly)
            notice_lang(s_OperServ, u, READ_ONLY_MODE);

    } else if (!stricmp(cmd, "LIST")) {
        char *mask;
        int res, sent_header = 0;

        if (szlines.count == 0) {
            notice_lang(s_OperServ, u, OPER_SZLINE_LIST_EMPTY);
            return MOD_CONT;
        }

        mask = strtok(NULL, " ");

        if (!mask || (isdigit(*mask)
                      && strspn(mask, "1234567890,-") == strlen(mask))) {
            res =
                slist_enum(&szlines, mask, &szline_list_callback, u,
                           &sent_header);
            if (res == 0) {
                notice_lang(s_OperServ, u, OPER_SZLINE_NO_MATCH);
                return MOD_CONT;
            }
        } else {
            int i;
            char *amask;

            for (i = 0; i < szlines.count; i++) {
                amask = ((SXLine *) szlines.list[i])->mask;
                if (!stricmp(mask, amask)
                    || match_wild_nocase(mask, amask))
                    szline_list(i + 1, szlines.list[i], u, &sent_header);
            }

            if (!sent_header)
                notice_lang(s_OperServ, u, OPER_SZLINE_NO_MATCH);
        }
    } else if (!stricmp(cmd, "VIEW")) {
        char *mask;
        int res, sent_header = 0;

        if (szlines.count == 0) {
            notice_lang(s_OperServ, u, OPER_SZLINE_LIST_EMPTY);
            return MOD_CONT;
        }

        mask = strtok(NULL, " ");

        if (!mask || (isdigit(*mask)
                      && strspn(mask, "1234567890,-") == strlen(mask))) {
            res =
                slist_enum(&szlines, mask, &szline_view_callback, u,
                           &sent_header);
            if (res == 0) {
                notice_lang(s_OperServ, u, OPER_SZLINE_NO_MATCH);
                return MOD_CONT;
            }
        } else {
            int i;
            char *amask;

            for (i = 0; i < szlines.count; i++) {
                amask = ((SXLine *) szlines.list[i])->mask;
                if (!stricmp(mask, amask)
                    || match_wild_nocase(mask, amask))
                    szline_view(i + 1, szlines.list[i], u, &sent_header);
            }

            if (!sent_header)
                notice_lang(s_OperServ, u, OPER_SZLINE_NO_MATCH);
        }
    } else if (!stricmp(cmd, "CLEAR")) {
        slist_clear(&szlines, 1);
        notice_lang(s_OperServ, u, OPER_SZLINE_CLEAR);
    } else {
        syntax_error(s_OperServ, u, "SZLINE", OPER_SZLINE_SYNTAX);
    }
#else
    notice_lang(s_OperServ, u, OPER_SZLINE_UNSUPPORTED);
#endif
    return MOD_CONT;
}

/*************************************************************************/

static int do_chanlist(User * u)
{
    char *pattern = strtok(NULL, " ");
    char *opt = strtok(NULL, " ");

    int modes = 0;
    User *u2;

    if (opt && !stricmp(opt, "SECRET"))
        modes |= (CMODE_s | CMODE_p);

    if (pattern && (u2 = finduser(pattern))) {
        struct u_chanlist *uc;

        notice_lang(s_OperServ, u, OPER_CHANLIST_HEADER_USER, u2->nick);

        for (uc = u2->chans; uc; uc = uc->next) {
            if (modes && !(uc->chan->mode & modes))
                continue;
            notice_lang(s_OperServ, u, OPER_CHANLIST_RECORD,
                        uc->chan->name, uc->chan->usercount,
                        chan_get_modes(uc->chan, 1, 1),
                        (uc->chan->topic ? uc->chan->topic : ""));
        }
    } else {
        int i;
        Channel *c;

        notice_lang(s_OperServ, u, OPER_CHANLIST_HEADER);

        for (i = 0; i < 1024; i++) {
            for (c = chanlist[i]; c; c = c->next) {
                if (pattern && !match_wild_nocase(pattern, c->name))
                    continue;
                if (modes && !(c->mode & modes))
                    continue;
                notice_lang(s_OperServ, u, OPER_CHANLIST_RECORD, c->name,
                            c->usercount, chan_get_modes(c, 1, 1),
                            (c->topic ? c->topic : ""));
            }
        }
    }

    notice_lang(s_OperServ, u, OPER_CHANLIST_END);
    return MOD_CONT;
}

/*************************************************************************/

static int do_userlist(User * u)
{
    char *pattern = strtok(NULL, " ");
    char *opt = strtok(NULL, " ");

    Channel *c;
    int modes = 0;

    if (opt && !stricmp(opt, "INVISIBLE"))
        modes |= UMODE_i;

    if (pattern && (c = findchan(pattern))) {
        struct c_userlist *cu;

        notice_lang(s_OperServ, u, OPER_USERLIST_HEADER_CHAN, pattern);

        for (cu = c->users; cu; cu = cu->next) {
            if (modes && !(cu->user->mode & modes))
                continue;
            notice_lang(s_OperServ, u, OPER_USERLIST_RECORD,
                        cu->user->nick, GetIdent(cu->user),
                        GetHost(cu->user));
        }
    } else {
        char mask[BUFSIZE];
        int i;
        User *u2;

        notice_lang(s_OperServ, u, OPER_USERLIST_HEADER);

        for (i = 0; i < 1024; i++) {
            for (u2 = userlist[i]; u2; u2 = u2->next) {
                if (pattern) {
                    snprintf(mask, sizeof(mask), "%s!%s@%s", u2->nick,
                             GetIdent(u2), GetHost(u2));
                    if (!match_wild_nocase(pattern, mask))
                        continue;
                    if (modes && !(u2->mode & modes))
                        continue;
                }
                notice_lang(s_OperServ, u, OPER_USERLIST_RECORD, u2->nick,
                            GetIdent(u2), GetHost(u2));
            }
        }
    }

    notice_lang(s_OperServ, u, OPER_USERLIST_END);
    return MOD_CONT;
}

/*************************************************************************/

/* Callback function used to sort the admin list */

static int compare_adminlist_entries(SList * slist, void *item1,
                                     void *item2)
{
    NickCore *nc1 = item1, *nc2 = item2;
    if (!nc1 || !nc2)
        return -1;              /* To tell to continue */
    return stricmp(nc1->display, nc2->display);
}

/* Callback function used when an admin list entry is deleted */

static void free_adminlist_entry(SList * slist, void *item)
{
    NickCore *nc = item;
    nc->flags &= ~NI_SERVICES_ADMIN;
}

/* Lists an admin entry, prefixing it with the header if needed */

static int admin_list(int number, NickCore * nc, User * u,
                      int *sent_header)
{
    if (!nc)
        return 0;

    if (!*sent_header) {
        notice_lang(s_OperServ, u, OPER_ADMIN_LIST_HEADER);
        *sent_header = 1;
    }

    notice_lang(s_OperServ, u, OPER_ADMIN_LIST_FORMAT, number,
                nc->display);
    return 1;
}

/* Callback for enumeration purposes */

static int admin_list_callback(SList * slist, int number, void *item,
                               va_list args)
{
    User *u = va_arg(args, User *);
    int *sent_header = va_arg(args, int *);

    return admin_list(number, item, u, sent_header);
}

/* Services admin list viewing/modification. */

static int do_admin(User * u)
{
    char *cmd = strtok(NULL, " ");
    char *nick = strtok(NULL, " ");
    NickAlias *na;
    int res = 0;

    if (skeleton) {
        notice_lang(s_OperServ, u, OPER_ADMIN_SKELETON);
        return MOD_CONT;
    }

    if (!cmd || (!nick && stricmp(cmd, "LIST") && stricmp(cmd, "CLEAR"))) {
        syntax_error(s_OperServ, u, "ADMIN", OPER_ADMIN_SYNTAX);
    } else if (!stricmp(cmd, "ADD")) {
        if (!is_services_root(u)) {
            notice_lang(s_OperServ, u, PERMISSION_DENIED);
            return MOD_CONT;
        }

        if (!(na = findnick(nick))) {
            notice_lang(s_OperServ, u, NICK_X_NOT_REGISTERED, nick);
            return MOD_CONT;
        }

        if (na->status & NS_VERBOTEN) {
            notice_lang(s_OperServ, u, NICK_X_FORBIDDEN, nick);
            return MOD_CONT;
        }

        if (na->nc->flags & NI_SERVICES_ADMIN
            || slist_indexof(&servadmins, na->nc) != -1) {
            notice_lang(s_OperServ, u, OPER_ADMIN_EXISTS, nick);
            return MOD_CONT;
        }

        res = slist_add(&servadmins, na->nc);
        if (res == -2) {
            notice_lang(s_OperServ, u, OPER_ADMIN_REACHED_LIMIT, nick);
            return MOD_CONT;
        } else {
            na->nc->flags |= NI_SERVICES_ADMIN;
            notice_lang(s_OperServ, u, OPER_ADMIN_ADDED, nick);
        }

        if (readonly)
            notice_lang(s_OperServ, u, READ_ONLY_MODE);
    } else if (!stricmp(cmd, "DEL")) {
        if (!is_services_root(u)) {
            notice_lang(s_OperServ, u, PERMISSION_DENIED);
            return MOD_CONT;
        }

        if (servadmins.count == 0) {
            notice_lang(s_OperServ, u, OPER_ADMIN_LIST_EMPTY);
            return MOD_CONT;
        }

        if (isdigit(*nick) && strspn(nick, "1234567890,-") == strlen(nick)) {
            /* Deleting a range */
            res = slist_delete_range(&servadmins, nick, NULL);
            if (res == 0) {
                notice_lang(s_OperServ, u, OPER_ADMIN_NO_MATCH);
                return MOD_CONT;
            } else if (res == 1) {
                notice_lang(s_OperServ, u, OPER_ADMIN_DELETED_ONE);
            } else {
                notice_lang(s_OperServ, u, OPER_ADMIN_DELETED_SEVERAL,
                            res);
            }
        } else {
            if (!(na = findnick(nick))) {
                notice_lang(s_OperServ, u, NICK_X_NOT_REGISTERED, nick);
                return MOD_CONT;
            }

            if (na->status & NS_VERBOTEN) {
                notice_lang(s_OperServ, u, NICK_X_FORBIDDEN, nick);
                return MOD_CONT;
            }

            if (!(na->nc->flags & NI_SERVICES_ADMIN)
                || (res = slist_indexof(&servadmins, na->nc)) == -1) {
                notice_lang(s_OperServ, u, OPER_ADMIN_NOT_FOUND, nick);
                return MOD_CONT;
            }

            slist_delete(&servadmins, res);
            notice_lang(s_OperServ, u, OPER_ADMIN_DELETED, nick);
        }

        if (readonly)
            notice_lang(s_OperServ, u, READ_ONLY_MODE);
    } else if (!stricmp(cmd, "LIST")) {
        int sent_header = 0;

        if (servadmins.count == 0) {
            notice_lang(s_OperServ, u, OPER_ADMIN_LIST_EMPTY);
            return MOD_CONT;
        }

        if (!nick || (isdigit(*nick)
                      && strspn(nick, "1234567890,-") == strlen(nick))) {
            res =
                slist_enum(&servadmins, nick, &admin_list_callback, u,
                           &sent_header);
            if (res == 0) {
                notice_lang(s_OperServ, u, OPER_ADMIN_NO_MATCH);
                return MOD_CONT;
            } else {
                notice_lang(s_OperServ, u, END_OF_ANY_LIST, "Admin");
            }
        } else {
            int i;

            for (i = 0; i < servadmins.count; i++)
                if (!stricmp
                    (nick, ((NickCore *) servadmins.list[i])->display)
                    || match_wild_nocase(nick,
                                         ((NickCore *) servadmins.
                                          list[i])->display))
                    admin_list(i + 1, servadmins.list[i], u, &sent_header);

            if (!sent_header)
                notice_lang(s_OperServ, u, OPER_ADMIN_NO_MATCH);
            else {
                notice_lang(s_OperServ, u, END_OF_ANY_LIST, "Admin");
            }
        }
    } else if (!stricmp(cmd, "CLEAR")) {
        if (!is_services_root(u)) {
            notice_lang(s_OperServ, u, PERMISSION_DENIED);
            return MOD_CONT;
        }

        if (servadmins.count == 0) {
            notice_lang(s_OperServ, u, OPER_ADMIN_LIST_EMPTY);
            return MOD_CONT;
        }

        slist_clear(&servadmins, 1);
        notice_lang(s_OperServ, u, OPER_ADMIN_CLEAR);
    } else {
        syntax_error(s_OperServ, u, "ADMIN", OPER_ADMIN_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

/* Callback function used to sort the oper list */

static int compare_operlist_entries(SList * slist, void *item1,
                                    void *item2)
{
    NickCore *nc1 = item1, *nc2 = item2;
    if (!nc1 || !nc2)
        return -1;              /* To tell to continue */
    return stricmp(nc1->display, nc2->display);
}

/* Callback function used when an oper list entry is deleted */

static void free_operlist_entry(SList * slist, void *item)
{
    NickCore *nc = item;
    nc->flags &= ~NI_SERVICES_OPER;
}

/* Lists an oper entry, prefixing it with the header if needed */

static int oper_list(int number, NickCore * nc, User * u, int *sent_header)
{
    if (!nc)
        return 0;

    if (!*sent_header) {
        notice_lang(s_OperServ, u, OPER_OPER_LIST_HEADER);
        *sent_header = 1;
    }

    notice_lang(s_OperServ, u, OPER_OPER_LIST_FORMAT, number, nc->display);
    return 1;
}

/* Callback for enumeration purposes */

static int oper_list_callback(SList * slist, int number, void *item,
                              va_list args)
{
    User *u = va_arg(args, User *);
    int *sent_header = va_arg(args, int *);

    return oper_list(number, item, u, sent_header);
}

/**
 * Display an Opers list Entry
 **/
static int opers_list(int number, NickCore * nc, User * u, char *level)
{
    User *au = NULL;
    NickAlias *na;
    int found;
    int i;

    if (!nc)
        return 0;

    found = 0;
    if ((au = finduser(nc->display))) { /* see if user is online */
        found = 1;
        notice_lang(s_OperServ, u, OPER_STAFF_FORMAT, '*', level,
                    nc->display);
    } else {
        for (i = 0; i < nc->aliases.count; i++) {       /* check all aliases */
            na = nc->aliases.list[i];
            if ((au = finduser(na->nick))) {    /* see if user is online */
                found = 1;
                notice_lang(s_OperServ, u, OPER_STAFF_AFORMAT, '*', level,
                            nc->display, na->nick);
            }
        }
    }

    if (!found)
        notice_lang(s_OperServ, u, OPER_STAFF_FORMAT, ' ', level,
                    nc->display);

    return 1;
}

/**
 * Function for the enumerator to call
 **/
static int opers_list_callback(SList * slist, int number, void *item,
                               va_list args)
{
    User *u = va_arg(args, User *);
    char *level = va_arg(args, char *);

    return opers_list(number, item, u, level);
}

/**
 * Display all Services Opers/Admins with Level + Online Status
 * /msg OperServ opers
 **/
static int do_staff(User * u)
{
    int idx = 0;
    User *au = NULL;
    NickCore *nc;
    NickAlias *na;
    int found;
    int i;

    notice_lang(s_OperServ, u, OPER_STAFF_LIST_HEADER);
    slist_enum(&servopers, NULL, &opers_list_callback, u, "OPER");
    slist_enum(&servadmins, NULL, &opers_list_callback, u, "ADMN");

    for (idx = 0; idx < RootNumber; idx++) {
        found = 0;
        if ((au = finduser(ServicesRoots[idx]))) {      /* see if user is online */
            found = 1;
            notice_lang(s_OperServ, u, OPER_STAFF_FORMAT, '*', "ROOT",
                        ServicesRoots[idx]);
        } else if ((nc = findcore(ServicesRoots[idx]))) {
            for (i = 0; i < nc->aliases.count; i++) {   /* check all aliases */
                na = nc->aliases.list[i];
                if ((au = finduser(na->nick))) {        /* see if user is online */
                    found = 1;
                    notice_lang(s_OperServ, u, OPER_STAFF_AFORMAT,
                                '*', "ROOT", ServicesRoots[idx], na->nick);
                }
            }
        }

        if (!found)
            notice_lang(s_OperServ, u, OPER_STAFF_FORMAT, ' ', "ROOT",
                        ServicesRoots[idx]);

    }
    notice_lang(s_OperServ, u, END_OF_ANY_LIST, "Staff");
    return MOD_CONT;
}

/* Services operator list viewing/modification. */

static int do_oper(User * u)
{
    char *cmd = strtok(NULL, " ");
    char *nick = strtok(NULL, " ");
    NickAlias *na;
    int res = 0;

    if (skeleton) {
        notice_lang(s_OperServ, u, OPER_OPER_SKELETON);
        return MOD_CONT;
    }

    if (!cmd || (!nick && stricmp(cmd, "LIST") && stricmp(cmd, "CLEAR"))) {
        syntax_error(s_OperServ, u, "OPER", OPER_OPER_SYNTAX);
    } else if (!stricmp(cmd, "ADD")) {
        if (!is_services_admin(u)) {
            notice_lang(s_OperServ, u, PERMISSION_DENIED);
            return MOD_CONT;
        }

        if (!(na = findnick(nick))) {
            notice_lang(s_OperServ, u, NICK_X_NOT_REGISTERED, nick);
            return MOD_CONT;
        }

        if (na->status & NS_VERBOTEN) {
            notice_lang(s_OperServ, u, NICK_X_FORBIDDEN, nick);
            return MOD_CONT;
        }

        if (na->nc->flags & NI_SERVICES_OPER
            || slist_indexof(&servopers, na->nc) != -1) {
            notice_lang(s_OperServ, u, OPER_OPER_EXISTS, nick);
            return MOD_CONT;
        }

        res = slist_add(&servopers, na->nc);
        if (res == -2) {
            notice_lang(s_OperServ, u, OPER_OPER_REACHED_LIMIT, nick);
            return MOD_CONT;
        } else {
            na->nc->flags |= NI_SERVICES_OPER;
            notice_lang(s_OperServ, u, OPER_OPER_ADDED, nick);
        }

        if (readonly)
            notice_lang(s_OperServ, u, READ_ONLY_MODE);
    } else if (!stricmp(cmd, "DEL")) {
        if (!is_services_admin(u)) {
            notice_lang(s_OperServ, u, PERMISSION_DENIED);
            return MOD_CONT;
        }

        if (isdigit(*nick) && strspn(nick, "1234567890,-") == strlen(nick)) {
            /* Deleting a range */
            res = slist_delete_range(&servopers, nick, NULL);
            if (res == 0) {
                notice_lang(s_OperServ, u, OPER_OPER_NO_MATCH);
                return MOD_CONT;
            } else if (res == 1) {
                notice_lang(s_OperServ, u, OPER_OPER_DELETED_ONE);
            } else {
                notice_lang(s_OperServ, u, OPER_OPER_DELETED_SEVERAL, res);
            }
        } else {
            if (!(na = findnick(nick))) {
                notice_lang(s_OperServ, u, NICK_X_NOT_REGISTERED, nick);
                return MOD_CONT;
            }

            if (na->status & NS_VERBOTEN) {
                notice_lang(s_OperServ, u, NICK_X_FORBIDDEN, nick);
                return MOD_CONT;
            }

            if (!(na->nc->flags & NI_SERVICES_OPER)
                || (res = slist_indexof(&servopers, na->nc)) == -1) {
                notice_lang(s_OperServ, u, OPER_OPER_NOT_FOUND, nick);
                return MOD_CONT;
            }

            slist_delete(&servopers, res);
            notice_lang(s_OperServ, u, OPER_OPER_DELETED, nick);
        }

        if (readonly)
            notice_lang(s_OperServ, u, READ_ONLY_MODE);
    } else if (!stricmp(cmd, "LIST")) {
        int sent_header = 0;

        if (servopers.count == 0) {
            notice_lang(s_OperServ, u, OPER_OPER_LIST_EMPTY);
            return MOD_CONT;
        }

        if (!nick || (isdigit(*nick)
                      && strspn(nick, "1234567890,-") == strlen(nick))) {
            res =
                slist_enum(&servopers, nick, &oper_list_callback, u,
                           &sent_header);
            if (res == 0) {
                notice_lang(s_OperServ, u, OPER_OPER_NO_MATCH);
                return MOD_CONT;
            } else {
                notice_lang(s_OperServ, u, END_OF_ANY_LIST, "Oper");
            }
        } else {
            int i;

            for (i = 0; i < servopers.count; i++)
                if (!stricmp
                    (nick, ((NickCore *) servopers.list[i])->display)
                    || match_wild_nocase(nick,
                                         ((NickCore *) servopers.list[i])->
                                         display))
                    oper_list(i + 1, servopers.list[i], u, &sent_header);

            if (!sent_header)
                notice_lang(s_OperServ, u, OPER_OPER_NO_MATCH);
            else {
                notice_lang(s_OperServ, u, END_OF_ANY_LIST, "Oper");
            }
        }
    } else if (!stricmp(cmd, "CLEAR")) {
        if (!is_services_admin(u)) {
            notice_lang(s_OperServ, u, PERMISSION_DENIED);
            return MOD_CONT;
        }

        if (servopers.count == 0) {
            notice_lang(s_OperServ, u, OPER_OPER_LIST_EMPTY);
            return MOD_CONT;
        }

        slist_clear(&servopers, 1);
        notice_lang(s_OperServ, u, OPER_OPER_CLEAR);
    } else {
        syntax_error(s_OperServ, u, "OPER", OPER_OPER_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

/* Set various Services runtime options. */

static int do_set(User * u)
{
    char *option = strtok(NULL, " ");
    char *setting = strtok(NULL, " ");

    if (!option || !setting) {
        syntax_error(s_OperServ, u, "SET", OPER_SET_SYNTAX);

    } else if (stricmp(option, "IGNORE") == 0) {
        if (stricmp(setting, "on") == 0) {
            allow_ignore = 1;
            notice_lang(s_OperServ, u, OPER_SET_IGNORE_ON);
        } else if (stricmp(setting, "off") == 0) {
            allow_ignore = 0;
            notice_lang(s_OperServ, u, OPER_SET_IGNORE_OFF);
        } else {
            notice_lang(s_OperServ, u, OPER_SET_IGNORE_ERROR);
        }

    } else if (stricmp(option, "READONLY") == 0) {
        if (stricmp(setting, "on") == 0) {
            readonly = 1;
            alog("Read-only mode activated");
            close_log();
            notice_lang(s_OperServ, u, OPER_SET_READONLY_ON);
        } else if (stricmp(setting, "off") == 0) {
            readonly = 0;
            open_log();
            alog("Read-only mode deactivated");
            notice_lang(s_OperServ, u, OPER_SET_READONLY_OFF);
        } else {
            notice_lang(s_OperServ, u, OPER_SET_READONLY_ERROR);
        }

    } else if (stricmp(option, "LOGCHAN") == 0) {
        /* Unlike the other SET commands where only stricmp is necessary,
         * we also have to ensure that LogChannel is defined or we can't
         * send to it.
         *
         * -jester
         */
        if (LogChannel && (stricmp(setting, "on") == 0)) {
#ifdef IRC_HYBRID
            send_cmd(NULL, "SJOIN %ld %s + :%s", (long int) time(NULL),
                     LogChannel, s_GlobalNoticer);
#endif
            logchan = 1;
            alog("Now sending log messages to %s", LogChannel);
            notice_lang(s_OperServ, u, OPER_SET_LOGCHAN_ON, LogChannel);
        } else if (LogChannel && (stricmp(setting, "off") == 0)) {
#ifdef IRC_HYBRID
            send_cmd(s_GlobalNoticer, "PART %s :Parting", LogChannel);
#endif
            logchan = 0;
            alog("No longer sending log messages to a channel");
            notice_lang(s_OperServ, u, OPER_SET_LOGCHAN_OFF);
        } else {
            notice_lang(s_OperServ, u, OPER_SET_LOGCHAN_ERROR);
        }
        /**
         * Allow the user to turn super admin on/off
	 *
	 * Rob
         **/
    } else if (stricmp(option, "SUPERADMIN") == 0) {
        if (SuperAdmin && (stricmp(setting, "on") == 0)) {
            u->isSuperAdmin = 1;
            notice_lang(s_OperServ, u, OPER_SUPER_ADMIN_ON);
            alog("%s: %s is a SuperAdmin ", s_OperServ, u->nick);
            wallops(s_OperServ, getstring2(NULL, OPER_SUPER_ADMIN_WALL_ON),
                    u->nick);
        } else if (SuperAdmin && (stricmp(setting, "off") == 0)) {
            u->isSuperAdmin = 0;
            notice_lang(s_OperServ, u, OPER_SUPER_ADMIN_OFF);
            alog("%s: %s is no longer a SuperAdmin", s_OperServ, u->nick);
            wallops(s_OperServ,
                    getstring2(NULL, OPER_SUPER_ADMIN_WALL_OFF), u->nick);
        } else {
            notice_lang(s_OperServ, u, OPER_SUPER_ADMIN_SYNTAX);
        }
    } else if (stricmp(option, "DEBUG") == 0) {
        if (stricmp(setting, "on") == 0) {
            debug = 1;
            alog("Debug mode activated");
            notice_lang(s_OperServ, u, OPER_SET_DEBUG_ON);
        } else if (stricmp(setting, "off") == 0 ||
                   (*setting == '0' && atoi(setting) == 0)) {
            alog("Debug mode deactivated");
            debug = 0;
            notice_lang(s_OperServ, u, OPER_SET_DEBUG_OFF);
        } else if (isdigit(*setting) && atoi(setting) > 0) {
            debug = atoi(setting);
            alog("Debug mode activated (level %d)", debug);
            notice_lang(s_OperServ, u, OPER_SET_DEBUG_LEVEL, debug);
        } else {
            notice_lang(s_OperServ, u, OPER_SET_DEBUG_ERROR);
        }

    } else if (stricmp(option, "NOEXPIRE") == 0) {
        if (stricmp(setting, "ON") == 0) {
            noexpire = 1;
            alog("No expire mode activated");
            notice_lang(s_OperServ, u, OPER_SET_NOEXPIRE_ON);
        } else if (stricmp(setting, "OFF") == 0) {
            noexpire = 0;
            alog("No expire mode deactivated");
            notice_lang(s_OperServ, u, OPER_SET_NOEXPIRE_OFF);
        } else {
            notice_lang(s_OperServ, u, OPER_SET_NOEXPIRE_ERROR);
        }

    } else {
        notice_lang(s_OperServ, u, OPER_SET_UNKNOWN_OPTION, option);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_noop(User * u)
{
    char *cmd = strtok(NULL, " ");
    char *server = strtok(NULL, " ");

    if (!cmd || !server) {
        syntax_error(s_OperServ, u, "NOOP", OPER_NOOP_SYNTAX);
    } else if (!stricmp(cmd, "SET")) {
        User *u2;
        User *u3 = NULL;
        char reason[NICKMAX + 32];

        /* Remove the O:lines */
        s_svsnoop(server, 1);

        snprintf(reason, sizeof(reason), "NOOP command used by %s",
                 u->nick);
        if (WallOSNoOp)
            wallops(s_OperServ, "\2%s\2 used NOOP on \2%s\2", u->nick,
                    server);
        notice_lang(s_OperServ, u, OPER_NOOP_SET, server);

        /* Kill all the IRCops of the server */
        for (u2 = firstuser(); u2; u2 = u3) {
            u3 = nextuser();
            if ((u2) && is_oper(u2) && (u2->server)
                && match_wild(server, u2->server)) {
#ifdef IRC_BAHAMUT
                send_cmd(ServerName, "SVSKILL %s :%s", u2->nick, reason);
#else
                kill_user(s_OperServ, u2->nick, reason);
#endif
            }
        }
    } else if (!stricmp(cmd, "REVOKE")) {
        s_svsnoop(server, 0);
        notice_lang(s_OperServ, u, OPER_NOOP_REVOKE, server);
    } else {
        syntax_error(s_OperServ, u, "NOOP", OPER_NOOP_SYNTAX);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_jupe(User * u)
{
    char *jserver = strtok(NULL, " ");
    char *reason = strtok(NULL, "");
    char rbuf[256];

    if (!jserver) {
        syntax_error(s_OperServ, u, "JUPE", OPER_JUPE_SYNTAX);
    } else {
        if (!isValidHost(jserver, 3)) {
            notice_lang(s_OperServ, u, OPER_JUPE_HOST_ERROR);
        } else {
            snprintf(rbuf, sizeof(rbuf), "Juped by %s%s%s", u->nick,
                     reason ? ": " : "", reason ? reason : "");

            send_cmd(NULL, "SQUIT %s :%s", jserver, rbuf);
#ifdef IRC_PTLINK
            send_cmd(NULL, "SERVER %s 1 Anope.Services%s :%s",
                     jserver, version_number, rbuf);
#else
            send_cmd(NULL, "SERVER %s 2 :%s", jserver, rbuf);
#endif

            if (WallOSJupe)
                wallops(s_OperServ, "\2%s\2 used JUPE on \2%s\2", u->nick,
                        jserver);
        }
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_raw(User * u)
{

    if (!DisableRaw) {

        char *text = strtok(NULL, "");

        if (!text)
            syntax_error(s_OperServ, u, "RAW", OPER_RAW_SYNTAX);
        else {
            send_cmd(NULL, "%s", text);
            if (WallOSRaw) {
                char *kw = strtok(text, " ");
                while (kw && *kw == ':')
                    kw = strtok(NULL, " ");
                wallops(s_OperServ, "\2%s\2 used RAW command for \2%s\2",
                        u->nick,
                        (kw ? kw : "\2non RFC compliant message\2"));
            }
            alog("%s used RAW command for %s", u->nick, text);
        }
    } else {
        notice_lang(s_OperServ, u, RAW_DISABLED);
    }
    return MOD_CONT;
}

/*************************************************************************/

static int do_update(User * u)
{
    notice_lang(s_OperServ, u, OPER_UPDATING);
    save_data = 1;
    return MOD_CONT;
}

/*************************************************************************/

static int do_reload(User * u)
{
    if (!read_config(1)) {
        quitmsg = calloc(28 + strlen(u->nick), 1);
        if (!quitmsg)
            quitmsg =
                "Error during the reload of the configuration file, but out of memory!";
        else
            sprintf(quitmsg,
                    "Error during the reload of the configuration file!");
        quitting = 1;
    }

    notice_lang(s_OperServ, u, OPER_RELOAD);
    return MOD_CONT;
}

/*************************************************************************/

static int do_os_quit(User * u)
{
    quitmsg = calloc(28 + strlen(u->nick), 1);
    if (!quitmsg)
        quitmsg = "QUIT command received, but out of memory!";
    else
        sprintf(quitmsg, "QUIT command received from %s", u->nick);

    if (GlobalOnCycle) {
        oper_global(NULL, "%s", GlobalOnCycleMessage);
    }
    quitting = 1;
    return MOD_CONT;
}

/*************************************************************************/

static int do_shutdown(User * u)
{
    quitmsg = calloc(32 + strlen(u->nick), 1);
    if (!quitmsg)
        quitmsg = "SHUTDOWN command received, but out of memory!";
    else
        sprintf(quitmsg, "SHUTDOWN command received from %s", u->nick);

    if (GlobalOnCycle) {
        oper_global(NULL, "%s", GlobalOnCycleMessage);
    }
    save_data = 1;
    delayed_quit = 1;
    return MOD_CONT;
}

/*************************************************************************/

static int do_restart(User * u)
{
#ifdef SERVICES_BIN
    quitmsg = calloc(31 + strlen(u->nick), 1);
    if (!quitmsg)
        quitmsg = "RESTART command received, but out of memory!";
    else
        sprintf(quitmsg, "RESTART command received from %s", u->nick);

    if (GlobalOnCycle) {
        oper_global(NULL, "%s", GlobalOnCycleMessage);
    }
    /*    raise(SIGHUP); */
    do_restart_services();
#else
    notice_lang(s_OperServ, u, OPER_CANNOT_RESTART);
#endif
    return MOD_CONT;
}

/*************************************************************************/

#ifdef DEBUG_COMMANDS

static int do_matchwild(User * u)
{
    char *pat = strtok(NULL, " ");
    char *str = strtok(NULL, " ");
    if (pat && str)
        notice(s_OperServ, u->nick, "%d", match_wild(pat, str));
    else
        notice(s_OperServ, u->nick, "Syntax error.");
    return MOD_CONT;
}

#endif                          /* DEBUG_COMMANDS */

/*************************************************************************/

/* Kill all users matching a certain host. The host is obtained from the
 * supplied nick. The raw hostmsk is not supplied with the command in an effort
 * to prevent abuse and mistakes from being made - which might cause *.com to
 * be killed. It also makes it very quick and simple to use - which is usually
 * what you want when someone starts loading numerous clones. In addition to
 * killing the clones, we add a temporary AKILL to prevent them from
 * immediately reconnecting.
 * Syntax: KILLCLONES nick
 * -TheShadow (29 Mar 1999)
 */

static int do_killclones(User * u)
{
    char *clonenick = strtok(NULL, " ");
    int count = 0;
    User *cloneuser, *user, *tempuser;
    char *clonemask, *akillmask;
    char killreason[NICKMAX + 32];
    char akillreason[] = "Temporary KILLCLONES akill.";

    if (!clonenick) {
        notice_lang(s_OperServ, u, OPER_KILLCLONES_SYNTAX);

    } else if (!(cloneuser = finduser(clonenick))) {
        notice_lang(s_OperServ, u, OPER_KILLCLONES_UNKNOWN_NICK,
                    clonenick);

    } else {
        clonemask = smalloc(strlen(cloneuser->host) + 5);
        sprintf(clonemask, "*!*@%s", cloneuser->host);

        akillmask = smalloc(strlen(cloneuser->host) + 3);
        sprintf(akillmask, "*@%s", cloneuser->host);

        user = firstuser();
        while (user) {
            if (match_usermask(clonemask, user) != 0) {
                tempuser = nextuser();
                count++;
                snprintf(killreason, sizeof(killreason),
                         "Cloning [%d]", count);
                kill_user(NULL, user->nick, killreason);
                user = tempuser;
            } else {
                user = nextuser();
            }
        }

        add_akill(u, akillmask, u->nick,
                  time(NULL) + KillClonesAkillExpire, akillreason);

        wallops(s_OperServ, "\2%s\2 used KILLCLONES for \2%s\2 killing "
                "\2%d\2 clones. A temporary AKILL has been added "
                "for \2%s\2.", u->nick, clonemask, count, akillmask);

        alog("%s: KILLCLONES: %d clone(s) matching %s killed.",
             s_OperServ, count, clonemask);

        free(akillmask);
        free(clonemask);
    }
    return MOD_CONT;
}

/**
 * Defcon - A method of impelemting various stages of securty, the hope is this will help serives
 * protect a network during an attack, allowing admins to choose the precautions taken at each
 * level.
 *
 * /msg OperServ DefCon [level]
 *
 **/

static int do_defcon(User * u)
{
    char *lvl = strtok(NULL, " ");
    int newLevel = 0;
    char *langglobal;
    langglobal = getstring(NULL, DEFCON_GLOBAL);

    if (!DefConLevel) {         /* If we dont have a .conf setting! */
        notice_lang(s_OperServ, u, OPER_DEFCON_NO_CONF);
        return MOD_CONT;
    }

    if (!lvl) {
        notice_lang(s_OperServ, u, OPER_DEFCON_CHANGED, DefConLevel);
        defcon_sendlvls(u);
        return MOD_CONT;
    }
    newLevel = atoi(lvl);
    if (newLevel < 1 || newLevel > 5) {
        notice_lang(s_OperServ, u, OPER_DEFCON_SYNTAX);
        return MOD_CONT;
    }

    DefConLevel = newLevel;
    DefContimer = time(NULL);
    notice_lang(s_OperServ, u, OPER_DEFCON_CHANGED, DefConLevel);
    defcon_sendlvls(u);
    alog("Defcon level changed to %d by Oper %s", newLevel, u->nick);
    wallops(s_OperServ, getstring2(NULL, OPER_DEFCON_WALL), u->nick,
            newLevel);
    /* Global notice the user what is happening. Also any Message that
       the Admin would like to add. Set in config file. */
    if (GlobalOnDefcon) {
        if ((DefConLevel == 5) && (DefConOffMessage)) {
            oper_global(NULL, "%s", DefConOffMessage);
        } else {
            oper_global(NULL, langglobal, DefConLevel);
        }
    }
    if (GlobalOnDefconMore) {
        if ((DefConOffMessage) && DefConLevel == 5) {
        } else {
            oper_global(NULL, "%s", DefconMessage);
        }
    }
    /* Run any defcon functions, e.g. FORCE CHAN MODE */
    runDefCon();
    return MOD_CONT;
}

/**
 * Reverse the mode string, used for remove DEFCON chan modes.
 **/
char *defconReverseModes(const char *modes)
{
    char *newmodes = NULL;
    int i = 0;
    if (!modes) {
        return NULL;
    }
    if (!(newmodes = malloc(sizeof(char) * strlen(modes) + 1))) {
        return NULL;
    }
    for (i = 0; i < strlen(modes); i++) {
        if (modes[i] == '+')
            newmodes[i] = '-';
        else if (modes[i] == '-')
            newmodes[i] = '+';
        else
            newmodes[i] = modes[i];
    }
    newmodes[i] = '\0';
    return newmodes;
}

/**
 * Returns 1 if the passed level is part of the CURRENT defcon, else 0 is returned
 **/
int checkDefCon(int level)
{
    return DefCon[DefConLevel] & level;
}

/**
 * Run DefCon level specific Functions.
 **/
void runDefCon(void)
{
    char *newmodes;
    if (checkDefCon(DEFCON_FORCE_CHAN_MODES)) {
        if (DefConChanModes && !DefConModesSet) {
            if (DefConChanModes[0] == '+' || DefConChanModes[0] == '-') {
                alog("DEFCON: setting %s on all chan's", DefConChanModes);
                do_mass_mode(DefConChanModes);
                DefConModesSet = 1;
            }
        }
    } else {
        if (DefConChanModes && (DefConModesSet != 0)) {
            if (DefConChanModes[0] == '+' || DefConChanModes[0] == '-') {
                if ((newmodes = defconReverseModes(DefConChanModes))) {
                    alog("DEFCON: setting %s on all chan's", newmodes);
                    do_mass_mode(newmodes);
                }
                DefConModesSet = 0;
            }
        }
    }
}

/**
 * Automaticaly re-set the DefCon level if the time limit has expired.
 **/
void resetDefCon(int level)
{
    if (DefConLevel != level) {
        if ((DefContimer)
            && (time(NULL) - DefContimer >= dotime(DefConTimeOut))) {
            DefConLevel = level;
            alog("Defcon level timeout, returning to lvl %d", level);
            wallops(s_OperServ, getstring2(NULL, OPER_DEFCON_WALL),
                    s_OperServ, level);
            if (GlobalOnDefcon) {
                if (DefConOffMessage) {
                    oper_global(NULL, "%s", DefConOffMessage);
                } else {
                    oper_global(NULL, getstring(NULL, DEFCON_GLOBAL),
                                DefConLevel);
                }
            }
            if (GlobalOnDefconMore && !DefConOffMessage) {
                oper_global(NULL, "%s", DefconMessage);
            }
            runDefCon();
        }
    }
}

/**
 * Send a message to the oper about which precautions are "active" for this level
 **/
static void defcon_sendlvls(User * u)
{
    if (checkDefCon(DEFCON_NO_NEW_CHANNELS)) {
        notice_lang(s_OperServ, u, OPER_HELP_DEFCON_NO_NEW_CHANNELS);
    }
    if (checkDefCon(DEFCON_NO_NEW_NICKS)) {
        notice_lang(s_OperServ, u, OPER_HELP_DEFCON_NO_NEW_NICKS);
    }
    if (checkDefCon(DEFCON_NO_MLOCK_CHANGE)) {
        notice_lang(s_OperServ, u, OPER_HELP_DEFCON_NO_MLOCK_CHANGE);
    }
    if (checkDefCon(DEFCON_FORCE_CHAN_MODES) && (DefConChanModes)) {
        notice_lang(s_OperServ, u, OPER_HELP_DEFCON_FORCE_CHAN_MODES,
                    DefConChanModes);
    }
    if (checkDefCon(DEFCON_REDUCE_SESSION)) {
        notice_lang(s_OperServ, u, OPER_HELP_DEFCON_REDUCE_SESSION,
                    DefConSessionLimit);
    }
    if (checkDefCon(DEFCON_NO_NEW_CLIENTS)) {
        notice_lang(s_OperServ, u, OPER_HELP_DEFCON_NO_NEW_CLIENTS);
    }
    if (checkDefCon(DEFCON_OPER_ONLY)) {
        notice_lang(s_OperServ, u, OPER_HELP_DEFCON_OPER_ONLY);
    }
    if (checkDefCon(DEFCON_SILENT_OPER_ONLY)) {
        notice_lang(s_OperServ, u, OPER_HELP_DEFCON_SILENT_OPER_ONLY);
    }
    if (checkDefCon(DEFCON_AKILL_NEW_CLIENTS)) {
        notice_lang(s_OperServ, u, OPER_HELP_DEFCON_AKILL_NEW_CLIENTS);
    }
    if (checkDefCon(DEFCON_NO_NEW_MEMOS)) {
        notice_lang(s_OperServ, u, OPER_HELP_DEFCON_NO_NEW_MEMOS);
    }
}

/**
 * ChanKill - Akill an entire channel (got botnet?)
 *
 * /msg OperServ ChanKill +expire #channel reason
 *
 **/

static int do_chankill(User * u)
{
    char *expiry, *channel, *reason;
    time_t expires;
    char breason[BUFSIZE];
    char mask[USERMAX + HOSTMAX + 2];
    struct c_userlist *cu, *next;
    Channel *c;

    channel = strtok(NULL, " ");
    if (channel && *channel == '+') {
        expiry = channel;
        channel = strtok(NULL, " ");
    } else {
        expiry = NULL;
    }

    expires = expiry ? dotime(expiry) : ChankillExpiry;
    if (expiry && isdigit(expiry[strlen(expiry) - 1]))
        expires *= 86400;
    if (expires != 0 && expires < 60) {
        notice_lang(s_OperServ, u, BAD_EXPIRY_TIME);
        return MOD_CONT;
    } else if (expires > 0) {
        expires += time(NULL);
    }

    if (channel && (reason = strtok(NULL, ""))) {

        if (AddAkiller) {
            snprintf(breason, sizeof(breason), "[%s] %s", u->nick, reason);
            reason = sstrdup(breason);
        }

        if ((c = findchan(channel))) {
            for (cu = c->users; cu; cu = next) {
                next = cu->next;
                if (is_oper(cu->user)) {
                    continue;
                }
                strncpy(mask, "*@", 3); /* Use *@" for the akill's, */
                strncat(mask, cu->user->host, HOSTMAX);
                add_akill(NULL, mask, s_OperServ, expires, reason);
                check_akill(cu->user->nick, cu->user->username,
                            cu->user->host, NULL, NULL);
            }
            if (WallOSAkill) {
                wallops(s_OperServ, "%s used CHANKILL on %s (%s)", u->nick,
                        channel, reason);
            }
        } else {
            notice_lang(s_OperServ, u, CHAN_X_NOT_IN_USE, channel);
        }
    } else {
        syntax_error(s_OperServ, u, "CHANKILL", OPER_CHANKILL_SYNTAX);
    }
    return MOD_CONT;
}

#ifdef USE_MODULES

int do_modload(User * u)
{
    char *name;
    Module *m;

    name = strtok(NULL, "");
    if (!name) {
        syntax_error(s_OperServ, u, "MODLOAD", OPER_MODULE_LOAD_SYNTAX);
        return MOD_CONT;
    }
    m = findModule(name);
    if (!m) {
        m = createModule(name);
        mod_current_module = m;
        mod_current_user = u;
        mod_current_op = 1;
    } else {
        notice_lang(s_OperServ, u, OPER_MODULE_LOAD_FAIL, name);
    }
    return MOD_CONT;
}

int do_modunload(User * u)
{
    char *name;
    Module *m;

    name = strtok(NULL, "");
    if (!name) {
        syntax_error(s_OperServ, u, "MODUNLOAD",
                     OPER_MODULE_UNLOAD_SYNTAX);
        return MOD_CONT;
    }
    m = findModule(name);
    if (m) {
        mod_current_user = u;
        mod_current_module = m;
        mod_current_op = 2;
    } else {
        notice_lang(s_OperServ, u, OPER_MODULE_REMOVE_FAIL, name);
    }
    return MOD_CONT;
}

int do_modlist(User * u)
{
    int idx;
    int count = 0;
    struct tm tm;
    char timebuf[64];
    ModuleHash *current = NULL;

    for (idx = 0; idx != MAX_CMD_HASH; idx++) {
        for (current = MODULE_HASH[idx]; current; current = current->next) {
            tm = *localtime(&current->m->time);

            strftime_lang(timebuf, sizeof(timebuf), u,
                          STRFTIME_DATE_TIME_FORMAT, &tm);
            notice_lang(s_OperServ, u, OPER_MODULE_LIST, current->name);
            count++;
        }
    }
    if (count == 0) {
        notice_lang(s_OperServ, u, OPER_MODULE_NO_LIST);
    }

    return MOD_CONT;
}

int do_modinfo(User * u)
{
    char *file;
    struct tm tm;
    char timebuf[64];
    Module *m;
    int idx = 0;
    int display = 0;

    file = strtok(NULL, "");
    if (!file) {
        syntax_error(s_OperServ, u, "MODINFO", OPER_MODULE_INFO_SYNTAX);
        return MOD_CONT;
    }
    m = findModule(file);
    if (m) {
        tm = *localtime(&m->time);
        strftime_lang(timebuf, sizeof(timebuf), u,
                      STRFTIME_DATE_TIME_FORMAT, &tm);
        notice_lang(s_OperServ, u, OPER_MODULE_INFO_LIST, m->name,
                    m->version ? m->version : "?",
                    m->author ? m->author : "?", timebuf);
        for (idx = 0; idx < MAX_CMD_HASH; idx++) {
            display += showModuleCmdLoaded(HOSTSERV[idx], m->name, u);
            display += showModuleCmdLoaded(OPERSERV[idx], m->name, u);
            display += showModuleCmdLoaded(NICKSERV[idx], m->name, u);
            display += showModuleCmdLoaded(CHANSERV[idx], m->name, u);
            display += showModuleCmdLoaded(BOTSERV[idx], m->name, u);
            display += showModuleCmdLoaded(MEMOSERV[idx], m->name, u);
            display += showModuleCmdLoaded(HELPSERV[idx], m->name, u);
            display += showModuleMsgLoaded(IRCD[idx], m->name, u);

        }
    }
    if (display == 0) {
        notice_lang(s_OperServ, u, OPER_MODULE_NO_INFO, file);
    }
    return MOD_CONT;
}

static int showModuleCmdLoaded(CommandHash * cmdList, char *mod_name,
                               User * u)
{
    Command *c;
    CommandHash *current;
    int display = 0;

    for (current = cmdList; current; current = current->next) {
        for (c = current->c; c; c = c->next) {
            if ((c->mod_name) && (stricmp(c->mod_name, mod_name) == 0)) {
                notice_lang(s_OperServ, u, OPER_MODULE_CMD_LIST,
                            c->service, c->name);
                display++;
            }
        }
    }
    return display;
}

static int showModuleMsgLoaded(MessageHash * msgList, char *mod_name,
                               User * u)
{
    Message *msg;
    MessageHash *mcurrent;
    int display = 0;
    for (mcurrent = msgList; mcurrent; mcurrent = mcurrent->next) {
        for (msg = mcurrent->m; msg; msg = msg->next) {
            if ((msg->mod_name) && (stricmp(msg->mod_name, mod_name) == 0)) {
                notice_lang(s_OperServ, u, OPER_MODULE_MSG_LIST,
                            msg->name);
                display++;
            }
        }
    }
    return display;
}

#endif

/*************************************************************************/


syntax highlighted by Code2HTML, v. 0.9.1