Author: Carlo Wood (run@alinoe.com> (Via Isomer>
authorPerry Lorier <isomer@undernet.org>
Wed, 13 Mar 2002 09:19:21 +0000 (09:19 +0000)
committerPerry Lorier <isomer@undernet.org>
Wed, 13 Mar 2002 09:19:21 +0000 (09:19 +0000)
Log message:

This patch finishes everything described on
http://www.xs4all.nl/~carlo17/irc/cpass.html
Todo:
- deal with brute force (dictionary) attacks on apass.
- backwards compatibility (two phase upgrade).
- education issues (add URLs in messages).

git-svn-id: file:///home/klmitch/undernet-ircu/undernet-ircu-svn/ircu2/trunk@673 c9e4aea6-c8fd-4c43-8297-357d70d61c8c

ChangeLog
include/channel.h
include/numeric.h
ircd/channel.c
ircd/destruct_event.c
ircd/m_destruct.c
ircd/m_join.c
ircd/m_kick.c
ircd/m_mode.c
ircd/s_err.c

index d950668f17396d73810eb7cd5ea1b1bcb5cdd2c6..d2c7544c5c21857769caff0ea8f09781d08be4ee 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,48 @@
+2002-03-13  Carlo Wood  <run@alinoe.com>
+
+       * include/channel.h: CHFL_CHANNEL_MANAGER, new local
+       channel flag set when someone creates a channel or joins
+       using the Apass.  IsChannelManager(), SetChannelManager():
+       macros to manipulate new channel flag.
+       channel_modes: Added new argument to avoid calling
+       find_member_link more often than needed.
+
+       * include/numeric.h: RPL_APASSWARN, ERR_NOTLOWEROPLEVEL,
+       ERR_NOTMANAGER, ERR_CHANSECURED, ERR_UPASSSET,
+       ERR_UPASSNOTSET: new numeric replies.
+
+       * ircd/channel.c: is_level0_op: removed.
+       member_can_send_to_channel: disallow channel manager
+       to talk.  channel_modes: show upass to level0 ops.
+       mode_parse_upass: Only the channel manager is allowed
+       to change the upass.  Only allow to set upass when apass
+       is also set.  mode_parse_apass: Don't allow to change the
+       Apass if the channel is older than 48 hours.  Only allow
+       to remove the apass when upass is not set.  Send clear
+       warnings regarding the importance of apass.
+       mode_process_clients: Don't change the oplevel of an opped
+       member in a channel where upass is not set.
+
+       * ircd/destruct_event.c: exec_expired_destruct_events:
+       Bug fix: send DESTRUCT message when destructing a channel.
+
+       * ircd/m_destruct.c: ms_destruct: Bug fix: use self as
+       prefix for DESTRUCT message.
+
+       * ircd/m_join.c: m_join: Handle apass and upass.
+
+       * ircd/m_kick.c: m_kick: Don't allow to kick member with
+       a higher or equal op-level.
+
+       * ircd/m_mode.c: m_mode: Now pass member to channel_modes.
+       ms_mode: Allow server to do modes on channels with apass
+       set.
+
+       * ircd/s_err.c: RPL_APASSWARN, ERR_NOTLOWEROPLEVEL,
+       ERR_NOTMANAGER, ERR_CHANSECURED, ERR_UPASSSET,
+       ERR_UPASSNOTSET: new numeric replies.
+
+
 2002-03-10 Joseph Bongaarts <foxxe@wtfs.net>
 
        * ircd/m_kill.c: Last of the bug fixes for do_kill()
index 131e11d84dc5c0b52f302ebdb1af2369c8ac2c87..861a0108ceaa60381641217089048ddfa5122930 100644 (file)
@@ -69,6 +69,7 @@ struct Client;
 #define CHFL_SILENCE_IPMASK     0x2000  /* silence mask is an IP-number mask */
 #define CHFL_BURST_ALREADY_OPPED       0x04000  /* In oob BURST, but was already joined and opped */
 #define CHFL_BURST_ALREADY_VOICED      0x08000  /* In oob BURST, but was already joined and voiced */
+#define CHFL_CHANNEL_MANAGER   0x10000 /* Set when creating channel or using Apass */
 
 #define CHFL_OVERLAP         (CHFL_CHANOP | CHFL_VOICE)
 #define CHFL_BANVALIDMASK    (CHFL_BANVALID | CHFL_BANNED)
@@ -186,6 +187,7 @@ struct Membership {
 #define IsServOpOk(x)       ((x)->status & CHFL_SERVOPOK)
 #define IsBurstJoined(x)    ((x)->status & CHFL_BURST_JOINED)
 #define IsVoicedOrOpped(x)  ((x)->status & CHFL_VOICED_OR_OPPED)
+#define IsChannelManager(x) ((x)->status & CHFL_CHANNEL_MANAGER)
 
 #define SetBanned(x)        ((x)->status |= CHFL_BANNED)
 #define SetBanValid(x)      ((x)->status |= CHFL_BANVALID)
@@ -193,6 +195,7 @@ struct Membership {
 #define SetServOpOk(x)      ((x)->status |= CHFL_SERVOPOK)
 #define SetBurstJoined(x)   ((x)->status |= CHFL_BURST_JOINED)
 #define SetZombie(x)        ((x)->status |= CHFL_ZOMBIE)
+#define SetChannelManager(x) ((x)->status |= CHFL_CHANNEL_MANAGER)
 #define SetOpLevel(x, v)    (void)((x)->oplevel = (v))
 
 #define ClearBanned(x)      ((x)->status &= ~CHFL_BANNED)
@@ -299,7 +302,8 @@ extern int             LocalChanOperMode;
  */
 extern void clean_channelname(char* name);
 extern void channel_modes(struct Client *cptr, char *mbuf, char *pbuf,
-                          int buflen, struct Channel *chptr);
+                          int buflen, struct Channel *chptr,
+                         struct Membership *member);
 extern int set_mode(struct Client* cptr, struct Client* sptr,
                     struct Channel* chptr, int parc, char* parv[],
                     char* mbuf, char* pbuf, char* npbuf, int* badop);
index 40343af5ae92ace8a57e3adba084ad064ca34e85..f4c2f484024402485d1895a09817e6a8b9077579 100644 (file)
@@ -63,6 +63,7 @@ extern const struct Numeric* get_error_numeric(int err);
 #define RPL_MAP               15        /* Undernet extension */
 #define RPL_MAPMORE           16        /* Undernet extension */
 #define RPL_MAPEND            17        /* Undernet extension */
+#define RPL_APASSWARN         30       /* Undernet extension */
 /*     RPL_YOURID            42        IRCnet extension */
 /*      RPL_ATTEMPTINGJUNC    50           aircd extension */
 /*      RPL_ATTEMPTINGREROUTE 51           aircd extension */
@@ -444,8 +445,12 @@ extern const struct Numeric* get_error_numeric(int err);
        ERR_WHOSYNTAX        522        dalnet
        ERR_WHOLIMEXCEED     523        dalnet */
 
-#define ERR_NOTLOWEROPLEVEL  550
-#define ERR_LASTERROR        551
+#define ERR_NOTLOWEROPLEVEL  550       /* Undernet extension */
+#define ERR_NOTMANAGER       551       /* Undernet extension */
+#define ERR_CHANSECURED      552       /* Undernet extension */
+#define ERR_UPASSSET         553       /* Undernet extension */
+#define ERR_UPASSNOTSET      554       /* Undernet extension */
+#define ERR_LASTERROR        555
 
 /*     RPL_LOGON            600        dalnet,unreal
        RPL_LOGOFF           601        dalnet,unreal
index 1c3d491549ff26c32f8a589c74ed680a7cc9be8b..a3a761706c77cd7da76cd0f63d6138d39427b77a 100644 (file)
@@ -621,11 +621,6 @@ int is_chan_op(struct Client *cptr, struct Channel *chptr)
   return 0;
 }
 
-int is_level0_op(struct Client *cptr, struct Channel *chptr)
-{
-  return 0;
-}
-
 int is_zombie(struct Client *cptr, struct Channel *chptr)
 {
   struct Membership* member;
@@ -652,6 +647,10 @@ int member_can_send_to_channel(struct Membership* member)
 {
   assert(0 != member);
 
+  /* Discourage using the Apass to get op.  They should use the upass. */
+  if (IsChannelManager(member) && *member->channel->mode.upass)
+    return 0;
+
   if (IsVoicedOrOpped(member))
     return 1;
   /*
@@ -721,7 +720,7 @@ const char* find_no_nickchange_channel(struct Client* cptr)
  * with the parameters in pbuf.
  */
 void channel_modes(struct Client *cptr, char *mbuf, char *pbuf, int buflen,
-                          struct Channel *chptr)
+                          struct Channel *chptr, struct Membership *member)
 {
   int previous_parameter = 0;
 
@@ -774,7 +773,7 @@ void channel_modes(struct Client *cptr, char *mbuf, char *pbuf, int buflen,
     *mbuf++ = 'u';
     if (previous_parameter)
       strcat(pbuf, " ");
-    if (is_level0_op(cptr, chptr) || IsServer(cptr)) {
+    if (IsServer(cptr) || (member && IsChanOp(member) && OpLevel(member) == 0)) {
       strcat(pbuf, chptr->mode.upass);
     } else
       strcat(pbuf, "*");
@@ -824,7 +823,7 @@ void send_channel_modes(struct Client *cptr, struct Channel *chptr)
   lp2 = chptr->banlist;
 
   *modebuf = *parabuf = '\0';
-  channel_modes(cptr, modebuf, parabuf, sizeof(parabuf), chptr);
+  channel_modes(cptr, modebuf, parabuf, sizeof(parabuf), chptr, 0);
 
   for (first = 1; full; first = 0)      /* Loop for multiple messages */
   {
@@ -2255,6 +2254,20 @@ mode_parse_upass(struct ParseState *state, int *flag_p)
     return;
   }
 
+  /* If they are not the channel manager, they are not allowed to change it */
+  if (MyUser(state->sptr) && !IsChannelManager(state->member)) {
+    if (*state->chptr->mode.apass) {
+      send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname,
+         "Use /JOIN", state->chptr->chname, "<AdminPass>.");
+    } else {
+      send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname,
+         "Re-create the channel.  The channel must be *empty* for",
+         TStime() - state->chptr->creationtime >= 171000 ? "48 contigious hours" : "a minute or two",
+         "before it can be recreated.");
+    }
+    return;
+  }
   if (state->done & DONE_UPASS) /* allow upass to be set only once */
     return;
   state->done |= DONE_UPASS;
@@ -2277,8 +2290,13 @@ mode_parse_upass(struct ParseState *state, int *flag_p)
   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))
+    /* can't add the upass while apass is not set */
+    if (state->dir == MODE_ADD && !*state->chptr->mode.apass) {
+      send_reply(state->sptr, ERR_UPASSNOTSET, state->chptr->chname, state->chptr->chname);
+      return;
+    }
+    /* can't add a upass if one is set, nor can one remove the wrong upass */
     if ((state->dir == MODE_ADD && *state->chptr->mode.upass) ||
        (state->dir == MODE_DEL &&
         ircd_strcmp(state->chptr->mode.upass, t_str))) {
@@ -2336,6 +2354,25 @@ mode_parse_apass(struct ParseState *state, int *flag_p)
     return;
   }
 
+  /* Don't allow to change the Apass if the channel is older than 48 hours. */
+  if (TStime() - state->chptr->creationtime >= 172800 && !IsAnOper(state->sptr)) {
+    send_reply(state->sptr, ERR_CHANSECURED, state->chptr->chname);
+    return;
+  }
+
+  /* If they are not the channel manager, they are not allowed to change it */
+  if (MyUser(state->sptr) && !IsChannelManager(state->member)) {
+    if (*state->chptr->mode.apass) {
+      send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname,
+         "Use /JOIN", state->chptr->chname, "<AdminPass>.");
+    } else {
+      send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname,
+         "Re-create the channel.  The channel must be *empty* for",
+         "at least a whole minute", "before it can be recreated.");
+    }
+    return;
+  }
   if (state->done & DONE_APASS) /* allow apass to be set only once */
     return;
   state->done |= DONE_APASS;
@@ -2358,14 +2395,19 @@ mode_parse_apass(struct ParseState *state, int *flag_p)
   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->flags & MODE_PARSE_FORCE)) {
+    /* can't remove the apass while upass is still set */
+    if (state->dir == MODE_DEL && *state->chptr->mode.upass) {
+      send_reply(state->sptr, ERR_UPASSSET, state->chptr->chname, state->chptr->chname);
+      return;
+    }
+    /* can't add an apass if one is set, nor can one remove the wrong apass */
     if ((state->dir == MODE_ADD && *state->chptr->mode.apass) ||
-       (state->dir == MODE_DEL &&
-        ircd_strcmp(state->chptr->mode.apass, t_str))) {
+       (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))
@@ -2381,10 +2423,30 @@ mode_parse_apass(struct ParseState *state, int *flag_p)
     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 */
+    if (state->dir == MODE_ADD) { /* set the new apass */
+      /* Make it VERY clear to the user that this is a one-time password */
       ircd_strncpy(state->chptr->mode.apass, t_str, PASSLEN);
-    else /* remove the old apass */
+      if (MyUser(state->sptr)) {
+       send_reply(state->sptr, RPL_APASSWARN,
+           "Channel Admin password (+A) set to '", state->chptr->mode.apass, "'. ",
+           "Are you SURE you want to use this as Admin password? ",
+           "You will NOT be able to change this password anymore once the channel is more than 48 hours old!");
+       send_reply(state->sptr, RPL_APASSWARN,
+           "Use \"/MODE ", state->chptr->chname, " -A ", state->chptr->mode.apass,
+           "\" to remove the password and then immediatly set a new one. "
+           "IMPORTANT: YOU CANNOT RECOVER THIS PASSWORD, EVER; "
+           "WRITE THE PASSWORD DOWN (don't store this rescue password on disk)! "
+           "Now set the channel user password (+u).");
+      }
+    } else { /* remove the old apass */
       *state->chptr->mode.apass = '\0';
+      if (MyUser(state->sptr))
+       send_reply(state->sptr, RPL_APASSWARN,
+           "WARNING: You removed the channel Admin password MODE (+A). ",
+           "If you would disconnect or leave the channel without setting a new password then you will ",
+           "not be able to set it again and lose ownership of this channel! ",
+           "SET A NEW PASSWORD NOW!", "");
+    }
   }
 }
 
@@ -2730,8 +2792,13 @@ mode_process_clients(struct ParseState *state)
     /* 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));
+      /* If on a channel with upass set, someone with level x gives ops to someone else,
+         then that person gets level x-1.  On other channels, where upass is not set,
+        the level stays the same. */
+      int level_increment = *state->chptr->mode.upass ? 1 : 0;
+      /* Someone being opped by a server gets op-level 0 */
+      int old_level = (state->member == NULL) ? -level_increment : OpLevel(state->member);
+      SetOpLevel(member, old_level == MAXOPLEVEL ? MAXOPLEVEL : (old_level + level_increment));
     }
 
     /* accumulate the change */
index a7412ff0e2e8dbeebd5341a69f91225d229b9622..799e6bdac7bbe2e748565fa8955856519cc6ad00 100644 (file)
@@ -25,6 +25,8 @@
 #include "ircd_alloc.h"
 #include "ircd.h"
 #include "ircd_events.h"
+#include "send.h"
+#include "msg.h"
 
 #include <assert.h>
 #include <stdlib.h>
@@ -126,6 +128,8 @@ void exec_expired_destruct_events(struct Event* ev)
     while (*list_bottom && TStime() >= (*list_bottom)->expires)
     {
       struct Channel* chptr = (*list_bottom)->chptr;
+      /* Send DESTRUCT message */
+      sendcmdto_serv_butone(&me, CMD_DESTRUCT, 0, "%s %Tu", chptr->chname, chptr->creationtime);
       remove_destruct_event(chptr);
       destruct_channel(chptr);
     }
index a81c6d8c677ecea0a6ddfceec5d8b54dac8895bc..4104af14bc7a6222b5cf3dbbf476f0a43389a705 100644 (file)
@@ -117,7 +117,7 @@ int ms_destruct(struct Client* cptr, struct Client* sptr, int parc, char* parv[]
   }
 
   /* Pass on DESTRUCT message and ALSO bounce it back! */
-  sendcmdto_serv_butone(sptr, CMD_DESTRUCT, 0, "%s %Tu", parv[1], chanTS);
+  sendcmdto_serv_butone(&me, CMD_DESTRUCT, 0, "%s %Tu", parv[1], chanTS);
 
   /* Remove the empty channel. */
   remove_destruct_event(chptr);
index 50ddfc761f39a59faf296639a2e9efdf5147a780..ef5c44adc142d8952a6b58fbb0671231ab12591f 100644 (file)
@@ -167,6 +167,7 @@ int m_join(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
   struct Channel *chptr;
   struct JoinBuf join;
   struct JoinBuf create;
+  struct ModeBuf mbuf;
   struct Gline *gline;
   unsigned int flags = 0;
   int i;
@@ -219,9 +220,24 @@ int m_join(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
     }
 
     if (chptr) {
+      int is_level0_op = 0;
+      if (!BadPtr(keys) && *chptr->mode.apass) {
+       /* Don't use compall for the apass, only a single key is allowed.
+          Test Apass first in case someone set Apass and upass equal. */
+       if (strcmp(chptr->mode.apass, keys) == 0) {
+         is_level0_op = 1;
+         flags &= ~CHFL_DEOPPED;
+         flags |= CHFL_CHANOP | CHFL_CHANNEL_MANAGER;
+       }
+       else if (*chptr->mode.upass && strcmp(chptr->mode.upass, keys) == 0) {
+         is_level0_op = 1;
+         flags &= ~CHFL_DEOPPED;
+         flags |= CHFL_CHANOP;
+       }
+      }
       if (check_target_limit(sptr, chptr, chptr->chname, 0))
        continue; /* exceeded target limit */
-      else if ((i = can_join(sptr, chptr, keys))) {
+      else if (!is_level0_op && (i = can_join(sptr, chptr, keys))) {
        if (i > MAGIC_OPER_OVERRIDE) { /* oper overrode mode */
          switch (i - MAGIC_OPER_OVERRIDE) {
          case ERR_CHANNELISFULL: /* figure out which mode */
@@ -251,6 +267,16 @@ int m_join(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
       } /* else if ((i = can_join(sptr, chptr, keys))) */
 
       joinbuf_join(&join, chptr, flags);
+      if (is_level0_op)
+      {
+       joinbuf_flush(&join);
+       modebuf_init(&mbuf, &me, cptr, chptr,
+           MODEBUF_DEST_CHANNEL |              /* Send mode to channel */
+           MODEBUF_DEST_SERVER);               /* And send it to the other servers */
+       modebuf_mode_client(&mbuf,
+           MODE_ADD | MODE_CHANOP, sptr);      /* Give ops to the level0 op */
+       modebuf_flush(&mbuf);
+      }
     } else if (!(chptr = get_channel(sptr, name, CGT_CREATE)))
       continue; /* couldn't get channel */
     else if (check_target_limit(sptr, chptr, chptr->chname, 1)) {
@@ -259,7 +285,7 @@ int m_join(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
       destruct_channel(chptr); /* created it... */
       continue;
     } else
-      joinbuf_join(&create, chptr, flags);
+      joinbuf_join(&create, chptr, flags | CHFL_CHANNEL_MANAGER);
 
     del_invite(sptr, chptr);
 
index 51c848e6e4aad693ddea4f635a4b1e3f5ca4c45e..4a7200590382fcb8cbd4d985a7cc32aa1be98470 100644 (file)
@@ -107,6 +107,7 @@ int m_kick(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
   struct Client *who;
   struct Channel *chptr;
   struct Membership *member = 0;
+  struct Membership* member2;
   char *name, *comment;
 
   cli_flags(sptr) &= ~FLAGS_TS8;
@@ -120,7 +121,8 @@ int m_kick(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
   if (!(chptr = get_channel(sptr, name, CGT_NO_CREATE)))
     return send_reply(sptr, ERR_NOSUCHCHANNEL, name);
 
-  if (!is_chan_op(sptr, chptr) || IsModelessChannel(name))
+  if (!(member2 = find_member_link(chptr, sptr)) || IsZombie(member2)
+      || !IsChanOp(member2) || IsModelessChannel(name))
     return send_reply(sptr, ERR_CHANOPRIVSNEEDED, name);
 
   if (!(who = find_chasing(sptr, parv[2], 0)))
@@ -138,6 +140,12 @@ int m_kick(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
   if (!(member = find_member_link(chptr, who)) || IsZombie(member))
     return send_reply(sptr, ERR_USERNOTINCHANNEL, cli_name(who), chptr->chname);
 
+  /* Don't allow to kick member with a higher or equal op-level */
+  if (OpLevel(member) <= OpLevel(member2))
+    return send_reply(sptr, ERR_NOTLOWEROPLEVEL, cli_name(who), chptr->chname,
+       OpLevel(member2), OpLevel(member), "kick",
+       OpLevel(member) == OpLevel(member2) ? "the same" : "a higher");
+
   /* We rely on ircd_snprintf to truncate the comment */
   comment = EmptyString(parv[parc - 1]) ? parv[0] : parv[parc - 1];
 
index fc77ac7da64c6e1dd22f1df6d7232c4d66afcbab..474f39b3f3f9fd1b8ae5840b49cb3c1d72263fbc 100644 (file)
@@ -118,19 +118,21 @@ m_mode(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
 
   cli_flags(sptr) &= ~FLAGS_TS8;
 
+  member = find_member_link(chptr, sptr);
+
   if (parc < 3) {
     char modebuf[MODEBUFLEN];
     char parabuf[MODEBUFLEN];
 
     *modebuf = *parabuf = '\0';
     modebuf[1] = '\0';
-    channel_modes(sptr, modebuf, parabuf, sizeof(parabuf), chptr);
+    channel_modes(sptr, modebuf, parabuf, sizeof(parabuf), chptr, member);
     send_reply(sptr, RPL_CHANNELMODEIS, chptr->chname, modebuf, parabuf);
     send_reply(sptr, RPL_CREATIONTIME, chptr->chname, chptr->creationtime);
     return 0;
   }
 
-  if (!(member = find_member_link(chptr, sptr)) || !IsChanOp(member)) {
+  if (!member || !IsChanOp(member)) {
     if (IsLocalChannel(chptr->chname) && HasPriv(sptr, PRIV_MODE_LCHAN)) {
       modebuf_init(&mbuf, sptr, cptr, chptr,
                   (MODEBUF_DEST_CHANNEL | /* Send mode to channel */
@@ -178,10 +180,13 @@ ms_mode(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
                    MODEBUF_DEST_SERVER  | /* Send mode to servers */
                    MODEBUF_DEST_HACK4));  /* Send a HACK(4) message */
     else
+      /* Servers need to be able to op people who join using the Apass
+       * or upass, therefore we accept modes for channels with an Apass
+       * without generating a HACK3. */
       modebuf_init(&mbuf, sptr, cptr, chptr,
                   (MODEBUF_DEST_CHANNEL | /* Send mode to clients */
-                   MODEBUF_DEST_SERVER  | /* Send mode to servers */
-                   MODEBUF_DEST_HACK3));  /* Send a HACK(3) message */
+                   MODEBUF_DEST_SERVER |   /* Send mode to servers */
+                   (*chptr->mode.apass ? 0 : MODEBUF_DEST_HACK3)));
 
     mode_parse(&mbuf, cptr, sptr, chptr, parc - 2, parv + 2,
               (MODE_PARSE_SET    | /* Set the mode */
index 5ad955c4ed01317c12735e49505cda3d0f91c60b..c9f629617a7904270cb3e3f2264773cf58308dad 100644 (file)
@@ -92,7 +92,7 @@ static Numeric replyTable[] = {
 /* 029 */
   { 0 },
 /* 030 */
-  { 0 },
+  { RPL_APASSWARN, ":%s%s%s%s%s", "030" },
 /* 031 */
   { 0 },
 /* 032 */
@@ -1134,13 +1134,13 @@ static Numeric replyTable[] = {
 /* 550 */
   { ERR_NOTLOWEROPLEVEL, "%s %s %hu %hu :Cannot %s someone with %s op-level", "550" },
 /* 551 */
-  { 0 },
+  { ERR_NOTMANAGER, "%s :You must be channel Admin to add or remove a password. %s %s %s", "551" },
 /* 552 */
-  { 0 },
+  { ERR_CHANSECURED, "%s :Channel is older than 48 hours and secured. Cannot change Admin pass anymore", "552" },
 /* 553 */
-  { 0 },
+  { ERR_UPASSSET, "%s :Cannot remove Admin pass (+A) while User pass (+u) is still set.  First use /MODE %s -u <userpass>", "553" },
 /* 554 */
-  { 0 },
+  { ERR_UPASSNOTSET, "%s :Cannot set user pass (+u) while Admin pass (+A) is not set.  First use /MODE %s +A <adminpass>", "554" },
 /* 555 */
   { 0 },
 /* 556 */