diff options
author | svn svn@31f1291d-b8d6-0310-a050-a5561fc1590b <svn svn@31f1291d-b8d6-0310-a050-a5561fc1590b@5417fbe8-f217-4b02-8779-1006273d7864> | 2004-03-28 21:59:56 +0000 |
---|---|---|
committer | svn svn@31f1291d-b8d6-0310-a050-a5561fc1590b <svn svn@31f1291d-b8d6-0310-a050-a5561fc1590b@5417fbe8-f217-4b02-8779-1006273d7864> | 2004-03-28 21:59:56 +0000 |
commit | 55bf4dbcabf378e9472b7d31d6edf87f6ac853e9 (patch) | |
tree | 7a9454ea6b8750256e242cf6d5fba3ca7a4b5044 /botserv.c |
Initial Anope Import
git-svn-id: svn://svn.anope.org/anope/trunk@1 31f1291d-b8d6-0310-a050-a5561fc1590b
git-svn-id: http://anope.svn.sourceforge.net/svnroot/anope/trunk@1 5417fbe8-f217-4b02-8779-1006273d7864
Diffstat (limited to 'botserv.c')
-rw-r--r-- | botserv.c | 2512 |
1 files changed, 2512 insertions, 0 deletions
diff --git a/botserv.c b/botserv.c new file mode 100644 index 000000000..ddbfdf856 --- /dev/null +++ b/botserv.c @@ -0,0 +1,2512 @@ +/* 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,v 1.46 2004/03/14 14:15:34 certus Exp $ + * + */ + +/*************************************************************************/ + +#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; +} + +/*************************************************************************/ |