/************************************************************************ * 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 sccsid[] = "@(#)channel.c 2.58 2/18/94 (C) 1990 University of Oulu, Computing\ Center and Jarkko Oikarinen"; #endif #include "struct.h" #include "common.h" #include "sys.h" #include "numeric.h" #include "channel.h" #ifdef DEFAULT_LIST_PARAM #include "msg.h" /* To pick up MAXPARA */ #endif #include "h.h" #ifdef EPATH #define m_names n_names #define m_list n_list #define m_join n_join #define m_mode n_mode #endif aChannel *channel = NullChn; static void add_invite PROTO((aClient *, aChannel *)); static int add_banid PROTO((aClient *, aChannel *, char *)); static int can_join PROTO((aClient *, aChannel *, char *)); static void channel_modes PROTO((aClient *, char *, char *, aChannel *)); static int del_banid PROTO((aChannel *, char *)); static Link *is_banned PROTO((aClient *, aChannel *)); static int have_ops PROTO((aChannel *)); static int number_of_zombies PROTO((aChannel *)); static int is_deopped PROTO((aClient *, aChannel *)); static int set_mode PROTO((aClient *, aClient *, aChannel *, int,\ char **, char *,char *, int *)); static void sub1_from_channel PROTO((aChannel *)); void clean_channelname PROTO((char *)); void del_invite PROTO((aClient *, aChannel *)); static char *PartFmt = ":%s PART %s"; /* * some buffers for rebuilding channel/nick lists with ,'s */ static char nickbuf[BUFSIZE], buf[BUFSIZE]; static char modebuf[MODEBUFLEN], parabuf[MODEBUFLEN]; extern int is_silenced PROTO((aClient *, aClient *)); /* * return the length (>=0) of a chain of links. */ static int list_length(lp) Reg1 Link *lp; { Reg2 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; Reg1 int *chasing; { Reg2 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), me.name, 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) Reg1 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) Reg1 char *nick, *name, *host; { static char namebuf[NICKLEN+USERLEN+HOSTLEN+6]; Reg2 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 */ /* add_banid - add an id to be banned to the channel (belongs to cptr) */ static int add_banid(cptr, chptr, banid) aClient *cptr; aChannel *chptr; char *banid; { Reg1 Link *ban; Reg2 int cnt = 0, len = 0; if (MyClient(cptr)) (void)collapse(banid); for (ban = chptr->banlist; ban; ban = ban->next) { len += strlen(ban->value.ban.banstr); if (MyClient(cptr)) if ((len > MAXBANLENGTH) || (++cnt >= MAXBANS)) { sendto_one(cptr, err_str(ERR_BANLISTFULL), me.name, cptr->name, chptr->chname, banid); return -1; } else { if (!match(ban->value.ban.banstr, banid) || !match(banid, ban->value.ban.banstr)) return -1; } else if (!mycmp(ban->value.ban.banstr, banid)) return -1; } ban = make_link(); bzero((char *)ban, sizeof(Link)); ban->flags = CHFL_BAN; ban->next = chptr->banlist; ban->value.ban.banstr = (char *)MyMalloc(strlen(banid)+1); (void)strcpy(ban->value.ban.banstr, banid); ban->value.ban.who = (char *)MyMalloc(strlen(cptr->name)+1); (void)strcpy(ban->value.ban.who, cptr->name); ban->value.ban.when = now; chptr->banlist = ban; return 0; } /* * del_banid - delete an id belonging to cptr * if banid is null, deleteall banids belonging to cptr. */ static int del_banid(chptr, banid) aChannel *chptr; char *banid; { Reg1 Link **ban; Reg2 Link *tmp; if (!banid) return -1; for (ban = &(chptr->banlist); *ban; ban = &((*ban)->next)) if (mycmp(banid, (*ban)->value.ban.banstr)==0) { tmp = *ban; *ban = tmp->next; MyFree(tmp->value.ban.banstr); MyFree(tmp->value.ban.who); free_link(tmp); break; } return 0; } /* * IsMember - returns 1 if a person is joined and not a zombie */ int IsMember(cptr, chptr) aClient *cptr; aChannel *chptr; { Link *lp; return (((lp=find_user_link(chptr->members, cptr)) && !(lp->flags & CHFL_ZOMBIE))?1:0); } /* * is_banned - returns a pointer to the ban structure if banned else NULL */ static Link *is_banned(cptr, chptr) aClient *cptr; aChannel *chptr; { Reg1 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->banlist; tmp; tmp = tmp->next) if (match(tmp->value.ban.banstr, 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; { Reg1 Link *ptr; if (who->user) { ptr = make_link(); ptr->value.cptr = who; ptr->flags = flags; ptr->next = chptr->members; chptr->members = ptr; chptr->users++; ptr = make_link(); ptr->value.chptr = chptr; ptr->next = who->user->channel; who->user->channel = ptr; who->user->joined++; } } void remove_user_from_channel(sptr, chptr) aClient *sptr; aChannel *chptr; { Reg1 Link **curr; Reg2 Link *tmp; Reg3 Link *lp = chptr->members; #ifdef NPATH note_leave(sptr, chptr); #endif for (; lp && (lp->flags & CHFL_ZOMBIE || lp->value.cptr==sptr); lp=lp->next); for (;;) { for (curr = &chptr->members; (tmp = *curr); curr = &tmp->next) if (tmp->value.cptr == sptr) { *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; } sptr->user->joined--; if (lp) break; if (chptr->members) sptr = chptr->members->value.cptr; else break; sub1_from_channel(chptr); } sub1_from_channel(chptr); } static int have_ops(chptr) aChannel *chptr; { Reg1 Link *lp; if (chptr) { lp=chptr->members; while (lp) { if (lp->flags & CHFL_CHANOP) return(1); lp = lp->next; } } return 0; } int is_chan_op(cptr, chptr) aClient *cptr; aChannel *chptr; { Reg1 Link *lp; if (chptr) if ((lp = find_user_link(chptr->members, cptr)) && !(lp->flags & CHFL_ZOMBIE)) return (lp->flags & CHFL_CHANOP); return 0; } static int is_deopped(cptr, chptr) aClient *cptr; aChannel *chptr; { Reg1 Link *lp; if (chptr) if ((lp = find_user_link(chptr->members, cptr))) return (lp->flags & CHFL_DEOPPED); return (IsPerson(cptr)?1:0); } int is_zombie(cptr, chptr) aClient *cptr; aChannel *chptr; { Reg1 Link *lp; if (chptr) if ((lp = find_user_link(chptr->members, cptr))) return (lp->flags & CHFL_ZOMBIE); return 0; } int has_voice(cptr, chptr) aClient *cptr; aChannel *chptr; { Reg1 Link *lp; if (chptr) if ((lp = find_user_link(chptr->members, cptr)) && !(lp->flags & CHFL_ZOMBIE)) return (lp->flags & CHFL_VOICE); return 0; } int can_send(cptr, chptr) aClient *cptr; aChannel *chptr; { Reg1 Link *lp; Reg2 int member; member = IsMember(cptr, chptr); lp = find_user_link(chptr->members, cptr); if ((!lp || !(lp->flags & (CHFL_CHANOP|CHFL_VOICE)) || (lp->flags & CHFL_ZOMBIE)) && MyClient(cptr) && is_banned(cptr, chptr)) return (MODE_BAN); if (chptr->mode.mode & MODE_MODERATED && (!lp || !(lp->flags & (CHFL_CHANOP|CHFL_VOICE)) || (lp->flags & CHFL_ZOMBIE))) return (MODE_MODERATED); if (chptr->mode.mode & MODE_NOPRIVMSGS && !member) return (MODE_NOPRIVMSGS); return 0; } aChannel *find_channel(chname, chptr) Reg1 char *chname; Reg2 aChannel *chptr; { return hash_find_channel(chname, chptr); } /* * write the "simple" list of channel modes for channel chptr onto buffer mbuf * with the parameters in pbuf. */ static void channel_modes(cptr, mbuf, pbuf, chptr) aClient *cptr; Reg1 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.limit) { *mbuf++ = 'l'; if (IsMember(cptr, chptr) || IsServer(cptr)) (void)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 int send_mode_list(cptr, chname, creationtime, top, mask, flag) aClient *cptr; Link *top; int mask; char flag, *chname; time_t creationtime; { Reg1 Link *lp; Reg2 char *cp, *name; int count = 0, send = 0, sent = 0; cp = modebuf + strlen(modebuf); if (*parabuf) /* mode +l or +k xx */ count = 1; for (lp = top; lp; lp = lp->next) { if (!(lp->flags & mask)) continue; if (mask == CHFL_BAN) name = lp->value.ban.banstr; else name = lp->value.cptr->name; if (strlen(parabuf) + strlen(name) + 11 < (size_t) MODEBUFLEN) { (void)strcat(parabuf, " "); (void)strcat(parabuf, name); count++; *cp++ = flag; *cp = '\0'; } else if (*parabuf) send = 1; if (count == 6) send = 1; if (send) { /* cptr is always a server! So we send creationtimes */ sendmodeto_one(cptr, me.name, chname, modebuf, parabuf, creationtime); sent = 1; send = 0; *parabuf = '\0'; cp = modebuf; *cp++ = '+'; if (count != 6) { (void)strcpy(parabuf, name); *cp++ = flag; } count = 0; *cp = '\0'; } } return sent; } /* * send "cptr" a full list of the modes for channel chptr. */ void send_channel_modes(cptr, chptr) aClient *cptr; aChannel *chptr; { int sent; if (*chptr->chname != '#') return; *modebuf = *parabuf = '\0'; channel_modes(cptr, modebuf, parabuf, chptr); sent=send_mode_list(cptr, chptr->chname, chptr->creationtime, chptr->members, CHFL_CHANOP, 'o'); if (!sent && chptr->creationtime) sendto_one(cptr, ":%s MODE %s %s %s %lu", me.name, chptr->chname, modebuf, parabuf, chptr->creationtime); else if (modebuf[1] || *parabuf) sendmodeto_one(cptr, me.name, chptr->chname, modebuf, parabuf, chptr->creationtime); *parabuf = '\0'; *modebuf = '+'; modebuf[1] = '\0'; (void)send_mode_list(cptr, chptr->chname,chptr->creationtime, chptr->banlist, CHFL_BAN, 'b'); if (modebuf[1] || *parabuf) sendmodeto_one(cptr, me.name, chptr->chname, modebuf, parabuf, chptr->creationtime); *parabuf = '\0'; *modebuf = '+'; modebuf[1] = '\0'; (void)send_mode_list(cptr, chptr->chname,chptr->creationtime, chptr->members, CHFL_VOICE, 'v'); if (modebuf[1] || *parabuf) sendmodeto_one(cptr, me.name, chptr->chname, modebuf, parabuf, chptr->creationtime); } /* * m_mode * parv[0] - sender * parv[1] - channel */ int m_mode(cptr, sptr, parc, parv) aClient *cptr; aClient *sptr; int parc; char *parv[]; { static char tmp[MODEBUFLEN]; int badop, sendts; aChannel *chptr; if (check_registered(sptr)) return 0; /* Now, try to find the channel in question */ if (parc > 1) { chptr = find_channel(parv[1], NullChn); if (chptr == NullChn) return m_umode(cptr, sptr, parc, parv); } else { sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS), me.name, parv[0], "MODE"); return 0; } sptr->flags&=~FLAGS_TS8; clean_channelname(parv[1]); if (*parv[1] == '&' && !MyClient(sptr)) return 0; if (parc < 3) { *modebuf = *parabuf = '\0'; modebuf[1] = '\0'; channel_modes(sptr, modebuf, parabuf, chptr); sendto_one(sptr, rpl_str(RPL_CHANNELMODEIS), me.name, parv[0], chptr->chname, modebuf, parabuf); sendto_one(sptr, rpl_str(RPL_CREATIONTIME), me.name, parv[0], chptr->chname, chptr->creationtime); return 0; } if (!(sendts = set_mode(cptr, sptr, chptr, parc - 2, parv + 2, modebuf, parabuf, &badop))) { sendto_one(sptr, err_str(IsMember(sptr, chptr) ? ERR_CHANOPRIVSNEEDED : ERR_NOTONCHANNEL), me.name, parv[0], chptr->chname); return 0; } if (badop>=2) { int i=3; *tmp='\0'; while (i < parc) { strcat(tmp, " "); strcat(tmp, parv[i++]); } sendto_ops("%sHACK(%d): %s MODE %s %s%s [%lu]", (badop==3)?"BOUNCE or ":"", badop, parv[0],parv[1],parv[2],tmp,chptr->creationtime); } if (strlen(modebuf) > (size_t)1 || sendts > 0) { if (badop!=2 && strlen(modebuf) > (size_t)1) sendto_channel_butserv(chptr, sptr, ":%s MODE %s %s %s", parv[0], chptr->chname, modebuf, parabuf); /* We send a creationtime of 0, to mark it as a hack --Run */ if (IsServer(sptr) && (badop==2 || sendts > 0)) { if (*modebuf == '\0') strcpy(modebuf,"+"); if (badop==2) sendto_serv_butone(cptr, ":%s WALLOPS :HACK: %s MODE %s %s%s", me.name,parv[0],parv[1],parv[2],tmp); else sendto_match_servs(chptr, cptr, ":%s MODE %s %s %s %lu", parv[0], chptr->chname, modebuf, parabuf, (badop==4)?(time_t)0:chptr->creationtime); } else sendto_match_servs(chptr, cptr, ":%s MODE %s %s %s", parv[0], chptr->chname, modebuf, parabuf); } return 0; } int DoesOp(modebuf) char *modebuf; { modebuf--; /* Is it possible that a mode starts with o and not +o ? */ while (*++modebuf) if (*modebuf=='o' || *modebuf=='v') return(1); return 0; } int sendmodeto_one(cptr, from, name, mode, param, creationtime) Reg2 aClient *cptr; char *from,*name,*mode,*param; time_t creationtime; { if (IsServer(cptr) && DoesOp(mode) && creationtime) sendto_one(cptr,":%s MODE %s %s %s %lu", from, name, mode, param, creationtime); else sendto_one(cptr,":%s MODE %s %s %s", from, name, mode, param); } char *pretty_mask(mask) char *mask; { Reg1 char *cp; Reg2 char *user; Reg3 char *host; if ((user = index((cp = mask), '!'))) *user++ = '\0'; if ((host = rindex(user ? user : cp, '@'))) { *host++ = '\0'; if (!user) return make_nick_user_host(NULL, cp, host); } else if (!user && index(cp, '.')) return make_nick_user_host(NULL, NULL, cp); return make_nick_user_host(cp, user, host); } /* * Check and try to apply the channel modes passed in the parv array for * the client ccptr 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, parc, parv, mbuf, pbuf, badop) Reg2 aClient *cptr, *sptr; aChannel *chptr; int parc, *badop; char *parv[], *mbuf, *pbuf; { static Link chops[MAXMODEPARAMS]; static int flags[] = { MODE_PRIVATE, 'p', MODE_SECRET, 's', MODE_MODERATED, 'm', MODE_NOPRIVMSGS, 'n', MODE_TOPICLIMIT, 't', MODE_INVITEONLY, 'i', MODE_VOICE, 'v', MODE_KEY, 'k', 0x0, 0x0 }; Reg1 Link *lp; Reg2 char *curr = parv[0], *cp; Reg3 int *ip; Link *member, *tmp = NULL; u_int whatt = MODE_ADD, bwhatt = 0; int limitset = 0, chasing = 0, bounce; int nusers, new, len, blen, keychange = 0, opcnt = 0, banlsent = 0; int doesdeop = 0, doesop = 0, hacknotice = 0, change, gotts = 0; char fm = '\0'; aClient *who; Mode *mode, oldm; char chase_mode[3]; static char bmodebuf[MODEBUFLEN], bparambuf[MODEBUFLEN], numeric[16]; char *bmbuf = bmodebuf, *bpbuf = bparambuf, *mbufp = mbuf; time_t newtime = (time_t)0; aConfItem *aconf; *mbuf=*pbuf=*bmbuf=*bpbuf='\0'; *badop=0; if (parc < 1) return 0; mode = &(chptr->mode); bcopy((char *)mode, (char *)&oldm, sizeof(Mode)); /* Mode is accepted when sptr is a channel operator * but also when the mode is received from a server. * At this point, let any member pass, so they are allowed * to see the bans. * Don't use 'IsMember', but remember the link in 'tmp' so * we can use this later to see if sptr is a chan op. --Run */ if (!(IsServer(cptr) || ((tmp = find_user_link(chptr->members, sptr)) && !(tmp->flags & CHFL_ZOMBIE)))) return 0; new = mode->mode; while (curr && *curr) { switch (*curr) { case '+': whatt = MODE_ADD; break; case '-': whatt = MODE_DEL; break; case 'o' : case 'v' : if (--parc <= 0) break; parv++; *parv = check_string(*parv); if (MyClient(sptr) && opcnt >= MAXMODEPARAMS) break; /* * Check for nickname changes and try to follow these * to make sure the right client is affected by the * mode change. * Even if we find a nick with find_chasing() there * is still a reason to ignore in a special case. * We need to ignore the mode when: * - It is part of a net.burst (from a server and * a MODE_ADD). Ofcourse we don't ignore mode * changes from Uworld. * - The found nick is not on the right side off * the net.junction. * This fixes the bug that when someone (tries to) * ride a net.break and does so with the nick of * someone on the otherside, that he is nick collided * (killed) but his +o still ops the other person. */ if (!(who = find_chasing(sptr, parv[0], &chasing)) || (whatt == MODE_ADD && IsServer(sptr) && who->from != sptr->from && !find_conf_host(cptr->confs, sptr->name, CONF_UWORLD))) break; if (!(member = find_user_link(chptr->members,who))) { sendto_one(cptr, err_str(ERR_USERNOTINCHANNEL), me.name, cptr->name, parv[0], chptr->chname); break; } /* if the user is +k, prevent a deop from local user */ if (whatt == MODE_DEL && (who->flags & FLAGS_NOKICK) && MyClient(cptr)) { sendto_one(cptr, err_str(ERR_ISCHANSERVICE), me.name, cptr->name, parv[0], chptr->chname); break; } if (whatt == MODE_ADD) { lp = &chops[opcnt++]; lp->value.cptr = who; if (IsServer(sptr) && (!(who->flags & FLAGS_TS8) || ((*curr == 'o') && !(member->flags & (CHFL_SERVOPOK|CHFL_CHANOP))))) *badop=((member->flags & CHFL_DEOPPED) && (*curr == 'o'))?2:3; lp->flags = (*curr == 'o') ? MODE_CHANOP: MODE_VOICE; lp->flags |= MODE_ADD; } else if (whatt == MODE_DEL) { lp = &chops[opcnt++]; lp->value.cptr = who; doesdeop = 1; /* Also when -v */ lp->flags = (*curr == 'o') ? MODE_CHANOP: MODE_VOICE; lp->flags |= MODE_DEL; } if (*curr == 'o') doesop=1; break; case 'k': if (--parc <= 0) break; parv++; /* check now so we eat the parameter if present */ if (keychange) break; *parv = check_string(*parv); { u_char *s1,*s2; for (s1 = s2 = (u_char *)*parv; *s2; s2++) if ((*s1 = *s2 & 0x7f) > (u_char)32 && *s1 != ':') s1++; *s1 = '\0'; } if (MyClient(sptr) && opcnt >= MAXMODEPARAMS) break; if (whatt == MODE_ADD) { if (*mode->key && !IsServer(cptr)) sendto_one(cptr, err_str(ERR_KEYSET), me.name, cptr->name, chptr->chname); else if (!*mode->key || IsServer(cptr)) { 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 (mycmp(mode->key, *parv) == 0 || IsServer(cptr)) { lp = &chops[opcnt++]; lp->value.cp = mode->key; lp->flags = MODE_KEY|MODE_DEL; keychange = 1; } } break; case 'b': if (--parc <= 0) { if (banlsent) /* Only send it once */ break; for (lp = chptr->banlist; lp; lp = lp->next) sendto_one(cptr, rpl_str(RPL_BANLIST), me.name, cptr->name, chptr->chname, lp->value.ban.banstr, lp->value.ban.who, lp->value.ban.when); sendto_one(cptr, rpl_str(RPL_ENDOFBANLIST), me.name, cptr->name, chptr->chname); banlsent = 1; break; } parv++; if (BadPtr(*parv)) break; if (MyClient(sptr) && opcnt >= MAXMODEPARAMS) break; if (whatt == MODE_ADD) { 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; } break; case 'l': /* * limit 'l' to only *1* change per mode command but * eat up others. */ if (limitset) { if (whatt == MODE_ADD && --parc > 0) parv++; break; } if (whatt == MODE_DEL) { limitset = 1; nusers = 0; break; } if (--parc > 0) { if (BadPtr(*parv)) break; if (MyClient(sptr) && opcnt >= MAXMODEPARAMS) break; if (!(nusers = atoi(*++parv))) continue; lp = &chops[opcnt++]; lp->flags = MODE_ADD|MODE_LIMIT; limitset = 1; break; } sendto_one(cptr, err_str(ERR_NEEDMOREPARAMS), me.name, 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: for (ip = flags; *ip; ip += 2) if (*(ip+1) == *curr) break; if (*ip) { if (whatt == MODE_ADD) { if (*ip == MODE_PRIVATE) new &= ~MODE_SECRET; else if (*ip == MODE_SECRET) new &= ~MODE_PRIVATE; new |= *ip; } else new &= ~*ip; } else if (!IsServer(cptr)) sendto_one(cptr, err_str(ERR_UNKNOWNMODE), me.name, cptr->name, *curr); break; } curr++; /* * Make sure mode strings such as "+m +t +p +i" are parsed * fully. */ if (!*curr && parc > 0) { curr = *++parv; parc--; /* If this was from a server, and it is the last * parameter and it starts with a digit, it must * be the creationtime. --Run */ if (IsServer(sptr)) { if (parc==1 && isdigit(*curr)) { newtime=atoi(curr); gotts=1; if (newtime == 0) { *badop=2; hacknotice = 1; } else if (newtime > chptr->creationtime) { /* It is a net-break ride if we have ops. * bounce modes if we have ops. * --Run */ if (doesdeop) *badop=2; else if (chptr->creationtime==0 || !have_ops(chptr)) { if (chptr->creationtime && doesop) sendto_ops("NET.RIDE on opless %s from %s", chptr->chname,sptr->name); if (chptr->creationtime == 0 || doesop) chptr->creationtime=newtime; *badop=0; } /* Bounce: */ else *badop=1; } else if (Protocol(cptr)>4 && doesdeop && newtime < chptr->creationtime) *badop=2; /* A legal *badop can occur when two * people join simultaneously a channel, * Allow for 10 min of lag (and thus hacking * on channels younger then 10 min) --Run */ else if (*badop==0 || chptr->creationtime > (TStime()-(time_t)600)) { if (newtime < chptr->creationtime) chptr->creationtime=newtime; *badop=0; } break; } } else *badop=0; } } /* end of while loop for MODE processing */ /* Now reject non chan ops */ if (!IsServer(cptr) && (!tmp || !(tmp->flags & CHFL_CHANOP))) { *badop = 0; return (opcnt || new != mode->mode || limitset || keychange) ? 0 : -1; } if (doesop && newtime==0 && IsServer(sptr)) *badop=2; if (*badop>=2 && (aconf = find_conf_host(cptr->confs, sptr->name, CONF_UWORLD))) *badop=4; bounce = (*badop==1 || *badop==2 || is_deopped(sptr, chptr))?1:0; whatt = 0; for (ip = flags; *ip; ip += 2) if ((*ip & new) && !(*ip & oldm.mode)) { if (bounce) { if (bwhatt != MODE_DEL) { *bmbuf++ = '-'; bwhatt = MODE_DEL; } *bmbuf++ = *(ip+1); } else { if (whatt != MODE_ADD) { *mbuf++ = '+'; whatt = MODE_ADD; } mode->mode |= *ip; *mbuf++ = *(ip+1); } } for (ip = flags; *ip; ip += 2) if ((*ip & oldm.mode) && !(*ip & new)) { if (bounce) { if (bwhatt != MODE_ADD) { *bmbuf++ = '+'; bwhatt = MODE_ADD; } *bmbuf++ = *(ip+1); } else { if (whatt != MODE_DEL) { *mbuf++ = '-'; whatt = MODE_DEL; } mode->mode &= ~*ip; *mbuf++ = *(ip+1); } } blen = 0; if (limitset && !nusers && mode->limit) { if (bounce) { if (bwhatt != MODE_ADD) { *bmbuf++ = '+'; bwhatt = MODE_ADD; } *bmbuf++ = 'l'; (void)sprintf(numeric, "%-15d", mode->limit); if ((cp = index(numeric, ' '))) *cp = '\0'; (void)strcat(bpbuf, numeric); blen += strlen(numeric); (void)strcat(bpbuf, " "); } else { if (whatt != MODE_DEL) { *mbuf++ = '-'; whatt = MODE_DEL; } mode->mode &= ~MODE_LIMIT; mode->limit = 0; *mbuf++ = 'l'; } } /* * Reconstruct "+bkov" chain. */ if (opcnt) { Reg1 int i = 0; Reg2 char c; char *user, *host; u_int prev_whatt; 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++ = '+'; prev_whatt = whatt; whatt = MODE_ADD; } else { *mbuf++ = '-'; prev_whatt = whatt; whatt = MODE_DEL; } len = strlen(pbuf); /* * get c as the mode char and tmp as a pointer to * the parameter for this mode change. */ switch(lp->flags & MODE_WPARAS) { case MODE_CHANOP : c = 'o'; cp = lp->value.cptr->name; break; case MODE_VOICE : c = 'v'; cp = lp->value.cptr->name; break; case MODE_BAN : /* I made this a bit more user-friendly (tm): * nick = nick!*@* * nick!user = nick!user@* * user@host = *!user@host * host.name = *!*@host.name --Run */ c = 'b'; cp = pretty_mask(lp->value.cp); 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) + 12 > (size_t) MODEBUFLEN) break; switch(lp->flags & MODE_WPARAS) { case MODE_KEY : if (strlen(cp) > (size_t) KEYLEN) *(cp+KEYLEN) = '\0'; if ((whatt == MODE_ADD && (*mode->key=='\0' || mycmp(mode->key,cp)!=0)) || (whatt == MODE_DEL && (*mode->key!='\0'))) { if (bounce) { if (*mode->key=='\0') { if (bwhatt != MODE_DEL) { *bmbuf++ = '-'; bwhatt = MODE_DEL; } (void)strcat(bpbuf, cp); blen += strlen(cp); (void)strcat(bpbuf, " "); blen++; } else { if (bwhatt != MODE_ADD) { *bmbuf++ = '+'; bwhatt = MODE_ADD; } (void)strcat(bpbuf, mode->key); blen += strlen(mode->key); (void)strcat(bpbuf, " "); blen++; } *bmbuf++ = c; mbuf--; if (*mbuf!='+' && *mbuf!='-') mbuf++; else whatt = prev_whatt; } else { *mbuf++ = c; (void)strcat(pbuf, cp); len += strlen(cp); (void)strcat(pbuf, " "); len++; if (whatt == MODE_ADD) strncpyzt(mode->key, cp, sizeof(mode->key)); else *mode->key = '\0'; } } break; case MODE_LIMIT : if (nusers && nusers != mode->limit) { if (bounce) { if (mode->limit == 0) { if (bwhatt != MODE_DEL) { *bmbuf++ = '-'; bwhatt = MODE_DEL; } } else { if (bwhatt != MODE_ADD) { *bmbuf++ = '+'; bwhatt = MODE_ADD; } (void)sprintf(numeric, "%-15d", mode->limit); if ((cp = index(numeric, ' '))) *cp = '\0'; (void)strcat(bpbuf, numeric); blen += strlen(numeric); (void)strcat(bpbuf, " "); blen++; } *bmbuf++ = c; mbuf--; if (*mbuf!='+' && *mbuf!='-') mbuf++; else whatt = prev_whatt; } else { *mbuf++ = c; (void)strcat(pbuf, cp); len += strlen(cp); (void)strcat(pbuf, " "); len++; mode->limit = nusers; } } break; case MODE_CHANOP : case MODE_VOICE : tmp = find_user_link(chptr->members, lp->value.cptr); if (lp->flags & MODE_ADD) { change=(~tmp->flags) & CHFL_OVERLAP & lp->flags; if (change && bounce) { if (lp->flags & MODE_CHANOP) tmp->flags |= CHFL_DEOPPED; if (bwhatt != MODE_DEL) { *bmbuf++ = '-'; bwhatt = MODE_DEL; } *bmbuf++ = c; (void)strcat(bpbuf, lp->value.cptr->name); blen += strlen(lp->value.cptr->name); (void)strcat(bpbuf, " "); blen++; change=0; } else if (change) { tmp->flags |= lp->flags & CHFL_OVERLAP; if (lp->flags & MODE_CHANOP) { tmp->flags &= ~CHFL_DEOPPED; if (IsServer(sptr)) tmp->flags &= ~CHFL_SERVOPOK; } } } else { change=tmp->flags & CHFL_OVERLAP & lp->flags; if (change && bounce) { if (lp->flags & MODE_CHANOP) tmp->flags &= ~CHFL_DEOPPED; if (bwhatt != MODE_ADD) { *bmbuf++ = '+'; bwhatt = MODE_ADD; } *bmbuf++ = c; (void)strcat(bpbuf, lp->value.cptr->name); blen += strlen(lp->value.cptr->name); (void)strcat(bpbuf, " "); blen++; change=0; } else { tmp->flags &= ~change; if ((change & MODE_CHANOP) && IsServer(sptr)) tmp->flags |= CHFL_DEOPPED; } } if (change || *badop==2 || *badop==4) { *mbuf++ = c; (void)strcat(pbuf, cp); len += strlen(cp); (void)strcat(pbuf, " "); len++; } else { mbuf--; if (*mbuf!='+' && *mbuf!='-') mbuf++; else whatt = prev_whatt; } break; case MODE_BAN : /* Only bans aren't bounced, it makes no sense to bounce last second * bans while propagating bans done before the net.rejoin. The reason * why I don't bounce net.rejoin bans is because it is too much * work to take care of too long strings adding the necessary TS to * net.burst bans -- RunLazy * We do have to check for *badop==2 now, we don't want HACKs to take * effect. */ if (*badop!=2 && ((whatt & MODE_ADD) && !add_banid(sptr, chptr, cp) || (whatt & MODE_DEL) && !del_banid(chptr, cp))) { *mbuf++ = c; (void)strcat(pbuf, cp); len += strlen(cp); (void)strcat(pbuf, " "); len++; } break; } } /* for (; i < opcnt; i++) */ } /* if (opcnt) */ *mbuf++ = '\0'; *bmbuf++ = '\0'; /* Bounce here */ if (!hacknotice && *bmodebuf && chptr->creationtime) sendto_one(cptr,":%s MODE %s %s %s %lu", me.name, chptr->chname, bmodebuf, bparambuf, *badop==2?(time_t)0:chptr->creationtime); return gotts?1:-1; } /* We are now treating the part of /join as a key ** ring; that is, we try one key against the actual channel key, and if that ** doesn't work, we try the next one, and so on. -Kev -Texaco ** Returns: 0 on match, 1 otherwise ** This version contributed by SeKs */ static int compall(key,keyring) register char *key,*keyring; { register char *p1; top: p1=key; /* point to the key... */ while(*p1 && *p1==*keyring){ /* step through the key and ring until they don't match... */ p1++; keyring++; } if(!*p1 && (!*keyring || *keyring==',')) /* ok, if we're at the end of the and also at the end of one of the keys in the keyring, we have a match */ return 0; if(!*keyring) /* if we're at the end of the key ring, there weren't any matches, so we return 1 */ return 1; while(*keyring && *(keyring++)!=','); /* not at the end of the key ring, so step through to the next key in the ring */ goto top; /* and check it against the key */ } static int can_join(sptr, chptr, key) aClient *sptr; Reg2 aChannel *chptr; char *key; { Reg1 Link *lp; if (is_banned(sptr, chptr)) return (ERR_BANNEDFROMCHAN); if (chptr->mode.mode & MODE_INVITEONLY) { for (lp = sptr->user->invited; lp; lp = lp->next) if (lp->value.chptr == chptr) break; if (!lp) return (ERR_INVITEONLYCHAN); } /* now using compall (above) to test against a whole key ring -Kev */ if (*chptr->mode.key && (BadPtr(key) || compall(chptr->mode.key, key))) return (ERR_BADCHANNELKEY); if (chptr->mode.limit && chptr->users >= chptr->mode.limit) return (ERR_CHANNELISFULL); return 0; } /* ** Remove bells and commas from channel name */ void clean_channelname(cn) Reg1 char *cn; { for (; *cn; cn++) if (*cn == '\007' || *cn == ' ' || *cn == ',') { *cn = '\0'; return; } } /* ** 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; { Reg1 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->creationtime = MyClient(cptr)?TStime():(time_t)0; channel = chptr; (void)add_to_channel_hash_table(chname, chptr); } return chptr; } static void add_invite(cptr, chptr) aClient *cptr; aChannel *chptr; { Reg1 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; /* * 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; } /* * Delete Invite block from channel invite list and client invite list */ void del_invite(cptr, chptr) aClient *cptr; aChannel *chptr; { Reg1 Link **inv, *tmp; for (inv = &(chptr->invites); (tmp = *inv); inv = &tmp->next) if (tmp->value.cptr == cptr) { *inv = tmp->next; free_link(tmp); break; } for (inv = &(cptr->user->invited); (tmp = *inv); inv = &tmp->next) if (tmp->value.chptr == chptr) { *inv = tmp->next; free_link(tmp); break; } } /* List and skip all channels that are listen */ void list_next_channels(cptr, nr) aClient *cptr; int nr; { aChannel *chptr = cptr->listing; chptr->mode.mode &= ~MODE_LISTED; while (is_listed(chptr) || nr > 0) { if (cptr->user && !(SecretChannel(chptr) && !IsMember(cptr, chptr))) { nr--; sendto_one(cptr, rpl_str(RPL_LIST), me.name, cptr->name, ShowChannel(cptr, chptr)?chptr->chname:"*", chptr->users, ShowChannel(cptr, chptr)?chptr->topic:""); } if (!(chptr = chptr->nextch)) { sendto_one(cptr, rpl_str(RPL_LISTEND), me.name, cptr->name); break; } } if ((cptr->listing = chptr)) chptr->mode.mode |= MODE_LISTED; } /* ** Subtract one user from channel i (and free channel ** block, if channel became empty). */ static void sub1_from_channel(chptr) Reg1 aChannel *chptr; { Reg2 Link *tmp; Link *obtmp; if (--chptr->users <= 0) { if (is_listed(chptr)) { int i; for (i = 0; i <= highest_fd; i++) { aClient *acptr; if ((acptr = local[i]) && acptr->listing == chptr) { list_next_channels(acptr, 1); break; /* Only one client can list a channel */ } } } /* * Now, find all invite links from channel structure */ while ((tmp = chptr->invites)) del_invite(tmp->value.cptr, chptr); tmp = chptr->banlist; while (tmp) { obtmp = tmp; tmp = tmp->next; MyFree(obtmp->value.ban.banstr); MyFree(obtmp->value.ban.who); free_link(obtmp); } if (chptr->prevch) chptr->prevch->nextch = chptr->nextch; else channel = chptr->nextch; if (chptr->nextch) chptr->nextch->prevch = chptr->prevch; (void)del_from_channel_hash_table(chptr->chname, chptr); MyFree((char *)chptr); } } /* ** m_join ** parv[0] = sender prefix ** parv[1] = channel ** parv[2] = channel password (key) */ int m_join(cptr, sptr, parc, parv) Reg2 aClient *cptr, *sptr; int parc; char *parv[]; { static char jbuf[BUFSIZE], mbuf[BUFSIZE]; Reg1 Link *lp; Reg3 aChannel *chptr; Reg4 char *name, *keys = NULL; int i, flags = 0, zombie = 0, jlen = 0, mlen = 0, sendmode = 0; char *p = NULL; if (check_registered_user(sptr)) return 0; if (parc < 2 || *parv[1] == '\0') { sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS), me.name, parv[0], "JOIN"); return 0; } for (p = parv[1]; *p; p++) /* find the last "JOIN 0" in the line -Kev */ if (*p == '0' && (*(p+1) == ',' || *(p+1) == '\0')) { /* if it's a single "0", remember the place; we will start parsing the channels after the last 0 in the line -Kev */ parv[1] = p; if (!*(p+1)) break; p++; } else { /* step through to the next comma or until the end of the line, in an attempt to save CPU -Kev */ while (*p != ',' && *p != '\0') p++; if (!*p) break; } keys = parv[2]; /* remember where our keys are; parv[2] needs to be NULL for the call to m_names below -Kev */ parv[2] = p = NULL; *jbuf = *mbuf = '\0'; /* clear both join and mode buffers -Kev */ /* ** Rebuild list of channels joined to be the actual result of the ** JOIN. Note that "JOIN 0" is the destructive problem. */ for (name = strtoken(&p, parv[1], ","); name; name = strtoken(&p, NULL, ",")) { clean_channelname(name); if (*name == '&' && !MyConnect(sptr)) continue; /* don't need atoi here; just takes up CPU -Kev*/ if (*name == '0' && *(name+1) == '\0') { /* remove the user from all his channels -Kev */ while ((lp = sptr->user->channel)) { chptr = lp->value.chptr; if (!is_zombie(sptr, chptr)) sendto_channel_butserv(chptr, sptr, PartFmt, parv[0], chptr->chname); remove_user_from_channel(sptr, chptr); } } else { /* not a /join 0, so treat it as a /join channel -Kev */ if (!IsChannelName(name)) { if (MyClient(sptr)) sendto_one(sptr, err_str(ERR_NOSUCHCHANNEL), me.name, parv[0], name); continue; } if (MyConnect(sptr)) { /* ** local client is first to enter previously nonexistant ** channel so make them (rightfully) the Channel ** Operator. */ if (ChannelExists(name)) flags = CHFL_DEOPPED; /* ** if the name is too long, we must check ** again, since truncation yields a new channel. ** The checks are in the wrong order to ** try to save cpu - Xorath */ else if (strlen(name) > CHANNELLEN) { *(name + CHANNELLEN) = '\0'; flags = (ChannelExists(name)) ? CHFL_DEOPPED : CHFL_CHANOP; } else flags = CHFL_CHANOP; if (sptr->user->joined >= MAXCHANNELSPERUSER) { sendto_one(sptr, err_str(ERR_TOOMANYCHANNELS), me.name, parv[0], name); break; /* can't return, else he won't get on ANY channels! break out of the for loop instead -Kev */ } } chptr = get_channel(sptr, name, CREATE); if (chptr && (lp=find_user_link(chptr->members, sptr))) { if (lp->flags & CHFL_ZOMBIE) { zombie = 1; flags = lp->flags & (CHFL_DEOPPED|CHFL_SERVOPOK); remove_user_from_channel(sptr, chptr); chptr = get_channel(sptr, name, CREATE); } else continue; } if (!zombie) { if (!MyConnect(sptr)) flags = CHFL_DEOPPED; if (sptr->flags & FLAGS_TS8) flags|=CHFL_SERVOPOK; } if (!chptr || (MyConnect(sptr) && (i = can_join(sptr, chptr, keys)))) /* only a variable change key->keys, just because it's better */ { /* we have the error str in s_err.c, so USE it! -Kev */ sendto_one(sptr, err_str(i), me.name, parv[0], name); continue; } /* ** Complete user entry to the new channel (if any) */ add_user_to_channel(chptr, sptr, flags); /* ** notify all other users on the new channel */ sendto_channel_butserv(chptr, sptr, ":%s JOIN :%s", parv[0], name); #ifdef NPATH note_join(sptr, chptr); #endif if (MyClient(sptr)) { /* ** Make a (temporal) creationtime, if someone joins ** during a net.reconnect : between remote join and ** the mode with TS. --Run */ if (chptr->creationtime == 0) { chptr->creationtime = TStime(); sendmode = 1; /* mark that we need to send a mode */ } del_invite(sptr, chptr); if (flags & CHFL_CHANOP) sendmode = 2; /* mark that we need to send an op */ if (sendmode) { /* if we need to send a mode, add it to mbuf... -Kev */ if (*mbuf) (void)strcat(mbuf, ","); if (sendmode == 2) (void)strcat(mbuf, "+"); /* If he needs to be chop, mark it for later -Kev */ (void)strncat(mbuf, name, sizeof(mbuf) - mlen - 1); mlen += strlen(name) + 1; sendmode = 0; /* clear sendmode so no confusion */ } if (chptr->topic[0] != '\0') { sendto_one(sptr, rpl_str(RPL_TOPIC), me.name, parv[0], name, chptr->topic); sendto_one(sptr, rpl_str(RPL_TOPICWHOTIME), me.name, parv[0], name, chptr->topic_nick, chptr->topic_time); } parv[1] = name; (void)m_names(cptr, sptr, 2, parv); } } if (*jbuf) (void)strcat(jbuf, ","); /* add channel to join buffer -Kev */ (void)strncat(jbuf, name, sizeof(jbuf) - jlen - 1); jlen += strlen(name)+1; } if (*jbuf) /* if there are channels in the join buffer, send out the joins to all servers -Kev */ sendto_serv_butone(cptr, ":%s JOIN %s", parv[0], jbuf); if (MyClient(sptr) && *mbuf) { /* if he's mine, and I have modes I need to send, let's send them. -Kev */ p = NULL; for (name = strtoken(&p, mbuf, ","); name; name = strtoken(&p, NULL, ",")){ if (*name == '+') { sendmode = 1; /* he needs to be +o on the channel... -Kev */ name++; /* channel name doesn't include a '+' -Kev */ } else sendmode = 0; /* he doesn't need to be +o -Kev */ chptr = get_channel(sptr, name, !CREATE); /* need the TS -Kev */ sendto_serv_butone(cptr, ":%s MODE %s +%s%s %lu", me.name, name, sendmode ? "o ":"", sendmode ? parv[0] : "", chptr->creationtime); /* send the MODE to the servers... -Kev */ } } 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[]; { Reg1 aChannel *chptr; Reg2 Link *lp; char *p = NULL, *name, pbuf[BUFSIZE]; *pbuf = '\0'; /* initialize the part buffer... -Kev */ if (check_registered_user(sptr)) return 0; sptr->flags&=~FLAGS_TS8; if (parc < 2 || parv[1][0] == '\0') { sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS), me.name, parv[0], "PART"); return 0; } for (; (name = strtoken(&p, parv[1], ",")); parv[1] = NULL) { chptr = get_channel(sptr, name, 0); if (!chptr) { sendto_one(sptr, err_str(ERR_NOSUCHCHANNEL), me.name, parv[0], name); continue; } if (*name == '&' && !MyClient(sptr)) continue; /* Do not use IsMember here: zombies must be able to part too */ if (!(lp=find_user_link(chptr->members, sptr))) { /* Normal to get get when our client did a kick ** for a remote client (who sends back a PART), ** so check for remote client or not --Run */ if (MyClient(sptr)) sendto_one(sptr, err_str(ERR_NOTONCHANNEL), me.name, parv[0], name); continue; } /* ** Remove user from the old channel (if any) ** Basically just removing the #ifdef V28PlusOnly, and using ** an internal array to store the info. We're recreating the ** /join list for sending out to all the servers... -Kev */ if (*pbuf) (void)strcat(pbuf, ","); (void)strcat(pbuf, name); if (!(lp->flags & CHFL_ZOMBIE)) sendto_channel_butserv(chptr, sptr, PartFmt, parv[0], name); else if (MyClient(sptr)) sendto_one(sptr, PartFmt, parv[0], name); remove_user_from_channel(sptr, chptr); } if (*pbuf) /* send out the parts to all servers... -Kev */ sendto_serv_butone(cptr, PartFmt, parv[0], pbuf); return 0; } /* ** 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; char *comment, *name, *p = NULL, *user, *p2 = NULL; Link *lp,*lp2; if (check_registered(sptr)) return 0; sptr->flags&=~FLAGS_TS8; if (parc < 3 || *parv[1] == '\0') { sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS), me.name, parv[0], "KICK"); return 0; } if (IsServer(sptr)) sendto_ops("HACK: 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'; for (; (name = strtoken(&p, parv[1], ",")); parv[1] = NULL) { chptr = get_channel(sptr, name, !CREATE); if (!chptr) { sendto_one(sptr, err_str(ERR_NOSUCHCHANNEL), me.name, parv[0], name); continue; } if (*name == '&' && !MyClient(sptr)) continue; if (!IsServer(cptr) && !is_chan_op(sptr, chptr)) { sendto_one(sptr, err_str(ERR_CHANOPRIVSNEEDED), me.name, parv[0], chptr->chname); continue; } lp2=find_user_link(chptr->members, sptr); for (; (user = strtoken(&p2, parv[2], ",")); parv[2] = NULL) { if (!(who = find_chasing(sptr, user, &chasing))) continue; /* No such user left! */ /* if the user is +k, prevent a kick from local user */ if ((who->flags & FLAGS_NOKICK) && MyClient(sptr)) { sendto_one(sptr, err_str(ERR_ISCHANSERVICE), me.name, parv[0], user, chptr->chname); continue; } if ((lp=find_user_link(chptr->members, who)) && !(lp->flags & CHFL_ZOMBIE) || IsServer(sptr)) { if (who->from!=cptr && ((lp2 && (lp2->flags & CHFL_DEOPPED)) || (!lp2 && IsPerson(sptr)))) { /* Bounce here: * cptr must be a server (or cptr==sptr and * sptr->flags can't have DEOPPED set * when CHANOP is set). */ sendto_one(cptr,":%s JOIN %s",who->name,name); if (lp->flags & CHFL_CHANOP) sendmodeto_one(cptr, me.name, name, "+o", who->name, chptr->creationtime); if (lp->flags & CHFL_VOICE) sendmodeto_one(cptr, me.name, name, "+v", who->name, chptr->creationtime); } else { if (lp) sendto_channel_butserv(chptr, sptr, ":%s KICK %s %s :%s", parv[0], name, who->name, comment); sendto_match_servs(chptr, cptr, ":%s KICK %s %s :%s", parv[0], name, who->name, comment); if (lp) { lp->flags|=CHFL_ZOMBIE; if (MyClient(who)) { sendto_match_servs(chptr, NULL, PartFmt, who->name, name); remove_user_from_channel(who, chptr); } else { for (lp=chptr->members; lp; lp=lp->next) if (!(lp->flags & CHFL_ZOMBIE)) break; if (!lp) remove_user_from_channel(who, chptr); } } } } else if (MyClient(sptr)) sendto_one(sptr, err_str(ERR_USERNOTINCHANNEL), me.name, parv[0], user, name); if (!IsServer(cptr)) break; } /* loop on parv[2] */ if (!IsServer(cptr)) break; } /* loop on parv[1] */ return (0); } int count_channels(sptr) aClient *sptr; { Reg1 aChannel *chptr; Reg2 int count = 0; for (chptr = channel; chptr; chptr = chptr->nextch) #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; if (check_registered(sptr)) return 0; if (parc < 2) { sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS), me.name, parv[0], "TOPIC"); return 0; } for (; (name = strtoken(&p, parv[1], ",")); parv[1] = NULL) { if (parc > 1 && IsChannelName(name)) { chptr = find_channel(name, NullChn); if (!chptr || !IsMember(sptr, chptr)) { sendto_one(sptr, err_str(ERR_NOTONCHANNEL), me.name, parv[0], name); continue; } if (parc > 2) topic = parv[2]; } if (!chptr) { sendto_one(sptr, rpl_str(RPL_NOTOPIC), me.name, parv[0], name); return 0; } if (*name == '&' && !MyClient(sptr)) continue; if (!topic) /* only asking for topic */ { if (chptr->topic[0] == '\0') sendto_one(sptr, rpl_str(RPL_NOTOPIC), me.name, parv[0], chptr->chname); else { sendto_one(sptr, rpl_str(RPL_TOPIC), me.name, parv[0], chptr->chname, chptr->topic); sendto_one(sptr, rpl_str(RPL_TOPICWHOTIME), me.name, parv[0], chptr->chname, chptr->topic_nick, chptr->topic_time); } } else if (((chptr->mode.mode & MODE_TOPICLIMIT) == 0 || is_chan_op(sptr, chptr)) && topic) { /* setting a topic */ strncpyzt(chptr->topic, topic, sizeof(chptr->topic)); strcpy(chptr->topic_nick, sptr->name); chptr->topic_time = now; 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); } else sendto_one(sptr, err_str(ERR_CHANOPRIVSNEEDED), me.name, parv[0], chptr->chname); } return 0; } /* ** 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 (check_registered_user(sptr)) return 0; if (parc < 3 || *parv[1] == '\0') { sendto_one(sptr, err_str(ERR_NEEDMOREPARAMS), me.name, parv[0], "INVITE"); return -1; } if (!(acptr = find_person(parv[1], (aClient *)NULL))) { sendto_one(sptr, err_str(ERR_NOSUCHNICK), me.name, parv[0], parv[1]); return 0; } if (is_silenced(sptr, acptr)) return 0; clean_channelname(parv[2]); if (*parv[2] == '&' && !MyClient(sptr)) return 0; if (!(chptr = find_channel(parv[2], NullChn))) { sendto_prefix_one(acptr, sptr, ":%s INVITE %s :%s", parv[0], parv[1], parv[2]); return 0; } if (chptr && !IsMember(sptr, chptr)) { sendto_one(sptr, err_str(ERR_NOTONCHANNEL), me.name, parv[0], parv[2]); return -1; } if (IsMember(acptr, chptr)) { sendto_one(sptr, err_str(ERR_USERONCHANNEL), me.name, parv[0], parv[1], parv[2]); return 0; } if (chptr && (chptr->mode.mode & MODE_INVITEONLY)) { if (!is_chan_op(sptr, chptr)) { sendto_one(sptr, err_str(ERR_CHANOPRIVSNEEDED), me.name, parv[0], chptr->chname); return -1; } else if (!IsMember(sptr, chptr)) { sendto_one(sptr, err_str(ERR_CHANOPRIVSNEEDED), me.name, parv[0], ((chptr) ? (chptr->chname) : parv[2])); return -1; } } if (MyConnect(sptr)) { sendto_one(sptr, rpl_str(RPL_INVITING), me.name, parv[0], acptr->name, ((chptr) ? (chptr->chname) : parv[2])); if (acptr->user->away) sendto_one(sptr, rpl_str(RPL_AWAY), me.name, parv[0], acptr->name, acptr->user->away); } 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 0; } static int number_of_zombies(chptr) aChannel *chptr; { Reg1 Link *lp; Reg2 int count = 0; for (lp=chptr->members; lp; lp=lp->next) if (lp->flags & CHFL_ZOMBIE) count++; return count; } /* ** m_list ** parv[0] = sender prefix ** parv[1] = channel list or user/time limit ** parv[2...] = more user/time limits */ int m_list(cptr, sptr, parc, parv) aClient *cptr, *sptr; int parc; char *parv[]; { aChannel *chptr; char *name, *p = NULL; int show_usage = 0, show_channels = 0, param, topic_limits = 0; unsigned long max_time = -1; unsigned long min_time = 0; unsigned int max_users = -1; unsigned int min_users = 0; unsigned long max_topic_time = -1; unsigned long min_topic_time = 0; if (check_registered_user(sptr)) return 0; if (!MyClient(sptr)) /* Don't give long listings to remote clients */ return 0; if (parc < 2) /* No arguments given to /LIST ? */ { #ifdef DEFAULT_LIST_PARAM static char *defparv[MAXPARA+1]; static int defparc = 0; int i; if (!defparc) { char *s = DEFAULT_LIST_PARAM, *t; defparc = 1; defparv[defparc++] = t = strtok(s, " "); while (t && defparc < MAXPARA) { if (t = strtok(NULL, " ")) defparv[defparc++] = t; } } for (i = 1; i < defparc; i++) parv[i] = defparv[i]; parv[i] = NULL; parc = defparc; #else if (sptr->listing) /* Already listing ? */ { sptr->listing->mode.mode &= ~MODE_LISTED; sptr->listing = NULL; sendto_one(sptr, rpl_str(RPL_LISTEND), me.name, sptr->name); return 0; /* Let LIST abort a listing. --Ensor */ } sendto_one(sptr, rpl_str(RPL_LISTSTART), me.name, parv[0]); if (!(sptr->listing = channel)) /* Is there a channel at all ? */ sendto_one(sptr, rpl_str(RPL_LISTEND), me.name, parv[0]); else { int m = channel->mode.mode & MODE_LISTED; list_next_channels(sptr, 64); channel->mode.mode |= m; } return 0; #endif /* DEFAULT_LIST_PARAM */ } /* Decode command */ for (param = 1; !show_usage && parv[param]; param++) { char *p = parv[param]; do { int is_time = 0; switch (*p) { case '#': case '&': if (parc != 2) /* Don't allow a mixture of channels with <,> */ show_usage = 1; show_channels = 1; p = NULL; break; case 'T': case 't': is_time++; topic_limits = 1; /* Fall through */ case 'C': case 'c': is_time++; p++; if (*p != '<' && *p != '>') { show_usage = 1; break; } /* Fall through */ case '<': case '>': { p++; if (!isdigit(*p)) show_usage = 1; else { if (is_time) { time_t val = atoi(p); if (p[-1] == '<') { if (val < 80000000) /* Toggle UTC/offset */ { /* Demands that 'TStime() - chptr->creationtime < val * 60' */ /* Which equals 'chptr->creationtime > TStime() - val * 60' */ if (is_time == 1) min_time = TStime() - val * 60; else min_topic_time = TStime() - val * 60; } else if (is_time == 1) max_time = val; /* Creation time in UTC was entered */ else max_topic_time = val; /* Topic time in UTC was entered */ } else if (val < 80000000) { if (is_time == 1) max_time = TStime() - val * 60; else max_topic_time = TStime() - val * 60; } else if (is_time == 1) min_time = val; else min_topic_time = val; } else if (p[-1] == '<') max_users = atoi(p); else min_users = atoi(p); if ((p = strchr(p, ','))) p++; } break; } default: show_usage = 1; } } while (!show_usage && p); /* p points after comma, or is NULL */ } if (show_usage) { sendto_one(sptr, rpl_str(RPL_LISTUSAGE), me.name, parv[0], "Usage on ircII: /QUOTE LIST parameters"); sendto_one(sptr, rpl_str(RPL_LISTUSAGE), me.name, parv[0], " on mIRC : /RAW LIST parameters"); sendto_one(sptr, rpl_str(RPL_LISTUSAGE), me.name, parv[0], "Where parameters is a space or comma seperated list of one or more of:"); sendto_one(sptr, rpl_str(RPL_LISTUSAGE), me.name, parv[0], " <max_users ; Show all channels with less then max_users."); sendto_one(sptr, rpl_str(RPL_LISTUSAGE), me.name, parv[0], " >min_users ; Show all channels with more then min_users."); sendto_one(sptr, rpl_str(RPL_LISTUSAGE), me.name, parv[0], " C<max_minutes ; Channels that exist less then max_minutes."); sendto_one(sptr, rpl_str(RPL_LISTUSAGE), me.name, parv[0], " C>min_minutes ; Channels that exist more then min_minutes."); sendto_one(sptr, rpl_str(RPL_LISTUSAGE), me.name, parv[0], " T<max_minutes ; Channels with a topic last set less then max_minutes ago."); sendto_one(sptr, rpl_str(RPL_LISTUSAGE), me.name, parv[0], " T>min_minutes ; Channels with a topic last set more then min_minutes ago."); sendto_one(sptr, rpl_str(RPL_LISTUSAGE), me.name, parv[0], "Example: LIST <3,>1,C<10,T>0 ; 2 users, younger then 10 min., topic set."); return 0; } sendto_one(sptr, rpl_str(RPL_LISTSTART), me.name, parv[0]); if (!show_channels) { if (max_users > min_users + 1 && max_time > min_time && max_topic_time > min_topic_time) /* Sanity check */ for (chptr = channel; chptr; chptr = chptr->nextch) { if (!sptr->user || (SecretChannel(chptr) && !IsMember(sptr, chptr))) continue; if (chptr->users > min_users && chptr->users < max_users && chptr->creationtime > min_time && chptr->creationtime < max_time && (!topic_limits || (*chptr->topic && chptr->topic_time > min_topic_time && chptr->topic_time < max_topic_time))) sendto_one(sptr, rpl_str(RPL_LIST), me.name, parv[0], ShowChannel(sptr, chptr)?chptr->chname:"*", chptr->users, ShowChannel(sptr, chptr)?chptr->topic:""); } sendto_one(sptr, rpl_str(RPL_LISTEND), me.name, parv[0]); return 0; } if (hunt_server(cptr, sptr, ":%s LIST %s %s", 2, parc, parv)) return 0; for (; (name = strtoken(&p, parv[1], ",")); parv[1] = NULL) { chptr = find_channel(name, NullChn); if (chptr && ShowChannel(sptr, chptr) && sptr->user) sendto_one(sptr, rpl_str(RPL_LIST), me.name, parv[0], ShowChannel(sptr,chptr) ? name : "*", chptr->users - number_of_zombies(chptr), chptr->topic); } sendto_one(sptr, rpl_str(RPL_LISTEND), me.name, parv[0]); return 0; } /************************************************************************ * 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[]; { Reg1 aChannel *chptr; Reg2 aClient *c2ptr; Reg3 Link *lp; aChannel *ch2ptr = NULL; int idx, flag, len, mlen; char *s, *para = parc > 1 ? parv[1] : NULL; if (parc > 1 && hunt_server(cptr, sptr, ":%s NAMES %s %s", 2, parc, parv)) return 0; mlen = strlen(me.name) + 10 + strlen(sptr->name); if (!BadPtr(para)) { s = index(para, ','); if (s) { parv[1] = ++s; (void)m_names(cptr, sptr, parc, parv); } clean_channelname(para); ch2ptr = find_channel(para, (aChannel *)NULL); } *buf = '\0'; /* Allow NAMES without registering * * First, do all visible channels (public and the one user self is) */ for (chptr = channel; chptr; chptr = chptr->nextch) { if ((chptr != ch2ptr) && !BadPtr(para)) continue; /* -- wanted a specific channel */ if (!MyConnect(sptr) && BadPtr(para)) continue; if (!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 = '@'; idx = len + 4; flag = 1; for (lp = chptr->members; lp; lp = lp->next) { c2ptr = lp->value.cptr; if (sptr!=c2ptr && IsInvisible(c2ptr) && !IsMember(sptr,chptr)) continue; if (lp->flags & CHFL_ZOMBIE) { if (lp->value.cptr!=sptr) continue; else { (void)strcat(buf, "!"); idx++; } } else 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 + 5 > BUFSIZE ) /* space, modifier, nick, \r \n \0 */ { sendto_one(sptr, rpl_str(RPL_NAMREPLY), me.name, 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) sendto_one(sptr, rpl_str(RPL_NAMREPLY), me.name, parv[0], buf); } if (!BadPtr(para)) { sendto_one(sptr, rpl_str(RPL_ENDOFNAMES), me.name, 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) || sptr!=c2ptr && IsInvisible(c2ptr)) continue; lp = c2ptr->user->channel; /* * dont 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 + 3 > BUFSIZE ) /* space, \r\n\0 */ { sendto_one(sptr, rpl_str(RPL_NAMREPLY), me.name, parv[0], buf); (void)strncpy(buf, "* * :", 6); idx = 5; flag = 0; } } if (flag) sendto_one(sptr, rpl_str(RPL_NAMREPLY), me.name, parv[0], buf); sendto_one(sptr, rpl_str(RPL_ENDOFNAMES), me.name, parv[0], "*"); return(1); } void send_user_joins(cptr, user) aClient *cptr, *user; { Reg1 Link *lp; Reg2 aChannel *chptr; Reg3 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 ((mask = index(chptr->chname, ':'))) if (matches(++mask, cptr->name)) continue; if (*chptr->chname == '&') continue; if (is_zombie(user, chptr)) continue; clen = strlen(chptr->chname); if (clen + 1 + len > BUFSIZE - 3) { if (cnt) { buf[len-1]='\0'; sendto_one(cptr, "%s", buf); } *buf = ':'; (void)strcpy(buf+1, user->name); (void)strcat(buf, " JOIN "); len = strlen(user->name) + 7; cnt = 0; } (void)strcpy(buf + len, chptr->chname); cnt++; len += clen; if (lp->next) { len++; (void)strcat(buf, ","); } } if (*buf && cnt) sendto_one(cptr, "%s", buf); return; }