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