diff options
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_ULTIMA |
