/************************************************************************ * 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.1.1.1 2003/06/27 17:56:50 root 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 int special_channel __P((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; } char *get_channelmask(chname) char *chname; { char *mask; mask = rindex(chname, ':'); if (mask && !index(mask, '\033')) return mask; return NULL; } int special_channel(chname) char *chname; { char *mask; mask = rindex(chname, ':'); if (mask && index(mask, '\033')) return 1; if (index(chname, ',')) return 1; return 0; } int valid_channel(cptr, chptr, chname) aClient *cptr; aChannel *chptr; char *chname; { if (((chptr && (chptr->flags & FLAGS_JP)) || (chname && special_channel(chname))) && (!cptr || (IsServer(cptr) && !(cptr->flags & FLAGS_JP)))) return 0; return 1; } 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; if (!valid_channel(cptr, chptr, 0)) 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 (!valid_channel(cptr, chptr, 0)) 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]), chptr->chname, 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], chptr->chname, 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; { int flag = 0; while (*cn) { if (*cn == '\007' || *cn == ' ') { *cn = '\0'; return; } else if (!flag && *cn == ',') { *cn = '\0'; return; } else if (cn[0] == '\033' && cn[1] == '$' && cn[2] == 'B') { cn += 3; flag = 1; } else if (cn[0] == '\033' && cn[1] == '(' && cn[2] == 'B') { cn += 3; flag = 0; } else { cn++; } } } /* ** 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 = get_channelmask(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 (check_channelmask(cptr, cptr, chname) == -1) return NULL; } 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; chptr->flags = 0; if (special_channel(chname)) chptr->flags = FLAGS_JP; 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. !!foo is ** a stupid thing to do because /join !!foo ** will not join !!foo but create !foo ** Some logic here could be reversed, but only ** to find that !foo would be impossible to ** create if !!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 (!chptr) continue; 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], chptr->chname); 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, chptr->chname, 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]), chptr->chname, chptr->topic); parv[1] = chptr->chname; (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 (!valid_channel(0, chptr, 0) || get_channelmask(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, chptr->chname); /* 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, chptr->chname, 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, chptr->chname, modebuf, parabuf); cnt = 0; } } } /* send eventual MODE leftover */ if (cnt) sendto_channel_butserv(chptr, &me, ":%s MODE %s +%s %s", sptr->name, chptr->chname, 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 (valid_channel(0, chptr, 0) && !get_channelmask(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], chptr->chname, 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], chptr->chname, who->name, comment); /* Don't send &local &kicks out */ if (*chptr->chname != '&' && *chptr->chname != '!' && !get_channelmask(chptr->chname) && valid_channel(0, chptr, 0)) { 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 (!valid_channel(acptr->from, chptr, parv[2])) { sendto_one(sptr, err_str(ERR_BADCHANMASK, parv[0]), chptr ? chptr->chname : parv[2]); return 1; } 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 && sptr->user && is_chan_op(sptr, chptr) ){ Link*banned; add_invite(acptr, chptr); banned =(match_modeid(CHFL_EXCEPTION, acptr, chptr)?0:match_modeid(CHFL_BAN, acptr, chptr)); if(banned ) sendto_channel_butone(&me, &me, chptr ,":%s NOTICE %s :%s invite %s,overriding ban mask %s." ,ME ,chptr->chname ,sptr->name ,acptr->name ,banned->value.cp ); } } 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]), chptr->chname, 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)) { para = strtoken(&s, para, ","); if (!para) para = parv[1]; 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]), ch2ptr ? ch2ptr->chname : 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 = get_channelmask(chptr->chname))) if (match(++mask, cptr->name)) continue; if (!valid_channel(cptr, chptr, 0)) 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); }