/* Channel-handling routines. * * IRC Services is copyright (c) 1996-2007 Andrew Church. * E-mail: * Parts written by Andrew Kempe and others. * This program is free but copyrighted software; see the file COPYING for * details. */ #include "services.h" #include "modules.h" /*************************************************************************/ #define HASHFUNC(key) (hashlookup[(uint8)((key)[1])]<<5 \ | ((key)[1] ? hashlookup[(uint8)((key)[2])] : 0)) #define HASHSIZE 1024 #include "hash.h" void add_channel(Channel *chan); /* to avoid "no prototype" warning */ void del_channel(Channel *chan); /* same */ DEFINE_HASH(channel, Channel, name) static int cb_create = -1; static int cb_delete = -1; static int cb_join = -1; static int cb_join_check = -1; static int cb_mode = -1; static int cb_mode_change = -1; static int cb_umode_change = -1; static int cb_topic = -1; /*************************************************************************/ /*************************************************************************/ int channel_init(int ac, char **av) { cb_create = register_callback(NULL, "channel create"); cb_delete = register_callback(NULL, "channel delete"); cb_join = register_callback(NULL, "channel JOIN"); cb_join_check = register_callback(NULL, "channel JOIN check"); cb_mode = register_callback(NULL, "channel MODE"); cb_mode_change = register_callback(NULL, "channel mode change"); cb_umode_change = register_callback(NULL, "channel umode change"); cb_topic = register_callback(NULL, "channel TOPIC"); if (cb_create < 0 || cb_delete < 0 || cb_join < 0 || cb_join_check < 0 || cb_mode < 0 || cb_mode_change < 0 || cb_umode_change < 0 || cb_topic < 0 ) { log("channel_init: register_callback() failed\n"); return 0; } return 1; } /*************************************************************************/ void channel_cleanup() { Channel *c; for (c = first_channel(); c; c = next_channel()) del_channel(c); unregister_callback(NULL, cb_topic); unregister_callback(NULL, cb_umode_change); unregister_callback(NULL, cb_mode_change); unregister_callback(NULL, cb_mode); unregister_callback(NULL, cb_join_check); unregister_callback(NULL, cb_join); unregister_callback(NULL, cb_delete); unregister_callback(NULL, cb_create); } /*************************************************************************/ /* Return statistics. Pointers are assumed to be valid. */ void get_channel_stats(long *nrec, long *memuse) { long count = 0, mem = 0; Channel *chan; struct c_userlist *cu; int i; for (chan = first_channel(); chan; chan = next_channel()) { count++; mem += sizeof(*chan); if (chan->topic) mem += strlen(chan->topic)+1; if (chan->key) mem += strlen(chan->key)+1; ARRAY_FOREACH (i, chan->bans) { mem += sizeof(char *); if (chan->bans[i]) mem += strlen(chan->bans[i])+1; } ARRAY_FOREACH (i, chan->excepts) { mem += sizeof(char *); if (chan->excepts[i]) mem += strlen(chan->excepts[i])+1; } LIST_FOREACH (cu, chan->users) mem += sizeof(*cu); } *nrec = count; *memuse = mem; } /*************************************************************************/ /*************************************************************************/ /* Add/remove a user to/from a channel, creating or deleting the channel as * necessary. If creating the channel, restore mode lock and topic as * necessary. Also check for auto-opping and auto-voicing. If a mode is * given, it is assumed to have been set by the remote server. * Returns the Channel structure for the given channel, or NULL if the user * was refused access to the channel (by the join check callback). */ Channel *chan_adduser(User *user, const char *chan, int32 modes) { Channel *c = get_channel(chan); int newchan = !c; struct c_userlist *u; if (call_callback_2(NULL, cb_join_check, chan, user) > 0) return NULL; if (newchan) { if (debug) log("debug: Creating channel %s", chan); /* Allocate pre-cleared memory */ c = scalloc(sizeof(Channel), 1); strscpy(c->name, chan, sizeof(c->name)); c->creation_time = time(NULL); add_channel(c); call_callback_3(NULL, cb_create, c, user, modes); } u = smalloc(sizeof(struct c_userlist)); LIST_INSERT(u, c->users); u->user = user; u->mode = modes; u->flags = 0; call_callback_2(NULL, cb_join, c, u); return c; } void chan_deluser(User *user, Channel *c) { struct c_userlist *u; int i; LIST_SEARCH_SCALAR(c->users, user, user, u); if (!u) { log("channel: BUG: chan_deluser() called for %s in %s but they " "were not found on the channel's userlist.", user->nick, c->name); return; } LIST_REMOVE(u, c->users); free(u); if (!c->users) { if (debug) log("debug: Deleting channel %s", c->name); call_callback_1(NULL, cb_delete, c); set_cmode(NULL, c); /* make sure nothing's left buffered */ free(c->topic); free(c->key); free(c->link); free(c->flood); for (i = 0; i < c->bans_count; i++) free(c->bans[i]); free(c->bans); for (i = 0; i < c->excepts_count; i++) free(c->excepts[i]); free(c->excepts); del_channel(c); free(c); } } /*************************************************************************/ /* Search for the given ban on the given channel, and return the index into * chan->bans[] if found, -1 otherwise. Nicknames are compared using * irc_stricmp(), usernames and hostnames using stricmp(). */ static int find_ban(const Channel *chan, const char *ban) { char *s, *t; int i; t = strchr(ban, '!'); i = 0; ARRAY_FOREACH (i, chan->bans) { s = strchr(chan->bans[i], '!'); if (s && t) { if (s-(chan->bans[i]) == t-ban && irc_strnicmp(chan->bans[i], ban, s-(chan->bans[i])) == 0 && stricmp(s+1, t+1) == 0 ) { return i; } } else if (!s && !t) { /* Bans without '!' should be impossible; just * do a case-insensitive compare */ if (stricmp(chan->bans[i], ban) == 0) return i; } } return -1; } /*************************************************************************/ /* Search for the given ban (case-insensitive) on the channel; return 1 if * it exists, 0 if not. */ int chan_has_ban(const char *chan, const char *ban) { Channel *c = get_channel(chan); if (!c) return 0; return find_ban(c, ban) >= 0; } /*************************************************************************/ /* Handle a channel MODE command. * When called internally to modify channel modes, callers may assume that * the contents of the argument strings will not be modified. */ static void do_cumode(const char *source, Channel *chan, int32 flag, int add, const char *nick); void do_cmode(const char *source, int ac, char **av) { Channel *chan; char *s; int add = 1; /* 1 if adding modes, 0 if deleting */ char *modestr; chan = get_channel(av[0]); if (!chan) { log("channel: MODE %s for nonexistent channel %s", merge_args(ac-1, av+1), av[0]); return; } if ((protocol_features & PF_MODETS_FIRST) && isdigit(av[1][0])) { ac--; av++; } modestr = av[1]; if (!NoBouncyModes) { /* Count identical server mode changes per second (mode bounce check)*/ /* Doesn't trigger on +/-[bov] or other multiply-settable modes */ static char multimodes[BUFSIZE]; if (!*multimodes) { int i = 0; i = snprintf(multimodes, sizeof(multimodes), "%s", chanmode_multiple); snprintf(multimodes+i, sizeof(multimodes)-i, "%s", mode_flags_to_string(MODE_ALL,MODE_CHANUSER)); } if (strchr(source, '.') && strcmp(source, ServerName) != 0 && !modestr[strcspn(modestr, multimodes)] ) { static char lastmodes[BUFSIZE]; if (time(NULL) != chan->server_modetime || strcmp(modestr, lastmodes) != 0 ) { chan->server_modecount = 0; chan->server_modetime = time(NULL); strscpy(lastmodes, modestr, sizeof(lastmodes)); } chan->server_modecount++; } } s = modestr; ac -= 2; av += 2; while (*s) { char modechar = *s++; int32 flag; int params; if (modechar == '+') { add = 1; continue; } else if (modechar == '-') { add = 0; continue; } else if (add < 0) { continue; } /* Check for it as a channel user mode */ flag = mode_char_to_flag(modechar, MODE_CHANUSER); if (flag) { if (--ac < 0) { log("channel: MODE %s %s: missing parameter for %c%c", chan->name, modestr, add ? '+' : '-', modechar); break; } do_cumode(source, chan, flag, add, *av++); continue; } /* Nope, must be a regular channel mode */ flag = mode_char_to_flag(modechar, MODE_CHANNEL); if (!flag) continue; if (flag == MODE_INVALID) flag = 0; params = mode_char_to_params(modechar, MODE_CHANNEL); params = (params >> (add*8)) & 0xFF; if (ac < params) { log("channel: MODE %s %s: missing parameter(s) for %c%c", chan->name, modestr, add ? '+' : '-', modechar); break; } if (call_callback_5(NULL, cb_mode, source,chan,modechar,add,av) <= 0) { if (add) chan->mode |= flag; else chan->mode &= ~flag; switch (modechar) { case 'k': free(chan->key); if (add) chan->key = sstrdup(av[0]); else chan->key = NULL; break; case 'l': if (add) chan->limit = atoi(av[0]); else chan->limit = 0; break; case 'b': if (add) { ARRAY_EXTEND(chan->bans); chan->bans[chan->bans_count-1] = sstrdup(av[0]); } else { int i = find_ban(chan, av[0]); if (i >= 0) { free(chan->bans[i]); ARRAY_REMOVE(chan->bans, i); } else { log("channel: MODE %s -b %s: ban not found", chan->name, *av); } } break; } /* switch (modechar) */ } /* if (callback() <= 0) */ ac -= params; av += params; } /* while (*s) */ call_callback_2(NULL, cb_mode_change, source, chan); } /* Modify a user's CUMODE. */ static void do_cumode(const char *source, Channel *chan, int32 flag, int add, const char *nick) { struct c_userlist *u; User *user; int32 oldmode; user = get_user(nick); if (!user) { log("channel: MODE %s %c%c for nonexistent user %s", chan->name, add ? '+' : '-', mode_flag_to_char(flag, MODE_CHANUSER), nick); return; } LIST_SEARCH_SCALAR(chan->users, user, user, u); if (!u) { log("channel: MODE %s %c%c for user %s not on channel", chan->name, add ? '+' : '-', mode_flag_to_char(flag, MODE_CHANUSER), nick); return; } oldmode = u->mode; if (flag == MODE_INVALID) return; if (add) u->mode |= flag; else u->mode &= ~flag; call_callback_4(NULL, cb_umode_change, source, chan, u, oldmode); } /*************************************************************************/ /* Handle a TOPIC command. */ void do_topic(const char *source, int ac, char **av) { Channel *c = get_channel(av[0]); const char *topic; char *s; if (!c) { log("channel: TOPIC %s for nonexistent channel %s", merge_args(ac-1, av+1), av[0]); return; } s = strchr(av[1], '!'); if (s) *s = 0; if (ac > 3) topic = av[3]; else topic = ""; if (call_callback_4(NULL, cb_topic, c, topic, av[1], strtotime(av[2],NULL)) > 0) return; strscpy(c->topic_setter, av[1], sizeof(c->topic_setter)); if (c->topic) { free(c->topic); c->topic = NULL; } if (ac > 3 && *av[3]) c->topic = sstrdup(av[3]); } /*************************************************************************/