/* Channel-handling routines. * * (C) 2003-2005 Anope Team * Contact us at info@anope.org * * Please read COPYING and README for further details. * * Based on the original code of Epona by Lara. * Based on the original code of Services by Andy Church. * * $Id$ * */ #include "services.h" #include "language.h" Channel *chanlist[1024]; #define HASH(chan) ((chan)[1] ? ((chan)[1]&31)<<5 | ((chan)[2]&31) : 0) /**************************** External Calls *****************************/ /*************************************************************************/ void chan_deluser(User * user, Channel * c) { struct c_userlist *u; if (c->ci) update_cs_lastseen(user, c->ci); for (u = c->users; u && u->user != user; u = u->next); if (!u) return; if (u->ud) { if (u->ud->lastline) free(u->ud->lastline); free(u->ud); } if (u->next) u->next->prev = u->prev; if (u->prev) u->prev->next = u->next; else c->users = u->next; free(u); c->usercount--; if (s_BotServ && c->ci && c->ci->bi && c->usercount == BSMinUsers - 1) { anope_cmd_part(c->ci->bi->nick, c->name, NULL); } if (!c->users) chan_delete(c); } /*************************************************************************/ /* Returns a fully featured binary modes string. If complete is 0, the * eventual parameters won't be added to the string. */ char *chan_get_modes(Channel * chan, int complete, int plus) { static char res[BUFSIZE]; char *end = res; if (chan->mode) { int n = 0; CBModeInfo *cbmi = cbmodeinfos; do { if (chan->mode & cbmi->flag) *end++ = cbmi->mode; } while ((++cbmi)->flag != 0 && ++n < sizeof(res) - 1); if (complete) { cbmi = cbmodeinfos; do { if (cbmi->getvalue && (chan->mode & cbmi->flag) && (plus || !(cbmi->flags & CBM_MINUS_NO_ARG))) { char *value = cbmi->getvalue(chan); if (value) { *end++ = ' '; while (*value) *end++ = *value++; } } } while ((++cbmi)->flag != 0 && ++n < sizeof(res) - 1); } } *end = 0; return res; } /*************************************************************************/ /* Retrieves the status of an user on a channel */ int chan_get_user_status(Channel * chan, User * user) { struct u_chanlist *uc; for (uc = user->chans; uc; uc = uc->next) if (uc->chan == chan) return uc->status; return 0; } /*************************************************************************/ /* Has the given user the given status on the given channel? :p */ int chan_has_user_status(Channel * chan, User * user, int16 status) { struct u_chanlist *uc; for (uc = user->chans; uc; uc = uc->next) { if (uc->chan == chan) { if (debug) { alog("debug: chan_has_user_status wanted %d the user is %d", status, uc->status); } return (uc->status & status); } } return 0; } /*************************************************************************/ /* Remove the status of an user on a channel */ void chan_remove_user_status(Channel * chan, User * user, int16 status) { struct u_chanlist *uc; if (debug >= 2) alog("debug: removing user status (%d) from %s for %s", status, user->nick, chan->name); for (uc = user->chans; uc; uc = uc->next) { if (uc->chan == chan) { uc->status &= ~status; break; } } } /*************************************************************************/ void chan_set_modes(const char *source, Channel * chan, int ac, char **av, int check) { int add = 1; char *modes = av[0], mode; CBMode *cbm; CMMode *cmm; CUMode *cum; unsigned char botmode = 0; BotInfo *bi; User *user; int i, real_ac = ac; char **real_av = av; if (debug) alog("debug: Changing modes for %s to %s", chan->name, merge_args(ac, av)); ac--; while ((mode = *modes++)) { switch (mode) { case '+': add = 1; continue; case '-': add = 0; continue; } if (((int) mode) < 0) { if (debug) alog("Debug: Malformed mode detected on %s.", chan->name); continue; } if ((cum = &cumodes[(int) mode])->status != 0) { if (ac == 0) { alog("channel: mode %c%c with no parameter (?) for channel %s", add ? '+' : '-', mode, chan->name); continue; } ac--; av++; if ((cum->flags & CUF_PROTECT_BOTSERV) && !add) { if ((bi = findbot(*av))) { if (!botmode || botmode != mode) { anope_cmd_mode(bi->nick, chan->name, "+%c %s", mode, bi->nick); botmode = mode; continue; } else { botmode = mode; continue; } } } else { if ((bi = findbot(*av))) { continue; } } if (!(user = finduser(*av))) { if (debug) { alog("debug: MODE %s %c%c for nonexistent user %s", chan->name, (add ? '+' : '-'), mode, *av); } continue; } if (debug) alog("debug: Setting %c%c on %s for %s", (add ? '+' : '-'), mode, chan->name, user->nick); if (add) chan_set_user_status(chan, user, cum->status); else chan_remove_user_status(chan, user, cum->status); } else if ((cbm = &cbmodes[(int) mode])->flag != 0) { if (check >= 0) { if (add) chan->mode |= cbm->flag; else chan->mode &= ~cbm->flag; } if (cbm->setvalue) { if (add || !(cbm->flags & CBM_MINUS_NO_ARG)) { if (ac == 0) { alog("channel: mode %c%c with no parameter (?) for channel %s", add ? '+' : '-', mode, chan->name); continue; } ac--; av++; } cbm->setvalue(chan, add ? *av : NULL); } if (check < 0) { if (add) chan->mode |= cbm->flag; else chan->mode &= ~cbm->flag; } } else if ((cmm = &cmmodes[(int) mode])->addmask) { if (ac == 0) { alog("channel: mode %c%c with no parameter (?) for channel %s", add ? '+' : '-', mode, chan->name); continue; } ac--; av++; if (add) cmm->addmask(chan, *av); else cmm->delmask(chan, *av); } } if (check > 0) { check_modes(chan); if (check < 2) { /* Walk through all users we've set modes for and see if they are * valid. Invalid modes (like +o with SECUREOPS on) will be removed */ real_ac--; real_av++; for (i = 0; i < real_ac; i++) { if ((user = finduser(*real_av)) && is_on_chan(chan, user)) chan_set_correct_modes(user, chan, 0); real_av++; } } } } /*************************************************************************/ /* Set the status of an user on a channel */ void chan_set_user_status(Channel * chan, User * user, int16 status) { struct u_chanlist *uc; if (debug >= 2) alog("debug: setting user status (%d) on %s for %s", status, user->nick, chan->name); if (HelpChannel && ircd->supporthelper && (status & CUS_OP) && (stricmp(chan->name, HelpChannel) == 0) && (!chan->ci || check_access(user, chan->ci, CA_AUTOOP))) { if (debug) { alog("debug: %s being given +h for having %d status in %s", user->nick, status, chan->name); } common_svsmode(user, "+h", NULL); } for (uc = user->chans; uc; uc = uc->next) { if (uc->chan == chan) { uc->status |= status; break; } } } /*************************************************************************/ /* Return the Channel structure corresponding to the named channel, or NULL * if the channel was not found. chan is assumed to be non-NULL and valid * (i.e. pointing to a channel name of 2 or more characters). */ Channel *findchan(const char *chan) { Channel *c; if (!chan || !*chan) { if (debug) { alog("debug: findchan() called with NULL values"); } return NULL; } if (debug >= 3) alog("debug: findchan(%p)", chan); c = chanlist[HASH(chan)]; while (c) { if (stricmp(c->name, chan) == 0) return c; c = c->next; } if (debug >= 3) alog("debug: findchan(%s) -> %p", chan, (void *) c); return NULL; } /*************************************************************************/ /* Iterate over all channels in the channel list. Return NULL at end of * list. */ static Channel *current; static int next_index; Channel *firstchan(void) { next_index = 0; while (next_index < 1024 && current == NULL) current = chanlist[next_index++]; if (debug >= 3) alog("debug: firstchan() returning %s", current ? current->name : "NULL (end of list)"); return current; } Channel *nextchan(void) { if (current) current = current->next; if (!current && next_index < 1024) { while (next_index < 1024 && current == NULL) current = chanlist[next_index++]; } if (debug >= 3) alog("debug: nextchan() returning %s", current ? current->name : "NULL (end of list)"); return current; } /*************************************************************************/ /* 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; BanData *bd; int i, j; for (i = 0; i < 1024; i++) { for (chan = chanlist[i]; chan; chan = chan->next) { count++; mem += sizeof(*chan); if (chan->topic) mem += strlen(chan->topic) + 1; if (chan->key) mem += strlen(chan->key) + 1; if (ircd->fmode) { if (chan->flood) mem += strlen(chan->flood) + 1; } if (ircd->Lmode) { if (chan->redirect) mem += strlen(chan->redirect) + 1; } mem += sizeof(char *) * chan->bansize; for (j = 0; j < chan->bancount; j++) { if (chan->bans[j]) mem += strlen(chan->bans[j]) + 1; } if (ircd->except) { mem += sizeof(char *) * chan->exceptsize; for (j = 0; j < chan->exceptcount; j++) { if (chan->excepts[j]) mem += strlen(chan->excepts[j]) + 1; } } if (ircd->invitemode) { mem += sizeof(char *) * chan->invitesize; for (j = 0; j < chan->invitecount; j++) { if (chan->invite[j]) mem += strlen(chan->invite[j]) + 1; } } for (cu = chan->users; cu; cu = cu->next) { mem += sizeof(*cu); if (cu->ud) { mem += sizeof(*cu->ud); if (cu->ud->lastline) mem += strlen(cu->ud->lastline) + 1; } } for (bd = chan->bd; bd; bd = bd->next) { if (bd->mask) mem += strlen(bd->mask) + 1; mem += sizeof(*bd); } } } *nrec = count; *memuse = mem; } /*************************************************************************/ /* Is the given nick on the given channel? */ int is_on_chan(Channel * c, User * u) { struct u_chanlist *uc; for (uc = u->chans; uc; uc = uc->next) if (uc->chan == c) return 1; return 0; } /*************************************************************************/ /* Is the given nick on the given channel? This function supports links. */ User *nc_on_chan(Channel * c, NickCore * nc) { struct c_userlist *u; if (!c || !nc) return NULL; for (u = c->users; u; u = u->next) { if (u->user->na && u->user->na->nc == nc && nick_recognized(u->user)) return u->user; } return NULL; } /*************************************************************************/ /*************************** Message Handling ****************************/ /*************************************************************************/ /* Handle a JOIN command. * av[0] = channels to join */ void do_join(const char *source, int ac, char **av) { User *user; Channel *chan; char *s, *t; struct u_chanlist *c, *nextc; char *channame; if (UseTS6 && ircd->ts6) { user = find_byuid(source); if (!user) user = finduser(source); } else { user = finduser(source); } if (!user) { if (debug) { alog("debug: JOIN from nonexistent user %s: %s", source, merge_args(ac, av)); } return; } t = av[0]; while (*(s = t)) { t = s + strcspn(s, ","); if (*t) *t++ = 0; if (debug) alog("debug: %s joins %s", source, s); if (*s == '0') { c = user->chans; while (c) { nextc = c->next; channame = sstrdup(c->chan->name); send_event(EVENT_PART_CHANNEL, 3, EVENT_START, user->nick, channame); chan_deluser(user, c->chan); send_event(EVENT_PART_CHANNEL, 3, EVENT_STOP, user->nick, channame); free(channame); free(c); c = nextc; } user->chans = NULL; continue; } /* how about not triggering the JOIN event on an actual /part :) -certus */ send_event(EVENT_JOIN_CHANNEL, 3, EVENT_START, source, s); /* Make sure check_kick comes before chan_adduser, so banned users * don't get to see things like channel keys. */ if (check_kick(user, s)) continue; chan = findchan(s); chan = join_user_update(user, chan, s); chan_set_correct_modes(user, chan, 1); send_event(EVENT_JOIN_CHANNEL, 3, EVENT_STOP, source, s); } } /*************************************************************************/ /* Handle a KICK command. * av[0] = channel * av[1] = nick(s) being kicked * av[2] = reason */ void do_kick(const char *source, int ac, char **av) { BotInfo *bi; ChannelInfo *ci; User *user; char *s, *t; struct u_chanlist *c; t = av[1]; while (*(s = t)) { t = s + strcspn(s, ","); if (*t) *t++ = 0; /* If it is the bot that is being kicked, we make it rejoin the * channel and stop immediately. * --lara */ if (s_BotServ && (bi = findbot(s)) && (ci = cs_findchan(av[0]))) { bot_join(ci); continue; } if (UseTS6 && ircd->ts6) { user = find_byuid(s); if (!user) { user = finduser(s); } } else { user = finduser(s); } if (!user) { if (debug) { alog("debug: KICK for nonexistent user %s on %s: %s", s, av[0], merge_args(ac - 2, av + 2)); } continue; } if (debug) { alog("debug: kicking %s from %s", user->nick, av[0]); } for (c = user->chans; c && stricmp(av[0], c->chan->name) != 0; c = c->next); if (c) { send_event(EVENT_CHAN_KICK, 2, user->nick, av[0]); chan_deluser(user, c->chan); if (c->next) c->next->prev = c->prev; if (c->prev) c->prev->next = c->next; else user->chans = c->next; free(c); } } } /*************************************************************************/ /* Handle a PART command. * av[0] = channels to leave * av[1] = reason (optional) */ void do_part(const char *source, int ac, char **av) { User *user; char *s, *t; struct u_chanlist *c; char *channame; if (UseTS6 && ircd->ts6) { user = find_byuid(source); if (!user) user = finduser(source); } else { user = finduser(source); } if (!user) { if (debug) { alog("debug: PART from nonexistent user %s: %s", source, merge_args(ac, av)); } return; } t = av[0]; while (*(s = t)) { t = s + strcspn(s, ","); if (*t) *t++ = 0; if (debug) alog("debug: %s leaves %s", source, s); for (c = user->chans; c && stricmp(s, c->chan->name) != 0; c = c->next); if (c) { if (!c->chan) { alog("user: BUG parting %s: channel entry found but c->chan NULL", s); return; } channame = sstrdup(c->chan->name); send_event(EVENT_PART_CHANNEL, 3, EVENT_START, user->nick, channame); chan_deluser(user, c->chan); if (c->next) c->next->prev = c->prev; if (c->prev) c->prev->next = c->next; else user->chans = c->next; free(c); send_event(EVENT_PART_CHANNEL, 3, EVENT_STOP, user->nick, channame); free(channame); } } } /*************************************************************************/ /* Handle a SJOIN command. On channel creation, syntax is: av[0] = timestamp av[1] = channel name av[2|3|4] = modes \ depends of whether the modes k and l av[3|4|5] = users / are set or not. When a single user joins an (existing) channel, it is: av[0] = timestamp av[1] = user ============================================================ Unreal SJOIN On Services connect there is SJOIN !11LkOb #ircops +nt :@Trystan &*!*@*.aol.com "*@*.home.com av[0] = time stamp (base64) av[1] = channel av[2] = modes av[3] = users + bans + exceptions On Channel Creation or a User joins an existing Luna.NomadIrc.Net SJOIN !11LkW9 #akill :@Trystan Luna.NomadIrc.Net SJOIN !11LkW9 #akill :Trystan` av[0] = time stamp (base64) av[1] = channel av[2] = users */ void do_sjoin(const char *source, int ac, char **av) { Channel *c; User *user; Server *serv; char *s = NULL; char *end, cubuf[7], *end2, *cumodes[6]; int is_sqlined = 0; int ts = 0; int is_created = 0; serv = findserver(servlist, source); if (ircd->sjb64) { ts = base64dects(av[0]); } else { ts = strtoul(av[0], NULL, 10); } /* Double check to avoid unknown modes that need parameters */ if (ac >= 4 && ac <= 6) { c = findchan(av[1]); if (ircd->chansqline) { if (!c) is_sqlined = check_chan_sqline(av[1]); } cubuf[0] = '+'; cumodes[0] = cubuf; /* We make all the users join */ s = av[ac - 1]; /* Users are always the last element */ while (*s) { end = strchr(s, ' '); if (end) *end = 0; end2 = cubuf + 1; if (ircd->sjoinbanchar) { if (*s == ircd->sjoinbanchar) { add_ban(c, myStrGetToken(s, ircd->sjoinbanchar, 1)); if (!end) break; s = end + 1; continue; } } if (ircd->sjoinexchar) { if (*s == ircd->sjoinexchar) { add_exception(c, myStrGetToken(s, ircd->sjoinexchar, 1)); if (!end) break; s = end + 1; continue; } } if (ircd->sjoininvchar) { if (*s == ircd->sjoininvchar) { add_invite(c, myStrGetToken(s, ircd->sjoininvchar, 1)); if (!end) break; s = end + 1; continue; } } while (csmodes[(int) *s] != 0) *end2++ = csmodes[(int) *s++]; *end2 = 0; if (UseTS6 && ircd->ts6) { user = find_byuid(s); if (!user) user = finduser(s); } else { user = finduser(s); } if (!user) { if (debug) { alog("debug: SJOIN for nonexistent user %s on %s", s, av[1]); } return; } if (is_sqlined && !is_oper(user)) { anope_cmd_kick(s_OperServ, av[1], s, "Q-Lined"); } else { if (!check_kick(user, av[1])) { send_event(EVENT_JOIN_CHANNEL, 3, EVENT_START, user->nick, av[1]); /* Make the user join; if the channel does not exist it * will be created there. This ensures that the channel * is not created to be immediately destroyed, and * that the locked key or topic is not shown to anyone * who joins the channel when empty. */ c = join_user_update(user, c, av[1]); /* We update user mode on the channel */ if (end2 - cubuf > 1) { int i; for (i = 1; i < end2 - cubuf; i++) cumodes[i] = user->nick; chan_set_modes(source, c, 1 + (end2 - cubuf - 1), cumodes, 2); } if (c->ci && (!serv || is_sync(serv)) && !c->topic_sync) restore_topic(c->name); chan_set_correct_modes(user, c, 1); send_event(EVENT_JOIN_CHANNEL, 3, EVENT_STOP, user->nick, av[1]); } } if (!end) break; s = end + 1; } if (c) { /* Set the timestamp */ c->creation_time = ts; /* We now update the channel mode. */ chan_set_modes(source, c, ac - 3, &av[2], 2); } /* Unreal just had to be different */ } else if (ac == 3 && !ircd->ts6) { c = findchan(av[1]); if (ircd->chansqline) { if (!c) is_sqlined = check_chan_sqline(av[1]); } cubuf[0] = '+'; cumodes[0] = cubuf; /* We make all the users join */ s = av[2]; /* Users are always the last element */ while (*s) { end = strchr(s, ' '); if (end) *end = 0; end2 = cubuf + 1; while (csmodes[(int) *s] != 0) *end2++ = csmodes[(int) *s++]; *end2 = 0; if (UseTS6 && ircd->ts6) { user = find_byuid(s); if (!user) user = finduser(s); } else { user = finduser(s); } if (!user) { if (debug) { alog("debug: SJOIN for nonexistent user %s on %s", s, av[1]); } return; } if (is_sqlined && !is_oper(user)) { anope_cmd_kick(s_OperServ, av[1], s, "Q-Lined"); } else { if (!check_kick(user, av[1])) { send_event(EVENT_JOIN_CHANNEL, 3, EVENT_START, user->nick, av[1]); /* Make the user join; if the channel does not exist it * will be created there. This ensures that the channel * is not created to be immediately destroyed, and * that the locked key or topic is not shown to anyone * who joins the channel when empty. */ c = join_user_update(user, c, av[1]); /* We update user mode on the channel */ if (end2 - cubuf > 1) { int i; for (i = 1; i < end2 - cubuf; i++) cumodes[i] = user->nick; chan_set_modes(source, c, 1 + (end2 - cubuf - 1), cumodes, 2); } chan_set_correct_modes(user, c, 1); send_event(EVENT_JOIN_CHANNEL, 3, EVENT_STOP, user->nick, av[1]); } } if (!end) break; s = end + 1; } if (c) { c->creation_time = ts; } } else if (ac == 3 && ircd->ts6) { c = findchan(av[1]); if (ircd->chansqline) { if (!c) is_sqlined = check_chan_sqline(av[1]); } cubuf[0] = '+'; cumodes[0] = cubuf; /* We make all the users join */ s = sstrdup(source); /* Users are always the last element */ while (*s) { end = strchr(s, ' '); if (end) *end = 0; end2 = cubuf + 1; while (csmodes[(int) *s] != 0) *end2++ = csmodes[(int) *s++]; *end2 = 0; if (UseTS6 && ircd->ts6) { user = find_byuid(s); if (!user) user = finduser(s); } else { user = finduser(s); } if (!user) { if (debug) { alog("debug: SJOIN for nonexistent user %s on %s", s, av[1]); } free(s); return; } if (is_sqlined && !is_oper(user)) { anope_cmd_kick(s_OperServ, av[1], s, "Q-Lined"); } else { if (!check_kick(user, av[1])) { send_event(EVENT_JOIN_CHANNEL, 3, EVENT_START, user->nick, av[1]); /* Make the user join; if the channel does not exist it * will be created there. This ensures that the channel * is not created to be immediately destroyed, and * that the locked key or topic is not shown to anyone * who joins the channel when empty. */ c = join_user_update(user, c, av[1]); /* We update user mode on the channel */ if (end2 - cubuf > 1) { int i; for (i = 1; i < end2 - cubuf; i++) cumodes[i] = user->nick; chan_set_modes(source, c, 1 + (end2 - cubuf - 1), cumodes, 2); } chan_set_correct_modes(user, c, 1); send_event(EVENT_JOIN_CHANNEL, 3, EVENT_STOP, user->nick, av[1]); } } if (!end) break; s = end + 1; } if (c) { c->creation_time = ts; } free(s); } else if (ac == 2) { if (UseTS6 && ircd->ts6) { user = find_byuid(source); if (!user) user = finduser(s); } else { user = finduser(source); } if (!user) { if (debug) { alog("debug: SJOIN for nonexistent user %s on %s", source, av[1]); } return; } if (check_kick(user, av[1])) return; c = findchan(av[1]); if (!c) is_created = 1; if (ircd->chansqline) { if (!c) is_sqlined = check_chan_sqline(av[1]); } if (is_sqlined && !is_oper(user)) { anope_cmd_kick(s_OperServ, av[1], user->nick, "Q-Lined"); } else { send_event(EVENT_JOIN_CHANNEL, 3, EVENT_START, user->nick, av[1]); c = join_user_update(user, c, av[1]); c->creation_time = ts; if (is_created && c->ci) restore_topic(c->name); chan_set_correct_modes(user, c, 1); send_event(EVENT_JOIN_CHANNEL, 3, EVENT_STOP, user->nick, av[1]); } } } /*************************************************************************/ /* Handle a channel MODE command. */ void do_cmode(const char *source, int ac, char **av) { Channel *chan; ChannelInfo *ci = NULL; int i, tofree0 = 0, tofree1 = 0, tofree2 = 0; char *t; if (ircdcap->tsmode) { /* TSMODE for bahamut - leave this code out to break MODEs. -GD */ /* if they don't send it in CAPAB check if we just want to enable it */ if (uplink_capab & ircdcap->tsmode || UseTSMODE) { for (i = 0; i < strlen(av[1]); i++) { if (!isdigit(av[1][i])) break; } if (av[1][i] == '\0') { /* We have a valid TS field in av[1] now, so we can strip it off */ /* After we swap av[0] and av[1] ofcourse to not break stuff! :) */ t = av[0]; av[0] = av[1]; av[1] = t; ac--; av++; } else { alog("TSMODE enabled but MODE has no valid TS"); } } } /* :42XAAAAAO TMODE 1106409026 #ircops +b *!*@*.aol.com */ if (UseTS6 && ircd->ts6) { if (*av[0] == '#' || *av[0] == '&') { if (debug) { alog("debug: Before TS6 swap: do_cmode() chan %s : mode %s : extra %s", av[1], av[2], av[3]); } if (ac >= 2) { av[0] = sstrdup(av[1]); tofree0 = 1; } else { av[0] = NULL; } if (ac >= 3) { av[1] = sstrdup(av[2]); tofree1 = 1; } else { av[1] = NULL; } if (ac >= 4) { av[2] = sstrdup(av[3]); tofree2 = 1; } else { av[2] = NULL; } if (debug) { alog("debug: After TS swap: do_cmode() chan %s : mode %s : extra %s", av[0], av[1], av[2]); } } else { if (debug) { alog("debug: TS swap not needed: do_cmode() chan %s : mode %s : extra %s", av[0], av[1], av[2]); } } } chan = findchan(av[0]); if (!chan) { ci = cs_findchan(av[0]); if (!(ci && (ci->flags & CI_VERBOTEN))) if (debug) { alog("debug: MODE %s for nonexistent channel %s", merge_args(ac - 1, av + 1), av[0]); } if (tofree0) free(av[0]); if (tofree1) free(av[1]); if (tofree2) free(av[2]); return; } /* This shouldn't trigger on +o, etc. */ if (strchr(source, '.') && !av[1][strcspn(av[1], "bovahq")]) { if (time(NULL) != chan->server_modetime) { chan->server_modecount = 0; chan->server_modetime = time(NULL); } chan->server_modecount++; } ac--; av++; chan_set_modes(source, chan, ac, av, 1); if (tofree0) free(av[0]); if (tofree1) free(av[1]); if (tofree2) free(av[2]); } /*************************************************************************/ /* Handle a TOPIC command. */ void do_topic(const char *source, int ac, char **av) { Channel *c = findchan(av[0]); ChannelInfo *ci; int ts; time_t topic_time; char *topicsetter; if (ircd->sjb64) { ts = base64dects(av[2]); if (debug) { alog("debug: encoded TOPIC TS %s converted to %d", av[2], ts); } } else { ts = strtoul(av[2], NULL, 10); } topic_time = ts; if (!c) { if (debug) { alog("debug: TOPIC %s for nonexistent channel %s", merge_args(ac - 1, av + 1), av[0]); } return; } /* We can be sure that the topic will be in sync here -GD */ c->topic_sync = 1; ci = c->ci; /* For Unreal, cut off the ! and any futher part of the topic setter. * This way, nick!ident@host setters will only show the nick. -GD */ topicsetter = myStrGetToken(av[1], '!', 0); /* If the current topic we have matches the last known topic for this * channel exactly, there's no need to update anything and we can as * well just return silently without updating anything. -GD */ if ((ac > 3) && *av[3] && ci && ci->last_topic && (strcmp(av[3], ci->last_topic) == 0) && (strcmp(topicsetter, ci->last_topic_setter) == 0)) { free(topicsetter); return; } if (check_topiclock(c, topic_time)) { free(topicsetter); return; } if (c->topic) { free(c->topic); c->topic = NULL; } if (ac > 3 && *av[3]) { c->topic = sstrdup(av[3]); } strscpy(c->topic_setter, topicsetter, sizeof(c->topic_setter)); c->topic_time = topic_time; free(topicsetter); record_topic(av[0]); if (ci && ci->last_topic) { send_event(EVENT_TOPIC_UPDATED, 2, av[0], ci->last_topic); } else { send_event(EVENT_TOPIC_UPDATED, 2, av[0], ""); } } /*************************************************************************/ /**************************** Internal Calls *****************************/ /*************************************************************************/ void add_ban(Channel * chan, char *mask) { /* check for NULL values otherwise we will segfault */ if (!chan || !mask) { if (debug) { alog("debug: add_ban called with NULL values"); } return; } if (s_BotServ && BSSmartJoin && chan->ci && chan->ci->bi && chan->usercount >= BSMinUsers) { char botmask[BUFSIZE]; BotInfo *bi = chan->ci->bi; snprintf(botmask, sizeof(botmask), "%s!%s@%s", bi->nick, bi->user, bi->host); if (match_wild_nocase(mask, botmask)) { anope_cmd_mode(bi->nick, chan->name, "-b %s", mask); return; } } if (chan->bancount >= chan->bansize) { chan->bansize += 8; chan->bans = srealloc(chan->bans, sizeof(char *) * chan->bansize); } chan->bans[chan->bancount++] = sstrdup(mask); if (debug) alog("debug: Added ban %s to channel %s", mask, chan->name); } /*************************************************************************/ void add_exception(Channel * chan, char *mask) { if (!chan || !mask) { if (debug) alog("debug: add_exception called with NULL values"); return; } if (chan->exceptcount >= chan->exceptsize) { chan->exceptsize += 8; chan->excepts = srealloc(chan->excepts, sizeof(char *) * chan->exceptsize); } chan->excepts[chan->exceptcount++] = sstrdup(mask); if (debug) alog("debug: Added except %s to channel %s", mask, chan->name); } /*************************************************************************/ void add_invite(Channel * chan, char *mask) { if (!chan || !mask) { if (debug) alog("debug: add_invite called with NULL values"); return; } if (chan->invitecount >= chan->invitesize) { chan->invitesize += 8; chan->invite = srealloc(chan->invite, sizeof(char *) * chan->invitesize); } chan->invite[chan->invitecount++] = sstrdup(mask); if (debug) alog("debug: Added invite %s to channel %s", mask, chan->name); } /*************************************************************************/ /** * Set the correct modes, or remove the ones granted without permission, * for the specified user on ths specified channel. This doesn't give * modes to ignored users, but does remove them if needed. * @param user The user to give/remove modes to/from * @param c The channel to give/remove modes on * @param give_modes Set to 1 to give modes, 0 to not give modes * @return void **/ void chan_set_correct_modes(User * user, Channel * c, int give_modes) { char *tmp; char modebuf[BUFSIZE]; char userbuf[BUFSIZE]; int status; int add_modes = 0; int rem_modes = 0; ChannelInfo *ci; if (!c || !(ci = c->ci)) return; if ((ci->flags & CI_VERBOTEN) || (*(c->name) == '+')) return; status = chan_get_user_status(c, user); if (debug) alog("debug: Setting correct user modes for %s on %s (current status: %d, %sgiving modes)", user->nick, c->name, status, (give_modes ? "" : "not ")); if (give_modes && (get_ignore(user->nick) == NULL)) { if (ircd->owner && is_founder(user, ci)) add_modes |= CUS_OWNER; else if ((ircd->protect || ircd->admin) && check_access(user, ci, CA_AUTOPROTECT)) add_modes |= CUS_PROTECT; if (check_access(user, ci, CA_AUTOOP)) add_modes |= CUS_OP; else if (ircd->halfop && check_access(user, ci, CA_AUTOHALFOP)) add_modes |= CUS_HALFOP; else if (check_access(user, ci, CA_AUTOVOICE)) add_modes |= CUS_VOICE; } /* We check if every mode they have is legally acquired here, and remove * the modes that they're not allowed to have. But only if SECUREOPS is * on, because else every mode is legal. -GD * Unless the channel has just been created. -heinz */ if (((ci->flags & CI_SECUREOPS) || (c->usercount == 1)) && !is_ulined(user->server->name)) { if (ircd->owner && (status & CUS_OWNER) && !is_founder(user, ci)) rem_modes |= CUS_OWNER; if ((ircd->protect || ircd->admin) && (status & CUS_PROTECT) && !check_access(user, ci, CA_AUTOPROTECT) && !check_access(user, ci, CA_PROTECTME)) rem_modes |= CUS_PROTECT; if ((status & CUS_OP) && !check_access(user, ci, CA_AUTOOP) && !check_access(user, ci, CA_OPDEOPME)) rem_modes |= CUS_OP; if (ircd->halfop && (status & CUS_HALFOP) && !check_access(user, ci, CA_AUTOHALFOP) && !check_access(user, ci, CA_HALFOPME)) rem_modes |= CUS_HALFOP; } /* No modes to add or remove, exit function -GD */ if (!add_modes && !rem_modes) return; /* No need for strn* functions for modebuf, as every possible string * will always fit in. -GD */ strcpy(modebuf, ""); strcpy(userbuf, ""); if (add_modes > 0) { strcat(modebuf, "+"); if ((add_modes & CUS_OWNER) && !(status & CUS_OWNER)) { tmp = stripModePrefix(ircd->ownerset); strcat(modebuf, tmp); free(tmp); strcat(userbuf, " "); strcat(userbuf, user->nick); } else { add_modes &= ~CUS_OWNER; } if ((add_modes & CUS_PROTECT) && !(status & CUS_PROTECT)) { tmp = stripModePrefix(ircd->adminset); strcat(modebuf, tmp); free(tmp); strcat(userbuf, " "); strcat(userbuf, user->nick); } else { add_modes &= ~CUS_PROTECT; } if ((add_modes & CUS_OP) && !(status & CUS_OP)) { strcat(modebuf, "o"); strcat(userbuf, " "); strcat(userbuf, user->nick); } else { add_modes &= ~CUS_OP; } if ((add_modes & CUS_HALFOP) && !(status & CUS_HALFOP)) { strcat(modebuf, "h"); strcat(userbuf, " "); strcat(userbuf, user->nick); } else { add_modes &= ~CUS_HALFOP; } if ((add_modes & CUS_VOICE) && !(status & CUS_VOICE)) { strcat(modebuf, "v"); strcat(userbuf, " "); strcat(userbuf, user->nick); } else { add_modes &= ~CUS_VOICE; } } if (rem_modes > 0) { strcat(modebuf, "-"); if (rem_modes & CUS_OWNER) { tmp = stripModePrefix(ircd->ownerset); strcat(modebuf, tmp); free(tmp); strcat(userbuf, " "); strcat(userbuf, user->nick); } if (rem_modes & CUS_PROTECT) { tmp = stripModePrefix(ircd->adminset); strcat(modebuf, tmp); free(tmp); strcat(userbuf, " "); strcat(userbuf, user->nick); } if (rem_modes & CUS_OP) { strcat(modebuf, "o"); strcat(userbuf, " "); strcat(userbuf, user->nick); } if (rem_modes & CUS_HALFOP) { strcat(modebuf, "h"); strcat(userbuf, " "); strcat(userbuf, user->nick); } } /* Here, both can be empty again due to the "isn't it set already?" * checks above. -GD */ if (!add_modes && !rem_modes) return; anope_cmd_mode(whosends(ci), c->name, "%s%s", modebuf, userbuf); if (add_modes > 0) chan_set_user_status(c, user, add_modes); if (rem_modes > 0) chan_remove_user_status(c, user, rem_modes); } /*************************************************************************/ /* 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. */ void chan_adduser2(User * user, Channel * c) { struct c_userlist *u; u = scalloc(sizeof(struct c_userlist), 1); u->next = c->users; if (c->users) c->users->prev = u; c->users = u; u->user = user; c->usercount++; if (get_ignore(user->nick) == NULL) { if (c->ci && (check_access(user, c->ci, CA_MEMO)) && (c->ci->memos.memocount > 0)) { if (c->ci->memos.memocount == 1) { notice_lang(s_MemoServ, user, MEMO_X_ONE_NOTICE, c->ci->memos.memocount, c->ci->name); } else { notice_lang(s_MemoServ, user, MEMO_X_MANY_NOTICE, c->ci->memos.memocount, c->ci->name); } } /* Added channelname to entrymsg - 30.03.2004, Certus */ /* Also, don't send the entrymsg when bursting -GD */ if (c->ci && c->ci->entry_message && is_sync(user->server)) notice_user(whosends(c->ci), user, "[%s] %s", c->name, c->ci->entry_message); } /** * We let the bot join even if it was an ignored user, as if we don't, * and the ignored user dosnt just leave, the bot will never * make it into the channel, leaving the channel botless even for * legit users - Rob **/ if (s_BotServ && c->ci && c->ci->bi) { if (c->usercount == BSMinUsers) bot_join(c->ci); if (c->usercount >= BSMinUsers && (c->ci->botflags & BS_GREET) && user->na && user->na->nc->greet && check_access(user, c->ci, CA_GREET)) { /* Only display the greet if the main uplink we're connected * to has synced, or we'll get greet-floods when the net * recovers from a netsplit. -GD */ if (is_sync(user->server)) { anope_cmd_privmsg(c->ci->bi->nick, c->name, "[%s] %s", user->na->nick, user->na->nc->greet); c->ci->bi->lastmsg = time(NULL); } } } } /*************************************************************************/ /* This creates the channel structure (was originally in chan_adduser, but splitted to make it more efficient to use for SJOINs). */ Channel *chan_create(char *chan) { Channel *c; Channel **list; if (debug) alog("debug: Creating channel %s", chan); /* Allocate pre-cleared memory */ c = scalloc(sizeof(Channel), 1); strscpy(c->name, chan, sizeof(c->name)); list = &chanlist[HASH(c->name)]; c->next = *list; if (*list) (*list)->prev = c; *list = c; c->creation_time = time(NULL); /* Store ChannelInfo pointer in channel record */ c->ci = cs_findchan(chan); if (c->ci) c->ci->c = c; /* Restore locked modes and saved topic */ if (c->ci) { check_modes(c); stick_all(c->ci); } if (serv_uplink && is_sync(serv_uplink) && (!(c->topic_sync))) { restore_topic(chan); } return c; } /*************************************************************************/ /* This destroys the channel structure, freeing everything in it. */ void chan_delete(Channel * c) { BanData *bd, *next; int i; if (debug) alog("debug: Deleting channel %s", c->name); for (bd = c->bd; bd; bd = next) { if (bd->mask) free(bd->mask); next = bd->next; free(bd); } if (c->ci) c->ci->c = NULL; if (c->topic) free(c->topic); if (c->key) free(c->key); if (ircd->fmode) { if (c->flood) free(c->flood); } if (ircd->Lmode) { if (c->redirect) free(c->redirect); } for (i = 0; i < c->bancount; ++i) { if (c->bans[i]) free(c->bans[i]); else alog("channel: BUG freeing %s: bans[%d] is NULL!", c->name, i); } if (c->bansize) free(c->bans); if (ircd->except) { for (i = 0; i < c->exceptcount; ++i) { if (c->excepts[i]) free(c->excepts[i]); else alog("channel: BUG freeing %s: excepts[%d] is NULL!", c->name, i); } if (c->exceptsize) free(c->excepts); } if (ircd->invitemode) { for (i = 0; i < c->invitecount; ++i) { if (c->invite[i]) free(c->invite[i]); else alog("channel: BUG freeing %s: invite[%d] is NULL!", c->name, i); } if (c->invitesize) free(c->invite); } if (c->next) c->next->prev = c->prev; if (c->prev) c->prev->next = c->next; else chanlist[HASH(c->name)] = c->next; free(c); } /*************************************************************************/ void del_ban(Channel * chan, char *mask) { char **s = chan->bans; int i = 0; AutoKick *akick; /* Sanity check as it seems some IRCD will just send -b without a mask */ if (!mask) { return; } while (i < chan->bancount && strcmp(*s, mask) != 0) { i++; s++; } if (i < chan->bancount) { chan->bancount--; if (i < chan->bancount) memmove(s, s + 1, sizeof(char *) * (chan->bancount - i)); if (debug) alog("debug: Deleted ban %s from channel %s", mask, chan->name); } if (chan->ci && (akick = is_stuck(chan->ci, mask))) stick_mask(chan->ci, akick); } /*************************************************************************/ void del_exception(Channel * chan, char *mask) { int i; int reset = 0; /* Sanity check as it seems some IRCD will just send -e without a mask */ if (!mask) { return; } for (i = 0; i < chan->exceptcount; i++) { if ((!reset) && (stricmp(chan->excepts[i], mask) == 0)) { free(chan->excepts[i]); reset = 1; } if (reset) chan->excepts[i] = (i == chan->exceptcount) ? NULL : chan->excepts[i + 1]; } if (reset) chan->exceptcount--; if (debug) alog("debug: Deleted except %s to channel %s", mask, chan->name); } /*************************************************************************/ void del_invite(Channel * chan, char *mask) { int i; int reset = 0; /* Sanity check as it seems some IRCD will just send -I without a mask */ if (!mask) { return; } for (i = 0; i < chan->invitecount; i++) { if ((!reset) && (stricmp(chan->invite[i], mask) == 0)) { free(chan->invite[i]); reset = 1; } if (reset) chan->invite[i] = (i == chan->invitecount) ? NULL : chan->invite[i + 1]; } if (reset) chan->invitecount--; if (debug) alog("debug: Deleted invite %s to channel %s", mask, chan->name); } /*************************************************************************/ char *get_flood(Channel * chan) { return chan->flood; } /*************************************************************************/ char *get_key(Channel * chan) { return chan->key; } /*************************************************************************/ char *get_limit(Channel * chan) { static char limit[16]; if (chan->limit == 0) return NULL; snprintf(limit, sizeof(limit), "%lu", (unsigned long int) chan->limit); return limit; } /*************************************************************************/ char *get_redirect(Channel * chan) { return chan->redirect; } /*************************************************************************/ Channel *join_user_update(User * user, Channel * chan, char *name) { struct u_chanlist *c; /* If it's a new channel, so we need to create it first. */ if (!chan) chan = chan_create(name); if (debug) alog("debug: %s joins %s", user->nick, chan->name); c = scalloc(sizeof(*c), 1); c->next = user->chans; if (user->chans) user->chans->prev = c; user->chans = c; c->chan = chan; chan_adduser2(user, chan); return chan; } /*************************************************************************/ void set_flood(Channel * chan, char *value) { if (chan->flood) free(chan->flood); chan->flood = value ? sstrdup(value) : NULL; if (debug) alog("debug: Flood mode for channel %s set to %s", chan->name, chan->flood ? chan->flood : "no flood settings"); } /*************************************************************************/ void set_key(Channel * chan, char *value) { if (chan->key) free(chan->key); chan->key = value ? sstrdup(value) : NULL; if (debug) alog("debug: Key of channel %s set to %s", chan->name, chan->key ? chan->key : "no key"); } /*************************************************************************/ void set_limit(Channel * chan, char *value) { chan->limit = value ? strtoul(value, NULL, 10) : 0; if (debug) alog("debug: Limit of channel %s set to %u", chan->name, chan->limit); } /*************************************************************************/ void set_redirect(Channel * chan, char *value) { if (chan->redirect) free(chan->redirect); chan->redirect = value ? sstrdup(value) : NULL; if (debug) alog("debug: Redirect of channel %s set to %s", chan->name, chan->redirect ? chan->redirect : "no redirect"); } void do_mass_mode(char *modes) { int ac, i; char **av; Channel *c; char *myModes; if (!modes) { return; } /* Prevent modes being altered by split_buf */ myModes = sstrdup(modes); ac = split_buf(myModes, &av, 1); for (i = 0; i < 1024; i++) { for (c = chanlist[i]; c; c = c->next) { if (c->bouncy_modes) { free(av); free(myModes); return; } else { anope_cmd_mode(s_OperServ, c->name, "%s", modes); chan_set_modes(s_OperServ, c, ac, av, 1); } } } free(av); free(myModes); } /*************************************************************************/ void restore_unsynced_topics(void) { Channel *c; int i; for (i = 0; i < 1024; i++) { for (c = chanlist[i]; c; c = c->next) { if (!(c->topic_sync)) restore_topic(c->name); } } } /*************************************************************************/