/* Channel-handling routines. * * (C) 2003-2012 Anope Team * Contact us at team@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. * * */ #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); } /* Channel is permament and shouldn't be deleted */ if (anope_get_permchan_mode() && c->mode & anope_get_permchan_mode()) return; 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)->mode != 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)->mode != 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 *u, *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)); if (UseTS6 && ircd->ts6) { u = find_byuid(source); if (!u) u = finduser(source); } else u = finduser(source); if (u && (chan_get_user_status(chan, u) & CUS_DEOPPED)) { char *s; if (debug) alog("debug: Removing instead of setting due to DEOPPED flag"); /* Swap adding and removing of the modes */ for (s = av[0]; *s; s++) { if (*s == '+') *s = '-'; else if (*s == '-') *s = '+'; } /* Set the resulting mode buffer */ anope_cmd_mode(whosends(chan->ci), chan->name, merge_args(ac, av)); return; } 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 (UseTS6 && ircd->ts6) { user = find_byuid(*av); if (!user) user = finduser(*av); } else user = finduser(*av); if (!user) { 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); /* If this does +o or +h, remove any DEOPPED flag */ if (cum->status & CUS_OP || cum->status & CUS_HALFOP) chan_remove_user_status(chan, user, CUS_DEOPPED); } 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); } } /* Don't bounce modes from u:lined clients or servers, bug #1004. * * We can get UUIDs as well.. don not assume nick ~ Viper */ if (UseTS6 && ircd->ts6) { user = find_byuid(source); if (!user) user = finduser(source); } else user = finduser(source); if ((user && is_ulined(user->server->name)) || is_ulined((char *)source)) return; if (check > 0) { check_modes(chan); if ((check < 2) || (check == 3)) { /* 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 (UseTS6 && ircd->ts6) { user = find_byuid(*real_av); if (!user) user = finduser(*real_av); } else user = finduser(*real_av); if (!user && UseTS6 && ircd->ts6) user = find_byuid(*real_av); if (user && is_on_chan(chan, user)) { if (check < 2) chan_set_correct_modes(user, chan, 0); else if ((chan->ci->flags) && (chan->ci->flags & CI_SECUREOPS)) 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) { if (debug >= 3) alog("debug: findchan(%s) -> %p", chan, (void *) c); 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; 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; } if (ircd->jmode) { if (chan->throttle) mem += strlen(chan->throttle) + 1; } mem += get_memuse(chan->bans); if (ircd->except) { mem += get_memuse(chan->excepts); } if (ircd->invitemode) { mem += get_memuse(chan->invites); } 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; time_t ts = time(NULL); 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 (*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 channel already exists, check_kick() will use correct TS. * Otherwise, we lose. */ if (check_kick(user, s, ts)) continue; if (ac == 2) { ts = strtoul(av[1], NULL, 10); if (debug) { alog("debug: recieved a new TS for JOIN: %ld", (long int) ts); } } chan = findchan(s); chan = join_user_update(user, chan, s, ts); 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; Uid *uid; t = av[1]; while (*(s = t)) { t = s + strcspn(s, ","); if (*t) *t++ = 0; if (ircd->ts6 && UseTS6) { uid = find_nickuid(s); if (uid) s = uid->nick; } /* 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, (ac >= 2 ? 4 : 3), EVENT_START, user->nick, channame, (ac >= 2 ? av[1] : "")); 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, (ac >= 2 ? 4 : 3), EVENT_STOP, user->nick, channame, (ac >= 2 ? av[1] : "")); 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 = NULL; struct c_userlist *cu; char *s = NULL; char *end, cubuf[7], *end2, *cumodes[6], *buf; int is_sqlined = 0; int ts = 0; int is_created = 0; int keep_their_modes = 1; if (UseTS6 && ircd->ts6) serv = findserver_uid(servlist, source); if (!serv) serv = findserver(servlist, source); if (ircd->sjb64) { ts = base64dects(av[0]); } else { ts = strtoul(av[0], NULL, 10); } c = findchan(av[1]); if (c != NULL) { if (c->creation_time == 0 || ts == 0) c->creation_time = 0; else if (c->creation_time > ts) { c->creation_time = ts; for (cu = c->users; cu; cu = cu->next) { /* XXX */ cumodes[0] = "-ov"; cumodes[1] = cu->user->nick; cumodes[2] = cu->user->nick; chan_set_modes(source, c, 3, cumodes, 2); } if (c->ci) { if (c->ci->bi) { /* This is ugly, but it always works */ anope_cmd_part(c->ci->bi->nick, c->name, "TS reop"); bot_join(c->ci); } /* Make sure +r is set */ if (ircd->chanreg && ircd->regmode) { c->mode |= ircd->regmode; anope_cmd_mode(whosends(c->ci), c->name, "+r"); } } /* XXX simple modes and bans */ } else if (c->creation_time < ts) keep_their_modes = 0; } else is_created = 1; /* Double check to avoid unknown modes that need parameters */ if (ac >= 4) { 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 && keep_their_modes) { buf = myStrGetToken(s, ircd->sjoinbanchar, 1); add_ban(c, buf); if (buf) free(buf); if (!end) break; s = end + 1; continue; } } if (ircd->sjoinexchar) { if (*s == ircd->sjoinexchar && keep_their_modes) { buf = myStrGetToken(s, ircd->sjoinexchar, 1); add_exception(c, buf); if (buf) free(buf); if (!end) break; s = end + 1; continue; } } if (ircd->sjoininvchar) { if (*s == ircd->sjoininvchar && keep_their_modes) { buf = myStrGetToken(s, ircd->sjoininvchar, 1); add_invite(c, buf); if (buf) free(buf); 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], ts)) { 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], ts); /* We update user mode on the channel */ if (end2 - cubuf > 1 && keep_their_modes) { 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 && keep_their_modes) { /* 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) { 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], ts)) { 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], ts); /* We update user mode on the channel */ if (end2 - cubuf > 1 && keep_their_modes) { 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; } } else if (ac == 3 && ircd->ts6) { 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], ts)) { 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], ts); /* We update user mode on the channel */ if (end2 - cubuf > 1 && keep_their_modes) { 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; } free(s); } else if (ac == 2) { if (UseTS6 && ircd->ts6) { user = find_byuid(source); if (!user) user = finduser(source); } 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], ts)) return; 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], 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; 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 (isdigit(av[0][0])) { ac--; av++; } } chan = findchan(av[0]); if (!chan) { if (debug) { ci = cs_findchan(av[0]); if (!(ci && (ci->flags & CI_VERBOTEN))) alog("debug: MODE %s for nonexistent channel %s", merge_args(ac - 1, av + 1), av[0]); } 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); } /*************************************************************************/ /* 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 * But we still need to update the topic internally for the channel - Adam */ if ((ac > 3) && *av[3] && ci && ci->last_topic && (strcmp(av[3], ci->last_topic) == 0) && (strcmp(topicsetter, ci->last_topic_setter) == 0)) { if (c->topic) free(c->topic); c->topic = sstrdup(av[3]); strscpy(c->topic_setter, topicsetter, sizeof(c->topic_setter)); c->topic_time = topic_time; 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]); /* Only call events if we are synced with the uplink */ if (serv_uplink && is_sync(serv_uplink)) { 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) { Entry *ban; /* check for NULL values otherwise we will segfault */ if (!chan || !mask) { if (debug) { alog("debug: add_ban called with NULL values"); } return; } /* Check if the list already exists, if not create it. * Create a new ban and add it to the list.. ~ Viper */ if (!chan->bans) chan->bans = list_create(); ban = entry_add(chan->bans, mask); if (!ban) fatal("Creating new ban entry failed"); /* Check whether it matches a botserv bot after adding internally * and parsing it through cidr support. ~ Viper */ if (s_BotServ && BSSmartJoin && chan->ci && chan->ci->bi && chan->usercount >= BSMinUsers) { BotInfo *bi = chan->ci->bi; if (entry_match(ban, bi->nick, bi->user, bi->host, 0)) { anope_cmd_mode(bi->nick, chan->name, "-b %s", mask); entry_delete(chan->bans, ban); return; } } if (debug) alog("debug: Added ban %s to channel %s", mask, chan->name); } /*************************************************************************/ void add_exception(Channel * chan, char *mask) { Entry *exception; if (!chan || !mask) { if (debug) alog("debug: add_exception called with NULL values"); return; } /* Check if the list already exists, if not create it. * Create a new exception and add it to the list.. ~ Viper */ if (!chan->excepts) chan->excepts = list_create(); exception = entry_add(chan->excepts, mask); if (!exception) fatal("Creating new exception entry failed"); if (debug) alog("debug: Added except %s to channel %s", mask, chan->name); } /*************************************************************************/ void add_invite(Channel * chan, char *mask) { Entry *invite; if (!chan || !mask) { if (debug) alog("debug: add_invite called with NULL values"); return; } /* Check if the list already exists, if not create it. * Create a new invite and add it to the list.. ~ Viper */ if (!chan->invites) chan->invites = list_create(); invite = entry_add(chan->invites, mask); if (!invite) fatal("Creating new exception entry failed"); 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 ")); /* Changed the second line of this if a bit, to make sure unregistered * users can always get modes (IE: they always have autoop enabled). Before * this change, you were required to have a registered nick to be able * to receive modes. I wonder who added that... *looks at Rob* ;) -GD */ if (give_modes && (get_ignore(user->nick) == NULL) && (!user->na || !(user->na->nc->flags & NI_AUTOOP))) { 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 * Or the user matches CA_AUTODEOP... -GD */ if (((ci->flags & CI_SECUREOPS) || (c->usercount == 1 && is_sync(user->server)) || check_access(user, ci, CA_AUTODEOP)) && !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, GET_USER(user)); } 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, GET_USER(user)); } else { add_modes &= ~CUS_PROTECT; } if ((add_modes & CUS_OP) && !(status & CUS_OP)) { strcat(modebuf, "o"); strcat(userbuf, " "); strcat(userbuf, GET_USER(user)); rem_modes |= CUS_DEOPPED; } else { add_modes &= ~CUS_OP; } if ((add_modes & CUS_HALFOP) && !(status & CUS_HALFOP)) { strcat(modebuf, "h"); strcat(userbuf, " "); strcat(userbuf, GET_USER(user)); /* Halfops are ops too, having a halfop with CUS_DEOPPED is not good - Adam */ rem_modes |= CUS_DEOPPED; } else { add_modes &= ~CUS_HALFOP; } if ((add_modes & CUS_VOICE) && !(status & CUS_VOICE)) { strcat(modebuf, "v"); strcat(userbuf, " "); strcat(userbuf, GET_USER(user)); } 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, GET_USER(user)); } if (rem_modes & CUS_PROTECT) { tmp = stripModePrefix(ircd->adminset); strcat(modebuf, tmp); free(tmp); strcat(userbuf, " "); strcat(userbuf, GET_USER(user)); } if (rem_modes & CUS_OP) { strcat(modebuf, "o"); strcat(userbuf, " "); strcat(userbuf, GET_USER(user)); /* Do not mark a user as deopped if they are halfopd - Adam */ if (!(add_modes & CUS_HALFOP) && !(status & CUS_HALFOP)) add_modes |= CUS_DEOPPED; } if (rem_modes & CUS_HALFOP) { strcat(modebuf, "h"); strcat(userbuf, " "); strcat(userbuf, GET_USER(user)); /* Do not mark a user as deopped if they are opped - Adam */ if (!(add_modes & CUS_OP) && !(status & CUS_OP)) add_modes |= CUS_DEOPPED; } } /* 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, time_t ts) { 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 = ts; /* 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; 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); } if (ircd->jmode) { if (c->throttle) free (c->throttle); } if (c->bans && c->bans->count) { while (c->bans->entries) { entry_delete(c->bans, c->bans->entries); } } if (ircd->except) { if (c->excepts && c->excepts->count) { while (c->excepts->entries) { entry_delete(c->excepts, c->excepts->entries); } } } if (ircd->invitemode) { if (c->invites && c->invites->count) { while (c->invites->entries) { entry_delete(c->invites, c->invites->entries); } } } 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) { AutoKick *akick; Entry *ban; /* Sanity check as it seems some IRCD will just send -b without a mask */ if (!mask || !chan->bans || (chan->bans->count == 0)) return; ban = elist_find_mask(chan->bans, mask); if (ban) { entry_delete(chan->bans, ban); 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) { Entry *exception; /* Sanity check as it seems some IRCD will just send -e without a mask */ if (!mask || !chan->excepts || (chan->excepts->count == 0)) return; exception = elist_find_mask(chan->excepts, mask); if (exception) { entry_delete(chan->excepts, exception); if (debug) alog("debug: Deleted except %s to channel %s", mask, chan->name); } } /*************************************************************************/ void del_invite(Channel * chan, char *mask) { Entry *invite; /* Sanity check as it seems some IRCD will just send -I without a mask */ if (!mask || !chan->invites || (chan->invites->count == 0)) { return; } invite = elist_find_mask(chan->invites, mask); if (invite) { entry_delete(chan->invites, invite); if (debug) alog("debug: Deleted invite %s to channel %s", mask, chan->name); } } /*************************************************************************/ char *get_flood(Channel * chan) { return chan->flood; } /*************************************************************************/ char *get_throttle(Channel * chan) { return chan->throttle; } /*************************************************************************/ 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; } /*************************************************************************/ /* This is a dummy function part of making anope accept modes * it does actively parse.. ~ Viper */ char *get_unkwn(Channel * chan) { return NULL; } /*************************************************************************/ Channel *join_user_update(User * user, Channel * chan, char *name, time_t chants) { struct u_chanlist *c; /* If it's a new channel, so we need to create it first. */ if (!chan) chan = chan_create(name, chants); else { /* Check chants against 0, as not every ircd sends JOIN with a TS. */ if (chan->creation_time > chants && chants != 0) { struct c_userlist *cu; char *modes[6]; chan->creation_time = chants; for (cu = chan->users; cu; cu = cu->next) { /* XXX */ modes[0] = "-ov"; modes[1] = cu->user->nick; modes[2] = cu->user->nick; chan_set_modes(s_OperServ, chan, 3, modes, 2); } if (chan->ci) { if (chan->ci->bi) { /* This is ugly, but it always works */ anope_cmd_part(chan->ci->bi->nick, chan->name, "TS reop"); bot_join(chan->ci); } /* Make sure +r is set */ if (ircd->chanreg && ircd->regmode) { chan->mode |= ircd->regmode; anope_cmd_mode(whosends(chan->ci), chan->name, "+r"); } } /* XXX simple modes and bans */ } } 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 chan_set_throttle(Channel * chan, char *value) { if (chan->throttle) free(chan->throttle); chan->throttle = value ? sstrdup(value) : NULL; if (debug) alog("debug: Throttle mode for channel %s set to %s", chan->name, chan->throttle ? chan->throttle : "none"); } /*************************************************************************/ void chan_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"); } /*************************************************************************/ /* This is a dummy function to make anope parse a param for a mode, * yet we don't use that param internally.. ~ Viper */ void set_unkwn(Channel *chan, char *value) { /* Do nothing.. */ } /*************************************************************************/ void do_mass_mode(char *modes) { int ac; 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 (c = firstchan(); c; c = nextchan()) { 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; for (c = firstchan(); c; c = nextchan()) { if (!(c->topic_sync)) restore_topic(c->name); } } /*************************************************************************/ /** * This handles creating a new Entry. * This function destroys and free's the given mask as a side effect. * @param mask Host/IP/CIDR mask to convert to an entry * @return Entry struct for the given mask, NULL if creation failed */ Entry *entry_create(char *mask) { Entry *entry; char *nick = NULL, *user, *host, *cidrhost; uint32 ip, cidr; entry = scalloc(1, sizeof(Entry)); entry->type = ENTRYTYPE_NONE; entry->prev = NULL; entry->next = NULL; entry->nick = NULL; entry->user = NULL; entry->host = NULL; entry->mask = sstrdup(mask); host = strchr(mask, '@'); if (host) { *host++ = '\0'; /* If the user is purely a wildcard, ignore it */ if (str_is_pure_wildcard(mask)) user = NULL; else { /* There might be a nick too */ user = strchr(mask, '!'); if (user) { *user++ = '\0'; /* If the nick is purely a wildcard, ignore it */ if (str_is_pure_wildcard(mask)) nick = NULL; else nick = mask; } else { nick = NULL; user = mask; } } } else { /* It is possibly an extended ban/invite mask, but we do * not support these at this point.. ~ Viper */ /* If there's no user in the mask, assume a pure wildcard */ user = NULL; host = mask; } if (nick) { entry->nick = sstrdup(nick); /* Check if we have a wildcard user */ if (str_is_wildcard(nick)) entry->type |= ENTRYTYPE_NICK_WILD; else entry->type |= ENTRYTYPE_NICK; } if (user) { entry->user = sstrdup(user); /* Check if we have a wildcard user */ if (str_is_wildcard(user)) entry->type |= ENTRYTYPE_USER_WILD; else entry->type |= ENTRYTYPE_USER; } /* Only check the host if it's not a pure wildcard */ if (*host && !str_is_pure_wildcard(host)) { if (ircd->cidrchanbei && str_is_cidr(host, &ip, &cidr, &cidrhost)) { entry->cidr_ip = ip; entry->cidr_mask = cidr; entry->type |= ENTRYTYPE_CIDR4; host = cidrhost; } else if (ircd->cidrchanbei && strchr(host, '/')) { /* Most IRCd's don't enforce sane bans therefore it is not * so unlikely we will encounter this. * Currently we only support strict CIDR without taking into * account quirks of every single ircd (nef) that ignore everything * after the first /cidr. To add this, sanitaze before sending to * str_is_cidr() as this expects a standard cidr. * Add it to the internal list (so it is included in for example clear) * but do not use if during matching.. ~ Viper */ entry->type = ENTRYTYPE_NONE; } else { entry->host = sstrdup(host); if (str_is_wildcard(host)) entry->type |= ENTRYTYPE_HOST_WILD; else entry->type |= ENTRYTYPE_HOST; } } free(mask); return entry; } /** * Create an entry and add it at the beginning of given list. * @param list The List the mask should be added to * @param mask The mask to parse and add to the list * @return Pointer to newly added entry. NULL if it fails. */ Entry *entry_add(EList * list, char *mask) { Entry *e; char *hostmask; hostmask = sstrdup(mask); e = entry_create(hostmask); if (!e) return NULL; e->next = list->entries; e->prev = NULL; if (list->entries) list->entries->prev = e; list->entries = e; list->count++; return e; } /** * Delete the given entry from a given list. * @param list Linked list from which entry needs to be removed. * @param e The entry to be deleted, must be member of list. */ void entry_delete(EList * list, Entry * e) { if (!list || !e) return; if (e->next) e->next->prev = e->prev; if (e->prev) e->prev->next = e->next; if (list->entries == e) list->entries = e->next; if (e->nick) free(e->nick); if (e->user) free(e->user); if (e->host) free(e->host); free(e->mask); free(e); list->count--; } /** * Create and initialize a new entrylist * @return Pointer to the created EList object **/ EList *list_create() { EList *list; list = scalloc(1, sizeof(EList)); list->entries = NULL; list->count = 0; return list; } /** * Match the given Entry to the given user/host and optional IP addy * @param e Entry struct to match against * @param nick Nick to match against * @param user User to match against * @param host Host to match against * @param ip IP to match against, set to 0 to not match this * @return 1 for a match, 0 for no match */ int entry_match(Entry * e, char *nick, char *user, char *host, uint32 ip) { /* If we don't get an entry, or it s an invalid one, no match ~ Viper */ if (!e || e->type == ENTRYTYPE_NONE) return 0; if (ircd->cidrchanbei && (e->type & ENTRYTYPE_CIDR4) && (!ip || (ip && ((ip & e->cidr_mask) != e->cidr_ip)))) return 0; if ((e->type & ENTRYTYPE_NICK) && (!nick || stricmp(e->nick, nick) != 0)) return 0; if ((e->type & ENTRYTYPE_USER) && (!user || stricmp(e->user, user) != 0)) return 0; if ((e->type & ENTRYTYPE_HOST) && (!host || stricmp(e->host, host) != 0)) return 0; if ((e->type & ENTRYTYPE_NICK_WILD) && !match_wild_nocase(e->nick, nick)) return 0; if ((e->type & ENTRYTYPE_USER_WILD) && !match_wild_nocase(e->user, user)) return 0; if ((e->type & ENTRYTYPE_HOST_WILD) && !match_wild_nocase(e->host, host)) return 0; return 1; } /** * Match the given Entry to the given hostmask and optional IP addy. * @param e Entry struct to match against * @param mask Hostmask to match against * @param ip IP to match against, set to 0 to not match this * @return 1 for a match, 0 for no match */ int entry_match_mask(Entry * e, char *mask, uint32 ip) { char *hostmask, *nick, *user, *host; int res; hostmask = sstrdup(mask); host = strchr(hostmask, '@'); if (host) { *host++ = '\0'; user = strchr(hostmask, '!'); if (user) { *user++ = '\0'; nick = hostmask; } else { nick = NULL; user = hostmask; } } else { nick = NULL; user = NULL; host = hostmask; } res = entry_match(e, nick, user, host, ip); /* Free the destroyed mask. */ free(hostmask); return res; } /** * Match a nick, user, host, and ip to a list entry * @param e List that should be matched against * @param nick The nick to match * @param user The user to match * @param host The host to match * @param ip The ip to match * @return Returns the first matching entry, if none, NULL is returned. */ Entry *elist_match(EList * list, char *nick, char *user, char *host, uint32 ip) { Entry *e; if (!list || !list->entries) return NULL; for (e = list->entries; e; e = e->next) { if (entry_match(e, nick, user, host, ip)) return e; } /* We matched none */ return NULL; } /** * Match a mask and ip to a list. * @param list EntryList that should be matched against * @param mask The nick!user@host mask to match * @param ip The ip to match * @return Returns the first matching entry, if none, NULL is returned. */ Entry *elist_match_mask(EList * list, char *mask, uint32 ip) { char *hostmask, *nick, *user, *host; Entry *res; if (!list || !list->entries || !mask) return NULL; hostmask = sstrdup(mask); host = strchr(hostmask, '@'); if (host) { *host++ = '\0'; user = strchr(hostmask, '!'); if (user) { *user++ = '\0'; nick = hostmask; } else { nick = NULL; user = hostmask; } } else { nick = NULL; user = NULL; host = hostmask; } res = elist_match(list, nick, user, host, ip); /* Free the destroyed mask. */ free(hostmask); return res; } /** * Check if a user matches an entry on a list. * @param list EntryList that should be matched against * @param user The user to match against the entries * @param full true to match against real host and real IP * @return Returns the first matching entry, if none, NULL is returned. */ static Entry *_elist_match_user(EList * list, User * u, boolean full) { Entry *res = NULL; char *host; uint32 ip = 0; if (!list || !list->entries || !u) return NULL; if (u->hostip == NULL) { host = host_resolve(u->host); /* we store the just resolved hostname so we don't * need to do this again */ if (host) { u->hostip = sstrdup(host); } } else { host = sstrdup(u->hostip); } /* Convert the host to an IP.. */ if (host) ip = str_is_ip(host); /* Match what we ve got against the lists.. */ if (full) res = elist_match(list, u->nick, u->username, u->host, ip); if (!res) res = elist_match(list, u->nick, u->vident, u->vhost, 0); if (!res) res = elist_match(list, u->nick, u->username, u->chost, 0); if (host) free(host); return res; } Entry *elist_match_user(EList *list, User *u) { return _elist_match_user(list, u, false); } Entry *elist_match_user_full(EList *list, User *u, boolean full) { return _elist_match_user(list, u, full); } /** * Find a entry identical to the given mask.. * @param list EntryList that should be matched against * @param mask The *!*@* mask to match * @return Returns the first matching entry, if none, NULL is returned. */ Entry *elist_find_mask(EList * list, char *mask) { Entry *e; if (!list || !list->entries || !mask) return NULL; for (e = list->entries; e; e = e->next) { if (!stricmp(e->mask, mask)) return e; } return NULL; } /** * Gets the total memory use of an entrylit. * @param list The list we should estimate the mem use of. * @return Returns the memory useage of the given list. */ long get_memuse(EList * list) { Entry *e; long mem = 0; if (!list) return 0; mem += sizeof(EList *); mem += sizeof(Entry *) * list->count; if (list->entries) { for (e = list->entries; e; e = e->next) { if (e->nick) mem += strlen(e->nick) + 1; if (e->user) mem += strlen(e->user) + 1; if (e->host) mem += strlen(e->host) + 1; if (e->mask) mem += strlen(e->mask) + 1; } } return mem; } /*************************************************************************/