Author: Carlo Wood <run@alinoe.com> (Via Isomer <isomer@undernet.org>)
[ircu2.10.12-pk.git] / ircd / channel.c
index cdb3e3689bc751ac3271dc8e051c88987745fc2d..1c3d491549ff26c32f8a589c74ed680a7cc9be8b 100644 (file)
  *
  * $Id$
  */
+#include "config.h"
+
 #include "channel.h"
 #include "client.h"
+#include "destruct_event.h"
 #include "hash.h"
 #include "ircd.h"
 #include "ircd_alloc.h"
 #include "ircd_chattr.h"
 #include "ircd_defs.h"
+#include "ircd_features.h"
 #include "ircd_log.h"
+#include "ircd_policy.h"
 #include "ircd_reply.h"
 #include "ircd_snprintf.h"
 #include "ircd_string.h"
@@ -43,7 +48,6 @@
 #include "s_misc.h"
 #include "s_user.h"
 #include "send.h"
-#include "sprintf_irc.h"
 #include "struct.h"
 #include "support.h"
 #include "sys.h"
@@ -59,8 +63,6 @@ struct Channel* GlobalChannelList = 0;
 static unsigned int membershipAllocCount;
 static struct Membership* membershipFreeList;
 
-static struct SLink *next_overlapped_ban(void);
-static int del_banid(struct Channel *, char *, int);
 void del_invite(struct Client *, struct Channel *);
 
 const char* const PartFmt1     = ":%s " MSG_PART " %s";
@@ -122,7 +124,7 @@ struct Membership* find_member_link(struct Channel* chptr, const struct Client*
    * and 99% are on 10 or less.
    */
   else {
-   m = cptr->user->channel;
+   m = (cli_user(cptr))->channel;
    while (m) {
      assert(m->user == cptr);
      if (m->channel == chptr)
@@ -148,7 +150,7 @@ struct Client* find_chasing(struct Client* sptr, const char* user, int* chasing)
   if (who)
     return who;
 
-  if (!(who = get_history(user, KILLCHASETIMELIMIT))) {
+  if (!(who = get_history(user, feature_int(FEAT_KILLCHASETIMELIMIT)))) {
     send_reply(sptr, ERR_NOSUCHNICK, user);
     return 0;
   }
@@ -161,11 +163,11 @@ struct Client* find_chasing(struct Client* sptr, const char* user, int* chasing)
  * 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(const char *nick, const char *name,
-                                 const char *host)
+#define NUH_BUFSIZE    (NICKLEN + USERLEN + HOSTLEN + 3)
+static char *make_nick_user_host(char *namebuf, const char *nick,
+                                const char *name, const char *host)
 {
-  static char namebuf[NICKLEN + USERLEN + HOSTLEN + 3];
-  sprintf_irc(namebuf, "%s!%s@%s", nick, name, host);
+  ircd_snprintf(0, namebuf, NUH_BUFSIZE, "%s!%s@%s", nick, name, host);
   return namebuf;
 }
 
@@ -173,51 +175,23 @@ static char *make_nick_user_host(const char *nick, const char *name,
  * Create a string of form "foo!bar@123.456.789.123" given foo, bar and the
  * IP-number as the parameters.  If NULL, they become "*".
  */
-static char *make_nick_user_ip(char *nick, char *name, struct in_addr ip)
+#define NUI_BUFSIZE    (NICKLEN + USERLEN + 16 + 3)
+static char *make_nick_user_ip(char *ipbuf, char *nick, char *name,
+                              struct in_addr ip)
 {
-  static char ipbuf[NICKLEN + USERLEN + 16 + 3];
-  sprintf_irc(ipbuf, "%s!%s@%s", nick, name, ircd_ntoa((const char*) &ip));
+  ircd_snprintf(0, ipbuf, NUI_BUFSIZE, "%s!%s@%s", nick, name,
+               ircd_ntoa((const char*) &ip));
   return ipbuf;
 }
 
-#if 0
-static int DoesOp(const char* modebuf)
-{
-  assert(0 != modebuf);
-  while (*modebuf) {
-    if (*modebuf == 'o' || *modebuf == 'v')
-      return 1;
-    ++modebuf;
-  }
-  return 0;
-}
-
-/*
- * This function should be removed when all servers are 2.10
- */
-static void sendmodeto_one(struct Client* cptr, const char* from,
-                           const char* name, const char* mode,
-                           const char* param, time_t creationtime)
-{
-  if (IsServer(cptr) && DoesOp(mode) && creationtime)
-    sendto_one(cptr, ":%s MODE %s %s %s " TIME_T_FMT, /* XXX DEAD */
-               from, name, mode, param, creationtime);
-  else
-    sendto_one(cptr, ":%s MODE %s %s %s", from, name, mode, param); /* XXX DEAD */
-}
-#endif /* 0 */
-
 /*
  * Subtract one user from channel i (and free channel
  * block, if channel became empty).
- * Returns: true  (1) if channel still exists
- *          false (0) if the channel was destroyed
+ * Returns: true  (1) if channel still has members.
+ *          false (0) if the channel is now empty.
  */
 int sub1_from_channel(struct Channel* chptr)
 {
-  struct SLink *tmp;
-  struct SLink *obtmp;
-
   if (chptr->users > 1)         /* Can be 0, called for an empty channel too */
   {
     assert(0 != chptr->members);
@@ -225,6 +199,39 @@ int sub1_from_channel(struct Channel* chptr)
     return 1;
   }
 
+  chptr->users = 0;
+
+  /*
+   * Also channels without Apass set need to be kept alive,
+   * otherwise Bad Guys(tm) would be able to takeover
+   * existing channels too easily, and then set an Apass!
+   * However, if a channel without Apass becomes empty
+   * then we try to be kind to them and remove possible
+   * limiting modes.
+   */
+  chptr->mode.mode &= ~MODE_INVITEONLY;
+  chptr->mode.limit = 0;
+  /*
+   * We do NOT reset a possible key or bans because when
+   * the 'channel owners' can't get in because of a key
+   * or ban then apparently there was a fight/takeover
+   * on the channel and we want them to contact IRC opers
+   * who then will educate them on the use of Apass/upass.
+   */
+
+  if (TStime() - chptr->creationtime < 172800) /* Channel younger than 48 hours? */
+    schedule_destruct_event_1m(chptr);         /* Get rid of it in approximately 4-5 minutes */
+  else
+    schedule_destruct_event_48h(chptr);                /* Get rid of it in approximately 48 hours */
+
+  return 0;
+}
+
+int destruct_channel(struct Channel* chptr)
+{
+  struct SLink *tmp;
+  struct SLink *obtmp;
+
   assert(0 == chptr->members);
 
   /* Channel became (or was) empty: Remove channel */
@@ -234,8 +241,8 @@ int sub1_from_channel(struct Channel* chptr)
     for (i = 0; i <= HighestFd; i++)
     {
       struct Client *acptr = 0;
-      if ((acptr = LocalClientArray[i]) && acptr->listing &&
-          acptr->listing->chptr == chptr)
+      if ((acptr = LocalClientArray[i]) && cli_listing(acptr) &&
+          (cli_listing(acptr))->chptr == chptr)
       {
         list_next_channels(acptr, 1);
         break;                  /* Only one client can list a channel */
@@ -336,18 +343,10 @@ int add_banid(struct Client *cptr, struct Channel *chptr, char *banid,
           len -= strlen(tmp->value.ban.banstr);
         }
         *banp = tmp->next;
-#if 0
-        /* Silently remove overlapping bans */
-        MyFree(tmp->value.ban.banstr);
-        MyFree(tmp->value.ban.who);
-        free_link(tmp);
-        tmp = 0;
-#else
         /* These will be sent to the user later as -b */
         tmp->next = removed_bans_list;
         removed_bans_list = tmp;
         removed_bans = 1;
-#endif
       }
       else if (!(tmp->flags & CHFL_BURST_BAN_WIPEOUT))
       {
@@ -366,7 +365,9 @@ int add_banid(struct Client *cptr, struct Channel *chptr, char *banid,
       banp = &(*banp)->next;
     }
   }
-  if (MyUser(cptr) && !removed_bans && (len > MAXBANLENGTH || (cnt >= MAXBANS)))
+  if (MyUser(cptr) && !removed_bans &&
+      (len > (feature_int(FEAT_AVBANLEN) * feature_int(FEAT_MAXBANS)) ||
+       (cnt >= feature_int(FEAT_MAXBANS))))
   {
     send_reply(cptr, ERR_BANLISTFULL, chptr->chname, banid);
     return -1;
@@ -382,9 +383,13 @@ int add_banid(struct Client *cptr, struct Channel *chptr, char *banid,
     assert(0 != ban->value.ban.banstr);
     strcpy(ban->value.ban.banstr, banid);
 
-    ban->value.ban.who = (char*) MyMalloc(strlen(cptr->name) + 1);
+#ifdef HEAD_IN_SAND_BANWHO
+    if (IsServer(cptr))
+      DupString(ban->value.ban.who, cli_name(&me));
+    else
+#endif
+      DupString(ban->value.ban.who, cli_name(cptr));
     assert(0 != ban->value.ban.who);
-    strcpy(ban->value.ban.who, cptr->name);
 
     ban->value.ban.when = TStime();
     ban->flags = CHFL_BAN;      /* This bit is never used I think... */
@@ -401,20 +406,6 @@ int add_banid(struct Client *cptr, struct Channel *chptr, char *banid,
   return 0;
 }
 
-static struct SLink *next_overlapped_ban(void)
-{
-  struct SLink *tmp = next_ban;
-  if (tmp)
-  {
-    struct SLink *ban;
-    for (ban = tmp->next; ban; ban = ban->next)
-      if ((ban->flags & CHFL_BAN_OVERLAPPED))
-        break;
-    next_ban = ban;
-  }
-  return tmp;
-}
-
 struct SLink *next_removed_overlapped_ban(void)
 {
   struct SLink *tmp = removed_bans_list;
@@ -432,43 +423,6 @@ struct SLink *next_removed_overlapped_ban(void)
   return tmp;
 }
 
-/*
- * del_banid
- *
- * If `change' is true, delete `banid' from channel `chptr'.
- * Returns `false' if removal was (or would have been) successful.
- */
-static int del_banid(struct Channel *chptr, char *banid, int change)
-{
-  struct SLink **ban;
-  struct SLink *tmp;
-
-  if (!banid)
-    return -1;
-  for (ban = &(chptr->banlist); *ban; ban = &((*ban)->next)) {
-    if (0 == ircd_strcmp(banid, (*ban)->value.ban.banstr))
-    {
-      tmp = *ban;
-      if (change)
-      {
-        struct Membership* member;
-        *ban = tmp->next;
-        MyFree(tmp->value.ban.banstr);
-        MyFree(tmp->value.ban.who);
-        free_link(tmp);
-        /*
-         * Erase ban-valid-bit, for channel members that are banned
-         */
-        for (member = chptr->members; member; member = member->next_member)
-          if (CHFL_BANVALIDMASK == (member->status & CHFL_BANVALIDMASK))
-            ClearBanValid(member);       /* `tmp' == channel member */
-      }
-      return 0;
-    }
-  }
-  return -1;
-}
-
 /*
  * find_channel_member - returns Membership * if a person is joined and not a zombie
  */
@@ -488,7 +442,11 @@ static int is_banned(struct Client *cptr, struct Channel *chptr,
                      struct Membership* member)
 {
   struct SLink* tmp;
+  char          nu_host[NUH_BUFSIZE];
+  char          nu_realhost[NUH_BUFSIZE];
+  char          nu_ip[NUI_BUFSIZE];
   char*         s;
+  char*         sr = NULL;
   char*         ip_s = NULL;
 
   if (!IsUser(cptr))
@@ -497,17 +455,25 @@ static int is_banned(struct Client *cptr, struct Channel *chptr,
   if (member && IsBanValid(member))
     return IsBanned(member);
 
-  s = make_nick_user_host(cptr->name, cptr->user->username, cptr->user->host);
+  s = make_nick_user_host(nu_host, cli_name(cptr), (cli_user(cptr))->username,
+                         (cli_user(cptr))->host);
+  if (HasHiddenHost(cptr))
+    sr = make_nick_user_host(nu_realhost, cli_name(cptr),
+                            (cli_user(cptr))->username,
+                            cli_user(cptr)->realhost);
 
   for (tmp = chptr->banlist; tmp; tmp = tmp->next) {
     if ((tmp->flags & CHFL_BAN_IPMASK)) {
       if (!ip_s)
-        ip_s = make_nick_user_ip(cptr->name, cptr->user->username, cptr->ip);
+        ip_s = make_nick_user_ip(nu_ip, cli_name(cptr),
+                                (cli_user(cptr))->username, cli_ip(cptr));
       if (match(tmp->value.ban.banstr, ip_s) == 0)
         break;
     }
     else if (match(tmp->value.ban.banstr, s) == 0)
       break;
+    else if (sr && match(tmp->value.ban.banstr, sr) == 0)
+      break;
   }
 
   if (member) {
@@ -530,12 +496,12 @@ static int is_banned(struct Client *cptr, struct Channel *chptr,
  * chain.
  */
 void add_user_to_channel(struct Channel* chptr, struct Client* who,
-                                unsigned int flags)
+                                unsigned int flags, int oplevel)
 {
   assert(0 != chptr);
   assert(0 != who);
 
-  if (who->user) {
+  if (cli_user(who)) {
    
     struct Membership* member = membershipFreeList;
     if (member)
@@ -549,6 +515,7 @@ void add_user_to_channel(struct Channel* chptr, struct Client* who,
     member->user         = who;
     member->channel      = chptr;
     member->status       = flags;
+    member->oplevel      = oplevel;
 
     member->next_member  = chptr->members;
     if (member->next_member)
@@ -556,14 +523,16 @@ void add_user_to_channel(struct Channel* chptr, struct Client* who,
     member->prev_member  = 0; 
     chptr->members       = member;
 
-    member->next_channel = who->user->channel;
+    member->next_channel = (cli_user(who))->channel;
     if (member->next_channel)
       member->next_channel->prev_channel = member;
     member->prev_channel = 0;
-    who->user->channel = member;
+    (cli_user(who))->channel = member;
 
+    if (chptr->destruct_event)
+      remove_destruct_event(chptr);
     ++chptr->users;
-    ++who->user->joined;
+    ++((cli_user(who))->joined);
   }
 }
 
@@ -590,9 +559,9 @@ static int remove_member_from_channel(struct Membership* member)
   if (member->prev_channel)
     member->prev_channel->next_channel = member->next_channel;
   else
-    member->user->user->channel = member->next_channel;
+    (cli_user(member->user))->channel = member->next_channel;
 
-  --member->user->user->joined;
+  --(cli_user(member->user))->joined;
 
   member->next_member = membershipFreeList;
   membershipFreeList = member;
@@ -636,9 +605,9 @@ void remove_user_from_all_channels(struct Client* cptr)
 {
   struct Membership* chan;
   assert(0 != cptr);
-  assert(0 != cptr->user);
+  assert(0 != cli_user(cptr));
 
-  while ((chan = cptr->user->channel))
+  while ((chan = (cli_user(cptr))->channel))
     remove_user_from_channel(cptr, chan->channel);
 }
 
@@ -652,15 +621,9 @@ int is_chan_op(struct Client *cptr, struct Channel *chptr)
   return 0;
 }
 
-static int is_deopped(struct Client *cptr, struct Channel *chptr)
+int is_level0_op(struct Client *cptr, struct Channel *chptr)
 {
-  struct Membership* member;
-
-  assert(0 != chptr);
-  if ((member = find_member_link(chptr, cptr)))
-    return IsDeopped(member);
-
-  return (IsUser(cptr) ? 1 : 0);
+  return 0;
 }
 
 int is_zombie(struct Client *cptr, struct Channel *chptr)
@@ -721,13 +684,15 @@ int client_can_send_to_channel(struct Client *cptr, struct Channel *chptr)
 
   /*
    * You can't speak if your off channel, if the channel is modeless, or
-   * +n.(no external messages)
+   * +n (no external messages) or +m (moderated).
    */
   if (!member) {
-    if ((chptr->mode.mode & MODE_NOPRIVMSGS) || IsModelessChannel(chptr->chname)) 
+    if ((chptr->mode.mode & (MODE_NOPRIVMSGS|MODE_MODERATED)) ||
+       IsModelessChannel(chptr->chname) ||
+       ((chptr->mode.mode & MODE_REGONLY) && !IsAccount(cptr)))
       return 0;
     else
-      return 1;
+      return !is_banned(cptr, chptr, NULL);
   }
   return member_can_send_to_channel(member); 
 }
@@ -741,7 +706,8 @@ const char* find_no_nickchange_channel(struct Client* cptr)
 {
   if (MyUser(cptr)) {
     struct Membership* member;
-    for (member = cptr->user->channel; member; member = member->next_channel) {
+    for (member = (cli_user(cptr))->channel; member;
+        member = member->next_channel) {
       if (!IsVoicedOrOpped(member) && is_banned(cptr, member->channel, member))
         return member->channel->chname;
     }
@@ -754,9 +720,11 @@ const char* find_no_nickchange_channel(struct Client* cptr)
  * write the "simple" list of channel modes for channel chptr onto buffer mbuf
  * with the parameters in pbuf.
  */
-void channel_modes(struct Client *cptr, char *mbuf, char *pbuf,
+void channel_modes(struct Client *cptr, char *mbuf, char *pbuf, int buflen,
                           struct Channel *chptr)
 {
+  int previous_parameter = 0;
+
   assert(0 != mbuf);
   assert(0 != pbuf);
   assert(0 != chptr);
@@ -774,87 +742,63 @@ void channel_modes(struct Client *cptr, char *mbuf, char *pbuf,
     *mbuf++ = 'i';
   if (chptr->mode.mode & MODE_NOPRIVMSGS)
     *mbuf++ = 'n';
+  if (chptr->mode.mode & MODE_REGONLY)
+    *mbuf++ = 'r';
   if (chptr->mode.limit) {
     *mbuf++ = 'l';
-    sprintf_irc(pbuf, "%d", chptr->mode.limit);
+    ircd_snprintf(0, pbuf, buflen, "%u", chptr->mode.limit);
+    previous_parameter = 1;
   }
 
   if (*chptr->mode.key) {
     *mbuf++ = 'k';
+    if (previous_parameter)
+      strcat(pbuf, " ");
     if (is_chan_op(cptr, chptr) || IsServer(cptr)) {
-      if (chptr->mode.limit)
-        strcat(pbuf, " ");
       strcat(pbuf, chptr->mode.key);
-    }
+    } else
+      strcat(pbuf, "*");
+    previous_parameter = 1;
+  }
+  if (*chptr->mode.apass) {
+    *mbuf++ = 'A';
+    if (previous_parameter)
+      strcat(pbuf, " ");
+    if (IsServer(cptr)) {
+      strcat(pbuf, chptr->mode.apass);
+    } else
+      strcat(pbuf, "*");
+    previous_parameter = 1;
+  }
+  if (*chptr->mode.upass) {
+    *mbuf++ = 'u';
+    if (previous_parameter)
+      strcat(pbuf, " ");
+    if (is_level0_op(cptr, chptr) || IsServer(cptr)) {
+      strcat(pbuf, chptr->mode.upass);
+    } else
+      strcat(pbuf, "*");
   }
   *mbuf = '\0';
 }
 
-#if 0
-static int send_mode_list(struct Client *cptr, char *chname,
-                          time_t creationtime, struct SLink *top,
-                          int mask, char flag)
+int compare_member_oplevel(const void *mp1, const void *mp2)
 {
-  struct SLink* lp;
-  char*         cp;
-  char*         name;
-  int           count = 0;
-  int           send = 0;
-  int           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 < MODEBUFLEN)
-    {
-      strcat(parabuf, " ");
-      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)
-      {
-        strcpy(parabuf, name);
-        *cp++ = flag;
-      }
-      count = 0;
-      *cp = '\0';
-    }
-  }
-  return sent;
+  struct Membership const* member1 = *(struct Membership const**)mp1;
+  struct Membership const* member2 = *(struct Membership const**)mp2;
+  if (member1->oplevel == member2->oplevel)
+    return 0;
+  return (member1->oplevel < member2->oplevel) ? -1 : 1;
 }
 
-#endif /* 0 */
-
 /*
  * send "cptr" a full list of the modes for channel chptr.
  */
 void send_channel_modes(struct Client *cptr, struct Channel *chptr)
 {
+  /* The order in which modes are generated is now mandatory */
   static unsigned int current_flags[4] =
-      { 0, CHFL_CHANOP | CHFL_VOICE, CHFL_VOICE, CHFL_CHANOP };
+      { 0, CHFL_VOICE, CHFL_CHANOP, CHFL_CHANOP | CHFL_VOICE };
   int                first = 1;
   int                full  = 1;
   int                flag_cnt = 0;
@@ -865,6 +809,10 @@ void send_channel_modes(struct Client *cptr, struct Channel *chptr)
   char modebuf[MODEBUFLEN];
   char parabuf[MODEBUFLEN];
   struct MsgBuf *mb;
+  int                 number_of_ops = 0;
+  int                 opped_members_index = 0;
+  struct Membership** opped_members = NULL;
+  int                 last_oplevel = 0;
 
   assert(0 != cptr);
   assert(0 != chptr); 
@@ -876,7 +824,7 @@ void send_channel_modes(struct Client *cptr, struct Channel *chptr)
   lp2 = chptr->banlist;
 
   *modebuf = *parabuf = '\0';
-  channel_modes(cptr, modebuf, parabuf, chptr);
+  channel_modes(cptr, modebuf, parabuf, sizeof(parabuf), chptr);
 
   for (first = 1; full; first = 0)      /* Loop for multiple messages */
   {
@@ -888,7 +836,7 @@ void send_channel_modes(struct Client *cptr, struct Channel *chptr)
     mb = msgq_make(&me, "%C " TOK_BURST " %H %Tu", &me, chptr,
                   chptr->creationtime);
 
-    if (first && modebuf[1])    /* Add simple modes (iklmnpst)
+    if (first && modebuf[1])    /* Add simple modes (Aiklmnpstu)
                                  if first message */
     {
       /* prefix: "<Y> B <channel> <TS>[ <modes>[ <params>]]" */
@@ -901,52 +849,119 @@ void send_channel_modes(struct Client *cptr, struct Channel *chptr)
     /*
      * Attach nicks, comma seperated " nick[:modes],nick[:modes],..."
      *
-     * Run 4 times over all members, to group the members with the
-     * same mode together
+     * First find all opless members.
+     * Run 2 times over all members, to group the members with
+     * and without voice together.
+     * Then run 2 times over all opped members (which are ordered
+     * by op-level) to also group voice and non-voice together.
      */
-    for (first = 1; flag_cnt < 4;
-         member = chptr->members, new_mode = 1, flag_cnt++)
+    for (first = 1; flag_cnt < 4; new_mode = 1, ++flag_cnt)
     {
-      for (; member; member = member->next_member)
+      while (member)
       {
-        if ((member->status & CHFL_VOICED_OR_OPPED) !=
-            current_flags[flag_cnt])
-          continue;             /* Skip members with different flags */
-       if (msgq_bufleft(mb) < NUMNICKLEN + 4)
-          /* The 4 is a possible ",:ov" */
-        {
-          full = 1;           /* Make sure we continue after
-                                 sending it so far */
-          new_mode = 1;       /* Ensure the new BURST line contains the current
-                                 mode. --Gte */
-          break;              /* Do not add this member to this message */
-        }
-       msgq_append(&me, mb, "%c%C", first ? ' ' : ',', member->user);
-        first = 0;              /* From now on, us comma's to add new nicks */
-
-        /*
-         * Do we have a nick with a new mode ?
-         * Or are we starting a new BURST line?
-         */
-        if (new_mode)
-        {
-          new_mode = 0;
-          if (IsVoicedOrOpped(member)) {
-           char tbuf[4] = ":";
+       if (flag_cnt < 2 && IsChanOp(member))
+       {
+         /*
+          * The first loop (to find all non-voice/op), we count the ops.
+          * The second loop (to find all voiced non-ops), store the ops
+          * in a dynamic array.
+          */
+         if (flag_cnt == 0)
+           ++number_of_ops;
+         else
+           opped_members[opped_members_index++] = member;
+       }
+       /* Only handle the members with the flags that we are interested in. */
+        if ((member->status & CHFL_VOICED_OR_OPPED) == current_flags[flag_cnt])
+       {
+         if (msgq_bufleft(mb) < NUMNICKLEN + 3 + MAXOPLEVELDIGITS)
+           /* The 3 + MAXOPLEVELDIGITS is a possible ",:v999". */
+         {
+           full = 1;           /* Make sure we continue after
+                                  sending it so far */
+           /* Ensure the new BURST line contains the current
+            * ":mode", except when there is no mode yet. */
+           new_mode = (flag_cnt > 0) ? 1 : 0;
+           break;              /* Do not add this member to this message */
+         }
+         msgq_append(&me, mb, "%c%C", first ? ' ' : ',', member->user);
+         first = 0;              /* From now on, use commas to add new nicks */
+
+         /*
+          * Do we have a nick with a new mode ?
+          * Or are we starting a new BURST line?
+          */
+         if (new_mode)
+         {
+           /*
+            * This means we are at the _first_ member that has only
+            * voice, or the first member that has only ops, or the
+            * first member that has voice and ops (so we get here
+            * at most three times, plus once for every start of
+            * a continued BURST line where only these modes is current.
+            * In the two cases where the current mode includes ops,
+            * we need to add the _absolute_ value of the oplevel to the mode.
+            */
+           char tbuf[3 + MAXOPLEVELDIGITS] = ":";
            int loc = 1;
 
-            if (IsChanOp(member))
-             tbuf[loc++] = 'o';
-            if (HasVoice(member))
+           if (HasVoice(member))       /* flag_cnt == 1 or 3 */
              tbuf[loc++] = 'v';
+           if (IsChanOp(member))       /* flag_cnt == 2 or 3 */
+           {
+             /* append the absolute value of the oplevel */
+             loc += ircd_snprintf(0, tbuf + loc, sizeof(tbuf) - loc, "%u", member->oplevel);
+             last_oplevel = member->oplevel;
+           }
            tbuf[loc] = '\0';
            msgq_append(&me, mb, tbuf);
-          }
-        }
+           new_mode = 0;
+         }
+         else if (flag_cnt > 1 && last_oplevel != member->oplevel)
+         {
+           /*
+            * This can't be the first member of a (continued) BURST
+            * message because then either flag_cnt == 0 or new_mode == 1
+            * Now we need to append the incremental value of the oplevel.
+            */
+            char tbuf[2 + MAXOPLEVELDIGITS];
+           ircd_snprintf(0, tbuf, sizeof(tbuf), ":%u", member->oplevel - last_oplevel);
+           last_oplevel = member->oplevel;
+           msgq_append(&me, mb, tbuf);
+         }
+       }
+       /* Go to the next `member'. */
+       if (flag_cnt < 2)
+         member = member->next_member;
+       else
+         member = opped_members[++opped_members_index];
       }
       if (full)
-        break;
-    }
+       break;
+
+      /* Point `member' at the start of the list again. */
+      if (flag_cnt == 0)
+      {
+       member = chptr->members;
+       /* Now, after one loop, we know the number of ops and can
+        * allocate the dynamic array with pointer to the ops. */
+       opped_members = (struct Membership**)
+         MyMalloc((number_of_ops + 1) * sizeof(struct Membership*));
+       opped_members[number_of_ops] = NULL;    /* Needed for loop termination */
+      }
+      else
+      {
+       /* At the end of the second loop, sort the opped members with
+        * increasing op-level, so that we will output them in the
+        * correct order (and all op-level increments stay positive) */
+       if (flag_cnt == 1)
+         qsort(opped_members, number_of_ops,
+               sizeof(struct Membership*), compare_member_oplevel);
+       /* The third and fourth loop run only over the opped members. */
+       member = opped_members[(opped_members_index = 0)];
+      }
+
+    } /* loop over 0,+v,+o,+ov */
 
     if (!full)
     {
@@ -972,6 +987,8 @@ void send_channel_modes(struct Client *cptr, struct Channel *chptr)
     msgq_clean(mb);
   }                             /* Continue when there was something
                                  that didn't fit (full==1) */
+  if (opped_members)
+    MyFree(opped_members);
 }
 
 /*
@@ -996,6 +1013,7 @@ void send_channel_modes(struct Client *cptr, struct Channel *chptr)
 char *pretty_mask(char *mask)
 {
   static char star[2] = { '*', 0 };
+  static char retmask[NUH_BUFSIZE];
   char *last_dot = NULL;
   char *ptr;
 
@@ -1068,7 +1086,7 @@ char *pretty_mask(char *mask)
     host = ptr - HOSTLEN;
     *host = '*';
   }
-  return make_nick_user_host(nick, user, host);
+  return make_nick_user_host(retmask, nick, user, host);
 }
 
 static void send_ban_list(struct Client* cptr, struct Channel* chptr)
@@ -1131,25 +1149,25 @@ int can_join(struct Client *sptr, struct Channel *chptr, char *key)
    * Now a user CAN escape anything if invited -- Isomer
    */
 
-  for (lp = sptr->user->invited; lp; lp = lp->next)
+  for (lp = (cli_user(sptr))->invited; lp; lp = lp->next)
     if (lp->value.chptr == chptr)
       return 0;
   
-#ifdef OPER_WALK_THROUGH_LMODES
   /* An oper can force a join on a local channel using "OVERRIDE" as the key. 
      a HACK(4) notice will be sent if he would not have been supposed
      to join normally. */ 
-  if (IsOperOnLocalChannel(sptr,chptr->chname) && !BadPtr(key) && compall("OVERRIDE",key) == 0)
-  {
+  if (IsLocalChannel(chptr->chname) && HasPriv(sptr, PRIV_WALK_LCHAN) &&
+      !BadPtr(key) && compall("OVERRIDE",key) == 0)
     overrideJoin = MAGIC_OPER_OVERRIDE;
-  }
-#endif
 
   if (chptr->mode.mode & MODE_INVITEONLY)
        return overrideJoin + ERR_INVITEONLYCHAN;
        
   if (chptr->mode.limit && chptr->users >= chptr->mode.limit)
        return overrideJoin + ERR_CHANNELISFULL;
+
+  if ((chptr->mode.mode & MODE_REGONLY) && !IsAccount(sptr))
+       return overrideJoin + ERR_NEEDREGGEDNICK;
        
   if (is_banned(sptr, chptr, NULL))
        return overrideJoin + ERR_BANNEDFROMCHAN;
@@ -1238,9 +1256,9 @@ void add_invite(struct Client *cptr, struct Channel *chptr)
   /*
    * Delete last link in chain if the list is max length
    */
-  assert(list_length(cptr->user->invited) == cptr->user->invites);
-  if (cptr->user->invites>=MAXCHANNELSPERUSER)
-    del_invite(cptr, cptr->user->invited->value.chptr);
+  assert(list_length((cli_user(cptr))->invited) == (cli_user(cptr))->invites);
+  if ((cli_user(cptr))->invites >= feature_int(FEAT_MAXCHANNELSPERUSER))
+    del_invite(cptr, (cli_user(cptr))->invited->value.chptr);
   /*
    * Add client to channel invite list
    */
@@ -1251,12 +1269,12 @@ void add_invite(struct Client *cptr, struct Channel *chptr)
   /*
    * Add channel to the end of the client invite list
    */
-  for (tmp = &(cptr->user->invited); *tmp; tmp = &((*tmp)->next));
+  for (tmp = &((cli_user(cptr))->invited); *tmp; tmp = &((*tmp)->next));
   inv = make_link();
   inv->value.chptr = chptr;
   inv->next = NULL;
   (*tmp) = inv;
-  cptr->user->invites++;
+  (cli_user(cptr))->invites++;
 }
 
 /*
@@ -1272,11 +1290,11 @@ void del_invite(struct Client *cptr, struct Channel *chptr)
       *inv = tmp->next;
       free_link(tmp);
       tmp = 0;
-      cptr->user->invites--;
+      (cli_user(cptr))->invites--;
       break;
     }
 
-  for (inv = &(cptr->user->invited); (tmp = *inv); inv = &tmp->next)
+  for (inv = &((cli_user(cptr))->invited); (tmp = *inv); inv = &tmp->next)
     if (tmp->value.chptr == chptr)
     {
       *inv = tmp->next;
@@ -1289,14 +1307,14 @@ void del_invite(struct Client *cptr, struct Channel *chptr)
 /* List and skip all channels that are listen */
 void list_next_channels(struct Client *cptr, int nr)
 {
-  struct ListingArgs *args = cptr->listing;
+  struct ListingArgs *args = cli_listing(cptr);
   struct Channel *chptr = args->chptr;
   chptr->mode.mode &= ~MODE_LISTED;
   while (is_listed(chptr) || --nr >= 0)
   {
     for (; chptr; chptr = chptr->next)
     {
-      if (!cptr->user || (SecretChannel(chptr) && !find_channel_member(cptr, chptr)))
+      if (!cli_user(cptr) || (SecretChannel(chptr) && !find_channel_member(cptr, chptr)))
         continue;
       if (chptr->users > args->min_users && chptr->users < args->max_users &&
           chptr->creationtime > args->min_time &&
@@ -1314,17 +1332,19 @@ void list_next_channels(struct Client *cptr, int nr)
     }
     if (!chptr)
     {
-      MyFree(cptr->listing);
-      cptr->listing = NULL;
+      MyFree(cli_listing(cptr));
+      cli_listing(cptr) = NULL;
       send_reply(cptr, RPL_LISTEND);
       break;
     }
   }
   if (chptr)
   {
-    cptr->listing->chptr = chptr;
+    (cli_listing(cptr))->chptr = chptr;
     chptr->mode.mode |= MODE_LISTED;
   }
+
+  update_write(cptr);
 }
 
 /*
@@ -1391,11 +1411,11 @@ void make_zombie(struct Membership* member, struct Client* who, struct Client* c
     remove_user_from_channel(who, chptr);
     return;
   }
-  if (who->from == cptr)        /* True on servers 1, 5 and 6 */
+  if (cli_from(who) == cptr)        /* True on servers 1, 5 and 6 */
   {
-    struct Client *acptr = IsServer(sptr) ? sptr : sptr->user->server;
-    for (; acptr != &me; acptr = acptr->serv->up)
-      if (acptr == who->user->server)   /* Case d) (server 5) */
+    struct Client *acptr = IsServer(sptr) ? sptr : (cli_user(sptr))->server;
+    for (; acptr != &me; acptr = (cli_serv(acptr))->up)
+      if (acptr == (cli_user(who))->server)   /* Case d) (server 5) */
       {
         remove_user_from_channel(who, chptr);
         return;
@@ -1463,9 +1483,12 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
     MODE_TOPICLIMIT,   't',
     MODE_INVITEONLY,   'i',
     MODE_NOPRIVMSGS,   'n',
+    MODE_REGONLY,      'r',
 /*  MODE_KEY,          'k', */
 /*  MODE_BAN,          'b', */
 /*  MODE_LIMIT,                'l', */
+/*  MODE_APASS,                'A', */
+/*  MODE_UPASS,                'u', */
     0x0, 0x0
   };
   int i;
@@ -1502,7 +1525,7 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
 
   /* Ok, if we were given the OPMODE flag, hide the source if its a user */
   if (mbuf->mb_dest & MODEBUF_DEST_OPMODE && !IsServer(mbuf->mb_source))
-    app_source = mbuf->mb_source->user->server;
+    app_source = &me;
   else
     app_source = mbuf->mb_source;
 
@@ -1532,7 +1555,7 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
     }
 
     if (MB_TYPE(mbuf, i) & (MODE_CHANOP | MODE_VOICE)) {
-      tmp = strlen(MB_CLIENT(mbuf, i)->name);
+      tmp = strlen(cli_name(MB_CLIENT(mbuf, i)));
 
       if ((totalbuflen - IRCD_MAX(5, tmp)) <= 0) /* don't overflow buffer */
        MB_TYPE(mbuf, i) |= MODE_SAVE; /* save for later */
@@ -1540,18 +1563,34 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
        bufptr[(*bufptr_i)++] = MB_TYPE(mbuf, i) & MODE_CHANOP ? 'o' : 'v';
        totalbuflen -= IRCD_MAX(5, tmp) + 1;
       }
-    } else if (MB_TYPE(mbuf, i) & (MODE_KEY | MODE_BAN)) {
+    } else if (MB_TYPE(mbuf, i) & (MODE_KEY | MODE_BAN | MODE_APASS | MODE_UPASS)) {
       tmp = strlen(MB_STRING(mbuf, i));
 
       if ((totalbuflen - tmp) <= 0) /* don't overflow buffer */
        MB_TYPE(mbuf, i) |= MODE_SAVE; /* save for later */
       else {
-       bufptr[(*bufptr_i)++] = MB_TYPE(mbuf, i) & MODE_KEY ? 'k' : 'b';
+       char mode_char;
+       switch(MB_TYPE(mbuf, i) & (MODE_KEY | MODE_BAN | MODE_APASS | MODE_UPASS))
+       {
+         case MODE_APASS:
+           mode_char = 'A';
+           break;
+         case MODE_UPASS:
+           mode_char = 'u';
+           break;
+         case MODE_KEY:
+           mode_char = 'k';
+           break;
+         default:
+           mode_char = 'b';
+           break;
+       }
+       bufptr[(*bufptr_i)++] = mode_char;
        totalbuflen -= tmp + 1;
       }
     } else if (MB_TYPE(mbuf, i) & MODE_LIMIT) {
       /* if it's a limit, we also format the number */
-      sprintf_irc(limitbuf, "%d", MB_UINT(mbuf, i));
+      ircd_snprintf(0, limitbuf, sizeof(limitbuf), "%u", MB_UINT(mbuf, i));
 
       tmp = strlen(limitbuf);
 
@@ -1592,12 +1631,16 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
 
       /* deal with clients... */
       if (MB_TYPE(mbuf, i) & (MODE_CHANOP | MODE_VOICE))
-       build_string(strptr, strptr_i, MB_CLIENT(mbuf, i)->name, 0, ' ');
+       build_string(strptr, strptr_i, cli_name(MB_CLIENT(mbuf, i)), 0, ' ');
 
       /* deal with strings... */
       else if (MB_TYPE(mbuf, i) & (MODE_KEY | MODE_BAN))
        build_string(strptr, strptr_i, MB_STRING(mbuf, i), 0, ' ');
 
+      /* deal with invisible passwords */
+      else if (MB_TYPE(mbuf, i) & (MODE_APASS | MODE_UPASS))
+       build_string(strptr, strptr_i, "*", 0, ' ');
+
       /*
        * deal with limit; note we cannot include the limit parameter if we're
        * removing it
@@ -1608,30 +1651,40 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
     }
 
     /* send the messages off to their destination */
-    if (mbuf->mb_dest & MODEBUF_DEST_HACK2) {
+    if (mbuf->mb_dest & MODEBUF_DEST_HACK2)
       sendto_opmask_butone(0, SNO_HACK2, "HACK(2): %s MODE %s %s%s%s%s%s%s "
-                          "[%Tu]", app_source->name, mbuf->mb_channel->chname,
+                          "[%Tu]",
+#ifdef HEAD_IN_SAND_SNOTICES
+                          cli_name(mbuf->mb_source),
+#else
+                          cli_name(app_source),
+#endif
+                          mbuf->mb_channel->chname,
                           rembuf_i ? "-" : "", rembuf, addbuf_i ? "+" : "",
                           addbuf, remstr, addstr,
                           mbuf->mb_channel->creationtime);
-      sendcmdto_serv_butone(&me, CMD_DESYNCH, mbuf->mb_connect,
-                           ":HACK: %s MODE %s %s%s%s%s%s%s [%Tu]",
-                           app_source->name, mbuf->mb_channel->chname,
-                           rembuf_i ? "-" : "", rembuf,
-                           addbuf_i ? "+" : "", addbuf, remstr, addstr,
-                           mbuf->mb_channel->creationtime);
-    }
 
     if (mbuf->mb_dest & MODEBUF_DEST_HACK3)
       sendto_opmask_butone(0, SNO_HACK3, "BOUNCE or HACK(3): %s MODE %s "
-                          "%s%s%s%s%s%s [%Tu]", app_source->name,
+                          "%s%s%s%s%s%s [%Tu]",
+#ifdef HEAD_IN_SAND_SNOTICES
+                          cli_name(mbuf->mb_source),
+#else
+                          cli_name(app_source),
+#endif
                           mbuf->mb_channel->chname, rembuf_i ? "-" : "",
                           rembuf, addbuf_i ? "+" : "", addbuf, remstr, addstr,
                           mbuf->mb_channel->creationtime);
 
     if (mbuf->mb_dest & MODEBUF_DEST_HACK4)
       sendto_opmask_butone(0, SNO_HACK4, "HACK(4): %s MODE %s %s%s%s%s%s%s "
-                          "[%Tu]", app_source->name, mbuf->mb_channel->chname,
+                          "[%Tu]",
+#ifdef HEAD_IN_SAND_SNOTICES
+                          cli_name(mbuf->mb_source),
+#else
+                          cli_name(app_source),
+#endif
+                          mbuf->mb_channel->chname,
                           rembuf_i ? "-" : "", rembuf, addbuf_i ? "+" : "",
                           addbuf, remstr, addstr,
                           mbuf->mb_channel->creationtime);
@@ -1643,7 +1696,7 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
                addbuf_i ? "+" : "", addbuf, remstr, addstr);
 
     if (mbuf->mb_dest & MODEBUF_DEST_CHANNEL)
-      sendcmdto_channel_butserv(app_source, CMD_MODE, mbuf->mb_channel,
+      sendcmdto_channel_butserv_butone(app_source, CMD_MODE, mbuf->mb_channel, NULL,
                                "%H %s%s%s%s%s%s", mbuf->mb_channel,
                                rembuf_i ? "-" : "", rembuf,
                                addbuf_i ? "+" : "", addbuf, remstr, addstr);
@@ -1680,7 +1733,7 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
        build_string(strptr, strptr_i, NumNick(MB_CLIENT(mbuf, i)), ' ');
 
       /* deal with modes that take strings */
-      else if (MB_TYPE(mbuf, i) & (MODE_KEY | MODE_BAN))
+      else if (MB_TYPE(mbuf, i) & (MODE_KEY | MODE_BAN | MODE_APASS | MODE_UPASS))
        build_string(strptr, strptr_i, MB_STRING(mbuf, i), 0, ' ');
 
       /*
@@ -1807,7 +1860,10 @@ modebuf_mode(struct ModeBuf *mbuf, unsigned int mode)
   assert(0 != (mode & (MODE_ADD | MODE_DEL)));
 
   mode &= (MODE_ADD | MODE_DEL | MODE_PRIVATE | MODE_SECRET | MODE_MODERATED |
-          MODE_TOPICLIMIT | MODE_INVITEONLY | MODE_NOPRIVMSGS);
+          MODE_TOPICLIMIT | MODE_INVITEONLY | MODE_NOPRIVMSGS | MODE_REGONLY);
+
+  if (!(mode & ~(MODE_ADD | MODE_DEL))) /* don't add empty modes... */
+    return;
 
   if (mode & MODE_ADD) {
     mbuf->mb_rem &= ~mode;
@@ -1905,14 +1961,18 @@ modebuf_extract(struct ModeBuf *mbuf, char *buf)
     MODE_INVITEONLY,   'i',
     MODE_NOPRIVMSGS,   'n',
     MODE_KEY,          'k',
+    MODE_APASS,                'A',
+    MODE_UPASS,                'u',
 /*  MODE_BAN,          'b', */
     MODE_LIMIT,                'l',
+    MODE_REGONLY,      'r',
     0x0, 0x0
   };
   unsigned int add;
   int i, bufpos = 0, len;
   int *flag_p;
   char *key = 0, limitbuf[20];
+  char *apass, *upass;
 
   assert(0 != mbuf);
   assert(0 != buf);
@@ -1923,12 +1983,16 @@ modebuf_extract(struct ModeBuf *mbuf, char *buf)
 
   for (i = 0; i < mbuf->mb_count; i++) { /* find keys and limits */
     if (MB_TYPE(mbuf, i) & MODE_ADD) {
-      add |= MB_TYPE(mbuf, i) & (MODE_KEY | MODE_LIMIT);
+      add |= MB_TYPE(mbuf, i) & (MODE_KEY | MODE_LIMIT | MODE_APASS | MODE_UPASS);
 
       if (MB_TYPE(mbuf, i) & MODE_KEY) /* keep strings */
        key = MB_STRING(mbuf, i);
       else if (MB_TYPE(mbuf, i) & MODE_LIMIT)
-       ircd_snprintf(0, limitbuf, sizeof(limitbuf), "%d", MB_UINT(mbuf, i));
+       ircd_snprintf(0, limitbuf, sizeof(limitbuf), "%u", MB_UINT(mbuf, i));
+      else if (MB_TYPE(mbuf, i) & MODE_UPASS)
+       upass = MB_STRING(mbuf, i);
+      else if (MB_TYPE(mbuf, i) & MODE_APASS)
+       apass = MB_STRING(mbuf, i);
     }
   }
 
@@ -1946,6 +2010,10 @@ modebuf_extract(struct ModeBuf *mbuf, char *buf)
       build_string(buf, &bufpos, key, 0, ' ');
     else if (buf[i] == 'l')
       build_string(buf, &bufpos, limitbuf, 0, ' ');
+    else if (buf[i] == 'u')
+      build_string(buf, &bufpos, upass, 0, ' ');
+    else if (buf[i] == 'A')
+      build_string(buf, &bufpos, apass, 0, ' ');
   }
 
   buf[bufpos] = '\0';
@@ -1981,12 +2049,15 @@ mode_invite_clear(struct Channel *chan)
 #define DONE_BANLIST   0x04    /* We've sent the ban list */
 #define DONE_NOTOPER   0x08    /* We've sent a "Not oper" error */
 #define DONE_BANCLEAN  0x10    /* We've cleaned bans... */
+#define DONE_UPASS     0x20    /* We've set user pass */
+#define DONE_APASS     0x40    /* We've set admin pass */
 
 struct ParseState {
   struct ModeBuf *mbuf;
   struct Client *cptr;
   struct Client *sptr;
   struct Channel *chptr;
+  struct Membership *member;
   int parc;
   char **parv;
   unsigned int flags;
@@ -2038,7 +2109,7 @@ mode_parse_limit(struct ParseState *state, int *flag_p)
       return;
     }
 
-    t_limit = atoi(state->parv[state->args_used++]); /* grab arg */
+    t_limit = strtoul(state->parv[state->args_used++], 0, 10); /* grab arg */
     state->parc--;
     state->max_args--;
 
@@ -2155,6 +2226,168 @@ mode_parse_key(struct ParseState *state, int *flag_p)
   }
 }
 
+/*
+ * Helper function to convert user passes
+ */
+static void
+mode_parse_upass(struct ParseState *state, int *flag_p)
+{
+  char *t_str, *s;
+  int t_len;
+
+  if (MyUser(state->sptr) && state->max_args <= 0) /* drop if too many args */
+    return;
+
+  if (state->parc <= 0) { /* warn if not enough args */
+    if (MyUser(state->sptr))
+      need_more_params(state->sptr, state->dir == MODE_ADD ? "MODE +u" :
+                      "MODE -u");
+    return;
+  }
+
+  t_str = state->parv[state->args_used++]; /* grab arg */
+  state->parc--;
+  state->max_args--;
+
+  /* If they're not an oper, they can't change modes */
+  if (state->flags & (MODE_PARSE_NOTOPER | MODE_PARSE_NOTMEMBER)) {
+    send_notoper(state);
+    return;
+  }
+
+  if (state->done & DONE_UPASS) /* allow upass to be set only once */
+    return;
+  state->done |= DONE_UPASS;
+
+  t_len = PASSLEN + 1;
+
+  /* clean up the upass string */
+  s = t_str;
+  while (*++s > ' ' && *s != ':' && --t_len)
+    ;
+  *s = '\0';
+
+  if (!*t_str) { /* warn if empty */
+    if (MyUser(state->sptr))
+      need_more_params(state->sptr, state->dir == MODE_ADD ? "MODE +u" :
+                      "MODE -u");
+    return;
+  }
+
+  if (!state->mbuf)
+    return;
+
+  /* can't add a upass if one is set, nor can one remove the wrong upass */
+  if (!(state->flags & MODE_PARSE_FORCE))
+    if ((state->dir == MODE_ADD && *state->chptr->mode.upass) ||
+       (state->dir == MODE_DEL &&
+        ircd_strcmp(state->chptr->mode.upass, t_str))) {
+      send_reply(state->sptr, ERR_KEYSET, state->chptr->chname);
+      return;
+    }
+
+  if (!(state->flags & MODE_PARSE_WIPEOUT) && state->dir == MODE_ADD &&
+      !ircd_strcmp(state->chptr->mode.upass, t_str))
+    return; /* no upass change */
+
+  if (state->flags & MODE_PARSE_BOUNCE) {
+    if (*state->chptr->mode.upass) /* reset old upass */
+      modebuf_mode_string(state->mbuf, MODE_DEL | flag_p[0],
+                         state->chptr->mode.upass, 0);
+    else /* remove new bogus upass */
+      modebuf_mode_string(state->mbuf, MODE_ADD | flag_p[0], t_str, 0);
+  } else /* send new upass */
+    modebuf_mode_string(state->mbuf, state->dir | flag_p[0], t_str, 0);
+
+  if (state->flags & MODE_PARSE_SET) {
+    if (state->dir == MODE_ADD) /* set the new upass */
+      ircd_strncpy(state->chptr->mode.upass, t_str, PASSLEN);
+    else /* remove the old upass */
+      *state->chptr->mode.upass = '\0';
+  }
+}
+
+/*
+ * Helper function to convert admin passes
+ */
+static void
+mode_parse_apass(struct ParseState *state, int *flag_p)
+{
+  char *t_str, *s;
+  int t_len;
+
+  if (MyUser(state->sptr) && state->max_args <= 0) /* drop if too many args */
+    return;
+
+  if (state->parc <= 0) { /* warn if not enough args */
+    if (MyUser(state->sptr))
+      need_more_params(state->sptr, state->dir == MODE_ADD ? "MODE +A" :
+                      "MODE -A");
+    return;
+  }
+
+  t_str = state->parv[state->args_used++]; /* grab arg */
+  state->parc--;
+  state->max_args--;
+
+  /* If they're not an oper, they can't change modes */
+  if (state->flags & (MODE_PARSE_NOTOPER | MODE_PARSE_NOTMEMBER)) {
+    send_notoper(state);
+    return;
+  }
+
+  if (state->done & DONE_APASS) /* allow apass to be set only once */
+    return;
+  state->done |= DONE_APASS;
+
+  t_len = PASSLEN + 1;
+
+  /* clean up the apass string */
+  s = t_str;
+  while (*++s > ' ' && *s != ':' && --t_len)
+    ;
+  *s = '\0';
+
+  if (!*t_str) { /* warn if empty */
+    if (MyUser(state->sptr))
+      need_more_params(state->sptr, state->dir == MODE_ADD ? "MODE +A" :
+                      "MODE -A");
+    return;
+  }
+
+  if (!state->mbuf)
+    return;
+
+  /* can't add a apass if one is set, nor can one remove the wrong apass */
+  if (!(state->flags & MODE_PARSE_FORCE))
+    if ((state->dir == MODE_ADD && *state->chptr->mode.apass) ||
+       (state->dir == MODE_DEL &&
+        ircd_strcmp(state->chptr->mode.apass, t_str))) {
+      send_reply(state->sptr, ERR_KEYSET, state->chptr->chname);
+      return;
+    }
+
+  if (!(state->flags & MODE_PARSE_WIPEOUT) && state->dir == MODE_ADD &&
+      !ircd_strcmp(state->chptr->mode.apass, t_str))
+    return; /* no apass change */
+
+  if (state->flags & MODE_PARSE_BOUNCE) {
+    if (*state->chptr->mode.apass) /* reset old apass */
+      modebuf_mode_string(state->mbuf, MODE_DEL | flag_p[0],
+                         state->chptr->mode.apass, 0);
+    else /* remove new bogus apass */
+      modebuf_mode_string(state->mbuf, MODE_ADD | flag_p[0], t_str, 0);
+  } else /* send new apass */
+    modebuf_mode_string(state->mbuf, state->dir | flag_p[0], t_str, 0);
+
+  if (state->flags & MODE_PARSE_SET) {
+    if (state->dir == MODE_ADD) /* set the new apass */
+      ircd_strncpy(state->chptr->mode.apass, t_str, PASSLEN);
+    else /* remove the old apass */
+      *state->chptr->mode.apass = '\0';
+  }
+}
+
 /*
  * Helper function to convert bans
  */
@@ -2204,7 +2437,7 @@ mode_parse_ban(struct ParseState *state, int *flag_p)
     newban->next = 0;
 
     DupString(newban->value.ban.banstr, t_str);
-    newban->value.ban.who = state->sptr->name;
+    newban->value.ban.who = cli_name(state->sptr);
     newban->value.ban.when = TStime();
 
     newban->flags = CHFL_BAN | MODE_ADD;
@@ -2251,16 +2484,18 @@ mode_parse_ban(struct ParseState *state, int *flag_p)
     } else if (state->dir == MODE_ADD) {
       /* if the ban already exists, don't worry about it */
       if (!ircd_strcmp(ban->value.ban.banstr, t_str)) {
+       newban->flags &= ~MODE_ADD; /* don't add ban at all */
+       MyFree(newban->value.ban.banstr); /* stopper a leak */
+       state->numbans--; /* deallocate last ban */
        if (state->done & DONE_BANCLEAN) /* If we're cleaning, finish */
          break;
-       continue;
       } else if (!mmatch(ban->value.ban.banstr, t_str)) {
        if (!(ban->flags & MODE_DEL))
          newban->flags |= CHFL_BAN_OVERLAPPED; /* our ban overlaps */
       } else if (!mmatch(t_str, ban->value.ban.banstr))
        ban->flags |= MODE_DEL; /* mark ban for deletion: overlapping */
 
-      if (!ban->next) {
+      if (!ban->next && (newban->flags & MODE_ADD)) {
        ban->next = newban; /* add our ban with its flags */
        break; /* get out of loop */
       }
@@ -2335,7 +2570,8 @@ mode_process_bans(struct ParseState *state)
        MyFree(ban->value.ban.banstr);
       } else {
        if (state->flags & MODE_PARSE_SET && MyUser(state->sptr) &&
-           (len > MAXBANLENGTH || count >= MAXBANS)) {
+           (len > (feature_int(FEAT_AVBANLEN) * feature_int(FEAT_MAXBANS)) ||
+            count >= feature_int(FEAT_MAXBANS))) {
          send_reply(state->sptr, ERR_BANLISTFULL, state->chptr->chname,
                     ban->value.ban.banstr);
          count--;
@@ -2433,7 +2669,8 @@ mode_process_clients(struct ParseState *state)
        (MyUser(state->sptr) && IsZombie(member))) {
       if (MyUser(state->sptr))
        send_reply(state->sptr, ERR_USERNOTINCHANNEL,
-                  state->cli_change[i].client->name, state->chptr->chname);
+                  cli_name(state->cli_change[i].client),
+                  state->chptr->chname);
       continue;
     }
 
@@ -2451,26 +2688,50 @@ mode_process_clients(struct ParseState *state)
        if (state->flags & MODE_PARSE_FORCE) /* it was forced */
          sendto_opmask_butone(0, SNO_HACK4, "Deop of +k user on %H by %s",
                               state->chptr,
-                              (IsServer(state->sptr) ? state->sptr->name :
-                               state->sptr->user->server->name));
+                              (IsServer(state->sptr) ? cli_name(state->sptr) :
+                               cli_name((cli_user(state->sptr))->server)));
 
        else if (MyUser(state->sptr) && state->flags & MODE_PARSE_SET) {
          send_reply(state->sptr, ERR_ISCHANSERVICE,
-                    state->cli_change[i].client->name, state->chptr->chname);
+                    cli_name(state->cli_change[i].client),
+                    state->chptr->chname);
          continue;
        }
       }
 
-#ifdef NO_OPER_DEOP_LCHAN
-      /* don't allow local opers to be deopped on local channels */
-      if (MyUser(state->sptr) && state->cli_change[i].client != state->sptr &&
-         IsOperOnLocalChannel(state->cli_change[i].client,
-                              state->chptr->chname)) {
-       send_reply(state->sptr, ERR_ISOPERLCHAN,
-                  state->cli_change[i].client->name, state->chptr->chname);
-       continue;
+      /* check deop for local user */
+      if (MyUser(state->sptr)) {
+
+       /* don't allow local opers to be deopped on local channels */
+       if (state->cli_change[i].client != state->sptr &&
+           IsLocalChannel(state->chptr->chname) &&
+           HasPriv(state->cli_change[i].client, PRIV_DEOP_LCHAN)) {
+         send_reply(state->sptr, ERR_ISOPERLCHAN,
+                    cli_name(state->cli_change[i].client),
+                    state->chptr->chname);
+         continue;
+        }
+
+       /* don't allow to deop members with an op level that is <= our own level */
+       if (state->sptr != state->cli_change[i].client          /* but allow to deop oneself */
+               && state->member
+               && OpLevel(member) <= OpLevel(state->member)) {
+           int equal = (OpLevel(member) == OpLevel(state->member));
+           send_reply(state->sptr, ERR_NOTLOWEROPLEVEL,
+                      cli_name(state->cli_change[i].client),
+                      state->chptr->chname,
+                      OpLevel(state->member), OpLevel(member),
+                      "deop", equal ? "the same" : "a higher");
+         continue;
+       }
       }
-#endif
+    }
+
+    /* set op-level of member being opped */
+    if ((state->cli_change[i].flag & (MODE_ADD | MODE_CHANOP)) ==
+       (MODE_ADD | MODE_CHANOP)) {
+      int old_level = (state->member == NULL) ? -1 : OpLevel(state->member);
+      SetOpLevel(member, old_level == MAXOPLEVEL ? MAXOPLEVEL : (old_level + 1));
     }
 
     /* accumulate the change */
@@ -2488,7 +2749,7 @@ mode_process_clients(struct ParseState *state)
        member->status &= ~(state->cli_change[i].flag &
                            (MODE_CHANOP | MODE_VOICE));
     }
-  } /* for (i = 0; state->cli_change[i].flags; i++) */
+  } /* for (i = 0; state->cli_change[i].flags; i++) */
 }
 
 /*
@@ -2534,7 +2795,8 @@ mode_parse_mode(struct ParseState *state, int *flag_p)
  */
 int
 mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr,
-          struct Channel *chptr, int parc, char *parv[], unsigned int flags)
+          struct Channel *chptr, int parc, char *parv[], unsigned int flags,
+          struct Membership* member)
 {
   static int chan_flags[] = {
     MODE_CHANOP,       'o',
@@ -2546,8 +2808,11 @@ mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr,
     MODE_INVITEONLY,   'i',
     MODE_NOPRIVMSGS,   'n',
     MODE_KEY,          'k',
+    MODE_APASS,                'A',
+    MODE_UPASS,                'u',
     MODE_BAN,          'b',
     MODE_LIMIT,                'l',
+    MODE_REGONLY,      'r',
     MODE_ADD,          '+',
     MODE_DEL,          '-',
     0x0, 0x0
@@ -2568,6 +2833,7 @@ mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr,
   state.cptr = cptr;
   state.sptr = sptr;
   state.chptr = chptr;
+  state.member = member;
   state.parc = parc;
   state.parv = parv;
   state.flags = flags;
@@ -2618,6 +2884,14 @@ mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr,
        mode_parse_key(&state, flag_p);
        break;
 
+      case 'A': /* deal with Admin passes */
+       mode_parse_apass(&state, flag_p);
+       break;
+
+      case 'u': /* deal with user passes */
+       mode_parse_upass(&state, flag_p);
+       break;
+
       case 'b': /* deal with bans */
        mode_parse_ban(&state, flag_p);
        break;
@@ -2630,8 +2904,8 @@ mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr,
       default: /* deal with other modes */
        mode_parse_mode(&state, flag_p);
        break;
-      } /* switch (*modestr) */
-    } /* for (; *modestr; modestr++) */
+      } /* switch (*modestr) */
+    } /* for (; *modestr; modestr++) */
 
     if (state.flags & MODE_PARSE_BURST)
       break; /* don't interpret any more arguments */
@@ -2660,7 +2934,7 @@ mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr,
        break; /* break out of while loop */
       }
     }
-  } /* while (*modestr) */
+  } /* while (*modestr) */
 
   /*
    * the rest of the function finishes building resultant MODEs; if the
@@ -2697,6 +2971,12 @@ mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr,
     if (*state.chptr->mode.key && !(state.done & DONE_KEY))
       modebuf_mode_string(state.mbuf, MODE_DEL | MODE_KEY,
                          state.chptr->mode.key, 0);
+    if (*state.chptr->mode.upass && !(state.done & DONE_UPASS))
+      modebuf_mode_string(state.mbuf, MODE_DEL | MODE_UPASS,
+                         state.chptr->mode.upass, 0);
+    if (*state.chptr->mode.apass && !(state.done & DONE_APASS))
+      modebuf_mode_string(state.mbuf, MODE_DEL | MODE_APASS,
+                         state.chptr->mode.apass, 0);
   }
 
   if (state.done & DONE_BANCLEAN) /* process bans */
@@ -2760,7 +3040,7 @@ joinbuf_join(struct JoinBuf *jbuf, struct Channel *chan, unsigned int flags)
       jbuf->jb_type == JOINBUF_TYPE_PARTALL) {
     /* Send notification to channel */
     if (!(flags & CHFL_ZOMBIE))
-      sendcmdto_channel_butserv(jbuf->jb_source, CMD_PART, chan,
+      sendcmdto_channel_butserv_butone(jbuf->jb_source, CMD_PART, chan, NULL,
                                (flags & CHFL_BANNED || !jbuf->jb_comment) ?
                                ":%H" : "%H :%s", chan, jbuf->jb_comment);
     else if (MyUser(jbuf->jb_source))
@@ -2779,7 +3059,7 @@ joinbuf_join(struct JoinBuf *jbuf, struct Channel *chan, unsigned int flags)
       remove_user_from_channel(jbuf->jb_source, chan);
   } else {
     /* Add user to channel */
-    add_user_to_channel(chan, jbuf->jb_source, flags);
+    add_user_to_channel(chan, jbuf->jb_source, flags, 0);
 
     /* send notification to all servers */
     if (jbuf->jb_type != JOINBUF_TYPE_CREATE && !IsLocalChannel(chan->chname))
@@ -2787,12 +3067,12 @@ joinbuf_join(struct JoinBuf *jbuf, struct Channel *chan, unsigned int flags)
                            "%H %Tu", chan, chan->creationtime);
 
     /* Send the notification to the channel */
-    sendcmdto_channel_butserv(jbuf->jb_source, CMD_JOIN, chan, ":%H", chan);
+    sendcmdto_channel_butserv_butone(jbuf->jb_source, CMD_JOIN, chan, NULL, ":%H", chan);
 
     /* send an op, too, if needed */
     if (!MyUser(jbuf->jb_source) && jbuf->jb_type == JOINBUF_TYPE_CREATE &&
        !IsModelessChannel(chan->chname))
-      sendcmdto_channel_butserv(jbuf->jb_source, CMD_MODE, chan, "%H +o %C",
+      sendcmdto_channel_butserv_butone(jbuf->jb_source, CMD_MODE, chan, NULL, "%H +o %C",
                                chan, jbuf->jb_source);
   }