diff options
Diffstat (limited to 'ircd/channel.c')
-rw-r--r-- | ircd/channel.c | 3396 |
1 files changed, 3396 insertions, 0 deletions
diff --git a/ircd/channel.c b/ircd/channel.c new file mode 100644 index 0000000..752f9ee --- /dev/null +++ b/ircd/channel.c @@ -0,0 +1,3396 @@ +/************************************************************************ + * IRC - Internet Relay Chat, ircd/channel.c + * Copyright (C) 1990 Jarkko Oikarinen and + * University of Oulu, Co Center + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 1, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/* -- Jto -- 09 Jul 1990 + * Bug fix + */ + +/* -- Jto -- 03 Jun 1990 + * Moved m_channel() and related functions from s_msg.c to here + * Many changes to start changing into string channels... + */ + +/* -- Jto -- 24 May 1990 + * Moved is_full() from list.c + */ + +#ifndef lint +static char rcsid[] = "@(#)$Id: channel.c,v 1.109 1999/08/13 17:30:16 kalt Exp $"; +#endif + +#include "os.h" +#include "s_defines.h" +#define CHANNEL_C +#include "s_externs.h" +#undef CHANNEL_C + +aChannel *channel = NullChn; + +static void add_invite __P((aClient *, aChannel *)); +static int can_join __P((aClient *, aChannel *, char *)); +void channel_modes __P((aClient *, char *, char *, aChannel *)); +static int check_channelmask __P((aClient *, aClient *, char *)); +static aChannel *get_channel __P((aClient *, char *, int)); +static int set_mode __P((aClient *, aClient *, aChannel *, int *, int,\ + char **, char *,char *)); +static void free_channel __P((aChannel *)); + +static int add_modeid __P((int, aClient *, aChannel *, char *)); +static int del_modeid __P((int, aChannel *, char *)); +static Link *match_modeid __P((int, aClient *, aChannel *)); + +static char *PartFmt = ":%s PART %s :%s"; + +/* + * some buffers for rebuilding channel/nick lists with ,'s + */ +static char nickbuf[BUFSIZE], buf[BUFSIZE]; +static char modebuf[MODEBUFLEN], parabuf[MODEBUFLEN]; + +/* + * return the length (>=0) of a chain of links. + */ +static int list_length(lp) +Reg Link *lp; +{ + Reg int count = 0; + + for (; lp; lp = lp->next) + count++; + return count; +} + +/* +** find_chasing +** Find the client structure for a nick name (user) using history +** mechanism if necessary. If the client is not found, an error +** message (NO SUCH NICK) is generated. If the client was found +** through the history, chasing will be 1 and otherwise 0. +*/ +static aClient *find_chasing(sptr, user, chasing) +aClient *sptr; +char *user; +Reg int *chasing; +{ + Reg aClient *who = find_client(user, (aClient *)NULL); + + if (chasing) + *chasing = 0; + if (who) + return who; + if (!(who = get_history(user, (long)KILLCHASETIMELIMIT))) + { + sendto_one(sptr, err_str(ERR_NOSUCHNICK, sptr->name), user); + return NULL; + } + if (chasing) + *chasing = 1; + return who; +} + +/* + * Fixes a string so that the first white space found becomes an end of + * string marker (`\-`). returns the 'fixed' string or "*" if the string + * was NULL length or a NULL pointer. + */ +static char *check_string(s) +Reg char *s; +{ + static char star[2] = "*"; + char *str = s; + + if (BadPtr(s)) + return star; + + for ( ;*s; s++) + if (isspace(*s)) + { + *s = '\0'; + break; + } + + return (BadPtr(str)) ? star : str; +} + +/* + * create a string of form "foo!bar@fubar" given foo, bar and fubar + * as the parameters. If NULL, they become "*". + */ +static char *make_nick_user_host(nick, name, host) +Reg char *nick, *name, *host; +{ + static char namebuf[NICKLEN+USERLEN+HOSTLEN+6]; + Reg char *s = namebuf; + + bzero(namebuf, sizeof(namebuf)); + nick = check_string(nick); + strncpyzt(namebuf, nick, NICKLEN + 1); + s += strlen(s); + *s++ = '!'; + name = check_string(name); + strncpyzt(s, name, USERLEN + 1); + s += strlen(s); + *s++ = '@'; + host = check_string(host); + strncpyzt(s, host, HOSTLEN + 1); + s += strlen(s); + *s = '\0'; + return (namebuf); +} + +/* + * Ban functions to work with mode +b/+e/+I + */ +/* add_modeid - add an id to the list of modes "type" for chptr + * (belongs to cptr) + */ + +static int add_modeid(type, cptr, chptr, modeid) +int type; +aClient *cptr; +aChannel *chptr; +char *modeid; +{ + Reg Link *mode; + Reg int cnt = 0, len = 0; + + if (MyClient(cptr)) + (void) collapse(modeid); + for (mode = chptr->mlist; mode; mode = mode->next) + { + len += strlen(mode->value.cp); + if (MyClient(cptr)) + { + if ((len > MAXBANLENGTH) || (++cnt >= MAXBANS)) + { + sendto_one(cptr, err_str(ERR_BANLISTFULL, + cptr->name), + chptr->chname, modeid); + return -1; + } + if (type == mode->flags && + (!match(mode->value.cp, modeid) || + !match(modeid, mode->value.cp))) + { + int rpl; + + if (type == CHFL_BAN) + rpl = RPL_BANLIST; + else if (type == CHFL_EXCEPTION) + rpl = RPL_EXCEPTLIST; + else + rpl = RPL_INVITELIST; + + sendto_one(cptr, rpl_str(rpl, cptr->name), + chptr->chname, mode->value.cp); + return -1; + } + } + else if (type == mode->flags && !mycmp(mode->value.cp, modeid)) + return -1; + + } + mode = make_link(); + istat.is_bans++; + bzero((char *)mode, sizeof(Link)); + mode->flags = type; + mode->next = chptr->mlist; + mode->value.cp = (char *)MyMalloc(len = strlen(modeid)+1); + istat.is_banmem += len; + (void)strcpy(mode->value.cp, modeid); + chptr->mlist = mode; + return 0; +} + +/* + * del_modeid - delete an id belonging to chptr + * if modeid is null, delete all ids belonging to chptr. + */ +static int del_modeid(type, chptr, modeid) +int type; +aChannel *chptr; +char *modeid; +{ + Reg Link **mode; + Reg Link *tmp; + + if (modeid == NULL) + { + for (mode = &(chptr->mlist); *mode; mode = &((*mode)->next)) + if (type == (*mode)->flags) + { + tmp = *mode; + *mode = tmp->next; + istat.is_banmem -= (strlen(tmp->value.cp) + 1); + istat.is_bans--; + MyFree(tmp->value.cp); + free_link(tmp); + break; + } + } + else for (mode = &(chptr->mlist); *mode; mode = &((*mode)->next)) + if (type == (*mode)->flags && + mycmp(modeid, (*mode)->value.cp)==0) + { + tmp = *mode; + *mode = tmp->next; + istat.is_banmem -= (strlen(modeid) + 1); + istat.is_bans--; + MyFree(tmp->value.cp); + free_link(tmp); + break; + } + return 0; +} + +/* + * match_modeid - returns a pointer to the mode structure if matching else NULL + */ +static Link *match_modeid(type, cptr, chptr) +int type; +aClient *cptr; +aChannel *chptr; +{ + Reg Link *tmp; + char *s; + + if (!IsPerson(cptr)) + return NULL; + + s = make_nick_user_host(cptr->name, cptr->user->username, + cptr->user->host); + + for (tmp = chptr->mlist; tmp; tmp = tmp->next) + if (tmp->flags == type && match(tmp->value.cp, s) == 0) + break; + + if (!tmp && MyConnect(cptr)) + { + char *ip = NULL; + +#ifdef INET6 + ip = (char *) inetntop(AF_INET6, (char *)&cptr->ip, + mydummy, MYDUMMY_SIZE); +#else + ip = (char *) inetntoa((char *)&cptr->ip); +#endif + + if (strcmp(ip, cptr->user->host)) + { + s = make_nick_user_host(cptr->name, + cptr->user->username, ip); + + for (tmp = chptr->mlist; tmp; tmp = tmp->next) + if (tmp->flags == type && + match(tmp->value.cp, s) == 0) + break; + } + } + + return (tmp); +} + +/* + * adds a user to a channel by adding another link to the channels member + * chain. + */ +static void add_user_to_channel(chptr, who, flags) +aChannel *chptr; +aClient *who; +int flags; +{ + Reg Link *ptr; + Reg int sz = sizeof(aChannel) + strlen(chptr->chname); + + if (who->user) + { + ptr = make_link(); + ptr->flags = flags; + ptr->value.cptr = who; + ptr->next = chptr->members; + chptr->members = ptr; + istat.is_chanusers++; + if (chptr->users++ == 0) + { + istat.is_chan++; + istat.is_chanmem += sz; + } + if (chptr->users == 1 && chptr->history) + { + /* Locked channel */ + istat.is_hchan--; + istat.is_hchanmem -= sz; + /* + ** The modes had been kept, but now someone is joining, + ** they should be reset to avoid desynchs + ** (you wouldn't want to join a +i channel, either) + ** + ** This can be wrong in some cases such as a netjoin + ** which will not complete, or on a mixed net (with + ** servers that don't do channel delay) - kalt + */ + if (*chptr->chname != '!') + bzero((char *)&chptr->mode, sizeof(Mode)); + } + +#ifdef USE_SERVICES + if (chptr->users == 1) + check_services_butone(SERVICE_WANT_CHANNEL| + SERVICE_WANT_VCHANNEL, + NULL, &me, "CHANNEL %s %d", + chptr->chname, chptr->users); + else + check_services_butone(SERVICE_WANT_VCHANNEL, + NULL, &me, "CHANNEL %s %d", + chptr->chname, chptr->users); +#endif + ptr = make_link(); + ptr->flags = flags; + ptr->value.chptr = chptr; + ptr->next = who->user->channel; + who->user->channel = ptr; + if (!IsQuiet(chptr)) + { + who->user->joined++; + istat.is_userc++; + } + + if (!(ptr = find_user_link(chptr->clist, who->from))) + { + ptr = make_link(); + ptr->value.cptr = who->from; + ptr->next = chptr->clist; + chptr->clist = ptr; + } + ptr->flags++; + } +} + +void remove_user_from_channel(sptr, chptr) +aClient *sptr; +aChannel *chptr; +{ + Reg Link **curr; + Reg Link *tmp, *tmp2; + + for (curr = &chptr->members; (tmp = *curr); curr = &tmp->next) + if (tmp->value.cptr == sptr) + { + /* + * if a chanop leaves (no matter how), record + * the time to be able to later massreop if + * necessary. + */ + if (*chptr->chname == '!' && + (tmp->flags & CHFL_CHANOP)) + chptr->reop = timeofday + LDELAYCHASETIMELIMIT; + + *curr = tmp->next; + free_link(tmp); + break; + } + for (curr = &sptr->user->channel; (tmp = *curr); curr = &tmp->next) + if (tmp->value.chptr == chptr) + { + *curr = tmp->next; + free_link(tmp); + break; + } + if (sptr->from) + tmp2 = find_user_link(chptr->clist, sptr->from); + else + tmp2 = find_user_link(chptr->clist, sptr); + if (tmp2 && !--tmp2->flags) + for (curr = &chptr->clist; (tmp = *curr); curr = &tmp->next) + if (tmp2 == tmp) + { + *curr = tmp->next; + free_link(tmp); + break; + } + if (!IsQuiet(chptr)) + { + sptr->user->joined--; + istat.is_userc--; + } +#ifdef USE_SERVICES + if (chptr->users == 1) + check_services_butone(SERVICE_WANT_CHANNEL| + SERVICE_WANT_VCHANNEL, NULL, &me, + "CHANNEL %s %d", chptr->chname, + chptr->users-1); + else + check_services_butone(SERVICE_WANT_VCHANNEL, NULL, &me, + "CHANNEL %s %d", chptr->chname, + chptr->users-1); +#endif + if (--chptr->users <= 0) + { + u_int sz = sizeof(aChannel) + strlen(chptr->chname); + + istat.is_chan--; + istat.is_chanmem -= sz; + istat.is_hchan++; + istat.is_hchanmem += sz; + free_channel(chptr); + } + istat.is_chanusers--; +} + +static void change_chan_flag(lp, chptr) +Link *lp; +aChannel *chptr; +{ + Reg Link *tmp; + aClient *cptr = lp->value.cptr; + + /* + * Set the channel members flags... + */ + tmp = find_user_link(chptr->members, cptr); + if (lp->flags & MODE_ADD) + tmp->flags |= lp->flags & MODE_FLAGS; + else + { + tmp->flags &= ~lp->flags & MODE_FLAGS; + if (lp->flags & CHFL_CHANOP) + tmp->flags &= ~CHFL_UNIQOP; + } + /* + * and make sure client membership mirrors channel + */ + tmp = find_user_link(cptr->user->channel, (aClient *)chptr); + if (lp->flags & MODE_ADD) + tmp->flags |= lp->flags & MODE_FLAGS; + else + { + tmp->flags &= ~lp->flags & MODE_FLAGS; + if (lp->flags & CHFL_CHANOP) + tmp->flags &= ~CHFL_UNIQOP; + } +} + +int is_chan_op(cptr, chptr) +aClient *cptr; +aChannel *chptr; +{ + Reg Link *lp; + int chanop = 0; + + if (MyConnect(cptr) && IsPerson(cptr) && IsRestricted(cptr) && + *chptr->chname != '&') + return 0; + if (chptr) + if ((lp = find_user_link(chptr->members, cptr))) + chanop = (lp->flags & (CHFL_CHANOP|CHFL_UNIQOP)); + if (chanop) + chptr->reop = 0; + return chanop; +} + +int has_voice(cptr, chptr) +aClient *cptr; +aChannel *chptr; +{ + Reg Link *lp; + + if (chptr) + if ((lp = find_user_link(chptr->members, cptr))) + return (lp->flags & CHFL_VOICE); + + return 0; +} + +int can_send(cptr, chptr) +aClient *cptr; +aChannel *chptr; +{ + Reg Link *lp; + Reg int member; + + member = IsMember(cptr, chptr); + lp = find_user_link(chptr->members, cptr); + + if ((!lp || !(lp->flags & (CHFL_CHANOP | CHFL_VOICE))) && + !match_modeid(CHFL_EXCEPTION, cptr, chptr) && + match_modeid(CHFL_BAN, cptr, chptr)) + return (MODE_BAN); + + if (chptr->mode.mode & MODE_MODERATED && + (!lp || !(lp->flags & (CHFL_CHANOP|CHFL_VOICE)))) + return (MODE_MODERATED); + + if (chptr->mode.mode & MODE_NOPRIVMSGS && !member) + return (MODE_NOPRIVMSGS); + + return 0; +} + +aChannel *find_channel(chname, chptr) +Reg char *chname; +Reg aChannel *chptr; +{ + aChannel *achptr = chptr; + + if (chname && *chname) + achptr = hash_find_channel(chname, chptr); + return achptr; +} + +void setup_server_channels(mp) +aClient *mp; +{ + aChannel *chptr; + int smode; + + smode = MODE_MODERATED|MODE_TOPICLIMIT|MODE_NOPRIVMSGS|MODE_ANONYMOUS| + MODE_QUIET; + + chptr = get_channel(mp, "&ERRORS", CREATE); + strcpy(chptr->topic, "SERVER MESSAGES: server errors"); + add_user_to_channel(chptr, mp, CHFL_CHANOP); + chptr->mode.mode = smode; + chptr = get_channel(mp, "&NOTICES", CREATE); + strcpy(chptr->topic, "SERVER MESSAGES: warnings and notices"); + add_user_to_channel(chptr, mp, CHFL_CHANOP); + chptr->mode.mode = smode; + chptr = get_channel(mp, "&KILLS", CREATE); + strcpy(chptr->topic, "SERVER MESSAGES: operator and server kills"); + add_user_to_channel(chptr, mp, CHFL_CHANOP); + chptr->mode.mode = smode; + chptr = get_channel(mp, "&CHANNEL", CREATE); + strcpy(chptr->topic, "SERVER MESSAGES: fake modes"); + add_user_to_channel(chptr, mp, CHFL_CHANOP); + chptr->mode.mode = smode; + chptr = get_channel(mp, "&NUMERICS", CREATE); + strcpy(chptr->topic, "SERVER MESSAGES: numerics received"); + add_user_to_channel(chptr, mp, CHFL_CHANOP); + chptr->mode.mode = smode; + chptr = get_channel(mp, "&SERVERS", CREATE); + strcpy(chptr->topic, "SERVER MESSAGES: servers joining and leaving"); + add_user_to_channel(chptr, mp, CHFL_CHANOP); + chptr->mode.mode = smode; + chptr = get_channel(mp, "&HASH", CREATE); + strcpy(chptr->topic, "SERVER MESSAGES: hash tables growth"); + add_user_to_channel(chptr, mp, CHFL_CHANOP); + chptr->mode.mode = smode; + chptr = get_channel(mp, "&LOCAL", CREATE); + strcpy(chptr->topic, "SERVER MESSAGES: notices about local connections"); + add_user_to_channel(chptr, mp, CHFL_CHANOP); + chptr->mode.mode = smode; + chptr = get_channel(mp, "&SERVICES", CREATE); + strcpy(chptr->topic, "SERVER MESSAGES: services joining and leaving"); + add_user_to_channel(chptr, mp, CHFL_CHANOP); + chptr->mode.mode = smode; +#if defined(USE_IAUTH) + chptr = get_channel(mp, "&AUTH", CREATE); + strcpy(chptr->topic, + "SERVER MESSAGES: messages from the authentication slave"); + add_user_to_channel(chptr, mp, CHFL_CHANOP); + chptr->mode.mode = smode; +#endif + chptr = get_channel(mp, "&DEBUG", CREATE); + strcpy(chptr->topic, "SERVER MESSAGES: debug messages [you shouldn't be here! ;)]"); + add_user_to_channel(chptr, mp, CHFL_CHANOP); + chptr->mode.mode = smode|MODE_SECRET; + + setup_svchans(); +} + +/* + * write the "simple" list of channel modes for channel chptr onto buffer mbuf + * with the parameters in pbuf. + */ +void channel_modes(cptr, mbuf, pbuf, chptr) +aClient *cptr; +Reg char *mbuf, *pbuf; +aChannel *chptr; +{ + *mbuf++ = '+'; + if (chptr->mode.mode & MODE_SECRET) + *mbuf++ = 's'; + else if (chptr->mode.mode & MODE_PRIVATE) + *mbuf++ = 'p'; + if (chptr->mode.mode & MODE_MODERATED) + *mbuf++ = 'm'; + if (chptr->mode.mode & MODE_TOPICLIMIT) + *mbuf++ = 't'; + if (chptr->mode.mode & MODE_INVITEONLY) + *mbuf++ = 'i'; + if (chptr->mode.mode & MODE_NOPRIVMSGS) + *mbuf++ = 'n'; + if (chptr->mode.mode & MODE_ANONYMOUS) + *mbuf++ = 'a'; + if (chptr->mode.mode & MODE_QUIET) + *mbuf++ = 'q'; + if (chptr->mode.mode & MODE_REOP) + *mbuf++ = 'r'; + if (chptr->mode.limit) + { + *mbuf++ = 'l'; + if (IsMember(cptr, chptr) || IsServer(cptr)) + SPRINTF(pbuf, "%d ", chptr->mode.limit); + } + if (*chptr->mode.key) + { + *mbuf++ = 'k'; + if (IsMember(cptr, chptr) || IsServer(cptr)) + (void)strcat(pbuf, chptr->mode.key); + } + *mbuf++ = '\0'; + return; +} + +static void send_mode_list(cptr, chname, top, mask, flag) +aClient *cptr; +Link *top; +int mask; +char flag, *chname; +{ + Reg Link *lp; + Reg char *cp, *name; + int count = 0, send = 0; + + cp = modebuf + strlen(modebuf); + if (*parabuf) /* mode +l or +k xx */ + count = strlen(modebuf)-1; + for (lp = top; lp; lp = lp->next) + { + if (!(lp->flags & mask)) + continue; + if (mask == CHFL_BAN || mask == CHFL_EXCEPTION || + mask == CHFL_INVITE) + name = lp->value.cp; + else + name = lp->value.cptr->name; + if (strlen(parabuf) + strlen(name) + 10 < (size_t) MODEBUFLEN) + { + (void)strcat(parabuf, " "); + (void)strcat(parabuf, name); + count++; + *cp++ = flag; + *cp = '\0'; + } + else if (*parabuf) + send = 1; + if (count == 3) + send = 1; + if (send) + { + sendto_one(cptr, ":%s MODE %s %s %s", + ME, chname, modebuf, parabuf); + send = 0; + *parabuf = '\0'; + cp = modebuf; + *cp++ = '+'; + if (count != 3) + { + (void)strcpy(parabuf, name); + *cp++ = flag; + } + count = 0; + *cp = '\0'; + } + } +} + +/* + * send "cptr" a full list of the modes for channel chptr. + */ +void send_channel_modes(cptr, chptr) +aClient *cptr; +aChannel *chptr; +{ +#if 0 +this is probably going to be very annoying, but leaving the following code +uncommented may just lead to desynchs.. + if ((*chptr->chname != '#' && *chptr->chname != '!') + || chptr->users == 0) /* channel is empty (locked), thus no mode */ + return; +#endif + + if (check_channelmask(&me, cptr, chptr->chname)) + return; + + *modebuf = *parabuf = '\0'; + channel_modes(cptr, modebuf, parabuf, chptr); + + if (modebuf[1] || *parabuf) + sendto_one(cptr, ":%s MODE %s %s %s", + ME, chptr->chname, modebuf, parabuf); + + *parabuf = '\0'; + *modebuf = '+'; + modebuf[1] = '\0'; + send_mode_list(cptr, chptr->chname, chptr->mlist, CHFL_BAN, 'b'); + if (cptr->serv->version & SV_NMODE) + { + if (modebuf[1] || *parabuf) + { + /* only needed to help compatibility */ + sendto_one(cptr, ":%s MODE %s %s %s", + ME, chptr->chname, modebuf, parabuf); + *parabuf = '\0'; + *modebuf = '+'; + modebuf[1] = '\0'; + } + send_mode_list(cptr, chptr->chname, chptr->mlist, + CHFL_EXCEPTION, 'e'); + send_mode_list(cptr, chptr->chname, chptr->mlist, + CHFL_INVITE, 'I'); + } + if (modebuf[1] || *parabuf) + sendto_one(cptr, ":%s MODE %s %s %s", + ME, chptr->chname, modebuf, parabuf); +} + +/* + * send "cptr" a full list of the channel "chptr" members and their + * +ov status, using NJOIN + */ +void send_channel_members(cptr, chptr) +aClient *cptr; +aChannel *chptr; +{ + Reg Link *lp; + Reg aClient *c2ptr; + Reg int cnt = 0, len = 0, nlen; + + if (check_channelmask(&me, cptr, chptr->chname) == -1) + return; + if (*chptr->chname == '!' && !(cptr->serv->version & SV_NCHAN)) + return; + + sprintf(buf, ":%s NJOIN %s :", ME, chptr->chname); + len = strlen(buf); + + for (lp = chptr->members; lp; lp = lp->next) + { + c2ptr = lp->value.cptr; + nlen = strlen(c2ptr->name); + if ((len + nlen) > (size_t) (BUFSIZE - 9)) /* ,@+ \r\n\0 */ + { + sendto_one(cptr, "%s", buf); + sprintf(buf, ":%s NJOIN %s :", ME, chptr->chname); + len = strlen(buf); + cnt = 0; + } + if (cnt) + { + buf[len++] = ','; + buf[len] = '\0'; + } + if (lp->flags & (CHFL_UNIQOP|CHFL_CHANOP|CHFL_VOICE)) + { + if (lp->flags & CHFL_UNIQOP) + { + buf[len++] = '@'; + buf[len++] = '@'; + } + else + { + if (lp->flags & CHFL_CHANOP) + buf[len++] = '@'; + } + if (lp->flags & CHFL_VOICE) + buf[len++] = '+'; + buf[len] = '\0'; + } + (void)strcpy(buf + len, c2ptr->name); + len += nlen; + cnt++; + } + if (*buf && cnt) + sendto_one(cptr, "%s", buf); + + return; +} + +/* + * m_mode + * parv[0] - sender + * parv[1] - target; channels and/or user + * parv[2] - optional modes + * parv[n] - optional parameters + */ + +int m_mode(cptr, sptr, parc, parv) +aClient *cptr; +aClient *sptr; +int parc; +char *parv[]; +{ + int mcount = 0, chanop; + int penalty = 0; + aChannel *chptr; + char *name, *p = NULL; + + if (parc < 1) + { + sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS, parv[0]), "MODE"); + return 1; + } + + parv[1] = canonize(parv[1]); + + for (name = strtoken(&p, parv[1], ","); name; + name = strtoken(&p, NULL, ",")) + { + clean_channelname(name); + chptr = find_channel(name, NullChn); + if (chptr == NullChn) + { + parv[1] = name; + penalty += m_umode(cptr, sptr, parc, parv); + continue; + } + if (check_channelmask(sptr, cptr, name)) + { + penalty += 1; + continue; + } + if (!UseModes(name)) + { + sendto_one(sptr, err_str(ERR_NOCHANMODES, parv[0]), + name); + penalty += 1; + continue; + } + chanop = is_chan_op(sptr, chptr) || IsServer(sptr); + + if (parc < 3) /* Only a query */ + { + *modebuf = *parabuf = '\0'; + modebuf[1] = '\0'; + channel_modes(sptr, modebuf, parabuf, chptr); + sendto_one(sptr, rpl_str(RPL_CHANNELMODEIS, parv[0]), + name, modebuf, parabuf); + penalty += 1; + } + else /* Check parameters for the channel */ + { + if(!(mcount = set_mode(cptr, sptr, chptr, &penalty, + parc - 2, parv + 2, + modebuf, parabuf))) + continue; /* no valid mode change */ + if ((mcount < 0) && MyConnect(sptr) && !IsServer(sptr)) + { /* rejected mode change */ + int num = ERR_CHANOPRIVSNEEDED; + + if (IsClient(sptr) && IsRestricted(sptr)) + num = ERR_RESTRICTED; + sendto_one(sptr, err_str(num, parv[0]), name); + continue; + } + if (strlen(modebuf) > (size_t)1) + { /* got new mode to pass on */ + if (modebuf[1] == 'e' || modebuf[1] == 'I') + /* 2.9.x compatibility */ + sendto_match_servs_v(chptr, cptr, + SV_NMODE, + ":%s MODE %s %s %s", + parv[0], name, + modebuf, parabuf); + else + sendto_match_servs(chptr, cptr, + ":%s MODE %s %s %s", + parv[0], name, + modebuf, parabuf); + if ((IsServer(cptr) && !IsServer(sptr) && + !chanop) || mcount < 0) + { + sendto_flag(SCH_CHAN, + "Fake: %s MODE %s %s %s", + parv[0], name, modebuf, + parabuf); + ircstp->is_fake++; + } + else + { + sendto_channel_butserv(chptr, sptr, + ":%s MODE %s %s %s", + parv[0], name, + modebuf, parabuf); +#ifdef USE_SERVICES + *modebuf = *parabuf = '\0'; + modebuf[1] = '\0'; + channel_modes(&me, modebuf, parabuf, + chptr); + check_services_butone(SERVICE_WANT_MODE, + NULL, sptr, + "MODE %s %s", + name, modebuf); +#endif + } + } /* if(modebuf) */ + } /* else(parc>2) */ + } /* for (parv1) */ + return penalty; +} + +/* + * Check and try to apply the channel modes passed in the parv array for + * the client cptr to channel chptr. The resultant changes are printed + * into mbuf and pbuf (if any) and applied to the channel. + */ +static int set_mode(cptr, sptr, chptr, penalty, parc, parv, mbuf, pbuf) +Reg aClient *cptr, *sptr; +aChannel *chptr; +int parc, *penalty; +char *parv[], *mbuf, *pbuf; +{ + static Link chops[MAXMODEPARAMS+3]; + static int flags[] = { + MODE_PRIVATE, 'p', MODE_SECRET, 's', + MODE_MODERATED, 'm', MODE_NOPRIVMSGS, 'n', + MODE_TOPICLIMIT, 't', MODE_INVITEONLY, 'i', + MODE_ANONYMOUS, 'a', MODE_REOP, 'r', + 0x0, 0x0 }; + + Reg Link *lp = NULL; + Reg char *curr = parv[0], *cp = NULL; + Reg int *ip; + u_int whatt = MODE_ADD; + int limitset = 0, count = 0, chasing = 0; + int nusers = 0, ischop, new, len, keychange = 0, opcnt = 0; + aClient *who; + Mode *mode, oldm; + Link *plp = NULL; + int compat = -1; /* to prevent mixing old/new modes */ + + *mbuf = *pbuf = '\0'; + if (parc < 1) + return 0; + + mode = &(chptr->mode); + bcopy((char *)mode, (char *)&oldm, sizeof(Mode)); + ischop = IsServer(sptr) || is_chan_op(sptr, chptr); + new = mode->mode; + + while (curr && *curr && count >= 0) + { + if (compat == -1 && *curr != '-' && *curr != '+') + if (*curr == 'e' || *curr == 'I') + compat = 1; + else + compat = 0; + switch (*curr) + { + case '+': + whatt = MODE_ADD; + break; + case '-': + whatt = MODE_DEL; + break; + case 'O': + if (parc > 0) + { + if (*chptr->chname == '!') + { + if (IsMember(sptr, chptr)) + { + *penalty += 1; + parc--; + /* Feature: no other modes after this query */ + *(curr+1) = '\0'; + for (lp = chptr->members; lp; lp = lp->next) + if (lp->flags & CHFL_UNIQOP) + { + sendto_one(sptr, + rpl_str(RPL_UNIQOPIS, + sptr->name), + chptr->chname, + lp->value.cptr->name); + break; + } + if (!lp) + sendto_one(sptr, + err_str(ERR_NOSUCHNICK, + sptr->name), + chptr->chname); + break; + } + else /* not IsMember() */ + { + if (!IsServer(sptr)) + { + sendto_one(sptr, err_str(ERR_NOTONCHANNEL, sptr->name), + chptr->chname); + *(curr+1) = '\0'; + break; + } + } + } + else /* *chptr->chname != '!' */ + sendto_one(cptr, err_str(ERR_UNKNOWNMODE, + sptr->name), *curr, chptr->chname); + *(curr+1) = '\0'; + break; + } + /* + * is this really ever used ? + * or do ^G & NJOIN do the trick? + */ + if (*chptr->chname != '!' || whatt == MODE_DEL || + !IsServer(sptr)) + { + *penalty += 1; + --parc; + parv++; + break; + } + case 'o' : + case 'v' : + *penalty += 1; + if (--parc <= 0) + break; + parv++; + *parv = check_string(*parv); + if (opcnt >= MAXMODEPARAMS) +#ifndef V29PlusOnly + if (MyClient(sptr) || opcnt >= MAXMODEPARAMS + 1) +#endif + break; + if (!IsServer(sptr) && !IsMember(sptr, chptr)) + { + sendto_one(sptr, err_str(ERR_NOTONCHANNEL, + sptr->name), + chptr->chname); + break; + } + /* + * Check for nickname changes and try to follow these + * to make sure the right client is affected by the + * mode change. + */ + if (!(who = find_chasing(sptr, parv[0], &chasing))) + break; + if (!IsMember(who, chptr)) + { + sendto_one(sptr, err_str(ERR_USERNOTINCHANNEL, + sptr->name), + parv[0], chptr->chname); + break; + } + if (who == cptr && whatt == MODE_ADD && *curr == 'o') + break; + + if (whatt == MODE_ADD) + { + lp = &chops[opcnt++]; + lp->value.cptr = who; + lp->flags = (*curr == 'O') ? MODE_UNIQOP: + (*curr == 'o') ? MODE_CHANOP: + MODE_VOICE; + lp->flags |= MODE_ADD; + } + else if (whatt == MODE_DEL) + { + lp = &chops[opcnt++]; + lp->value.cptr = who; + lp->flags = (*curr == 'o') ? MODE_CHANOP: + MODE_VOICE; + lp->flags |= MODE_DEL; + } + if (plp && plp->flags == lp->flags && + plp->value.cptr == lp->value.cptr) + { + opcnt--; + break; + } + plp = lp; + /* + ** If this server noticed the nick change, the + ** information must be propagated back upstream. + ** This is a bit early, but at most this will generate + ** just some extra messages if nick appeared more than + ** once in the MODE message... --msa + */ +/* nobody can figure this part of the code anymore.. -kalt + if (chasing && ischop) + sendto_one(cptr, ":%s MODE %s %c%c %s", + ME, chptr->chname, + whatt == MODE_ADD ? '+' : '-', + *curr, who->name); +*/ + count++; + *penalty += 2; + break; + case 'k': + *penalty += 1; + if (--parc <= 0) + break; + parv++; + /* check now so we eat the parameter if present */ + if (keychange) + break; + { + Reg u_char *s; + + for (s = (u_char *)*parv; *s; ) + if (*s > 0x7f) + if (*s > 0xa0) + *s++ &= 0x7f; + else + *s = '\0'; + else + s++; + } + + if (!**parv) + break; + *parv = check_string(*parv); + if (opcnt >= MAXMODEPARAMS) +#ifndef V29PlusOnly + if (MyClient(sptr) || opcnt >= MAXMODEPARAMS + 1) +#endif + break; + if (whatt == MODE_ADD) + { + if (*mode->key && !IsServer(cptr)) + sendto_one(cptr, err_str(ERR_KEYSET, + cptr->name), chptr->chname); + else if (ischop && + (!*mode->key || IsServer(cptr))) + { + if (**parv == ':') + /* this won't propagate right*/ + break; + lp = &chops[opcnt++]; + lp->value.cp = *parv; + if (strlen(lp->value.cp) > + (size_t) KEYLEN) + lp->value.cp[KEYLEN] = '\0'; + lp->flags = MODE_KEY|MODE_ADD; + keychange = 1; + } + } + else if (whatt == MODE_DEL) + { + if (ischop && (mycmp(mode->key, *parv) == 0 || + IsServer(cptr))) + { + lp = &chops[opcnt++]; + lp->value.cp = mode->key; + lp->flags = MODE_KEY|MODE_DEL; + keychange = 1; + } + } + count++; + *penalty += 2; + break; + case 'b': + *penalty += 1; + if (--parc <= 0) /* ban list query */ + { + /* Feature: no other modes after ban query */ + *(curr+1) = '\0'; /* Stop MODE # bb.. */ + for (lp = chptr->mlist; lp; lp = lp->next) + if (lp->flags == CHFL_BAN) + sendto_one(cptr, + rpl_str(RPL_BANLIST, + cptr->name), + chptr->chname, + lp->value.cp); + sendto_one(cptr, rpl_str(RPL_ENDOFBANLIST, + cptr->name), chptr->chname); + break; + } + parv++; + if (BadPtr(*parv)) + break; + if (opcnt >= MAXMODEPARAMS) +#ifndef V29PlusOnly + if (MyClient(sptr) || opcnt >= MAXMODEPARAMS + 1) +#endif + break; + if (whatt == MODE_ADD) + { + if (**parv == ':') + /* this won't propagate right */ + break; + lp = &chops[opcnt++]; + lp->value.cp = *parv; + lp->flags = MODE_ADD|MODE_BAN; + } + else if (whatt == MODE_DEL) + { + lp = &chops[opcnt++]; + lp->value.cp = *parv; + lp->flags = MODE_DEL|MODE_BAN; + } + count++; + *penalty += 2; + break; + case 'e': + *penalty += 1; + if (--parc <= 0) /* exception list query */ + { + /* Feature: no other modes after query */ + *(curr+1) = '\0'; /* Stop MODE # bb.. */ + for (lp = chptr->mlist; lp; lp = lp->next) + if (lp->flags == CHFL_EXCEPTION) + sendto_one(cptr, + rpl_str(RPL_EXCEPTLIST, + cptr->name), + chptr->chname, + lp->value.cp); + sendto_one(cptr, rpl_str(RPL_ENDOFEXCEPTLIST, + cptr->name), chptr->chname); + break; + } + parv++; + if (BadPtr(*parv)) + break; + if (opcnt >= MAXMODEPARAMS) +#ifndef V29PlusOnly + if (MyClient(sptr) || opcnt >= MAXMODEPARAMS + 1) +#endif + break; + if (whatt == MODE_ADD) + { + if (**parv == ':') + /* this won't propagate right */ + break; + lp = &chops[opcnt++]; + lp->value.cp = *parv; + lp->flags = MODE_ADD|MODE_EXCEPTION; + } + else if (whatt == MODE_DEL) + { + lp = &chops[opcnt++]; + lp->value.cp = *parv; + lp->flags = MODE_DEL|MODE_EXCEPTION; + } + count++; + *penalty += 2; + break; + case 'I': + *penalty += 1; + if (--parc <= 0) /* invite list query */ + { + /* Feature: no other modes after query */ + *(curr+1) = '\0'; /* Stop MODE # bb.. */ + for (lp = chptr->mlist; lp; lp = lp->next) + if (lp->flags == CHFL_INVITE) + sendto_one(cptr, + rpl_str(RPL_INVITELIST, + cptr->name), + chptr->chname, + lp->value.cp); + sendto_one(cptr, rpl_str(RPL_ENDOFINVITELIST, + cptr->name), chptr->chname); + break; + } + parv++; + if (BadPtr(*parv)) + break; + if (opcnt >= MAXMODEPARAMS) +#ifndef V29PlusOnly + if (MyClient(sptr) || opcnt >= MAXMODEPARAMS + 1) +#endif + break; + if (whatt == MODE_ADD) + { + if (**parv == ':') + /* this won't propagate right */ + break; + lp = &chops[opcnt++]; + lp->value.cp = *parv; + lp->flags = MODE_ADD|MODE_INVITE; + } + else if (whatt == MODE_DEL) + { + lp = &chops[opcnt++]; + lp->value.cp = *parv; + lp->flags = MODE_DEL|MODE_INVITE; + } + count++; + *penalty += 2; + break; + case 'l': + *penalty += 1; + /* + * limit 'l' to only *1* change per mode command but + * eat up others. + */ + if (limitset || !ischop) + { + if (whatt == MODE_ADD && --parc > 0) + parv++; + break; + } + if (whatt == MODE_DEL) + { + limitset = 1; + nusers = 0; + count++; + break; + } + if (--parc > 0) + { + if (BadPtr(*parv)) + break; + if (opcnt >= MAXMODEPARAMS) +#ifndef V29PlusOnly + if (MyClient(sptr) || + opcnt >= MAXMODEPARAMS + 1) +#endif + break; + if (!(nusers = atoi(*++parv))) + break; + lp = &chops[opcnt++]; + lp->flags = MODE_ADD|MODE_LIMIT; + limitset = 1; + count++; + *penalty += 2; + break; + } + sendto_one(cptr, err_str(ERR_NEEDMOREPARAMS, + cptr->name), "MODE +l"); + break; + case 'i' : /* falls through for default case */ + if (whatt == MODE_DEL) + while ((lp = chptr->invites)) + del_invite(lp->value.cptr, chptr); + default: + *penalty += 1; + for (ip = flags; *ip; ip += 2) + if (*(ip+1) == *curr) + break; + + if (*ip) + { + if (*ip == MODE_ANONYMOUS && + whatt == MODE_DEL && *chptr->chname == '!') + sendto_one(sptr, + err_str(ERR_UNIQOPRIVSNEEDED, + sptr->name), chptr->chname); + else if (((*ip == MODE_ANONYMOUS && + whatt == MODE_ADD && + *chptr->chname == '#') || + (*ip == MODE_REOP && + *chptr->chname != '!')) && + !IsServer(sptr)) + sendto_one(cptr, + err_str(ERR_UNKNOWNMODE, + sptr->name), *curr, + chptr->chname); + else if ((*ip == MODE_REOP || + *ip == MODE_ANONYMOUS) && + !IsServer(sptr) && + !(is_chan_op(sptr,chptr) &CHFL_UNIQOP) + && *chptr->chname == '!') + /* 2 modes restricted to UNIQOP */ + sendto_one(sptr, + err_str(ERR_UNIQOPRIVSNEEDED, + sptr->name), chptr->chname); + else + { + /* + ** If the channel is +s, ignore +p + ** modes coming from a server. + ** (Otherwise, it's desynch'ed) -kalt + */ + if (whatt == MODE_ADD && + *ip == MODE_PRIVATE && + IsServer(sptr) && + (new & MODE_SECRET)) + break; + if (whatt == MODE_ADD) + { + if (*ip == MODE_PRIVATE) + new &= ~MODE_SECRET; + else if (*ip == MODE_SECRET) + new &= ~MODE_PRIVATE; + new |= *ip; + } + else + new &= ~*ip; + count++; + *penalty += 2; + } + } + else if (!IsServer(cptr)) + sendto_one(cptr, err_str(ERR_UNKNOWNMODE, + cptr->name), *curr, chptr->chname); + break; + } + curr++; + /* + * Make sure modes strings such as "+m +t +p +i" are parsed + * fully. + */ + if (!*curr && parc > 0) + { + curr = *++parv; + parc--; + } + /* + * Make sure old and new (+e/+I) modes won't get mixed + * together on the same line + */ + if (MyClient(sptr) && curr && *curr != '-' && *curr != '+') + if (*curr == 'e' || *curr == 'I') + { + if (compat == 0) + *curr = '\0'; + } + else if (compat == 1) + *curr = '\0'; + } /* end of while loop for MODE processing */ + + whatt = 0; + + for (ip = flags; *ip; ip += 2) + if ((*ip & new) && !(*ip & oldm.mode)) + { + if (whatt == 0) + { + *mbuf++ = '+'; + whatt = 1; + } + if (ischop) + { + mode->mode |= *ip; + if (*ip == MODE_ANONYMOUS && MyPerson(sptr)) + { + sendto_channel_butone(NULL, &me, chptr, ":%s NOTICE %s :The anonymous flag is being set on channel %s.", ME, chptr->chname, chptr->chname); + sendto_channel_butone(NULL, &me, chptr, ":%s NOTICE %s :Be aware that anonymity on IRC is NOT securely enforced!", ME, chptr->chname); + } + } + *mbuf++ = *(ip+1); + } + + for (ip = flags; *ip; ip += 2) + if ((*ip & oldm.mode) && !(*ip & new)) + { + if (whatt != -1) + { + *mbuf++ = '-'; + whatt = -1; + } + if (ischop) + mode->mode &= ~*ip; + *mbuf++ = *(ip+1); + } + + if (limitset && !nusers && mode->limit) + { + if (whatt != -1) + { + *mbuf++ = '-'; + whatt = -1; + } + mode->mode &= ~MODE_LIMIT; + mode->limit = 0; + *mbuf++ = 'l'; + } + + /* + * Reconstruct "+beIkOov" chain. + */ + if (opcnt) + { + Reg int i = 0; + Reg char c = '\0'; + char *user, *host, numeric[16]; + +/* if (opcnt > MAXMODEPARAMS) + opcnt = MAXMODEPARAMS; +*/ + for (; i < opcnt; i++) + { + lp = &chops[i]; + /* + * make sure we have correct mode change sign + */ + if (whatt != (lp->flags & (MODE_ADD|MODE_DEL))) + if (lp->flags & MODE_ADD) + { + *mbuf++ = '+'; + whatt = MODE_ADD; + } + else + { + *mbuf++ = '-'; + whatt = MODE_DEL; + } + len = strlen(pbuf); + /* + * get c as the mode char and tmp as a pointer to + * the paramter for this mode change. + */ + switch(lp->flags & MODE_WPARAS) + { + case MODE_CHANOP : + c = 'o'; + cp = lp->value.cptr->name; + break; + case MODE_UNIQOP : + c = 'O'; + cp = lp->value.cptr->name; + break; + case MODE_VOICE : + c = 'v'; + cp = lp->value.cptr->name; + break; + case MODE_BAN : + c = 'b'; + cp = lp->value.cp; + if ((user = index(cp, '!'))) + *user++ = '\0'; + if ((host = rindex(user ? user : cp, '@'))) + *host++ = '\0'; + cp = make_nick_user_host(cp, user, host); + if (user) + *(--user) = '!'; + if (host) + *(--host) = '@'; + break; + case MODE_EXCEPTION : + c = 'e'; + cp = lp->value.cp; + if ((user = index(cp, '!'))) + *user++ = '\0'; + if ((host = rindex(user ? user : cp, '@'))) + *host++ = '\0'; + cp = make_nick_user_host(cp, user, host); + if (user) + *(--user) = '!'; + if (host) + *(--host) = '@'; + break; + case MODE_INVITE : + c = 'I'; + cp = lp->value.cp; + if ((user = index(cp, '!'))) + *user++ = '\0'; + if ((host = rindex(user ? user : cp, '@'))) + *host++ = '\0'; + cp = make_nick_user_host(cp, user, host); + if (user) + *(--user) = '!'; + if (host) + *(--host) = '@'; + break; + case MODE_KEY : + c = 'k'; + cp = lp->value.cp; + break; + case MODE_LIMIT : + c = 'l'; + (void)sprintf(numeric, "%-15d", nusers); + if ((cp = index(numeric, ' '))) + *cp = '\0'; + cp = numeric; + break; + } + + if (len + strlen(cp) + 2 > (size_t) MODEBUFLEN) + break; + /* + * pass on +/-o/v regardless of whether they are + * redundant or effective but check +b's to see if + * it existed before we created it. + */ + switch(lp->flags & MODE_WPARAS) + { + case MODE_KEY : + *mbuf++ = c; + (void)strcat(pbuf, cp); + len += strlen(cp); + (void)strcat(pbuf, " "); + len++; + if (!ischop) + break; + if (strlen(cp) > (size_t) KEYLEN) + *(cp+KEYLEN) = '\0'; + if (whatt == MODE_ADD) + strncpyzt(mode->key, cp, + sizeof(mode->key)); + else + *mode->key = '\0'; + break; + case MODE_LIMIT : + *mbuf++ = c; + (void)strcat(pbuf, cp); + len += strlen(cp); + (void)strcat(pbuf, " "); + len++; + if (!ischop) + break; + mode->limit = nusers; + break; + case MODE_CHANOP : /* fall through case */ + if (ischop && lp->value.cptr == sptr && + lp->flags == MODE_CHANOP|MODE_DEL) + chptr->reop = timeofday + + LDELAYCHASETIMELIMIT; + case MODE_UNIQOP : + case MODE_VOICE : + *mbuf++ = c; + (void)strcat(pbuf, cp); + len += strlen(cp); + (void)strcat(pbuf, " "); + len++; + if (ischop) + change_chan_flag(lp, chptr); + break; + case MODE_BAN : + if (ischop && + (((whatt & MODE_ADD) && + !add_modeid(CHFL_BAN, sptr, chptr, cp))|| + ((whatt & MODE_DEL) && + !del_modeid(CHFL_BAN, chptr, cp)))) + { + *mbuf++ = c; + (void)strcat(pbuf, cp); + len += strlen(cp); + (void)strcat(pbuf, " "); + len++; + } + break; + case MODE_EXCEPTION : + if (ischop && + (((whatt & MODE_ADD) && + !add_modeid(CHFL_EXCEPTION, sptr, chptr, cp))|| + ((whatt & MODE_DEL) && + !del_modeid(CHFL_EXCEPTION, chptr, cp)))) + { + *mbuf++ = c; + (void)strcat(pbuf, cp); + len += strlen(cp); + (void)strcat(pbuf, " "); + len++; + } + break; + case MODE_INVITE : + if (ischop && + (((whatt & MODE_ADD) && + !add_modeid(CHFL_INVITE, sptr, chptr, cp))|| + ((whatt & MODE_DEL) && + !del_modeid(CHFL_INVITE, chptr, cp)))) + { + *mbuf++ = c; + (void)strcat(pbuf, cp); + len += strlen(cp); + (void)strcat(pbuf, " "); + len++; + } + break; + } + } /* for (; i < opcnt; i++) */ + } /* if (opcnt) */ + + *mbuf++ = '\0'; + + return ischop ? count : -count; +} + +static int can_join(sptr, chptr, key) +aClient *sptr; +Reg aChannel *chptr; +char *key; +{ + Link *lp = NULL, *banned; + + if (chptr->users == 0 && (bootopt & BOOT_PROT) && + chptr->history != 0 && *chptr->chname != '!') + return (timeofday > chptr->history) ? 0 : ERR_UNAVAILRESOURCE; + + for (lp = sptr->user->invited; lp; lp = lp->next) + if (lp->value.chptr == chptr) + break; + + if (banned = match_modeid(CHFL_BAN, sptr, chptr)) + if (match_modeid(CHFL_EXCEPTION, sptr, chptr)) + banned = NULL; + else if (lp == NULL) + return (ERR_BANNEDFROMCHAN); + + if ((chptr->mode.mode & MODE_INVITEONLY) + && !match_modeid(CHFL_INVITE, sptr, chptr) + && (lp == NULL)) + return (ERR_INVITEONLYCHAN); + + if (*chptr->mode.key && (BadPtr(key) || mycmp(chptr->mode.key, key))) + return (ERR_BADCHANNELKEY); + + if (chptr->mode.limit && (chptr->users >= chptr->mode.limit) && + (lp == NULL)) + return (ERR_CHANNELISFULL); + + if (banned) + sendto_channel_butone(&me, &me, chptr, + ":%s NOTICE %s :%s carries an invitation (overriding ban on %s).", + ME, chptr->chname, sptr->name, + banned->value.cp); + return 0; +} + +/* +** Remove bells and commas from channel name +*/ + +void clean_channelname(cn) +Reg char *cn; +{ + for (; *cn; cn++) + if (*cn == '\007' || *cn == ' ' || *cn == ',') + { + *cn = '\0'; + return; + } +} + +/* +** Return -1 if mask is present and doesnt match our server name. +*/ +static int check_channelmask(sptr, cptr, chname) +aClient *sptr, *cptr; +char *chname; +{ + Reg char *s, *t; + + if (*chname == '&' && IsServer(cptr)) + return -1; + s = rindex(chname, ':'); + if (!s) + return 0; + if ((t = index(s, '\007'))) + *t = '\0'; + + s++; + if (match(s, ME) || (IsServer(cptr) && match(s, cptr->name))) + { + if (MyClient(sptr)) + sendto_one(sptr, err_str(ERR_BADCHANMASK, sptr->name), + chname); + if (t) + *t = '\007'; + return -1; + } + if (t) + *t = '\007'; + return 0; +} + +/* +** Get Channel block for i (and allocate a new channel +** block, if it didn't exists before). +*/ +static aChannel *get_channel(cptr, chname, flag) +aClient *cptr; +char *chname; +int flag; + { + Reg aChannel *chptr; + int len; + + if (BadPtr(chname)) + return NULL; + + len = strlen(chname); + if (MyClient(cptr) && len > CHANNELLEN) + { + len = CHANNELLEN; + *(chname+CHANNELLEN) = '\0'; + } + if ((chptr = find_channel(chname, (aChannel *)NULL))) + return (chptr); + if (flag == CREATE) + { + chptr = (aChannel *)MyMalloc(sizeof(aChannel) + len); + bzero((char *)chptr, sizeof(aChannel)); + strncpyzt(chptr->chname, chname, len+1); + if (channel) + channel->prevch = chptr; + chptr->prevch = NULL; + chptr->nextch = channel; + chptr->history = 0; + channel = chptr; + (void)add_to_channel_hash_table(chname, chptr); + } + return chptr; + } + +static void add_invite(cptr, chptr) +aClient *cptr; +aChannel *chptr; +{ + Reg Link *inv, **tmp; + + del_invite(cptr, chptr); + /* + * delete last link in chain if the list is max length + */ + if (list_length(cptr->user->invited) >= MAXCHANNELSPERUSER) + { +/* This forgets the channel side of invitation -Vesa + inv = cptr->user->invited; + cptr->user->invited = inv->next; + free_link(inv); +*/ + del_invite(cptr, cptr->user->invited->value.chptr); + } + /* + * add client to channel invite list + */ + inv = make_link(); + inv->value.cptr = cptr; + inv->next = chptr->invites; + chptr->invites = inv; + istat.is_useri++; + /* + * add channel to the end of the client invite list + */ + for (tmp = &(cptr->user->invited); *tmp; tmp = &((*tmp)->next)) + ; + inv = make_link(); + inv->value.chptr = chptr; + inv->next = NULL; + (*tmp) = inv; + istat.is_invite++; +} + +/* + * Delete Invite block from channel invite list and client invite list + */ +void del_invite(cptr, chptr) +aClient *cptr; +aChannel *chptr; +{ + Reg Link **inv, *tmp; + + for (inv = &(chptr->invites); (tmp = *inv); inv = &tmp->next) + if (tmp->value.cptr == cptr) + { + *inv = tmp->next; + free_link(tmp); + istat.is_invite--; + break; + } + + for (inv = &(cptr->user->invited); (tmp = *inv); inv = &tmp->next) + if (tmp->value.chptr == chptr) + { + *inv = tmp->next; + free_link(tmp); + istat.is_useri--; + break; + } +} + +/* +** The last user has left the channel, free data in the channel block, +** and eventually the channel block itself. +*/ +static void free_channel(chptr) +aChannel *chptr; +{ + Reg Link *tmp; + Link *obtmp; + int len = sizeof(aChannel) + strlen(chptr->chname), now = 0; + + if (chptr->history == 0 || timeofday >= chptr->history) + /* no lock, nor expired lock, channel is no more, free it */ + now = 1; + + if (*chptr->chname != '!' || now) + { + while ((tmp = chptr->invites)) + del_invite(tmp->value.cptr, chptr); + + tmp = chptr->mlist; + while (tmp) + { + obtmp = tmp; + tmp = tmp->next; + istat.is_banmem -= (strlen(obtmp->value.cp) + 1); + istat.is_bans--; + MyFree(obtmp->value.cp); + free_link(obtmp); + } + chptr->mlist = NULL; + } + + if (now) + { + istat.is_hchan--; + istat.is_hchanmem -= len; + if (chptr->prevch) + chptr->prevch->nextch = chptr->nextch; + else + channel = chptr->nextch; + if (chptr->nextch) + chptr->nextch->prevch = chptr->prevch; + del_from_channel_hash_table(chptr->chname, chptr); + + if (*chptr->chname == '!' && close_chid(chptr->chname+1)) + cache_chid(chptr); + else + MyFree((char *)chptr); + } +} + +/* +** m_join +** parv[0] = sender prefix +** parv[1] = channel +** parv[2] = channel password (key) +*/ +int m_join(cptr, sptr, parc, parv) +Reg aClient *cptr, *sptr; +int parc; +char *parv[]; +{ + static char jbuf[BUFSIZE], cbuf[BUFSIZE]; + Reg Link *lp; + Reg aChannel *chptr; + Reg char *name, *key = NULL; + int i, flags = 0; + char *p = NULL, *p2 = NULL, *s, chop[5]; + + if (parc < 2 || *parv[1] == '\0') + { + sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS, parv[0]), "JOIN"); + return 1; + } + + *jbuf = '\0'; + *cbuf = '\0'; + /* + ** Rebuild list of channels joined to be the actual result of the + ** JOIN. Note that "JOIN 0" is the destructive problem. + ** Also note that this can easily trash the correspondance between + ** parv[1] and parv[2] lists. + */ + for (i = 0, name = strtoken(&p, parv[1], ","); name; + name = strtoken(&p, NULL, ",")) + { + if (check_channelmask(sptr, cptr, name)==-1) + continue; + if (*name == '&' && !MyConnect(sptr)) + continue; + if (*name == '0' && !atoi(name)) + { + (void)strcpy(jbuf, "0"); + continue; + } + if (*name == '!') + { + chptr = NULL; + /* + ** !channels are special: + ** !!channel is supposed to be a new channel, + ** and requires a unique name to be built. + ** ( !#channel is obsolete ) + ** !channel cannot be created, and must already + ** exist. + */ + if (*(name+1) == '\0' || + (*(name+1) == '#' && *(name+2) == '\0') || + (*(name+1) == '!' && *(name+2) == '\0')) + { + if (MyClient(sptr)) + sendto_one(sptr, + err_str(ERR_NOSUCHCHANNEL, + parv[0]), name); + continue; + } + if (*name == '!' && (*(name+1) == '#' || + *(name+1) == '!')) + { + if (!MyClient(sptr)) + { + sendto_flag(SCH_NOTICE, + "Invalid !%c channel from %s for %s", + *(name+1), + get_client_name(cptr,TRUE), + sptr->name); + continue; + } +#if 0 + /* + ** Note: creating !!!foo, e.g. !<ID>!foo is + ** a stupid thing to do because /join !!foo + ** will not join !<ID>!foo but create !<ID>foo + ** Some logic here could be reversed, but only + ** to find that !<ID>foo would be impossible to + ** create if !<ID>!foo exists. + ** which is better? it's hard to say -kalt + */ + if (*(name+3) == '!') + { + sendto_one(sptr, + err_str(ERR_NOSUCHCHANNEL, + parv[0]), name); + continue; + } +#endif + chptr = hash_find_channels(name+2, NULL); + if (chptr) + { + sendto_one(sptr, + err_str(ERR_TOOMANYTARGETS, + parv[0]), + "Duplicate", name, + "Join aborted."); + continue; + } + if (check_chid(name+2)) + { + /* + * This is a bit wrong: if a channel + * rightfully ceases to exist, it + * can still be *locked* for up to + * 2*CHIDNB^3 seconds (~24h) + * Is it a reasonnable price to pay to + * ensure shortname uniqueness? -kalt + */ + sendto_one(sptr, + err_str(ERR_UNAVAILRESOURCE, + parv[0]), name); + continue; + } + sprintf(buf, "!%.*s%s", CHIDLEN, get_chid(), + name+2); + name = buf; + } + else if (!find_channel(name, NullChn) && + !(*name == '!' && *name != 0 && + (chptr = hash_find_channels(name+1, NULL)))) + { + if (MyClient(sptr)) + sendto_one(sptr, + err_str(ERR_NOSUCHCHANNEL, + parv[0]), name); + if (!IsServer(cptr)) + continue; + /* from a server, it is legitimate */ + } + else if (chptr) + { + /* joining a !channel using the short name */ + if (MyConnect(sptr) && + hash_find_channels(name+1, chptr)) + { + sendto_one(sptr, + err_str(ERR_TOOMANYTARGETS, + parv[0]), + "Duplicate", name, + "Join aborted."); + continue; + } + name = chptr->chname; + } + } + if (!IsChannelName(name) || + (*name == '!' && IsChannelName(name+1))) + { + if (MyClient(sptr)) + sendto_one(sptr, err_str(ERR_NOSUCHCHANNEL, + parv[0]), name); + continue; + } + if (*jbuf) + (void)strcat(jbuf, ","); + (void)strncat(jbuf, name, sizeof(jbuf) - i - 1); + i += strlen(name)+1; + } + + p = NULL; + if (parv[2]) + key = strtoken(&p2, parv[2], ","); + parv[2] = NULL; /* for m_names call later, parv[parc] must == NULL */ + for (name = strtoken(&p, jbuf, ","); name; + key = (key) ? strtoken(&p2, NULL, ",") : NULL, + name = strtoken(&p, NULL, ",")) + { + /* + ** JOIN 0 sends out a part for all channels a user + ** has joined. + */ + if (*name == '0' && !atoi(name)) + { + if (sptr->user->channel == NULL) + continue; + while ((lp = sptr->user->channel)) + { + chptr = lp->value.chptr; + sendto_channel_butserv(chptr, sptr, + PartFmt, + parv[0], chptr->chname, + IsAnonymous(chptr) ? "None" : + (key ? key : parv[0])); + remove_user_from_channel(sptr, chptr); + } + sendto_match_servs(NULL, cptr, ":%s JOIN 0 :%s", + parv[0], key ? key : parv[0]); + continue; + } + + if (cptr->serv && (s = index(name, '\007'))) + *s++ = '\0'; + else + clean_channelname(name), s = NULL; + + if (MyConnect(sptr) && + sptr->user->joined >= MAXCHANNELSPERUSER) { + /* Feature: Cannot join &flagchannels either + if already joined MAXCHANNELSPERUSER times. */ + sendto_one(sptr, err_str(ERR_TOOMANYCHANNELS, + parv[0]), name); + /* can't return, need to send the info everywhere */ + continue; + } + + chptr = get_channel(sptr, name, CREATE); + + if (IsMember(sptr, chptr)) + continue; + if (!chptr || + (MyConnect(sptr) && (i = can_join(sptr, chptr, key)))) + { + sendto_one(sptr, err_str(i, parv[0]), name); + continue; + } + + /* + ** local client is first to enter previously nonexistant + ** channel so make them (rightfully) the Channel + ** Operator. + */ + flags = 0; + chop[0] = '\0'; + if (MyConnect(sptr) && UseModes(name) && + (!IsRestricted(sptr) || (*name == '&')) && !chptr->users && + !(chptr->history && *chptr->chname == '!')) + { + if (*name == '!') + strcpy(chop, "\007O"); + else + strcpy(chop, "\007o"); + s = chop+1; /* tricky */ + } + /* + ** Complete user entry to the new channel (if any) + */ + if (s && UseModes(name)) + { + if (*s == 'O') + /* + * there can never be another mode here, + * because we use NJOIN for netjoins. + * here, it *must* be a channel creation. -kalt + */ + flags |= CHFL_UNIQOP|CHFL_CHANOP; + else if (*s == 'o') + { + flags |= CHFL_CHANOP; + if (*(s+1) == 'v') + flags |= CHFL_VOICE; + } + else if (*s == 'v') + flags |= CHFL_VOICE; + } + add_user_to_channel(chptr, sptr, flags); + /* + ** notify all users on the channel + */ + sendto_channel_butserv(chptr, sptr, ":%s JOIN :%s", + parv[0], name); + if (s && UseModes(name)) + { + /* no need if user is creating the channel */ + if (chptr->users != 1) + sendto_channel_butserv(chptr, sptr, + ":%s MODE %s +%s %s %s", + cptr->name, name, s, + parv[0], + *(s+1)=='v'?parv[0]:""); + *--s = '\007'; + } + /* + ** If s wasn't set to chop+1 above, name is now #chname^Gov + ** again (if coming from a server, and user is +o and/or +v + ** of course ;-) + ** This explains the weird use of name and chop.. + ** Is this insane or subtle? -krys + */ + if (MyClient(sptr)) + { + del_invite(sptr, chptr); + if (chptr->topic[0] != '\0') + sendto_one(sptr, rpl_str(RPL_TOPIC, parv[0]), + name, chptr->topic); + parv[1] = name; + (void)m_names(cptr, sptr, 2, parv); + if (IsAnonymous(chptr) && !IsQuiet(chptr)) + { + sendto_one(sptr, ":%s NOTICE %s :Channel %s has the anonymous flag set.", ME, chptr->chname, chptr->chname); + sendto_one(sptr, ":%s NOTICE %s :Be aware that anonymity on IRC is NOT securely enforced!", ME, chptr->chname); + } + } + /* + ** notify other servers + */ + if (index(name, ':') || *chptr->chname == '!') /* compat */ + sendto_match_servs(chptr, cptr, ":%s JOIN :%s%s", + parv[0], name, chop); + else if (*chptr->chname != '&') + { + if (*cbuf) + strcat(cbuf, ","); + strcat(cbuf, name); + if (chop) + strcat(cbuf, chop); + } + } + if (*cbuf) + sendto_serv_butone(cptr, ":%s JOIN :%s", parv[0], cbuf); + return 2; +} + +/* +** m_njoin +** parv[0] = sender prefix +** parv[1] = channel +** parv[2] = channel members and modes +*/ +int m_njoin(cptr, sptr, parc, parv) +Reg aClient *cptr, *sptr; +int parc; +char *parv[]; +{ + char nbuf[BUFSIZE], *q, *name, *target, *p, mbuf[4]; + int chop, cnt = 0, nj = 0; + aChannel *chptr = NULL; + aClient *acptr; + + if (parc < 3 || *parv[2] == '\0') + { + sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS, parv[0]),"NJOIN"); + return 1; + } + *nbuf = '\0'; q = nbuf; + for (target = strtoken(&p, parv[2], ","); target; + target = strtoken(&p, NULL, ",")) + { + /* check for modes */ + chop = 0; + mbuf[0] = '\0'; + if (*target == '@') + { + if (*(target+1) == '@') + { + /* actually never sends in a JOIN ^G */ + if (*(target+2) == '+') + { + strcpy(mbuf, "\007Ov"); + chop = CHFL_UNIQOP|CHFL_CHANOP| \ + CHFL_VOICE; + name = target + 3; + } + else + { + strcpy(mbuf, "\007O"); + chop = CHFL_UNIQOP|CHFL_CHANOP; + name = target + 2; + } + } + else + { + if (*(target+1) == '+') + { + strcpy(mbuf, "\007ov"); + chop = CHFL_CHANOP|CHFL_VOICE; + name = target+2; + } + else + { + strcpy(mbuf, "\007o"); + chop = CHFL_CHANOP; + name = target+1; + } + } + } + else if (*target == '+') + { + strcpy(mbuf, "\007v"); + chop = CHFL_VOICE; + name = target+1; + } + else + name = target; + /* find user */ + if (!(acptr = find_person(name, (aClient *)NULL))) + continue; + /* is user who we think? */ + if (acptr->from != cptr) + continue; + /* get channel pointer */ + if (!chptr) + { + if (check_channelmask(sptr, cptr, parv[1]) == -1) + { + sendto_flag(SCH_DEBUG, + "received NJOIN for %s from %s", + parv[1], + get_client_name(cptr, TRUE)); + return 0; + } + chptr = get_channel(acptr, parv[1], CREATE); + if (!IsChannelName(parv[1]) || chptr == NULL) + { + sendto_one(sptr, err_str(ERR_NOSUCHCHANNEL, + parv[0]), parv[1]); + return 0; + } + } + /* make sure user isn't already on channel */ + if (IsMember(acptr, chptr)) + { + sendto_flag(SCH_ERROR, "NJOIN protocol error from %s", + get_client_name(cptr, TRUE)); + sendto_one(cptr, "ERROR :NJOIN protocol error"); + continue; + } + /* add user to channel */ + add_user_to_channel(chptr, acptr, UseModes(parv[1]) ? chop :0); + /* build buffer for NJOIN capable servers */ + if (q != nbuf) + *q++ = ','; + while (*target) + *q++ = *target++; + /* send 2.9 style join to other servers */ + if (*chptr->chname != '!') + nj = sendto_match_servs_notv(chptr, cptr, SV_NJOIN, + ":%s JOIN %s%s", name, + parv[1], + UseModes(parv[1]) ? mbuf : + ""); + /* send join to local users on channel */ + sendto_channel_butserv(chptr, acptr, ":%s JOIN %s", name, + parv[1]); + /* build MODE for local users on channel, eventually send it */ + if (*mbuf) + { + if (!UseModes(parv[1])) + { + sendto_one(cptr, err_str(ERR_NOCHANMODES, + parv[0]), parv[1]); + continue; + } + switch (cnt) + { + case 0: + *parabuf = '\0'; *modebuf = '\0'; + /* fall through */ + case 1: + strcat(modebuf, mbuf+1); + cnt += strlen(mbuf+1); + if (*parabuf) + { + strcat(parabuf, " "); + } + strcat(parabuf, name); + if (mbuf[2]) + { + strcat(parabuf, " "); + strcat(parabuf, name); + } + break; + case 2: + sendto_channel_butserv(chptr, &me, + ":%s MODE %s +%s%c %s %s", + sptr->name, parv[1], + modebuf, mbuf[1], + parabuf, name); + if (mbuf[2]) + { + strcpy(modebuf, mbuf+2); + strcpy(parabuf, name); + cnt = 1; + } + else + cnt = 0; + break; + } + if (cnt == 3) + { + sendto_channel_butserv(chptr, &me, + ":%s MODE %s +%s %s", + sptr->name, parv[1], + modebuf, parabuf); + cnt = 0; + } + } + } + /* send eventual MODE leftover */ + if (cnt) + sendto_channel_butserv(chptr, &me, ":%s MODE %s +%s %s", + sptr->name, parv[1], modebuf, parabuf); + + /* send NJOIN to capable servers */ + *q = '\0'; + if (nbuf[0]) + sendto_match_servs_v(chptr, cptr, SV_NJOIN, ":%s NJOIN %s :%s", + parv[0], parv[1], nbuf); + return 0; +} + +/* +** m_part +** parv[0] = sender prefix +** parv[1] = channel +*/ +int m_part(cptr, sptr, parc, parv) +aClient *cptr, *sptr; +int parc; +char *parv[]; + { + Reg aChannel *chptr; + char *p = NULL, *name, *comment = ""; + + if (parc < 2 || parv[1][0] == '\0') + { + sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS, parv[0]), "PART"); + return 1; + } + + *buf = '\0'; + + for (; (name = strtoken(&p, parv[1], ",")); parv[1] = NULL) + { + chptr = get_channel(sptr, name, 0); + if (!chptr) + { + if (MyPerson(sptr)) + sendto_one(sptr, + err_str(ERR_NOSUCHCHANNEL, parv[0]), + name); + continue; + } + if (check_channelmask(sptr, cptr, name)) + continue; + if (!IsMember(sptr, chptr)) + { + sendto_one(sptr, err_str(ERR_NOTONCHANNEL, parv[0]), + name); + continue; + } + comment = (BadPtr(parv[2])) ? parv[0] : parv[2]; + if (IsAnonymous(chptr) && (comment == parv[0])) + comment = "None"; + if (strlen(comment) > (size_t) TOPICLEN) + comment[TOPICLEN] = '\0'; + + /* + ** Remove user from the old channel (if any) + */ + if (!index(name, ':') && (*chptr->chname != '!')) + { /* channel:*.mask */ + if (*name != '&') + { + if (*buf) + (void)strcat(buf, ","); + (void)strcat(buf, name); + } + } + else + sendto_match_servs(chptr, cptr, PartFmt, + parv[0], name, comment); + sendto_channel_butserv(chptr, sptr, PartFmt, + parv[0], name, comment); + remove_user_from_channel(sptr, chptr); + } + if (*buf) + sendto_serv_butone(cptr, PartFmt, parv[0], buf, comment); + return 4; + } + +/* +** m_kick +** parv[0] = sender prefix +** parv[1] = channel +** parv[2] = client to kick +** parv[3] = kick comment +*/ +int m_kick(cptr, sptr, parc, parv) +aClient *cptr, *sptr; +int parc; +char *parv[]; +{ + aClient *who; + aChannel *chptr; + int chasing = 0, penalty = 0; + char *comment, *name, *p = NULL, *user, *p2 = NULL; + int mlen, len = 0, nlen; + + if (parc < 3 || *parv[1] == '\0') + { + sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS, parv[0]), "KICK"); + return 1; + } + if (IsServer(sptr)) + sendto_flag(SCH_NOTICE, "KICK from %s for %s %s", + parv[0], parv[1], parv[2]); + comment = (BadPtr(parv[3])) ? parv[0] : parv[3]; + if (strlen(comment) > (size_t) TOPICLEN) + comment[TOPICLEN] = '\0'; + + *nickbuf = *buf = '\0'; + mlen = 7 + strlen(parv[0]); + + for (; (name = strtoken(&p, parv[1], ",")); parv[1] = NULL) + { + if (penalty++ >= MAXPENALTY && MyPerson(sptr)) + break; + chptr = get_channel(sptr, name, !CREATE); + if (!chptr) + { + if (MyPerson(sptr)) + sendto_one(sptr, + err_str(ERR_NOSUCHCHANNEL, parv[0]), + name); + continue; + } + if (check_channelmask(sptr, cptr, name)) + continue; + if (!UseModes(name)) + { + sendto_one(sptr, err_str(ERR_NOCHANMODES, parv[0]), + name); + continue; + } + if (!IsServer(sptr) && !is_chan_op(sptr, chptr)) + { + if (!IsMember(sptr, chptr)) + sendto_one(sptr, err_str(ERR_NOTONCHANNEL, + parv[0]), chptr->chname); + else + sendto_one(sptr, err_str(ERR_CHANOPRIVSNEEDED, + parv[0]), chptr->chname); + continue; + } + if (len + mlen + strlen(name) < (size_t) BUFSIZE / 2) + { + if (*buf) + (void)strcat(buf, ","); + (void)strcat(buf, name); + len += strlen(name) + 1; + } + else + continue; + nlen = 0; + + for (; (user = strtoken(&p2, parv[2], ",")); parv[2] = NULL) + { + penalty++; + if (!(who = find_chasing(sptr, user, &chasing))) + continue; /* No such user left! */ + if (nlen + mlen + strlen(who->name) > + (size_t) BUFSIZE - NICKLEN) + continue; + if (IsMember(who, chptr)) + { + sendto_channel_butserv(chptr, sptr, + ":%s KICK %s %s :%s", parv[0], + name, who->name, comment); + /* Don't send &local &kicks out */ + if (*chptr->chname != '&' && + *chptr->chname != '!' && + index(chptr->chname, ':') == NULL) { + if (*nickbuf) + (void)strcat(nickbuf, ","); + (void)strcat(nickbuf, who->name); + nlen += strlen(who->name); + } + else + sendto_match_servs(chptr, cptr, + ":%s KICK %s %s :%s", + parv[0], name, + who->name, comment); + remove_user_from_channel(who,chptr); + penalty += 2; + if (penalty > MAXPENALTY && MyPerson(sptr)) + break; + } + else + sendto_one(sptr, + err_str(ERR_USERNOTINCHANNEL, + parv[0]), user, name); + } /* loop on parv[2] */ + } /* loop on parv[1] */ + + if (*buf && *nickbuf) + sendto_serv_butone(cptr, ":%s KICK %s %s :%s", + parv[0], buf, nickbuf, comment); + return penalty; +} + +int count_channels(sptr) +aClient *sptr; +{ +Reg aChannel *chptr; + Reg int count = 0; + + for (chptr = channel; chptr; chptr = chptr->nextch) + { + if (chptr->users) /* don't count channels in history */ +#ifdef SHOW_INVISIBLE_LUSERS + if (SecretChannel(chptr)) + { + if (IsAnOper(sptr)) + count++; + } + else +#endif + count++; + } + return (count); +} + +/* +** m_topic +** parv[0] = sender prefix +** parv[1] = topic text +*/ +int m_topic(cptr, sptr, parc, parv) +aClient *cptr, *sptr; +int parc; +char *parv[]; + { + aChannel *chptr = NullChn; + char *topic = NULL, *name, *p = NULL; + int penalty = 1; + + if (parc < 2) + { + sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS, parv[0]), + "TOPIC"); + return 1; + } + + parv[1] = canonize(parv[1]); + + for (; (name = strtoken(&p, parv[1], ",")); parv[1] = NULL) + { + if (!UseModes(name)) + { + sendto_one(sptr, err_str(ERR_NOCHANMODES, parv[0]), + name); + continue; + } + if (parc > 1 && IsChannelName(name)) + { + chptr = find_channel(name, NullChn); + if (!chptr || !IsMember(sptr, chptr)) + { + sendto_one(sptr, err_str(ERR_NOTONCHANNEL, + parv[0]), name); + continue; + } + if (parc > 2) + topic = parv[2]; + } + + if (!chptr) + { + sendto_one(sptr, rpl_str(RPL_NOTOPIC, parv[0]), name); + return penalty; + } + + if (check_channelmask(sptr, cptr, name)) + continue; + + if (!topic) /* only asking for topic */ + { + if (chptr->topic[0] == '\0') + sendto_one(sptr, rpl_str(RPL_NOTOPIC, parv[0]), + chptr->chname); + else + sendto_one(sptr, rpl_str(RPL_TOPIC, parv[0]), + chptr->chname, chptr->topic); + } + else if ((chptr->mode.mode & MODE_TOPICLIMIT) == 0 || + is_chan_op(sptr, chptr)) + { /* setting a topic */ + strncpyzt(chptr->topic, topic, sizeof(chptr->topic)); + sendto_match_servs(chptr, cptr,":%s TOPIC %s :%s", + parv[0], chptr->chname, + chptr->topic); + sendto_channel_butserv(chptr, sptr, ":%s TOPIC %s :%s", + parv[0], + chptr->chname, chptr->topic); +#ifdef USE_SERVICES + check_services_butone(SERVICE_WANT_TOPIC, + NULL, sptr, ":%s TOPIC %s :%s", + parv[0], chptr->chname, + chptr->topic); +#endif + penalty += 2; + } + else + sendto_one(sptr, err_str(ERR_CHANOPRIVSNEEDED, parv[0]), + chptr->chname); + } + return penalty; + } + +/* +** m_invite +** parv[0] - sender prefix +** parv[1] - user to invite +** parv[2] - channel number +*/ +int m_invite(cptr, sptr, parc, parv) +aClient *cptr, *sptr; +int parc; +char *parv[]; +{ + aClient *acptr; + aChannel *chptr; + + if (parc < 3 || *parv[1] == '\0') + { + sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS, parv[0]), + "INVITE"); + return 1; + } + + if (!(acptr = find_person(parv[1], (aClient *)NULL))) + { + sendto_one(sptr, err_str(ERR_NOSUCHNICK, parv[0]), parv[1]); + return 1; + } + clean_channelname(parv[2]); + if (check_channelmask(sptr, acptr->user->servp->bcptr, parv[2])) + return 1; + if (*parv[2] == '&' && !MyClient(acptr)) + return 1; + chptr = find_channel(parv[2], NullChn); + + if (!chptr) + { + sendto_prefix_one(acptr, sptr, ":%s INVITE %s :%s", + parv[0], parv[1], parv[2]); + if (MyConnect(sptr)) + { + sendto_one(sptr, rpl_str(RPL_INVITING, parv[0]), + acptr->name, parv[2]); + if (acptr->user->flags & FLAGS_AWAY) + sendto_one(sptr, rpl_str(RPL_AWAY, parv[0]), + acptr->name, (acptr->user->away) ? + acptr->user->away : "Gone"); + } + return 3; + } + + if (!IsMember(sptr, chptr)) + { + sendto_one(sptr, err_str(ERR_NOTONCHANNEL, parv[0]), parv[2]); + return 1; + } + + if (IsMember(acptr, chptr)) + { + sendto_one(sptr, err_str(ERR_USERONCHANNEL, parv[0]), + parv[1], parv[2]); + return 1; + } + + if ((chptr->mode.mode & MODE_INVITEONLY) && !is_chan_op(sptr, chptr)) + { + sendto_one(sptr, err_str(ERR_CHANOPRIVSNEEDED, + parv[0]), chptr->chname); + return 1; + } + + if (MyConnect(sptr)) + { + sendto_one(sptr, rpl_str(RPL_INVITING, parv[0]), + acptr->name, ((chptr) ? (chptr->chname) : parv[2])); + if (acptr->user->flags & FLAGS_AWAY) + sendto_one(sptr, rpl_str(RPL_AWAY, parv[0]), + acptr->name, + (acptr->user->away) ? acptr->user->away : + "Gone"); + } + + if (MyConnect(acptr)) + if (chptr && /* (chptr->mode.mode & MODE_INVITEONLY) && */ + sptr->user && is_chan_op(sptr, chptr)) + add_invite(acptr, chptr); + + sendto_prefix_one(acptr, sptr, ":%s INVITE %s :%s",parv[0], + acptr->name, ((chptr) ? (chptr->chname) : parv[2])); + return 2; +} + + +/* +** m_list +** parv[0] = sender prefix +** parv[1] = channel +*/ +int m_list(cptr, sptr, parc, parv) +aClient *cptr, *sptr; +int parc; +char *parv[]; + { + aChannel *chptr; + char *name, *p = NULL; + int rlen = 0; + + if (parc > 1 && + hunt_server(cptr, sptr, ":%s LIST %s %s", 2, parc, parv)) + return 10; + if (BadPtr(parv[1])) + for (chptr = channel; chptr; chptr = chptr->nextch) + { + if (!sptr->user || + !chptr->users || /* empty locked channel */ + (SecretChannel(chptr) && !IsMember(sptr, chptr))) + continue; + name = ShowChannel(sptr, chptr) ? chptr->chname : NULL; + rlen += sendto_one(sptr, rpl_str(RPL_LIST, parv[0]), + name ? name : "*", chptr->users, + name ? chptr->topic : ""); + if (!MyConnect(sptr) && rlen > CHREPLLEN) + break; + } + else { + parv[1] = canonize(parv[1]); + for (; (name = strtoken(&p, parv[1], ",")); parv[1] = NULL) + { + chptr = find_channel(name, NullChn); + if (chptr && ShowChannel(sptr, chptr) && sptr->user) + { + rlen += sendto_one(sptr, rpl_str(RPL_LIST, + parv[0]), name, + chptr->users, chptr->topic); + if (!MyConnect(sptr) && rlen > CHREPLLEN) + break; + } + if (*name == '!') + { + chptr = NULL; + while (chptr=hash_find_channels(name+1, chptr)) + { + int scr = SecretChannel(chptr) && + !IsMember(sptr, chptr); + rlen += sendto_one(sptr, + rpl_str(RPL_LIST, + parv[0]), + chptr->chname, + (scr) ? -1 : + chptr->users, + (scr) ? "" : + chptr->topic); + if (!MyConnect(sptr) && + rlen > CHREPLLEN) + break; + } + } + } + } + if (!MyConnect(sptr) && rlen > CHREPLLEN) + sendto_one(sptr, err_str(ERR_TOOMANYMATCHES, parv[0]), + !BadPtr(parv[1]) ? parv[1] : "*"); + sendto_one(sptr, rpl_str(RPL_LISTEND, parv[0])); + return 2; + } + + +/************************************************************************ + * m_names() - Added by Jto 27 Apr 1989 + ************************************************************************/ + +/* +** m_names +** parv[0] = sender prefix +** parv[1] = channel +*/ +int m_names(cptr, sptr, parc, parv) +aClient *cptr, *sptr; +int parc; +char *parv[]; +{ + Reg aChannel *chptr; + Reg aClient *c2ptr; + Reg Link *lp; + aChannel *ch2ptr = NULL; + int idx, flag, len, mlen, rlen = 0; + char *s, *para = parc > 1 ? parv[1] : NULL; + + if (parc > 1 && + hunt_server(cptr, sptr, ":%s NAMES %s %s", 2, parc, parv)) + return 10; + + mlen = strlen(ME) + 10; /* server names + : : + spaces + "353" */ + mlen += strlen(parv[0]); + if (!BadPtr(para)) + { + s = index(para, ','); + if (s && MyConnect(sptr) && s != para) + { + parv[1] = ++s; + (void)m_names(cptr, sptr, parc, parv); + } + clean_channelname(para); + ch2ptr = find_channel(para, (aChannel *)NULL); + } + + *buf = '\0'; + + /* + * First, do all visible channels (public and the one user self is) + */ + + for (chptr = channel; chptr; chptr = chptr->nextch) + { + if (!chptr->users || /* locked empty channel */ + ((chptr != ch2ptr) && !BadPtr(para))) /* 'wrong' channel */ + continue; + if (!MyConnect(sptr) && (BadPtr(para) || (rlen > CHREPLLEN))) + break; + if ((BadPtr(para) || !HiddenChannel(chptr)) && + !ShowChannel(sptr, chptr)) + continue; /* -- users on this are not listed */ + + /* Find users on same channel (defined by chptr) */ + + (void)strcpy(buf, "* "); + len = strlen(chptr->chname); + (void)strcpy(buf + 2, chptr->chname); + (void)strcpy(buf + 2 + len, " :"); + + if (PubChannel(chptr)) + *buf = '='; + else if (SecretChannel(chptr)) + *buf = '@'; + + if (IsAnonymous(chptr)) + { + if ((lp = find_user_link(chptr->members, sptr))) + { + if (lp->flags & CHFL_CHANOP) + (void)strcat(buf, "@"); + else if (lp->flags & CHFL_VOICE) + (void)strcat(buf, "+"); + (void)strcat(buf, parv[0]); + } + rlen += strlen(buf); + sendto_one(sptr, rpl_str(RPL_NAMREPLY, parv[0]), buf); + continue; + } + idx = len + 4; /* channel name + [@=] + 2?? */ + flag = 1; + for (lp = chptr->members; lp; lp = lp->next) + { + c2ptr = lp->value.cptr; + if (IsInvisible(c2ptr) && !IsMember(sptr,chptr)) + continue; + if (lp->flags & CHFL_CHANOP) + { + (void)strcat(buf, "@"); + idx++; + } + else if (lp->flags & CHFL_VOICE) + { + (void)strcat(buf, "+"); + idx++; + } + (void)strncat(buf, c2ptr->name, NICKLEN); + idx += strlen(c2ptr->name) + 1; + flag = 1; + (void)strcat(buf," "); + if (mlen + idx + NICKLEN + 1 > BUFSIZE - 2) + { + sendto_one(sptr, rpl_str(RPL_NAMREPLY, + parv[0]), buf); + (void)strncpy(buf, "* ", 3); + (void)strncpy(buf+2, chptr->chname, + len + 1); + (void)strcat(buf, " :"); + if (PubChannel(chptr)) + *buf = '='; + else if (SecretChannel(chptr)) + *buf = '@'; + idx = len + 4; + flag = 0; + } + } + if (flag) + { + rlen += strlen(buf); + sendto_one(sptr, rpl_str(RPL_NAMREPLY, parv[0]), buf); + } + } /* for(channels) */ + if (!BadPtr(para)) + { + if (!MyConnect(sptr) && (rlen > CHREPLLEN)) + sendto_one(sptr, err_str(ERR_TOOMANYMATCHES, parv[0]), + para); + sendto_one(sptr, rpl_str(RPL_ENDOFNAMES, parv[0]), para); + return(1); + } + + /* Second, do all non-public, non-secret channels in one big sweep */ + + (void)strncpy(buf, "* * :", 6); + idx = 5; + flag = 0; + for (c2ptr = client; c2ptr; c2ptr = c2ptr->next) + { + aChannel *ch3ptr; + int showflag = 0, secret = 0; + + if (!IsPerson(c2ptr) || IsInvisible(c2ptr)) + continue; + if (!MyConnect(sptr) && (BadPtr(para) || (rlen > CHREPLLEN))) + break; + lp = c2ptr->user->channel; + /* + * don't show a client if they are on a secret channel or + * they are on a channel sptr is on since they have already + * been show earlier. -avalon + */ + while (lp) + { + ch3ptr = lp->value.chptr; + if (PubChannel(ch3ptr) || IsMember(sptr, ch3ptr)) + showflag = 1; + if (SecretChannel(ch3ptr)) + secret = 1; + lp = lp->next; + } + if (showflag) /* have we already shown them ? */ + continue; + if (secret) /* on any secret channels ? */ + continue; + (void)strncat(buf, c2ptr->name, NICKLEN); + idx += strlen(c2ptr->name) + 1; + (void)strcat(buf," "); + flag = 1; + if (mlen + idx + NICKLEN > BUFSIZE - 2) + { + rlen += strlen(buf); + sendto_one(sptr, rpl_str(RPL_NAMREPLY, parv[0]), buf); + (void)strncpy(buf, "* * :", 6); + idx = 5; + flag = 0; + } + } + if (flag) + { + rlen += strlen(buf); + sendto_one(sptr, rpl_str(RPL_NAMREPLY, parv[0]), buf); + } + if (!MyConnect(sptr) && rlen > CHREPLLEN) + sendto_one(sptr, err_str(ERR_TOOMANYMATCHES, parv[0]), + para ? para : "*"); + /* This is broken.. remove the recursion? */ + sendto_one(sptr, rpl_str(RPL_ENDOFNAMES, parv[0]), "*"); + return 2; +} + +void send_user_joins(cptr, user) +aClient *cptr, *user; +{ + Reg Link *lp; + Reg aChannel *chptr; + Reg int cnt = 0, len = 0, clen; + char *mask; + + *buf = ':'; + (void)strcpy(buf+1, user->name); + (void)strcat(buf, " JOIN "); + len = strlen(user->name) + 7; + + for (lp = user->user->channel; lp; lp = lp->next) + { + chptr = lp->value.chptr; + if (*chptr->chname == '&') + continue; + if (*chptr->chname == '!' && !(cptr->serv->version & SV_NCHAN)) + /* in reality, testing SV_NCHAN here is pointless */ + continue; + if ((mask = index(chptr->chname, ':'))) + if (match(++mask, cptr->name)) + continue; + clen = strlen(chptr->chname); + if ((clen + len) > (size_t) BUFSIZE - 7) + { + if (cnt) + sendto_one(cptr, "%s", buf); + *buf = ':'; + (void)strcpy(buf+1, user->name); + (void)strcat(buf, " JOIN "); + len = strlen(user->name) + 7; + cnt = 0; + } + if (cnt) + { + len++; + (void)strcat(buf, ","); + } + (void)strcpy(buf + len, chptr->chname); + len += clen; + if (lp->flags & (CHFL_UNIQOP|CHFL_CHANOP|CHFL_VOICE)) + { + buf[len++] = '\007'; + if (lp->flags & CHFL_UNIQOP) /*this should be useless*/ + buf[len++] = 'O'; + if (lp->flags & CHFL_CHANOP) + buf[len++] = 'o'; + if (lp->flags & CHFL_VOICE) + buf[len++] = 'v'; + buf[len] = '\0'; + } + cnt++; + } + if (*buf && cnt) + sendto_one(cptr, "%s", buf); + + return; +} + +#define CHECKFREQ 300 +/* consider reoping an opless !channel */ +static int +reop_channel(now, chptr) +time_t now; +aChannel *chptr; +{ + Link *lp, op; + + op.value.chptr = NULL; + if (chptr->users <= 5 && (now - chptr->history > DELAYCHASETIMELIMIT)) + { + /* few users, no recent split: this is really a small channel */ + char mbuf[4], nbuf[3*(NICKLEN+1)+1]; + int cnt; + + lp = chptr->members; + while (lp) + { + if (lp->flags & CHFL_CHANOP) + { + chptr->reop = 0; + return 0; + } + if (MyConnect(lp->value.cptr)) + op.value.cptr = lp->value.cptr; + lp = lp->next; + } + if (op.value.cptr == NULL && + ((now - chptr->reop) < LDELAYCHASETIMELIMIT)) + /* + ** do nothing if no local users, + ** unless the reop is really overdue. + */ + return 0; + sendto_channel_butone(&me, &me, chptr, + ":%s NOTICE %s :Enforcing channel mode +r (%d)", + ME, chptr->chname, now - chptr->reop); + op.flags = MODE_ADD|MODE_CHANOP; + lp = chptr->members; + cnt = 3; + while (lp) + { + if (cnt == 3) + { + mbuf[cnt] = '\0'; + if (lp != chptr->members) + { + sendto_match_servs_v(chptr, NULL, SV_NCHAN, + ":%s MODE %s +%s %s", + ME, chptr->chname, + mbuf, nbuf); + sendto_channel_butserv(chptr, &me, + ":%s MODE %s +%s %s", + ME, chptr->chname, + mbuf, nbuf); + } + cnt = 0; + mbuf[0] = nbuf[0] = '\0'; + } + op.value.cptr = lp->value.cptr; + change_chan_flag(&op, chptr); + mbuf[cnt++] = 'o'; + strcat(nbuf, lp->value.cptr->name); + strcat(nbuf, " "); + lp = lp->next; + } + if (cnt) + { + mbuf[cnt] = '\0'; + sendto_match_servs_v(chptr, NULL, SV_NCHAN, + ":%s MODE %s +%s %s", + ME, chptr->chname, mbuf, nbuf); + sendto_channel_butserv(chptr, &me, ":%s MODE %s +%s %s", + ME, chptr->chname, mbuf, nbuf); + } + } + else + { + time_t idlelimit = now - + MIN((LDELAYCHASETIMELIMIT/2), (2*CHECKFREQ)); + + lp = chptr->members; + while (lp) + { + if (lp->flags & CHFL_CHANOP) + { + chptr->reop = 0; + return 0; + } + if (MyConnect(lp->value.cptr) && + lp->value.cptr->user->last > idlelimit && + (op.value.cptr == NULL || + lp->value.cptr->user->last>op.value.cptr->user->last)) + op.value.cptr = lp->value.cptr; + lp = lp->next; + } + if (op.value.cptr == NULL) + return 0; + sendto_channel_butone(&me, &me, chptr, + ":%s NOTICE %s :Enforcing channel mode +r (%d)", ME, + chptr->chname, now - chptr->reop); + op.flags = MODE_ADD|MODE_CHANOP; + change_chan_flag(&op, chptr); + sendto_match_servs_v(chptr, NULL, SV_NCHAN, ":%s MODE %s +o %s", + ME, chptr->chname, op.value.cptr->name); + sendto_channel_butserv(chptr, &me, ":%s MODE %s +o %s", + ME, chptr->chname, op.value.cptr->name); + } + chptr->reop = 0; + return 1; +} + +/* + * Cleanup locked channels, run frequently. + * + * A channel life is defined by its users and the history stamp. + * It is alive if one of the following is true: + * chptr->users > 0 (normal state) + * chptr->history >= time(NULL) (eventually locked) + * It is locked if empty but alive. + * + * The history stamp is set when a remote user with channel op exits. + */ +time_t collect_channel_garbage(now) +time_t now; +{ + static u_int max_nb = 0; /* maximum of live channels */ + static u_char split = 0; + Reg aChannel *chptr = channel; + Reg u_int cur_nb = 1, curh_nb = 0, r_cnt = 0; + aChannel *del_ch; +#ifdef DEBUGMODE + u_int del = istat.is_hchan; +#endif +#define SPLITBONUS (CHECKFREQ - 50) + + collect_chid(); + + while (chptr) + { + if (chptr->users == 0) + curh_nb++; + else + { + cur_nb++; + if (*chptr->chname == '!' && + (chptr->mode.mode & MODE_REOP) && + chptr->reop && chptr->reop <= now) + r_cnt += reop_channel(now, chptr); + } + chptr = chptr->nextch; + } + if (cur_nb > max_nb) + max_nb = cur_nb; + + if (r_cnt) + sendto_flag(SCH_CHAN, "Re-opped %u channel(s).", r_cnt); + + /* + ** check for huge netsplits, if so, garbage collection is not really + ** done but make sure there aren't too many channels kept for + ** history - krys + */ + if ((2*curh_nb > cur_nb) && curh_nb < max_nb) + split = 1; + else + { + split = 0; + /* no empty channel? let's skip the while! */ + if (curh_nb == 0) + { +#ifdef DEBUGMODE + sendto_flag(SCH_LOCAL, + "Channel garbage: live %u (max %u), hist %u (extended)", + cur_nb - 1, max_nb - 1, curh_nb); +#endif + /* Check again after CHECKFREQ seconds */ + return (time_t) (now + CHECKFREQ); + } + } + + chptr = channel; + while (chptr) + { + /* + ** In case we are likely to be split, extend channel locking. + ** most splits should be short, but reality seems to prove some + ** aren't. + */ + if (!chptr->history) + { + chptr = chptr->nextch; + continue; + } + if (split) /* net splitted recently and we have a lock */ + chptr->history += SPLITBONUS; /* extend lock */ + + if ((chptr->users == 0) && (chptr->history <= now)) + { + del_ch = chptr; + + chptr = del_ch->nextch; + free_channel(del_ch); + } + else + chptr = chptr->nextch; + } + +#ifdef DEBUGMODE + sendto_flag(SCH_LOCAL, + "Channel garbage: live %u (max %u), hist %u (removed %u)%s", + cur_nb - 1, max_nb - 1, curh_nb, del - istat.is_hchan, + (split) ? " split detected" : ""); +#endif + /* Check again after CHECKFREQ seconds */ + return (time_t) (now + CHECKFREQ); +} |