/* BotServ 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: botserv.c 5 2004-03-29 01:29:50Z dane $
 *
 */

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

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

/**
 * RFC: defination of a valid nick
 * nickname   =  ( letter / special ) *8( letter / digit / special / "-" )
 * letter     =  %x41-5A / %x61-7A       ; A-Z / a-z
 * digit      =  %x30-39                 ; 0-9
 * special    =  %x5B-60 / %x7B-7D       ; "[", "]", "\", "`", "_", "^", "{", "|", "}"
 **/
#define isvalidnick(c) ( isalnum(c) || ((c) >='\x5B' && (c) <='\x60') || ((c) >='\x7B' && (c) <='\x7D') || (c)=='-' )


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

BotInfo *botlists[256];         /* Hash list of bots */
int nbots = 0;

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

static BotInfo *makebot(char *nick);
static UserData *get_user_data(Channel * c, User * u);
static void unassign(User * u, ChannelInfo * ci);

static void check_ban(ChannelInfo * ci, User * u, int ttbtype);
static void bot_kick(ChannelInfo * ci, User * u, int message, ...);
static void bot_raw_ban(User * requester, ChannelInfo * ci, char *nick,
                        char *reason);
static void bot_raw_kick(User * requester, ChannelInfo * ci, char *nick,
                         char *reason);
static void bot_raw_mode(User * requester, ChannelInfo * ci, char *mode,
                         char *nick);
static void bot_raw_unban(ChannelInfo * ci, char *nick);

static int do_help(User * u);
static int do_bot(User * u);
static int do_botlist(User * u);
static int do_assign(User * u);
static int do_unassign(User * u);
static int do_info(User * u);
static int do_set(User * u);
static int do_kickcmd(User * u);
static int do_badwords(User * u);
static int do_say(User * u);
static int do_act(User * u);
void moduleAddBotServCmds(void);
/*************************************************************************/
/* *INDENT-OFF* */
void moduleAddBotServCmds(void) {
    Command *c;
    c = createCommand("HELP",do_help,NULL,  -1,-1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("BOTLIST",		    do_botlist,  NULL,  BOT_HELP_BOTLIST,            -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("ASSIGN",			    do_assign,   NULL,  BOT_HELP_ASSIGN,             -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("UNASSIGN",		    do_unassign, NULL,  BOT_HELP_UNASSIGN,           -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("INFO",               do_info,     NULL,  BOT_HELP_INFO,               -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("SET",                do_set,      NULL,  BOT_HELP_SET,-1, BOT_SERVADMIN_HELP_SET,BOT_SERVADMIN_HELP_SET, BOT_SERVADMIN_HELP_SET); addCoreCommand(BOTSERV,c);
    c = createCommand("SET DONTKICKOPS",    NULL,        NULL,  BOT_HELP_SET_DONTKICKOPS,    -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("SET DONTKICKVOICES", NULL,        NULL,  BOT_HELP_SET_DONTKICKVOICES, -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("SET FANTASY",		NULL,		 NULL,	BOT_HELP_SET_FANTASY,		 -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("SET GREET",			NULL,		 NULL,	BOT_HELP_SET_GREET,		 	 -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("SET SYMBIOSIS",		NULL,		 NULL,	BOT_HELP_SET_SYMBIOSIS,		 -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("KICK",               do_kickcmd,  NULL,  BOT_HELP_KICK,               -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("KICK BADWORDS",      NULL,        NULL,  BOT_HELP_KICK_BADWORDS,      -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("KICK BOLDS",         NULL,        NULL,  BOT_HELP_KICK_BOLDS,         -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("KICK CAPS",          NULL,        NULL,  BOT_HELP_KICK_CAPS,          -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("KICK COLORS",        NULL,        NULL,  BOT_HELP_KICK_COLORS,        -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("KICK FLOOD",         NULL,        NULL,  BOT_HELP_KICK_FLOOD,         -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("KICK REPEAT",        NULL,        NULL,  BOT_HELP_KICK_REPEAT,        -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("KICK REVERSES",      NULL,        NULL,  BOT_HELP_KICK_REVERSES,      -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("KICK UNDERLINES",    NULL,        NULL,  BOT_HELP_KICK_UNDERLINES,    -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("BADWORDS",           do_badwords, NULL,  BOT_HELP_BADWORDS,           -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("SAY",				do_say,		 NULL,  BOT_HELP_SAY,				 -1,-1,-1,-1); addCoreCommand(BOTSERV,c);
    c = createCommand("ACT",				do_act,		 NULL,  BOT_HELP_ACT,				 -1,-1,-1,-1); addCoreCommand(BOTSERV,c);

    /* Services admins commands */
    c = createCommand("BOT",		do_bot,   is_services_admin,  -1,-1, BOT_SERVADMIN_HELP_BOT,BOT_SERVADMIN_HELP_BOT, BOT_SERVADMIN_HELP_BOT); addCoreCommand(BOTSERV,c);
    c = createCommand("SET NOBOT", 	NULL,	 NULL,	-1,-1, BOT_SERVADMIN_HELP_SET_NOBOT,BOT_SERVADMIN_HELP_SET_NOBOT, BOT_SERVADMIN_HELP_SET_NOBOT); addCoreCommand(BOTSERV,c);
    c = createCommand("SET PRIVATE", NULL,	 NULL,	-1,-1, BOT_SERVADMIN_HELP_SET_PRIVATE,BOT_SERVADMIN_HELP_SET_PRIVATE, BOT_SERVADMIN_HELP_SET_PRIVATE); addCoreCommand(BOTSERV,c);
}
/* *INDENT-ON* */
/*************************************************************************/
/*************************************************************************/

/* Return information on memory use.  Assumes pointers are valid. */

void get_botserv_stats(long *nrec, long *memuse)
{
    long count = 0, mem = 0;
    int i;
    BotInfo *bi;

    for (i = 0; i < 256; i++) {
        for (bi = botlists[i]; bi; bi = bi->next) {
            count++;
            mem += sizeof(*bi);
            mem += strlen(bi->nick) + 1;
            mem += strlen(bi->user) + 1;
            mem += strlen(bi->host) + 1;
            mem += strlen(bi->real) + 1;
        }
    }

    *nrec = count;
    *memuse = mem;
}

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

/* BotServ initialization. */

void bs_init(void)
{
    Command *cmd;
    moduleAddBotServCmds();
    cmd = findCommand(BOTSERV, "SET SYMBIOSIS");
    if (cmd)
        cmd->help_param1 = s_ChanServ;
}

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

/* Main BotServ routine. */

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

    cmd = strtok(buf, " ");

    if (!cmd) {
        return;
    } else if (stricmp(cmd, "\1PING") == 0) {
        if (!(s = strtok(NULL, "")))
            s = "\1";
        notice(s_BotServ, u->nick, "\1PING %s", s);
    } else if (skeleton) {
        notice_lang(s_BotServ, u, SERVICE_OFFLINE, s_BotServ);
    } else {
        mod_run_cmd(s_BotServ, u, BOTSERV, cmd);
    }

}

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

/* Handles all messages sent to bots. (Currently only answers to pings ;) */

void botmsgs(User * u, BotInfo * bi, char *buf)
{
    char *cmd = strtok(buf, " ");
    char *s;

    if (!cmd || !u)
        return;

    if (!stricmp(cmd, "\1PING")) {
        if (!(s = strtok(NULL, "")))
            s = "\1";
        notice(bi->nick, u->nick, "\1PING %s", s);
    }
}

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

/* Handles all messages that are sent to registered channels where a
 * bot is on.
 *
 */

void botchanmsgs(User * u, ChannelInfo * ci, char *buf)
{
    int c;
    int16 cstatus = 0;
    char *cmd;
    UserData *ud;

    if (!u)
        return;

    /* Answer to ping if needed, without breaking the buffer. */
    if (!strnicmp(buf, "\1PING", 5))
        notice(ci->bi->nick, u->nick, buf);

    /* If it's a /me, cut the CTCP part at the beginning (not
     * at the end, because one character just doesn't matter,
     * but the ACTION may create strange behaviours with the
     * caps or badwords kickers */
    if (!strnicmp(buf, "\1ACTION ", 8))
        buf += 8;

    /* Now we can make kicker stuff. We try to order the checks
     * from the fastest one to the slowest one, since there's
     * no need to process other kickers if an user is kicked before
     * the last kicker check.
     *
     * But FIRST we check whether the user is protected in any
     * way.
     */

    /* We first retrieve the user status on the channel if needed */
    if (ci->botflags & (BS_DONTKICKOPS | BS_DONTKICKVOICES))
        cstatus = chan_get_user_status(ci->c, u);

    if (buf && !check_access(u, ci, CA_NOKICK) &&
#ifdef HAS_HALFOP
#if defined(IRC_UNREAL) || defined (IRC_VIAGRA)
        (!(ci->botflags & BS_DONTKICKOPS)
         || !(cstatus & (CUS_HALFOP | CUS_OP | CUS_OWNER | CUS_PROTECT)))
# elif defined (IRC_ULTIMATE3)
        (!(ci->botflags & BS_DONTKICKOPS)
         || !(cstatus & (CUS_HALFOP | CUS_OP | CUS_PROTECT)))
# else
        (!(ci->botflags & BS_DONTKICKOPS)
         || !(cstatus & (CUS_HALFOP | CUS_OP)))
# endif
#else
        (!(ci->botflags & BS_DONTKICKOPS) || !(cstatus & CUS_OP))
#endif
        && (!(ci->botflags & BS_DONTKICKVOICES) || !(cstatus & CUS_VOICE))) {
        /* Bolds kicker */
        if ((ci->botflags & BS_KICK_BOLDS) && strchr(buf, 2)) {
            check_ban(ci, u, TTB_BOLDS);
            bot_kick(ci, u, BOT_REASON_BOLD);
            return;
        }

        /* Color kicker */
        if ((ci->botflags & BS_KICK_COLORS) && strchr(buf, 3)) {
            check_ban(ci, u, TTB_COLORS);
            bot_kick(ci, u, BOT_REASON_COLOR);
            return;
        }

        /* Reverses kicker */
        if ((ci->botflags & BS_KICK_REVERSES) && strchr(buf, 22)) {
            check_ban(ci, u, TTB_REVERSES);
            bot_kick(ci, u, BOT_REASON_REVERSE);
            return;
        }

        /* Underlines kicker */
        if ((ci->botflags & BS_KICK_UNDERLINES) && strchr(buf, 31)) {
            check_ban(ci, u, TTB_UNDERLINES);
            bot_kick(ci, u, BOT_REASON_UNDERLINE);
            return;
        }

        /* Caps kicker */
        if ((ci->botflags & BS_KICK_CAPS)
            && ((c = strlen(buf)) >= ci->capsmin)) {
            int i = 0;
            char *s = buf;

            do {
                if (isupper(*s))
                    i++;
            } while (*s++);

            if (i >= ci->capsmin && i * 100 / c >= ci->capspercent) {
                check_ban(ci, u, TTB_CAPS);
                bot_kick(ci, u, BOT_REASON_CAPS);
                return;
            }
        }

        /* Bad words kicker */
        if (ci->botflags & BS_KICK_BADWORDS) {
            int i;
            int mustkick = 0;
            BadWord *bw;

            for (i = 0, bw = ci->badwords; i < ci->bwcount; i++, bw++) {
                if (!bw->in_use)
                    continue;

                if (bw->type == BW_ANY && stristr(buf, bw->word)) {
                    mustkick = 1;
                } else if (bw->type == BW_SINGLE) {
                    int len = strlen(bw->word);

                    if (!stricmp(buf, bw->word)) {
                        mustkick = 1;
                        /* two next if are quite odd isn't it? =) */
                    } else if ((strchr(buf, ' ') == buf + len)
                               && (stristr(buf, bw->word) == buf)) {
                        mustkick = 1;
                    } else
                        if ((strrchr(buf, ' ') ==
                             buf + strlen(buf) - len - 1)
                            && (stristr(buf, bw->word) ==
                                buf + strlen(buf) - len)) {
                        mustkick = 1;
                    } else {
                        char *wordbuf = scalloc(len + 3, 1);

                        wordbuf[0] = ' ';
                        wordbuf[len + 1] = ' ';
                        wordbuf[len + 2] = '\0';
                        memcpy(wordbuf + 1, bw->word, len);

                        if (stristr(buf, wordbuf))
                            mustkick = 1;
                    }
                } else if (bw->type == BW_START) {
                    int len = strlen(bw->word);

                    if (!strnicmp(buf, bw->word, len)) {
                        mustkick = 1;
                    } else {
                        char *wordbuf = scalloc(len + 2, 1);

                        memcpy(wordbuf + 1, bw->word, len);
                        wordbuf[0] = ' ';
                        wordbuf[len + 1] = '\0';

                        if (stristr(buf, wordbuf))
                            mustkick = 1;

                        free(wordbuf);
                    }
                } else if (bw->type == BW_END) {
                    int len = strlen(bw->word);

                    if (!strnicmp(buf + strlen(buf) - len, bw->word, len)) {
                        mustkick = 1;
                    } else {
                        char *wordbuf = scalloc(len + 2, 1);

                        memcpy(wordbuf, bw->word, len);
                        wordbuf[len] = ' ';
                        wordbuf[len + 1] = '\0';

                        if (stristr(buf, wordbuf))
                            mustkick = 1;

                        free(wordbuf);
                    }
                }

                if (mustkick) {
                    check_ban(ci, u, TTB_BADWORDS);
                    if (BSGentleBWReason)
                        bot_kick(ci, u, BOT_REASON_BADWORD_GENTLE);
                    else
                        bot_kick(ci, u, BOT_REASON_BADWORD, bw->word);
                    return;
                }
            }
        }

        /* Flood kicker */
        if (ci->botflags & BS_KICK_FLOOD) {
            time_t now = time(NULL);

            ud = get_user_data(ci->c, u);
            if (!ud)
                return;

            if (now - ud->last_start > ci->floodsecs) {
                ud->last_start = time(NULL);
                ud->lines = 0;
            }

            ud->lines++;
            if (ud->lines >= ci->floodlines) {
                check_ban(ci, u, TTB_FLOOD);
                bot_kick(ci, u, BOT_REASON_FLOOD);
                return;
            }
        }

        /* Repeat kicker */
        if (ci->botflags & BS_KICK_REPEAT) {
            ud = get_user_data(ci->c, u);
            if (!ud)
                return;

            if (ud->lastline && stricmp(ud->lastline, buf)) {
                free(ud->lastline);
                ud->lastline = sstrdup(buf);
                ud->times = 0;
            } else {
                if (!ud->lastline)
                    ud->lastline = sstrdup(buf);
                ud->times++;
            }

            if (ud->times >= ci->repeattimes) {
                check_ban(ci, u, TTB_REPEAT);
                bot_kick(ci, u, BOT_REASON_REPEAT);
                return;
            }
        }
    }


    /* return if the user is on the ignore list  */
    if (get_ignore(u->nick) != NULL) {
        return;
    }

    /* Fantaisist commands */

    if (buf && (ci->botflags & BS_FANTASY) && *buf == '!'
        && check_access(u, ci, CA_FANTASIA)) {
        cmd = strtok(buf, " ");

        if (cmd) {
#if defined(IRC_UNREAL) || defined (IRC_VIAGRA)
            if (!stricmp(cmd, "!deowner")) {
                if (is_founder(u, ci))
                    bot_raw_mode(u, ci, "-q", u->nick);
            } else
#endif
            if (!stricmp(cmd, "!kb")) {
                char *target = strtok(NULL, " ");
                char *reason = strtok(NULL, "");

                if (!target && check_access(u, ci, CA_BANME)) {
                    bot_raw_ban(u, ci, u->nick, "Requested");
                } else if (target && check_access(u, ci, CA_BAN)) {
                    if (!stricmp(target, ci->bi->nick)) {
                        bot_raw_ban(u, ci, u->nick, "Oops!");
                    } else {
                        if (!reason)
                            bot_raw_ban(u, ci, target, "Requested");
                        else
                            bot_raw_ban(u, ci, target, reason);
                    }
                }
            } else if ((!stricmp(cmd, "!kick")) || (!stricmp(cmd, "!k"))) {
                char *target = strtok(NULL, " ");
                char *reason = strtok(NULL, "");

                if (!target && check_access(u, ci, CA_KICKME)) {
                    bot_raw_kick(u, ci, u->nick, "Requested");
                } else if (target && check_access(u, ci, CA_KICK)) {
                    if (!stricmp(target, ci->bi->nick))
                        bot_raw_kick(u, ci, u->nick, "Oops!");
                    else if (!reason)
                        bot_raw_kick(u, ci, target, "Requested");
                    else
                        bot_raw_kick(u, ci, target, reason);
                }
#if defined(IRC_UNREAL) || defined (IRC_VIAGRA)
            } else if (!stricmp(cmd, "!owner")) {
                if (is_founder(u, ci))
                    bot_raw_mode(u, ci, "+q", u->nick);
#endif
            } else if (!stricmp(cmd, "!seen")) {
                char *target = strtok(NULL, " ");
                char buf[BUFSIZE];

                if (target) {
                    User *u2;
                    NickAlias *na;
                    ChanAccess *access;

                    if (!stricmp(ci->bi->nick, target)) {
                        /* If we look for the bot */
                        snprintf(buf, sizeof(buf),
                                 getstring(u->na, BOT_SEEN_BOT), u->nick);
                        send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name,
                                 buf);
                    } else if (!(na = findnick(target))
                               || (na->status & NS_VERBOTEN)) {
                        /* If the nick is not registered or forbidden */
                        snprintf(buf, sizeof(buf),
                                 getstring(u->na, BOT_SEEN_UNKNOWN),
                                 target);
                        send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name,
                                 buf);
                    } else if ((u2 = nc_on_chan(ci->c, na->nc))) {
                        /* If the nick we're looking for is on the channel,
                         * there are three possibilities: it's yourself,
                         * it's the nick we look for, it's an alias of the
                         * nick we look for.
                         */
                        if (u == u2 || (u->na && u->na->nc == na->nc))
                            snprintf(buf, sizeof(buf),
                                     getstring(u->na, BOT_SEEN_YOU),
                                     u->nick);
                        else if (!stricmp(u2->nick, target))
                            snprintf(buf, sizeof(buf),
                                     getstring(u->na, BOT_SEEN_ON_CHANNEL),
                                     u2->nick);
                        else
                            snprintf(buf, sizeof(buf),
                                     getstring(u->na,
                                               BOT_SEEN_ON_CHANNEL_AS),
                                     target, u2->nick);
                        send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name,
                                 buf);
                    } else if ((access = get_access_entry(na->nc, ci))) {
                        /* User is on the access list but not present actually.
                           Special case: if access->last_seen is 0 it's that we
                           never seen the user.
                         */
                        if (access->last_seen) {
                            char durastr[192];
                            duration(u->na, durastr, sizeof(durastr),
                                     time(NULL) - access->last_seen);
                            snprintf(buf, sizeof(buf),
                                     getstring(u->na, BOT_SEEN_ON), target,
                                     durastr);
                        } else {
                            snprintf(buf, sizeof(buf),
                                     getstring(u->na, BOT_SEEN_NEVER),
                                     target);
                        }
                        send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name,
                                 buf);
                    } else {
                        /* All other cases */
                        snprintf(buf, sizeof(buf),
                                 getstring(u->na, BOT_SEEN_UNKNOWN),
                                 target);
                        send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name,
                                 buf);
                    }
                }
            } else if (!stricmp(cmd, "!unban")
                       && check_access(u, ci, CA_UNBAN)) {
                char *target = strtok(NULL, " ");

                if (!target)
                    bot_raw_unban(ci, u->nick);
                else
                    bot_raw_unban(ci, target);
            } else {
                CSModeUtil *util = csmodeutils;

                do {
                    if (!stricmp(cmd, util->bsname)) {
                        char *target = strtok(NULL, " ");

                        if (!target
                            && check_access(u, ci, util->levelself))
                            bot_raw_mode(u, ci, util->mode, u->nick);
                        else if (target
                                 && check_access(u, ci, util->level))
                            bot_raw_mode(u, ci, util->mode, target);
                    }
                } while ((++util)->name != NULL);
            }
        }
    }
}

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

/* Load/save data files. */


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

void load_bs_dbase(void)
{
    dbFILE *f;
    int c, ver;
    int16 tmp16;
    int32 tmp32;
    BotInfo *bi;
    int failed = 0;

    if (!(f = open_db(s_BotServ, BotDBName, "r", BOT_VERSION)))
        return;

    ver = get_file_version(f);

    while (!failed && (c = getc_db(f)) != 0) {
        char *s;

        if (c != 1)
            fatal("Invalid format in %s %d", BotDBName, c);

        SAFE(read_string(&s, f));
        bi = makebot(s);
        free(s);
        SAFE(read_string(&bi->user, f));
        SAFE(read_string(&bi->host, f));
        SAFE(read_string(&bi->real, f));
        if (ver >= 10) {
            SAFE(read_int16(&tmp16, f));
            bi->flags = tmp16;
        }
        SAFE(read_int32(&tmp32, f));
        bi->created = tmp32;
        SAFE(read_int16(&tmp16, f));
        bi->chancount = tmp16;
    }

    close_db(f);
}

#undef SAFE

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

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

void save_bs_dbase(void)
{
    dbFILE *f;
    BotInfo *bi;
    static time_t lastwarn = 0;
    int i;

    if (!(f = open_db(s_BotServ, BotDBName, "w", BOT_VERSION)))
        return;

    for (i = 0; i < 256; i++) {
        for (bi = botlists[i]; bi; bi = bi->next) {
            SAFE(write_int8(1, f));
            SAFE(write_string(bi->nick, f));
            SAFE(write_string(bi->user, f));
            SAFE(write_string(bi->host, f));
            SAFE(write_string(bi->real, f));
            SAFE(write_int16(bi->flags, f));
            SAFE(write_int32(bi->created, f));
            SAFE(write_int16(bi->chancount, f));
        }
    }
    SAFE(write_int8(0, f));

    close_db(f);

}

#undef SAFE

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

void save_bs_rdb_dbase(void)
{
#ifdef USE_RDB
    int i;
    BotInfo *bi;

    if (!rdb_open())
        return;

    rdb_clear_table("anope_bs_core");

    for (i = 0; i < 256; i++) {
        for (bi = botlists[i]; bi; bi = bi->next) {
            rdb_save_bs_core(bi);
        }
    }
    rdb_close();
#endif
}

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

/* Inserts a bot in the bot list. I can't be much explicit mh? */

static void insert_bot(BotInfo * bi)
{
    BotInfo *ptr, *prev;

    for (prev = NULL, ptr = botlists[tolower(*bi->nick)];
         ptr != NULL && stricmp(ptr->nick, bi->nick) < 0;
         prev = ptr, ptr = ptr->next);
    bi->prev = prev;
    bi->next = ptr;
    if (!prev)
        botlists[tolower(*bi->nick)] = bi;
    else
        prev->next = bi;
    if (ptr)
        ptr->prev = bi;
}

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

static BotInfo *makebot(char *nick)
{
    BotInfo *bi;

    bi = scalloc(sizeof(BotInfo), 1);
    bi->nick = sstrdup(nick);
    bi->lastmsg = time(NULL);
    insert_bot(bi);
    nbots++;
    return bi;
}

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

static void change_bot_nick(BotInfo * bi, char *newnick)
{
    if (bi->next)
        bi->next->prev = bi->prev;
    if (bi->prev)
        bi->prev->next = bi->next;
    else
        botlists[tolower(*bi->nick)] = bi->next;

    if (bi->nick)
        free(bi->nick);
    bi->nick = sstrdup(newnick);

    insert_bot(bi);
}

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

static int delbot(BotInfo * bi)
{
    cs_remove_bot(bi);

    if (bi->next)
        bi->next->prev = bi->prev;
    if (bi->prev)
        bi->prev->next = bi->next;
    else
        botlists[tolower(*bi->nick)] = bi->next;

    nbots--;

    free(bi->nick);
    free(bi->user);
    free(bi->host);
    free(bi->real);

    free(bi);

    return 1;
}

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

BotInfo *findbot(char *nick)
{
    BotInfo *bi;

    if (!nick || !*nick)
        return NULL;

    for (bi = botlists[tolower(*nick)]; bi; bi = bi->next)
        if (!stricmp(nick, bi->nick))
            return bi;

    return NULL;
}

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

/* Unassign a bot from a channel. Assumes u, ci and ci->bi are not NULL */

static void unassign(User * u, ChannelInfo * ci)
{
    if (ci->c && ci->c->usercount >= BSMinUsers) {
        send_cmd(ci->bi->nick, "PART %s :UNASSIGN from %s", ci->name,
                 u->nick);
    }
    ci->bi->chancount--;
    ci->bi = NULL;
}

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

/* Returns ban data associated with an user if it exists, allocates it
   otherwise. */

static BanData *get_ban_data(Channel * c, User * u)
{
    char mask[BUFSIZE];
    BanData *bd, *next;
    time_t now = time(NULL);

    if (!c || !u)
        return NULL;

    snprintf(mask, sizeof(mask), "%s@%s", u->username, GetHost(u));

    for (bd = c->bd; bd; bd = next) {
        if (now - bd->last_use > BSKeepData) {
            if (bd->next)
                bd->next->prev = bd->prev;
            if (bd->prev)
                bd->prev->next = bd->next;
            else
                c->bd = bd->next;
            if (bd->mask)
                free(bd->mask);
            next = bd->next;
            free(bd);
            continue;
        }
        if (!stricmp(bd->mask, mask)) {
            bd->last_use = now;
            return bd;
        }
        next = bd->next;
    }

    /* If we fall here it is that we haven't found the record */
    bd = scalloc(sizeof(BanData), 1);
    bd->mask = sstrdup(mask);
    bd->last_use = now;

    bd->prev = NULL;
    bd->next = c->bd;
    if (bd->next)
        bd->next->prev = bd;
    c->bd = bd;

    return bd;
}

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

/* Returns BotServ data associated with an user on a given channel.
 * Allocates it if necessary.
 */

static UserData *get_user_data(Channel * c, User * u)
{
    struct c_userlist *user;

    if (!c || !u)
        return NULL;

    for (user = c->users; user; user = user->next) {
        if (user->user == u) {
            if (user->ud) {
                time_t now = time(NULL);

                /* Checks whether data is obsolete */
                if (now - user->ud->last_use > BSKeepData) {
                    if (user->ud->lastline)
                        free(user->ud->lastline);
                    /* We should not free and realloc, but reset to 0
                       instead. */
                    memset(user->ud, 0, sizeof(UserData));
                    user->ud->last_use = now;
                }

                return user->ud;
            } else {
                user->ud = scalloc(sizeof(UserData), 1);
                user->ud->last_use = time(NULL);
                return user->ud;
            }
        }
    }

    return NULL;
}

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

/* Makes the bot join a channel and op himself. */

void bot_join(ChannelInfo * ci)
{
    int i;

    if (!ci || !ci->c || !ci->bi)
        return;

    if (BSSmartJoin) {
        /* We check for bans */
        int count = ci->c->bancount;
        if (count) {
            char botmask[BUFSIZE];
            char **bans = scalloc(sizeof(char *) * count, 1);
            char *av[3];

            memcpy(bans, ci->c->bans, sizeof(char *) * count);
            snprintf(botmask, sizeof(botmask), "%s!%s@%s", ci->bi->nick,
                     ci->bi->user, ci->bi->host);

            av[0] = ci->c->name;
            av[1] = sstrdup("-b");
            for (i = 0; i < count; i++) {
                if (match_wild_nocase(ci->c->bans[i], botmask)) {
                    send_cmd(ci->bi->nick, "MODE %s -b %s", ci->name,
                             bans[i]);
                    av[2] = sstrdup(bans[i]);
                    do_cmode(ci->bi->nick, 3, av);
                    free(av[2]);
                }
            }
            free(av[1]);
            free(bans);
        }

        /* Should we be invited? */
        if ((ci->c->mode & CMODE_i)
            || (ci->c->limit && ci->c->usercount >= ci->c->limit))
            send_cmd(NULL, "NOTICE @%s :%s invited %s into the channel.",
                     ci->c->name, ci->bi->nick, ci->bi->nick);
    }
#ifdef IRC_BAHAMUT
    send_cmd(ci->bi->nick, "SJOIN %ld %s", ci->c->creation_time,
             ci->c->name);
#elif defined(IRC_HYBRID)
    send_cmd(NULL, "SJOIN %ld %s + :%s", time(NULL), ci->c->name,
             ci->bi->nick);
#else
    send_cmd(ci->bi->nick, "JOIN %s", ci->c->name);
#endif

#if defined(IRC_UNREAL) || defined (IRC_VIAGRA)
    send_cmd(ci->bi->nick, "MODE %s +ao %s %s", ci->c->name, ci->bi->nick,
             ci->bi->nick);
#elif defined(IRC_PTLINK)
    /* PTLinks requieres an IRCop to u-line changes, so use ChanServ */
    send_cmd(s_ChanServ, "MODE %s +ao %s %s", ci->c->name, ci->bi->nick,
             ci->bi->nick);
#else
    send_cmd(ci->bi->nick, "MODE %s +o %s", ci->c->name, ci->bi->nick);
#endif
}

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

/* This makes the bot rejoin all channel he is on when he gets killed
 * or changed.
 */

void bot_rejoin_all(BotInfo * bi)
{
    int i;
    ChannelInfo *ci;

    for (i = 0; i < 256; i++)
        for (ci = chanlists[i]; ci; ci = ci->next)
            if (ci->bi == bi && ci->c && (ci->c->usercount >= BSMinUsers))
                bot_join(ci);
}

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

/* This makes a ban if the user has to have one. In every cases it increments
   the kick count for the user. */

static void check_ban(ChannelInfo * ci, User * u, int ttbtype)
{
    BanData *bd = get_ban_data(ci->c, u);

    if (!bd)
        return;

    bd->ttb[ttbtype]++;
    if (bd->ttb[ttbtype] == ci->ttb[ttbtype]) {
        char *av[3];
        char mask[BUFSIZE];

        bd->ttb[ttbtype] = 0;

        av[0] = ci->name;
        av[1] = sstrdup("+b");
        get_idealban(ci, u, mask, sizeof(mask));
        av[2] = mask;
        send_cmd(ci->bi->nick, "MODE %s +b %s", av[0], av[2]);
        do_cmode(ci->bi->nick, 3, av);
        free(av[1]);
    }
}

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

/* This makes a bot kick an user. Works somewhat like notice_lang in fact ;) */

static void bot_kick(ChannelInfo * ci, User * u, int message, ...)
{
    va_list args;
    char buf[1024];
    const char *fmt;
    char *av[3];

    if (!ci || !ci->bi || !ci->c || !u)
        return;

    va_start(args, message);
    fmt = getstring(u->na, message);
    if (!fmt)
        return;
    vsnprintf(buf, sizeof(buf), fmt, args);

    av[0] = ci->name;
    av[1] = u->nick;
    av[2] = buf;
    send_cmd(ci->bi->nick, "KICK %s %s :%s", av[0], av[1], av[2]);
    do_kick(ci->bi->nick, 3, av);
}

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

/* Makes a simple ban and kicks the target */

static void bot_raw_ban(User * requester, ChannelInfo * ci, char *nick,
                        char *reason)
{
    char *av[3];
    char mask[BUFSIZE];
    User *u = finduser(nick);

    if (!u)
        return;

#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3)
    if (is_protected(u) && (requester != u)) {
        send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name,
                 getstring2(NULL, PERMISSION_DENIED));
        return;
    }
#endif

    if ((ci->flags & CI_PEACE) && stricmp(requester->nick, nick)
        && (get_access(u, ci) >= get_access(requester, ci)))
        return;

#if defined (IRC_ULTIMATE) || defined(IRC_ULTIMATE3) || defined(IRC_UNREAL) || defined(IRC_VIAGRA) || defined(IRC_HYBRID)
    if (is_excepted(ci, u) == 1) {
        send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name,
                 getstring2(NULL, BOT_EXCEPT));
        return;
    }
#endif

    av[0] = ci->name;
    av[1] = sstrdup("+b");
    get_idealban(ci, u, mask, sizeof(mask));
    av[2] = mask;
    send_cmd(ci->bi->nick, "MODE %s +b %s", av[0], av[2]);
    do_cmode(ci->bi->nick, 3, av);
    free(av[1]);

    av[0] = ci->name;
    av[1] = nick;

    if (!reason) {
        av[2] = ci->bi->nick;
    } else {
        if (strlen(reason) > 200)
            reason[200] = '\0';
        av[2] = reason;
    }

    send_cmd(ci->bi->nick, "KICK %s %s :%s", av[0], av[1], av[2]);
    do_kick(ci->bi->nick, 3, av);
}

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

/* Makes a kick with a "dynamic" reason ;) */

static void bot_raw_kick(User * requester, ChannelInfo * ci, char *nick,
                         char *reason)
{
    char *av[3];
    User *u = finduser(nick);

    if (!u || !is_on_chan(ci->c, u))
        return;

#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3)
    if (is_protected(u) && (requester != u)) {
        send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name,
                 getstring2(NULL, PERMISSION_DENIED));
        return;
    }
#endif

    if ((ci->flags & CI_PEACE) && stricmp(requester->nick, nick)
        && (get_access(u, ci) >= get_access(requester, ci)))
        return;

    av[0] = ci->name;
    av[1] = nick;

    if (!reason) {
        av[2] = ci->bi->nick;
    } else {
        if (strlen(reason) > 200)
            reason[200] = '\0';
        av[2] = reason;
    }

    send_cmd(ci->bi->nick, "KICK %s %s :%s", av[0], av[1], av[2]);
    do_kick(ci->bi->nick, 3, av);
}

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

/* Makes a mode operation on a channel for a nick */

static void bot_raw_mode(User * requester, ChannelInfo * ci, char *mode,
                         char *nick)
{
    char *av[3];
    User *u = finduser(nick);

    if (!u || !is_on_chan(ci->c, u))
        return;

#if defined(IRC_ULTIMATE) || defined(IRC_ULTIMATE3)
    if (is_protected(u) && *mode == '-' && (requester != u)) {
        send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name,
                 getstring2(NULL, PERMISSION_DENIED));
        return;
    }
#endif

    if (*mode == '-' && (ci->flags & CI_PEACE)
        && stricmp(requester->nick, nick)
        && (get_access(u, ci) >= get_access(requester, ci)))
        return;

    av[0] = ci->name;
    av[1] = mode;
    av[2] = nick;

    send_cmd(ci->bi->nick, "MODE %s %s %s", av[0], av[1], av[2]);
    do_cmode(ci->bi->nick, 3, av);
}

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

/* Removes all bans for a nick on a channel */

static void bot_raw_unban(ChannelInfo * ci, char *nick)
{
#ifndef IRC_BAHAMUT
    int count, i;
    char *av[3], **bans;
    User *u;
#endif

    if (!ci || !ci->c || !ci->bi || !nick)
        return;
#ifndef IRC_BAHAMUT
    if (!(u = finduser(nick)))
        return;
#else
    if (!finduser(nick))
        return;
#endif

#ifndef IRC_BAHAMUT
    av[0] = ci->name;
    av[1] = sstrdup("-b");

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

    for (i = 0; i < count; i++) {
        if (match_usermask(bans[i], u)) {
            send_cmd(ci->bi->nick, "MODE %s -b %s", ci->name, bans[i]);
            av[2] = bans[i];
            do_cmode(ci->bi->nick, 3, av);
        }
    }
    free(bans);
    free(av[1]);
#else
    send_cmd(ServerName, "SVSMODE %s -b %s", ci->name, nick);
#endif
}

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

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

    if (!cmd) {
        notice_help(s_BotServ, u, BOT_HELP, BSMinUsers);
        if (is_services_oper(u))
            notice_help(s_BotServ, u, BOT_SERVADMIN_HELP);
        moduleDisplayHelp(4, u);
    } else {
        mod_help_cmd(s_BotServ, u, BOTSERV, cmd);
    }
    return MOD_CONT;
}

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

static int do_bot(User * u)
{
    BotInfo *bi;
    char *cmd = strtok(NULL, " ");
    char *ch = NULL;

    if (!cmd)
        syntax_error(s_BotServ, u, "BOT", BOT_BOT_SYNTAX);
    else if (!stricmp(cmd, "ADD")) {
        char *nick = strtok(NULL, " ");
        char *user = strtok(NULL, " ");
        char *host = strtok(NULL, " ");
        char *real = strtok(NULL, "");

        if (!nick || !user || !host || !real)
            syntax_error(s_BotServ, u, "BOT", BOT_BOT_SYNTAX);
        else if (readonly)
            notice_lang(s_BotServ, u, BOT_BOT_READONLY);
        else if (findbot(nick))
            notice_lang(s_BotServ, u, BOT_BOT_ALREADY_EXISTS, nick);
        else {
            User *u2;
            NickAlias *na;

                /**
		 * Check the nick is valid re RFC 2812
		 **/
            if (isdigit(nick[0]) || nick[0] == '-') {
                notice_lang(s_BotServ, u, BOT_BAD_NICK);
                return MOD_CONT;
            }
            for (ch = nick; *ch && (ch - nick) < NICKMAX; ch++) {
                if (!isvalidnick(*ch)) {
                    notice_lang(s_BotServ, u, BOT_BAD_NICK);
                    return MOD_CONT;
                }
            }
            if (!isValidHost(host, 3)) {
                notice_lang(s_BotServ, u, BOT_BAD_HOST);
                return MOD_CONT;
            }
            for (ch = user; *ch && (ch - user) < USERMAX; ch++) {
                if (!isalnum(*ch)) {
                    notice_lang(s_BotServ, u, BOT_BAD_IDENT);
                    return MOD_CONT;
                }
            }

                /**
		 * Check the host is valid re RFC 2812
		 **/

            /* Check whether it's a services client's nick and return if so - Certus */

            if ((s_NickServ && (stricmp(nick, s_NickServ) == 0))
                || (s_NickServAlias && !stricmp(nick, s_NickServAlias))) {
                notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
                return MOD_CONT;
            } else if ((s_ChanServ && (stricmp(nick, s_ChanServ) == 0))
                       || (s_ChanServAlias
                           && !stricmp(nick, s_ChanServAlias))) {
                notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
                return MOD_CONT;
            } else if ((s_MemoServ && (stricmp(nick, s_MemoServ) == 0))
                       || (s_MemoServAlias
                           && !stricmp(nick, s_MemoServAlias))) {
                notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
                return MOD_CONT;
            } else if ((s_BotServ && (stricmp(nick, s_BotServ) == 0))
                       || (s_BotServAlias
                           && !stricmp(nick, s_BotServAlias))) {
                notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
                return MOD_CONT;
            } else if ((s_HelpServ && (stricmp(nick, s_HelpServ) == 0))
                       || (s_HelpServAlias
                           && !stricmp(nick, s_HelpServAlias))) {
                notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
                return MOD_CONT;
            } else if ((s_OperServ && (stricmp(nick, s_OperServ) == 0))
                       || (s_OperServAlias
                           && !stricmp(nick, s_OperServAlias))) {
                notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
                return MOD_CONT;
            } else
                if ((s_GlobalNoticer
                     && (stricmp(nick, s_GlobalNoticer) == 0))
                    || (s_GlobalNoticerAlias
                        && !stricmp(nick, s_GlobalNoticerAlias))) {
                notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
                return MOD_CONT;
            } else if ((s_HostServ && (stricmp(nick, s_HostServ) == 0))
                       || (s_HostServAlias
                           && !stricmp(nick, s_HostServAlias))) {
                notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
                return MOD_CONT;
            }

            /* We check whether the nick is registered, and drop it if so. */
            if ((na = findnick(nick))) {
                if (NSSecureAdmins && nick_is_services_admin(na->nc)
                    && !is_services_root(u)) {
                    notice_lang(s_BotServ, u, PERMISSION_DENIED);
                    return MOD_CONT;
                }
                delnick(na);
            }

            bi = makebot(nick);
            if (!bi) {
                notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
                return MOD_CONT;
            }

            bi->user = sstrdup(user);
            bi->host = sstrdup(host);
            bi->real = sstrdup(real);
            bi->created = time(NULL);
            bi->chancount = 0;

            /* We check whether user with this nick is online, and kill it if so */
            if ((u2 = finduser(nick)))
                kill_user(s_BotServ, u2->nick,
                          "This nick is now used by Services");

            /* We make the bot online, ready to serve */
#if defined(IRC_UNREAL) || defined (IRC_VIAGRA)
            NEWNICK(bi->nick, bi->user, bi->host, bi->real, "+qS", 1);
#elif defined(IRC_ULTIMATE) || defined (IRC_ULTIMATE3)
            NEWNICK(bi->nick, bi->user, bi->host, bi->real, "+pS", 1);
#else
            NEWNICK(bi->nick, bi->user, bi->host, bi->real, "+", 1);
#endif

            notice_lang(s_BotServ, u, BOT_BOT_ADDED, bi->nick, bi->user,
                        bi->host, bi->real);
        }
    } else if (!stricmp(cmd, "CHANGE")) {
        char *oldnick = strtok(NULL, " ");
        char *nick = strtok(NULL, " ");
        char *user = strtok(NULL, " ");
        char *host = strtok(NULL, " ");
        char *real = strtok(NULL, "");

        if (!oldnick || !nick)
            syntax_error(s_BotServ, u, "BOT", BOT_BOT_SYNTAX);
        else if (readonly)
            notice_lang(s_BotServ, u, BOT_BOT_READONLY);
        else if (!(bi = findbot(oldnick)))
            notice_lang(s_BotServ, u, BOT_DOES_NOT_EXIST, oldnick);
        else {
            User *u2;
            NickAlias *na;

            /* Checks whether there *are* changes.
             * Case sensitive because we may want to change just the case.
             * And we must finally check that the nick is not already
             * taken by another bot.
             */
            if (!strcmp(bi->nick, nick)
                && ((user) ? !strcmp(bi->user, user) : 1)
                && ((host) ? !strcmp(bi->host, host) : 1)
                && ((real) ? !strcmp(bi->real, real) : 1)) {
                notice_lang(s_BotServ, u, BOT_BOT_ANY_CHANGES);
                return MOD_CONT;
            }

            /* Check whether it's a services client's nick and return if so - Certus */
            if ((s_NickServ && !stricmp(nick, s_NickServ))
                || (s_NickServAlias && !stricmp(nick, s_NickServAlias))) {
                notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
                return MOD_CONT;
            } else if ((s_ChanServ && !stricmp(nick, s_ChanServ))
                       || (s_ChanServAlias
                           && !stricmp(nick, s_ChanServAlias))) {
                notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
                return MOD_CONT;
            } else if ((s_MemoServ && !stricmp(nick, s_MemoServ))
                       || (s_MemoServAlias
                           && !stricmp(nick, s_MemoServAlias))) {
                notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
                return MOD_CONT;
            } else if ((s_BotServ && !stricmp(nick, s_BotServ))
                       || (s_BotServAlias
                           && !stricmp(nick, s_BotServAlias))) {
                notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
                return MOD_CONT;
            } else if ((s_HelpServ && !stricmp(nick, s_HelpServ))
                       || (s_HelpServAlias
                           && !stricmp(nick, s_HelpServAlias))) {
                notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
                return MOD_CONT;
            } else if ((s_OperServ && !stricmp(nick, s_OperServ))
                       || (s_OperServAlias
                           && !stricmp(nick, s_OperServAlias))) {
                notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
                return MOD_CONT;
            } else if ((s_GlobalNoticer && !stricmp(nick, s_GlobalNoticer))
                       || (s_GlobalNoticerAlias
                           && !stricmp(nick, s_GlobalNoticerAlias))) {
                notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
                return MOD_CONT;
            } else if ((s_HostServ && !stricmp(nick, s_HostServ))
                       || (s_HostServAlias
                           && !stricmp(nick, s_HostServAlias))) {
                notice_lang(s_BotServ, u, BOT_BOT_CREATION_FAILED);
                return MOD_CONT;
            }

               /**
		 * Check the nick is valid re RFC 2812
		 **/
            if (isdigit(nick[0]) || nick[0] == '-') {
                notice_lang(s_BotServ, u, BOT_BAD_NICK);
                return MOD_CONT;
            }
            for (ch = nick; *ch && (ch - nick) < NICKMAX; ch++) {
                if (!isvalidnick(*ch)) {
                    notice_lang(s_BotServ, u, BOT_BAD_NICK);
                    return MOD_CONT;
                }
            }
            if (!isValidHost(host, 3)) {
                notice_lang(s_BotServ, u, BOT_BAD_HOST);
                return MOD_CONT;
            }

            for (ch = user; *ch && (ch - user) < USERMAX; ch++) {
                if (!isalnum(*ch)) {
                    notice_lang(s_BotServ, u, BOT_BAD_IDENT);
                    return MOD_CONT;
                }
            }

            if (stricmp(bi->nick, nick) && findbot(nick)) {
                notice_lang(s_BotServ, u, BOT_BOT_ALREADY_EXISTS, nick);
                return MOD_CONT;
            }

            if (stricmp(bi->nick, nick)) {
                /* The new nick is really different, so we remove the Q line for
                   the old nick. */
#ifndef IRC_HYBRID
                send_cmd(NULL, "UNSQLINE %s", bi->nick);
#endif

                /* We check whether the nick is registered, and drop it if so */
                if ((na = findnick(nick)))
                    delnick(na);

                /* We check whether user with this nick is online, and kill it if so */
                if ((u2 = finduser(nick)))
                    kill_user(s_BotServ, u2->nick,
                              "This nick is now used by Services");
            }

            if (strcmp(nick, bi->nick))
                change_bot_nick(bi, nick);

            if (user && strcmp(user, bi->user)) {
                free(bi->user);
                bi->user = sstrdup(user);
            }
            if (host && strcmp(host, bi->host)) {
                free(bi->host);
                bi->host = sstrdup(host);
            }
            if (real && strcmp(real, bi->real)) {
                free(bi->real);
                bi->real = sstrdup(real);
            }

            /* If only the nick changes, we just make the bot change his nick,
               else we must make it quit and rejoin. */
            if (!user)
                send_cmd(oldnick, "NICK %s", bi->nick);
            else {
                send_cmd(oldnick, "QUIT :Quit: Be right back");
#if defined(IRC_UNREAL)
                NEWNICK(bi->nick, bi->user, bi->host, bi->real, "+qS", 1);
#elif defined(IRC_ULTIMATE)
                NEWNICK(bi->nick, bi->user, bi->host, bi->real, "+pS", 1);
#else
                NEWNICK(bi->nick, bi->user, bi->host, bi->real, "+", 1);
#endif
                bot_rejoin_all(bi);
            }

            notice_lang(s_BotServ, u, BOT_BOT_CHANGED, oldnick, bi->nick,
                        bi->user, bi->host, bi->real);
        }
    } else if (!stricmp(cmd, "DEL")) {
        char *nick = strtok(NULL, " ");

        if (!nick)
            syntax_error(s_BotServ, u, "BOT", BOT_BOT_SYNTAX);
        else if (readonly)
            notice_lang(s_BotServ, u, BOT_BOT_READONLY);
        else if (!(bi = findbot(nick)))
            notice_lang(s_BotServ, u, BOT_DOES_NOT_EXIST, nick);
        else {
            send_cmd(bi->nick,
                     "QUIT :Quit: Help! I'm being deleted by %s!",
                     u->nick);
#ifndef IRC_HYBRID
            send_cmd(NULL, "UNSQLINE %s", bi->nick);
#endif
            delbot(bi);

            notice_lang(s_BotServ, u, BOT_BOT_DELETED, nick);
        }
    } else if (!stricmp(cmd, "LIST"))
        do_botlist(u);
    else
        syntax_error(s_BotServ, u, "BOT", BOT_BOT_SYNTAX);

    return MOD_CONT;
}

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

static int do_botlist(User * u)
{
    int i, count = 0;
    BotInfo *bi;

    if (!nbots) {
        notice_lang(s_BotServ, u, BOT_BOTLIST_EMPTY);
        return MOD_CONT;
    }

    for (i = 0; i < 256; i++) {
        for (bi = botlists[i]; bi; bi = bi->next) {
            if (!(bi->flags & BI_PRIVATE)) {
                if (!count)
                    notice_lang(s_BotServ, u, BOT_BOTLIST_HEADER);
                count++;
                notice_user(s_BotServ, u, "   %-15s  (%s@%s)", bi->nick,
                            bi->user, bi->host);
            }
        }
    }

    if (is_oper(u) && count < nbots) {
        notice_lang(s_BotServ, u, BOT_BOTLIST_PRIVATE_HEADER);

        for (i = 0; i < 256; i++) {
            for (bi = botlists[i]; bi; bi = bi->next) {
                if (bi->flags & BI_PRIVATE) {
                    notice_user(s_BotServ, u, "   %-15s  (%s@%s)",
                                bi->nick, bi->user, bi->host);
                    count++;
                }
            }
        }
    }

    if (!count)
        notice_lang(s_BotServ, u, BOT_BOTLIST_EMPTY);
    else
        notice_lang(s_BotServ, u, BOT_BOTLIST_FOOTER, count);
    return MOD_CONT;
}

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

static int do_assign(User * u)
{
    char *chan = strtok(NULL, " ");
    char *nick = strtok(NULL, " ");
    BotInfo *bi;
    ChannelInfo *ci;

    if (readonly)
        notice_lang(s_BotServ, u, BOT_ASSIGN_READONLY);
    else if (!chan || !nick)
        syntax_error(s_BotServ, u, "ASSIGN", BOT_ASSIGN_SYNTAX);
    else if (!(bi = findbot(nick)))
        notice_lang(s_BotServ, u, BOT_DOES_NOT_EXIST, nick);
    else if (bi->flags & BI_PRIVATE && !is_oper(u))
        notice_lang(s_BotServ, u, PERMISSION_DENIED);
    else if (!(ci = cs_findchan(chan)))
        notice_lang(s_BotServ, u, CHAN_X_NOT_REGISTERED, chan);
    else if (ci->flags & CI_VERBOTEN)
        notice_lang(s_BotServ, u, CHAN_X_FORBIDDEN, chan);
    else if ((ci->bi) && (stricmp(ci->bi->nick, nick) == 0))
        notice_lang(s_BotServ, u, BOT_ASSIGN_ALREADY, ci->bi->nick, chan);
    else if ((ci->botflags & BS_NOBOT)
             || (!check_access(u, ci, CA_ASSIGN) && !is_services_admin(u)))
        notice_lang(s_BotServ, u, PERMISSION_DENIED);
    else {
        if (ci->bi)
            unassign(u, ci);
        ci->bi = bi;
        bi->chancount++;
        if (ci->c && ci->c->usercount >= BSMinUsers) {
            bot_join(ci);
        }
        notice_lang(s_BotServ, u, BOT_ASSIGN_ASSIGNED, bi->nick, ci->name);
    }
    return MOD_CONT;
}

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

static int do_unassign(User * u)
{
    char *chan = strtok(NULL, " ");
    ChannelInfo *ci;

    if (readonly)
        notice_lang(s_BotServ, u, BOT_ASSIGN_READONLY);
    else if (!chan)
        syntax_error(s_BotServ, u, "UNASSIGN", BOT_UNASSIGN_SYNTAX);
    else if (!(ci = cs_findchan(chan)))
        notice_lang(s_BotServ, u, CHAN_X_NOT_REGISTERED, chan);
    else if (ci->flags & CI_VERBOTEN)
        notice_lang(s_BotServ, u, CHAN_X_FORBIDDEN, chan);
    else if (!is_services_admin(u) && !check_access(u, ci, CA_ASSIGN))
        notice_lang(s_BotServ, u, ACCESS_DENIED);
    else {
        if (ci->bi)
            unassign(u, ci);
        notice_lang(s_BotServ, u, BOT_UNASSIGN_UNASSIGNED, ci->name);
    }
    return MOD_CONT;
}

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

static void send_bot_channels(User * u, BotInfo * bi)
{
    int i;
    ChannelInfo *ci;
    char buf[307], *end;

    *buf = 0;
    end = buf;

    for (i = 0; i < 256; i++) {
        for (ci = chanlists[i]; ci; ci = ci->next) {
            if (ci->bi == bi) {
                if (strlen(buf) + strlen(ci->name) > 300) {
                    notice_user(s_BotServ, u, buf);
                    *buf = 0;
                    end = buf;
                }
                end +=
                    snprintf(end, sizeof(buf) - (end - buf), " %s ",
                             ci->name);
            }
        }
    }

    if (*buf)
        notice_user(s_BotServ, u, buf);
    return;
}

static int do_info(User * u)
{
    BotInfo *bi;
    ChannelInfo *ci;
    char *query = strtok(NULL, " ");

    int need_comma = 0, is_servadmin = is_services_admin(u);
    char buf[BUFSIZE], *end;
    const char *commastr = getstring(u->na, COMMA_SPACE);

    if (!query)
        syntax_error(s_BotServ, u, "INFO", BOT_INFO_SYNTAX);
    else if ((bi = findbot(query))) {
        char buf[BUFSIZE];
        struct tm *tm;

        notice_lang(s_BotServ, u, BOT_INFO_BOT_HEADER, bi->nick);
        notice_lang(s_BotServ, u, BOT_INFO_BOT_MASK, bi->user, bi->host);
        notice_lang(s_BotServ, u, BOT_INFO_BOT_REALNAME, bi->real);
        tm = localtime(&bi->created);
        strftime_lang(buf, sizeof(buf), u, STRFTIME_DATE_TIME_FORMAT, tm);
        notice_lang(s_BotServ, u, BOT_INFO_BOT_CREATED, buf);
        notice_lang(s_BotServ, u, BOT_INFO_BOT_OPTIONS,
                    getstring(u->na,
                              (bi->
                               flags & BI_PRIVATE) ? BOT_INFO_OPT_PRIVATE :
                              BOT_INFO_OPT_NONE));
        notice_lang(s_BotServ, u, BOT_INFO_BOT_USAGE, bi->chancount);

        if (is_services_admin(u))
            send_bot_channels(u, bi);
    } else if ((ci = cs_findchan(query))) {
        if (!is_servadmin && !is_founder(u, ci)) {
            notice_lang(s_BotServ, u, PERMISSION_DENIED);
            return MOD_CONT;
        }

        notice_lang(s_BotServ, u, BOT_INFO_CHAN_HEADER, ci->name);
        if (ci->bi)
            notice_lang(s_BotServ, u, BOT_INFO_CHAN_BOT, ci->bi->nick);
        else
            notice_lang(s_BotServ, u, BOT_INFO_CHAN_BOT_NONE);

        if (ci->botflags & BS_KICK_BADWORDS) {
            if (ci->ttb[TTB_BADWORDS])
                notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_BADWORDS_BAN,
                            getstring(u->na, BOT_INFO_ACTIVE),
                            ci->ttb[TTB_BADWORDS]);
            else
                notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_BADWORDS,
                            getstring(u->na, BOT_INFO_ACTIVE));
        } else
            notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_BADWORDS,
                        getstring(u->na, BOT_INFO_INACTIVE));
        if (ci->botflags & BS_KICK_BOLDS) {
            if (ci->ttb[TTB_BOLDS])
                notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_BOLDS_BAN,
                            getstring(u->na, BOT_INFO_ACTIVE),
                            ci->ttb[TTB_BOLDS]);
            else
                notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_BOLDS,
                            getstring(u->na, BOT_INFO_ACTIVE));
        } else
            notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_BOLDS,
                        getstring(u->na, BOT_INFO_INACTIVE));
        if (ci->botflags & BS_KICK_CAPS) {
            if (ci->ttb[TTB_CAPS])
                notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_CAPS_BAN,
                            getstring(u->na, BOT_INFO_ACTIVE),
                            ci->ttb[TTB_CAPS], ci->capsmin,
                            ci->capspercent);
            else
                notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_CAPS_ON,
                            getstring(u->na, BOT_INFO_ACTIVE), ci->capsmin,
                            ci->capspercent);
        } else
            notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_CAPS_OFF,
                        getstring(u->na, BOT_INFO_INACTIVE));
        if (ci->botflags & BS_KICK_COLORS) {
            if (ci->ttb[TTB_COLORS])
                notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_COLORS_BAN,
                            getstring(u->na, BOT_INFO_ACTIVE),
                            ci->ttb[TTB_COLORS]);
            else
                notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_COLORS,
                            getstring(u->na, BOT_INFO_ACTIVE));
        } else
            notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_COLORS,
                        getstring(u->na, BOT_INFO_INACTIVE));
        if (ci->botflags & BS_KICK_FLOOD) {
            if (ci->ttb[TTB_FLOOD])
                notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_FLOOD_BAN,
                            getstring(u->na, BOT_INFO_ACTIVE),
                            ci->ttb[TTB_FLOOD], ci->floodlines,
                            ci->floodsecs);
            else
                notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_FLOOD_ON,
                            getstring(u->na, BOT_INFO_ACTIVE),
                            ci->floodlines, ci->floodsecs);
        } else
            notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_FLOOD_OFF,
                        getstring(u->na, BOT_INFO_INACTIVE));
        if (ci->botflags & BS_KICK_REPEAT) {
            if (ci->ttb[TTB_REPEAT])
                notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_REPEAT_BAN,
                            getstring(u->na, BOT_INFO_ACTIVE),
                            ci->ttb[TTB_REPEAT], ci->repeattimes);
            else
                notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_REPEAT_ON,
                            getstring(u->na, BOT_INFO_ACTIVE),
                            ci->repeattimes);
        } else
            notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_REPEAT_OFF,
                        getstring(u->na, BOT_INFO_INACTIVE));
        if (ci->botflags & BS_KICK_REVERSES) {
            if (ci->ttb[TTB_REVERSES])
                notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_REVERSES_BAN,
                            getstring(u->na, BOT_INFO_ACTIVE),
                            ci->ttb[TTB_REVERSES]);
            else
                notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_REVERSES,
                            getstring(u->na, BOT_INFO_ACTIVE));
        } else
            notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_REVERSES,
                        getstring(u->na, BOT_INFO_INACTIVE));
        if (ci->botflags & BS_KICK_UNDERLINES) {
            if (ci->ttb[TTB_UNDERLINES])
                notice_lang(s_BotServ, u,
                            BOT_INFO_CHAN_KICK_UNDERLINES_BAN,
                            getstring(u->na, BOT_INFO_ACTIVE),
                            ci->ttb[TTB_UNDERLINES]);
            else
                notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_UNDERLINES,
                            getstring(u->na, BOT_INFO_ACTIVE));
        } else
            notice_lang(s_BotServ, u, BOT_INFO_CHAN_KICK_UNDERLINES,
                        getstring(u->na, BOT_INFO_INACTIVE));

        end = buf;
        *end = 0;
        if (ci->botflags & BS_DONTKICKOPS) {
            end += snprintf(end, sizeof(buf) - (end - buf), "%s",
                            getstring(u->na, BOT_INFO_OPT_DONTKICKOPS));
            need_comma = 1;
        }
        if (ci->botflags & BS_DONTKICKVOICES) {
            end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
                            need_comma ? commastr : "",
                            getstring(u->na, BOT_INFO_OPT_DONTKICKVOICES));
            need_comma = 1;
        }
        if (ci->botflags & BS_FANTASY) {
            end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
                            need_comma ? commastr : "",
                            getstring(u->na, BOT_INFO_OPT_FANTASY));
            need_comma = 1;
        }
        if (ci->botflags & BS_GREET) {
            end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
                            need_comma ? commastr : "",
                            getstring(u->na, BOT_INFO_OPT_GREET));
            need_comma = 1;
        }
        if (ci->botflags & BS_NOBOT) {
            end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
                            need_comma ? commastr : "",
                            getstring(u->na, BOT_INFO_OPT_NOBOT));
            need_comma = 1;
        }
        if (ci->botflags & BS_SYMBIOSIS) {
            end += snprintf(end, sizeof(buf) - (end - buf), "%s%s",
                            need_comma ? commastr : "",
                            getstring(u->na, BOT_INFO_OPT_SYMBIOSIS));
            need_comma = 1;
        }
        notice_lang(s_BotServ, u, BOT_INFO_CHAN_OPTIONS,
                    *buf ? buf : getstring(u->na, BOT_INFO_OPT_NONE));

    } else
        notice_lang(s_BotServ, u, BOT_INFO_NOT_FOUND, query);
    return MOD_CONT;
}

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

static int do_set(User * u)
{
    char *chan = strtok(NULL, " ");
    char *option = strtok(NULL, " ");
    char *value = strtok(NULL, " ");
    int is_servadmin = is_services_admin(u);

    ChannelInfo *ci;

    if (readonly)
        notice_lang(s_BotServ, u, BOT_SET_DISABLED);
    else if (!chan || !option || !value)
        syntax_error(s_BotServ, u, "SET", BOT_SET_SYNTAX);
    else if (is_servadmin && !stricmp(option, "PRIVATE")) {
        BotInfo *bi;

        if ((bi = findbot(chan))) {
            if (!stricmp(value, "ON")) {
                bi->flags |= BI_PRIVATE;
                notice_lang(s_BotServ, u, BOT_SET_PRIVATE_ON, bi->nick);
            } else if (!stricmp(value, "OFF")) {
                bi->flags &= ~BI_PRIVATE;
                notice_lang(s_BotServ, u, BOT_SET_PRIVATE_OFF, bi->nick);
            } else {
                syntax_error(s_BotServ, u, "SET PRIVATE",
                             BOT_SET_PRIVATE_SYNTAX);
            }
        } else {
            notice_lang(s_BotServ, u, BOT_DOES_NOT_EXIST, chan);
        }
        return MOD_CONT;
    } else if (!(ci = cs_findchan(chan)))
        notice_lang(s_BotServ, u, CHAN_X_NOT_REGISTERED, chan);
    else if (ci->flags & CI_VERBOTEN)
        notice_lang(s_BotServ, u, CHAN_X_FORBIDDEN, chan);
    else if (!is_servadmin && !check_access(u, ci, CA_SET))
        notice_lang(s_BotServ, u, ACCESS_DENIED);
    else {
        if (!stricmp(option, "DONTKICKOPS")) {
            if (!stricmp(value, "ON")) {
                ci->botflags |= BS_DONTKICKOPS;
                notice_lang(s_BotServ, u, BOT_SET_DONTKICKOPS_ON,
                            ci->name);
            } else if (!stricmp(value, "OFF")) {
                ci->botflags &= ~BS_DONTKICKOPS;
                notice_lang(s_BotServ, u, BOT_SET_DONTKICKOPS_OFF,
                            ci->name);
            } else {
                syntax_error(s_BotServ, u, "SET DONTKICKOPS",
                             BOT_SET_DONTKICKOPS_SYNTAX);
            }
        } else if (!stricmp(option, "DONTKICKVOICES")) {
            if (!stricmp(value, "ON")) {
                ci->botflags |= BS_DONTKICKVOICES;
                notice_lang(s_BotServ, u, BOT_SET_DONTKICKVOICES_ON,
                            ci->name);
            } else if (!stricmp(value, "OFF")) {
                ci->botflags &= ~BS_DONTKICKVOICES;
                notice_lang(s_BotServ, u, BOT_SET_DONTKICKVOICES_OFF,
                            ci->name);
            } else {
                syntax_error(s_BotServ, u, "SET DONTKICKVOICES",
                             BOT_SET_DONTKICKVOICES_SYNTAX);
            }
        } else if (!stricmp(option, "FANTASY")) {
            if (!stricmp(value, "ON")) {
                ci->botflags |= BS_FANTASY;
                notice_lang(s_BotServ, u, BOT_SET_FANTASY_ON, ci->name);
            } else if (!stricmp(value, "OFF")) {
                ci->botflags &= ~BS_FANTASY;
                notice_lang(s_BotServ, u, BOT_SET_FANTASY_OFF, ci->name);
            } else {
                syntax_error(s_BotServ, u, "SET FANTASY",
                             BOT_SET_FANTASY_SYNTAX);
            }
        } else if (!stricmp(option, "GREET")) {
            if (!stricmp(value, "ON")) {
                ci->botflags |= BS_GREET;
                notice_lang(s_BotServ, u, BOT_SET_GREET_ON, ci->name);
            } else if (!stricmp(value, "OFF")) {
                ci->botflags &= ~BS_GREET;
                notice_lang(s_BotServ, u, BOT_SET_GREET_OFF, ci->name);
            } else {
                syntax_error(s_BotServ, u, "SET GREET",
                             BOT_SET_GREET_SYNTAX);
            }
        } else if (is_servadmin && !stricmp(option, "NOBOT")) {
            if (!stricmp(value, "ON")) {
                ci->botflags |= BS_NOBOT;
                if (ci->bi)
                    unassign(u, ci);
                notice_lang(s_BotServ, u, BOT_SET_NOBOT_ON, ci->name);
            } else if (!stricmp(value, "OFF")) {
                ci->botflags &= ~BS_NOBOT;
                notice_lang(s_BotServ, u, BOT_SET_NOBOT_OFF, ci->name);
            } else {
                syntax_error(s_BotServ, u, "SET NOBOT",
                             BOT_SET_NOBOT_SYNTAX);
            }
        } else if (!stricmp(option, "SYMBIOSIS")) {
            if (!stricmp(value, "ON")) {
                ci->botflags |= BS_SYMBIOSIS;
                notice_lang(s_BotServ, u, BOT_SET_SYMBIOSIS_ON, ci->name);
            } else if (!stricmp(value, "OFF")) {
                ci->botflags &= ~BS_SYMBIOSIS;
                notice_lang(s_BotServ, u, BOT_SET_SYMBIOSIS_OFF, ci->name);
            } else {
                syntax_error(s_BotServ, u, "SET SYMBIOSIS",
                             BOT_SET_SYMBIOSIS_SYNTAX);
            }
        } else {
            notice_help(s_BotServ, u, BOT_SET_UNKNOWN, option);
        }
    }
    return MOD_CONT;
}

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

static int do_kickcmd(User * u)
{
    char *chan = strtok(NULL, " ");
    char *option = strtok(NULL, " ");
    char *value = strtok(NULL, " ");
    char *ttb = strtok(NULL, " ");

    ChannelInfo *ci;

    if (readonly)
        notice_lang(s_BotServ, u, BOT_KICK_DISABLED);
    else if (!chan || !option || !value)
        syntax_error(s_BotServ, u, "KICK", BOT_KICK_SYNTAX);
    else if (stricmp(value, "ON") && stricmp(value, "OFF"))
        syntax_error(s_BotServ, u, "KICK", BOT_KICK_SYNTAX);
    else if (!(ci = cs_findchan(chan)))
        notice_lang(s_BotServ, u, CHAN_X_NOT_REGISTERED, chan);
    else if (ci->flags & CI_VERBOTEN)
        notice_lang(s_BotServ, u, CHAN_X_FORBIDDEN, chan);
    else if (!is_services_admin(u) && !check_access(u, ci, CA_SET))
        notice_lang(s_BotServ, u, ACCESS_DENIED);
    else {
        if (!stricmp(option, "BADWORDS")) {
            if (!stricmp(value, "ON")) {
                if (ttb) {
                    ci->ttb[TTB_BADWORDS] = atol(ttb);
                    if (ci->ttb[TTB_BADWORDS] < 0) {
                        notice_lang(s_BotServ, u, BOT_KICK_BAD_TTB, ttb);
                        return MOD_CONT;
                    }
                } else
                    ci->ttb[TTB_BADWORDS] = 0;
                ci->botflags |= BS_KICK_BADWORDS;
                if (ci->ttb[TTB_BADWORDS])
                    notice_lang(s_BotServ, u, BOT_KICK_BADWORDS_ON_BAN,
                                ci->ttb[TTB_BADWORDS]);
                else
                    notice_lang(s_BotServ, u, BOT_KICK_BADWORDS_ON);
            } else {
                ci->botflags &= ~BS_KICK_BADWORDS;
                notice_lang(s_BotServ, u, BOT_KICK_BADWORDS_OFF);
            }
        } else if (!stricmp(option, "BOLDS")) {
            if (!stricmp(value, "ON")) {
                if (ttb) {
                    ci->ttb[TTB_BOLDS] = atol(ttb);
                    if (ci->ttb[TTB_BOLDS] < 0) {
                        notice_lang(s_BotServ, u, BOT_KICK_BAD_TTB, ttb);
                        return MOD_CONT;
                    }
                } else
                    ci->ttb[TTB_BOLDS] = 0;
                ci->botflags |= BS_KICK_BOLDS;
                if (ci->ttb[TTB_BOLDS])
                    notice_lang(s_BotServ, u, BOT_KICK_BOLDS_ON_BAN,
                                ci->ttb[TTB_BOLDS]);
                else
                    notice_lang(s_BotServ, u, BOT_KICK_BOLDS_ON);
            } else {
                ci->botflags &= ~BS_KICK_BOLDS;
                notice_lang(s_BotServ, u, BOT_KICK_BOLDS_OFF);
            }
        } else if (!stricmp(option, "CAPS")) {
            if (!stricmp(value, "ON")) {
                char *min = strtok(NULL, " ");
                char *percent = strtok(NULL, " ");

                if (ttb) {
                    ci->ttb[TTB_CAPS] = atol(ttb);
                    if (ci->ttb[TTB_CAPS] < 0) {
                        notice_lang(s_BotServ, u, BOT_KICK_BAD_TTB, ttb);
                        return MOD_CONT;
                    }
                } else
                    ci->ttb[TTB_CAPS] = 0;

                if (!min)
                    ci->capsmin = 10;
                else
                    ci->capsmin = atol(min);
                if (ci->capsmin < 1)
                    ci->capsmin = 10;

                if (!percent)
                    ci->capspercent = 25;
                else
                    ci->capspercent = atol(percent);
                if (ci->capspercent < 1 || ci->capspercent > 100)
                    ci->capspercent = 25;

                ci->botflags |= BS_KICK_CAPS;
                if (ci->ttb[TTB_CAPS])
                    notice_lang(s_BotServ, u, BOT_KICK_CAPS_ON_BAN,
                                ci->capsmin, ci->capspercent,
                                ci->ttb[TTB_CAPS]);
                else
                    notice_lang(s_BotServ, u, BOT_KICK_CAPS_ON,
                                ci->capsmin, ci->capspercent);
            } else {
                ci->botflags &= ~BS_KICK_CAPS;
                notice_lang(s_BotServ, u, BOT_KICK_CAPS_OFF);
            }
        } else if (!stricmp(option, "COLORS")) {
            if (!stricmp(value, "ON")) {
                if (ttb) {
                    ci->ttb[TTB_COLORS] = atol(ttb);
                    if (ci->ttb[TTB_COLORS] < 0) {
                        notice_lang(s_BotServ, u, BOT_KICK_BAD_TTB, ttb);
                        return MOD_CONT;
                    }
                } else
                    ci->ttb[TTB_COLORS] = 0;
                ci->botflags |= BS_KICK_COLORS;
                if (ci->ttb[TTB_COLORS])
                    notice_lang(s_BotServ, u, BOT_KICK_COLORS_ON_BAN,
                                ci->ttb[TTB_COLORS]);
                else
                    notice_lang(s_BotServ, u, BOT_KICK_COLORS_ON);
            } else {
                ci->botflags &= ~BS_KICK_COLORS;
                notice_lang(s_BotServ, u, BOT_KICK_COLORS_OFF);
            }
        } else if (!stricmp(option, "FLOOD")) {
            if (!stricmp(value, "ON")) {
                char *lines = strtok(NULL, " ");
                char *secs = strtok(NULL, " ");

                if (ttb) {
                    ci->ttb[TTB_FLOOD] = atol(ttb);
                    if (ci->ttb[TTB_FLOOD] < 0) {
                        notice_lang(s_BotServ, u, BOT_KICK_BAD_TTB, ttb);
                        return MOD_CONT;
                    }
                } else
                    ci->ttb[TTB_FLOOD] = 0;

                if (!lines)
                    ci->floodlines = 6;
                else
                    ci->floodlines = atol(lines);
                if (ci->floodlines < 2)
                    ci->floodlines = 6;

                if (!secs)
                    ci->floodsecs = 10;
                else
                    ci->floodsecs = atol(secs);
                if (ci->floodsecs < 1 || ci->floodsecs > BSKeepData)
                    ci->floodsecs = 10;

                ci->botflags |= BS_KICK_FLOOD;
                if (ci->ttb[TTB_FLOOD])
                    notice_lang(s_BotServ, u, BOT_KICK_FLOOD_ON_BAN,
                                ci->floodlines, ci->floodsecs,
                                ci->ttb[TTB_FLOOD]);
                else
                    notice_lang(s_BotServ, u, BOT_KICK_FLOOD_ON,
                                ci->floodlines, ci->floodsecs);
            } else {
                ci->botflags &= ~BS_KICK_FLOOD;
                notice_lang(s_BotServ, u, BOT_KICK_FLOOD_OFF);
            }
        } else if (!stricmp(option, "REPEAT")) {
            if (!stricmp(value, "ON")) {
                char *times = strtok(NULL, " ");

                if (ttb) {
                    ci->ttb[TTB_REPEAT] = atol(ttb);
                    if (ci->ttb[TTB_REPEAT] < 0) {
                        notice_lang(s_BotServ, u, BOT_KICK_BAD_TTB, ttb);
                        return MOD_CONT;
                    }
                } else
                    ci->ttb[TTB_REPEAT] = 0;

                if (!times)
                    ci->repeattimes = 3;
                else
                    ci->repeattimes = atol(times);
                if (ci->repeattimes < 2)
                    ci->repeattimes = 3;

                ci->botflags |= BS_KICK_REPEAT;
                if (ci->ttb[TTB_REPEAT])
                    notice_lang(s_BotServ, u, BOT_KICK_REPEAT_ON_BAN,
                                ci->repeattimes, ci->ttb[TTB_REPEAT]);
                else
                    notice_lang(s_BotServ, u, BOT_KICK_REPEAT_ON,
                                ci->repeattimes);
            } else {
                ci->botflags &= ~BS_KICK_REPEAT;
                notice_lang(s_BotServ, u, BOT_KICK_REPEAT_OFF);
            }
        } else if (!stricmp(option, "REVERSES")) {
            if (!stricmp(value, "ON")) {
                if (ttb) {
                    ci->ttb[TTB_REVERSES] = atol(ttb);
                    if (ci->ttb[TTB_REVERSES] < 0) {
                        notice_lang(s_BotServ, u, BOT_KICK_BAD_TTB, ttb);
                        return MOD_CONT;
                    }
                } else
                    ci->ttb[TTB_REVERSES] = 0;
                ci->botflags |= BS_KICK_REVERSES;
                if (ci->ttb[TTB_REVERSES])
                    notice_lang(s_BotServ, u, BOT_KICK_REVERSES_ON_BAN,
                                ci->ttb[TTB_REVERSES]);
                else
                    notice_lang(s_BotServ, u, BOT_KICK_REVERSES_ON);
            } else {
                ci->botflags &= ~BS_KICK_REVERSES;
                notice_lang(s_BotServ, u, BOT_KICK_REVERSES_OFF);
            }
        } else if (!stricmp(option, "UNDERLINES")) {
            if (!stricmp(value, "ON")) {
                if (ttb) {
                    ci->ttb[TTB_UNDERLINES] = atol(ttb);
                    if (ci->ttb[TTB_UNDERLINES] < 0) {
                        notice_lang(s_BotServ, u, BOT_KICK_BAD_TTB, ttb);
                        return MOD_CONT;
                    }
                } else
                    ci->ttb[TTB_UNDERLINES] = 0;
                ci->botflags |= BS_KICK_UNDERLINES;
                if (ci->ttb[TTB_UNDERLINES])
                    notice_lang(s_BotServ, u, BOT_KICK_UNDERLINES_ON_BAN,
                                ci->ttb[TTB_UNDERLINES]);
                else
                    notice_lang(s_BotServ, u, BOT_KICK_UNDERLINES_ON);
            } else {
                ci->botflags &= ~BS_KICK_UNDERLINES;
                notice_lang(s_BotServ, u, BOT_KICK_UNDERLINES_OFF);
            }
        } else
            notice_help(s_BotServ, u, BOT_KICK_UNKNOWN, option);
    }
    return MOD_CONT;
}

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

static int badwords_del_callback(User * u, int num, va_list args)
{
    BadWord *bw;
    ChannelInfo *ci = va_arg(args, ChannelInfo *);
    int *last = va_arg(args, int *);
    if (num < 1 || num > ci->bwcount)
        return 0;
    *last = num;

    bw = &ci->badwords[num - 1];
    if (bw->word)
        free(bw->word);
    bw->word = NULL;
    bw->in_use = 0;

    return 1;
}

static int badwords_list(User * u, int index, ChannelInfo * ci,
                         int *sent_header)
{
    BadWord *bw = &ci->badwords[index];

    if (!bw->in_use)
        return 0;
    if (!*sent_header) {
        notice_lang(s_BotServ, u, BOT_BADWORDS_LIST_HEADER, ci->name);
        *sent_header = 1;
    }

    notice_lang(s_BotServ, u, BOT_BADWORDS_LIST_FORMAT, index + 1,
                bw->word,
                ((bw->type ==
                  BW_SINGLE) ? "(SINGLE)" : ((bw->type ==
                                              BW_START) ? "(START)"
                                             : ((bw->type ==
                                                 BW_END) ? "(END)" : "")))
        );
    return 1;
}

static int badwords_list_callback(User * u, int num, va_list args)
{
    ChannelInfo *ci = va_arg(args, ChannelInfo *);
    int *sent_header = va_arg(args, int *);
    if (num < 1 || num > ci->bwcount)
        return 0;
    return badwords_list(u, num - 1, ci, sent_header);
}

static int do_badwords(User * u)
{
    char *chan = strtok(NULL, " ");
    char *cmd = strtok(NULL, " ");
    char *word = strtok(NULL, "");
    ChannelInfo *ci;
    BadWord *bw;

    int i;
    int need_args = (cmd
                     && (!stricmp(cmd, "LIST") || !stricmp(cmd, "CLEAR")));

    if (!cmd || (need_args ? 0 : !word)) {
        syntax_error(s_BotServ, u, "BADWORDS", BOT_BADWORDS_SYNTAX);
    } else if (!(ci = cs_findchan(chan))) {
        notice_lang(s_BotServ, u, CHAN_X_NOT_REGISTERED, chan);
    } else if (ci->flags & CI_VERBOTEN) {
        notice_lang(s_BotServ, u, CHAN_X_FORBIDDEN, chan);
    } else if (!check_access(u, ci, CA_BADWORDS)
               && (!need_args || !is_services_admin(u))) {
        notice_lang(s_BotServ, u, ACCESS_DENIED);
    } else if (stricmp(cmd, "ADD") == 0) {

        char *opt, *pos;
        int type = BW_ANY;

        if (readonly) {
            notice_lang(s_BotServ, u, BOT_BADWORDS_DISABLED);
            return MOD_CONT;
        }

        pos = strrchr(word, ' ');
        if (pos) {
            opt = pos + 1;
            if (*opt) {
                if (!stricmp(opt, "SINGLE"))
                    type = BW_SINGLE;
                else if (!stricmp(opt, "START"))
                    type = BW_START;
                else if (!stricmp(opt, "END"))
                    type = BW_END;
                if (type != BW_ANY)
                    *pos = 0;
            }
        }

        for (bw = ci->badwords, i = 0; i < ci->bwcount; bw++, i++) {
            if (bw->word && !stricmp(bw->word, word)) {
                notice_lang(s_BotServ, u, BOT_BADWORDS_ALREADY_EXISTS,
                            bw->word, ci->name);
                return MOD_CONT;
            }
        }

        for (i = 0; i < ci->bwcount; i++) {
            if (!ci->badwords[i].in_use)
                break;
        }
        if (i == ci->bwcount) {
            if (i < BSBadWordsMax) {
                ci->bwcount++;
                ci->badwords =
                    srealloc(ci->badwords, sizeof(BadWord) * ci->bwcount);
            } else {
                notice_lang(s_BotServ, u, BOT_BADWORDS_REACHED_LIMIT,
                            BSBadWordsMax);
                return MOD_CONT;
            }
        }
        bw = &ci->badwords[i];
        bw->in_use = 1;
        bw->word = sstrdup(word);
        bw->type = type;

        notice_lang(s_BotServ, u, BOT_BADWORDS_ADDED, bw->word, ci->name);

    } else if (stricmp(cmd, "DEL") == 0) {

        if (readonly) {
            notice_lang(s_BotServ, u, BOT_BADWORDS_DISABLED);
            return MOD_CONT;
        }

        /* Special case: is it a number/list?  Only do search if it isn't. */
        if (isdigit(*word) && strspn(word, "1234567890,-") == strlen(word)) {
            int count, deleted, last = -1;
            deleted =
                process_numlist(word, &count, badwords_del_callback, u, ci,
                                &last);
            if (!deleted) {
                if (count == 1) {
                    notice_lang(s_BotServ, u, BOT_BADWORDS_NO_SUCH_ENTRY,
                                last, ci->name);
                } else {
                    notice_lang(s_BotServ, u, BOT_BADWORDS_NO_MATCH,
                                ci->name);
                }
            } else if (deleted == 1) {
                notice_lang(s_BotServ, u, BOT_BADWORDS_DELETED_ONE,
                            ci->name);
            } else {
                notice_lang(s_BotServ, u, BOT_BADWORDS_DELETED_SEVERAL,
                            deleted, ci->name);
            }
        } else {
            for (i = 0; i < ci->bwcount; i++) {
                if (ci->badwords[i].in_use
                    && !stricmp(ci->badwords[i].word, word))
                    break;
            }
            if (i == ci->bwcount) {
                notice_lang(s_BotServ, u, BOT_BADWORDS_NOT_FOUND, word,
                            chan);
                return MOD_CONT;
            }
            bw = &ci->badwords[i];
            notice_lang(s_BotServ, u, BOT_BADWORDS_DELETED, bw->word,
                        ci->name);
            if (bw->word)
                free(bw->word);
            bw->word = NULL;
            bw->in_use = 0;
        }

    } else if (stricmp(cmd, "LIST") == 0) {
        int sent_header = 0;

        if (ci->bwcount == 0) {
            notice_lang(s_BotServ, u, BOT_BADWORDS_LIST_EMPTY, chan);
            return MOD_CONT;
        }
        if (word && strspn(word, "1234567890,-") == strlen(word)) {
            process_numlist(word, NULL, badwords_list_callback, u, ci,
                            &sent_header);
        } else {
            for (i = 0; i < ci->bwcount; i++) {
                if (!(ci->badwords[i].in_use))
                    continue;
                if (word && ci->badwords[i].word
                    && !match_wild_nocase(word, ci->badwords[i].word))
                    continue;
                badwords_list(u, i, ci, &sent_header);
            }
        }
        if (!sent_header)
            notice_lang(s_BotServ, u, BOT_BADWORDS_NO_MATCH, chan);

    } else if (stricmp(cmd, "CLEAR") == 0) {

        if (readonly) {
            notice_lang(s_BotServ, u, BOT_BADWORDS_DISABLED);
            return MOD_CONT;
        }

        for (i = 0; i < ci->bwcount; i++)
            if (ci->badwords[i].word)
                free(ci->badwords[i].word);

        free(ci->badwords);
        ci->badwords = NULL;
        ci->bwcount = 0;

        notice_lang(s_BotServ, u, BOT_BADWORDS_CLEAR);

    } else {
        syntax_error(s_BotServ, u, "BADWORDS", BOT_BADWORDS_SYNTAX);
    }
    return MOD_CONT;
}

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

static int do_say(User * u)
{
    ChannelInfo *ci;

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

    if (!chan || !text)
        syntax_error(s_BotServ, u, "SAY", BOT_SAY_SYNTAX);
    else if (!(ci = cs_findchan(chan)))
        notice_lang(s_BotServ, u, CHAN_X_NOT_REGISTERED, chan);
    else if (ci->flags & CI_VERBOTEN)
        notice_lang(s_BotServ, u, CHAN_X_FORBIDDEN, chan);
    else if (!ci->bi)
        notice_help(s_BotServ, u, BOT_NOT_ASSIGNED);
    else if (!ci->c || ci->c->usercount < BSMinUsers)
        notice_lang(s_BotServ, u, BOT_NOT_ON_CHANNEL, ci->name);
    else if (!check_access(u, ci, CA_SAY))
        notice_lang(s_BotServ, u, ACCESS_DENIED);
    else {
        if (text[0] != '\001') {
            send_cmd(ci->bi->nick, "PRIVMSG %s :%s", ci->name, text);
            ci->bi->lastmsg = time(NULL);
            if (logchan && LogBot)
                send_cmd(ci->bi->nick, "PRIVMSG %s :SAY %s %s %s",
                         LogChannel, u->nick, ci->name, text);
        } else {
            syntax_error(s_BotServ, u, "SAY", BOT_SAY_SYNTAX);
        }
    }
    return MOD_CONT;
}

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

static int do_act(User * u)
{
    ChannelInfo *ci;

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

    if (!chan || !text)
        syntax_error(s_BotServ, u, "ACT", BOT_ACT_SYNTAX);
    else if (!(ci = cs_findchan(chan)))
        notice_lang(s_BotServ, u, CHAN_X_NOT_REGISTERED, chan);
    else if (ci->flags & CI_VERBOTEN)
        notice_lang(s_BotServ, u, CHAN_X_FORBIDDEN, chan);
    else if (!ci->bi)
        notice_help(s_BotServ, u, BOT_NOT_ASSIGNED);
    else if (!ci->c || ci->c->usercount < BSMinUsers)
        notice_lang(s_BotServ, u, BOT_NOT_ON_CHANNEL, ci->name);
    else if (!check_access(u, ci, CA_SAY))
        notice_lang(s_BotServ, u, ACCESS_DENIED);
    else {
        send_cmd(ci->bi->nick, "PRIVMSG %s :%cACTION %s%c", ci->name, 1,
                 text, 1);
        ci->bi->lastmsg = time(NULL);
        if (logchan && LogBot)
            send_cmd(ci->bi->nick, "PRIVMSG %s :ACT %s %s %s", LogChannel,
                     u->nick, ci->name, text);
    }
    return MOD_CONT;
}

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