Support ircu's XQUERY and XRESPONSE.
[srvx.git] / src / proto-p10.c
index dbf0bd0a34245ee4d3414541ee63398ace8dce5f..71f6d3766acd5f2ef97de6a6f155b5d1ec31038a 100644 (file)
 #define CMD_WHO                 "WHO"
 #define CMD_WHOIS               "WHOIS"
 #define CMD_WHOWAS              "WHOWAS"
+#define CMD_XQUERY              "XQUERY"
+#define CMD_XRESPONSE           "XRESPONSE"
 
 /* Tokenized commands. */
 #define TOK_ACCOUNT             "AC"
 #define TOK_WHO                 "H"
 #define TOK_WHOIS               "W"
 #define TOK_WHOWAS              "X"
+#define TOK_XQUERY              "XQ"
+#define TOK_XRESPONSE           "XR"
 
 /* Protocol messages; aliased to full commands or tokens depending
    on compile-time configuration. ircu prefers tokens WITH THE
 #define P10_WHO                 TYPE(WHO)
 #define P10_WHOIS               TYPE(WHOIS)
 #define P10_WHOWAS              TYPE(WHOWAS)
+#define P10_XQUERY              TYPE(XQUERY)
+#define P10_XRESPONSE           TYPE(XRESPONSE)
 
 /* Servers claiming to have a boot or link time before PREHISTORY
  * trigger errors to the log.  We hope no server has been running
@@ -291,12 +297,26 @@ static unsigned int num_notice_funcs;
 static struct dict *unbursted_channels;
 static const char *his_servername;
 static const char *his_servercomment;
+static struct channelList dead_channels;
+
+/* These correspond to 1 << X:      012345678901234567 */
+const char irc_user_mode_chars[] = "o iw dkgn    x   I";
 
 static struct userNode *AddUser(struct server* uplink, const char *nick, const char *ident, const char *hostname, const char *modes, const char *numeric, const char *userinfo, unsigned long timestamp, const char *realip);
 
 extern int off_channel;
 
-static int parse_oplevel(char *str);
+/*
+ * Oplevel parsing
+ */
+static int
+parse_oplevel(char *str)
+{
+    int oplevel = 0;
+    while (isdigit(*str))
+        oplevel = oplevel * 10 + *str++ - '0';
+    return oplevel;
+}
 
 /* Numerics can be XYY, XYYY, or XXYYY; with X's identifying the
  * server and Y's indentifying the client on that server. */
@@ -392,7 +412,7 @@ irc_p10_pton(irc_in_addr_t *ip, const char *input)
         do {
             if (*input == '_') {
                 unsigned int left;
-                for (left = (25 - strlen(input)) / 3; left; left--)
+                for (left = (25 - strlen(input)) / 3 - pos; left; left--)
                     ip->in6[pos++] = 0;
                 input++;
             } else {
@@ -443,6 +463,7 @@ irc_p10_ntop(char *output, const irc_in_addr_t *ip)
             } else {
                 inttobase64(output, ntohs(ip->in6[ii]), 3);
                 output += 3;
+                ii += 1;
             }
         }
         *output = '\0';
@@ -459,31 +480,8 @@ irc_user(struct userNode *user)
         return;
     irc_p10_ntop(b64ip, &user->ip);
     if (user->modes) {
-        int modelen;
         char modes[32];
-
-        modelen = 0;
-        if (IsOper(user))
-            modes[modelen++] = 'o';
-        if (IsInvisible(user))
-            modes[modelen++] = 'i';
-        if (IsWallOp(user))
-            modes[modelen++] = 'w';
-        if (IsService(user))
-            modes[modelen++] = 'k';
-        if (IsDeaf(user))
-            modes[modelen++] = 'd';
-        if (IsGlobal(user))
-            modes[modelen++] = 'g';
-        if (IsNoChan(user))
-            modes[modelen++] = 'n';
-        if (IsHiddenHost(user))
-            modes[modelen++] = 'x';
-        if (IsNoIdle(user))
-            modes[modelen++] = 'I';
-        modes[modelen] = 0;
-
-        /* we don't need to put the + in modes because it's in the format string. */
+        irc_user_modes(user, modes, sizeof(modes));
         putsock("%s " P10_NICK " %s %d %lu %s %s +%s %s %s :%s",
                 user->uplink->numeric, user->nick, user->uplink->hops+1, (unsigned long)user->timestamp, user->ident, user->hostname, modes, b64ip, user->numeric, user->info);
     } else {
@@ -499,9 +497,9 @@ irc_account(struct userNode *user, const char *stamp, unsigned long timestamp, u
 }
 
 void
-irc_fakehost(struct userNode *user, const char *host)
+irc_fakehost(struct userNode *user, const char *host, const char *ident, int force)
 {
-    putsock("%s " P10_FAKEHOST " %s %s", self->numeric, user->numeric, host);
+    putsock("%s " P10_FAKEHOST " %s %s %s%s", self->numeric, user->numeric, ident, host, force ? " FORCE" : "");
 }
 
 void
@@ -663,11 +661,11 @@ void
 irc_gline(struct server *srv, struct gline *gline)
 {
     if (gline->lastmod)
-        putsock("%s " P10_GLINE " %s +%s %lu %lu :%s",
-                self->numeric, (srv ? srv->numeric : "*"), gline->target, (unsigned long)(gline->expires-now), (unsigned long)gline->lastmod, gline->reason);
+        putsock("%s " P10_GLINE " %s +%s %lu %lu %lu :%s",
+                self->numeric, (srv ? srv->numeric : "*"), gline->target, gline->expires, gline->lastmod, gline->lifetime, gline->reason);
     else
         putsock("%s " P10_GLINE " %s +%s %lu :%s",
-                self->numeric, (srv ? srv->numeric : "*"), gline->target, (unsigned long)(gline->expires-now), gline->reason);
+                self->numeric, (srv ? srv->numeric : "*"), gline->target, gline->expires, gline->reason);
 }
 
 void
@@ -682,7 +680,33 @@ irc_settime(const char *srv_name_mask, unsigned long new_time)
 void
 irc_ungline(const char *mask)
 {
-    putsock("%s " P10_GLINE " * -%s", self->numeric, mask);
+    putsock("%s " P10_GLINE " * -%s %lu", self->numeric, mask, now);
+}
+
+/* Return negative if *(struct modeNode**)pa is "less than" pb,
+ * positive if pa is "larger than" pb.  Comparison is based on sorting
+ * so that non-voiced/non-opped users are first, voiced-only users are
+ * next, and the "strongest" oplevels are before "weaker" oplevels.
+ * Within those sets, ordering is arbitrary.
+ */
+static int
+modeNode_sort_p10(const void *pa, const void *pb)
+{
+        struct modeNode *a = *(struct modeNode**)pa;
+        struct modeNode *b = *(struct modeNode**)pb;
+
+        if (a->modes & MODE_CHANOP) {
+            if (!(b->modes & MODE_CHANOP))
+                return 1;
+            else if ((a->modes & MODE_VOICE) != (b->modes & MODE_VOICE))
+                return (a->modes & MODE_VOICE) - (b->modes & MODE_VOICE);
+            else if (a->oplevel != b->oplevel)
+                return a->oplevel - b->oplevel;
+        } else if (b->modes & MODE_CHANOP)
+            return -1;
+        else if ((a->modes & MODE_VOICE) != (b->modes & MODE_VOICE))
+            return (a->modes & MODE_VOICE) - (b->modes & MODE_VOICE);
+        return (a < b) ? -1 : 1;
 }
 
 static void
@@ -692,7 +716,9 @@ irc_burst(struct chanNode *chan)
     int pos, base_len, len;
     struct modeNode *mn;
     struct banNode *bn;
-    long last_mode=-1;
+    int last_oplevel = 0;
+    int last_mode = 0;
+    int new_modes;
     unsigned int first_ban;
     unsigned int n;
 
@@ -704,6 +730,9 @@ irc_burst(struct chanNode *chan)
     if (len > 0 && chan->members.used > 0)
         burst_line[pos++] = ' ';
 
+    /* sort the users for oplevel-sending purposes */
+    qsort(chan->members.list, chan->members.used, sizeof(chan->members.list[0]), modeNode_sort_p10);
+
     /* dump the users */
     for (n=0; n<chan->members.used; n++) {
         mn = chan->members.list[n];
@@ -711,17 +740,32 @@ irc_burst(struct chanNode *chan)
             burst_line[pos-1] = 0; /* -1 to back up over the space or comma */
             putsock("%s", burst_line);
             pos = base_len;
-            last_mode = -1;
+            last_mode = 0;
+            last_oplevel = 0;
         }
         memcpy(burst_line+pos, mn->user->numeric, strlen(mn->user->numeric));
         pos += strlen(mn->user->numeric);
-        if (mn->modes && (mn->modes != last_mode)) {
-            last_mode = mn->modes;
+        new_modes = mn->modes & (MODE_CHANOP | MODE_VOICE);
+        if (new_modes != last_mode) {
+            last_mode = new_modes;
             burst_line[pos++] = ':';
-            if (last_mode & MODE_CHANOP)
-                burst_line[pos++] = 'o';
-            if (last_mode & MODE_VOICE)
+            if (new_modes & MODE_VOICE)
                 burst_line[pos++] = 'v';
+            /* Note: :vNNN (oplevel NNN with voice) resets the
+             * implicit oplevel back to zero, so we always use the raw
+             * oplevel value here.  Read ircu's m_burst.c for more
+             * examples.
+             */
+            if (new_modes & MODE_CHANOP) {
+                last_oplevel = mn->oplevel;
+                if (mn->oplevel < MAXOPLEVEL)
+                    pos += sprintf(burst_line + pos, "%u", mn->oplevel);
+                else
+                    burst_line[pos++] = 'o';
+            }
+        } else if ((last_mode & MODE_CHANOP) && (mn->oplevel != last_oplevel)) {
+            pos += sprintf(burst_line + pos, ":%u", mn->oplevel - last_oplevel);
+            last_oplevel = mn->oplevel;
         }
         if ((n+1)<chan->members.used)
             burst_line[pos++] = ',';
@@ -781,8 +825,8 @@ void
 irc_kill(struct userNode *from, struct userNode *target, const char *message)
 {
     if (from) {
-        putsock("%s " P10_KILL " %s :%s!%s (%s)",
-                from->numeric, target->numeric, self->name, from->nick, message);
+        putsock("%s " P10_KILL " %s :%s (%s)",
+                from->numeric, target->numeric, from->nick, message);
     } else {
         putsock("%s " P10_KILL " %s :%s (%s)",
                 self->numeric, target->numeric, self->name, message);
@@ -870,6 +914,12 @@ irc_numeric(struct userNode *user, unsigned int num, const char *format, ...)
     putsock(":%s %03d %s %s", self->name, num, user->nick, buffer);
 }
 
+void
+irc_xresponse(struct server *target, const char *routing, const char *response)
+{
+    putsock("%s " P10_XRESPONSE " %s %s :%s", self->numeric, target->numeric, routing, response);
+}
+
 static void send_burst(void);
 
 static void
@@ -908,7 +958,11 @@ static CMD_FUNC(cmd_whois)
         return 1;
     }
 
-    if (IsFakeHost(who) && IsHiddenHost(who))
+    if (IsFakeHost(who) && IsFakeIdent(who) && IsHiddenHost(who))
+        irc_numeric(from, RPL_WHOISUSER, "%s %s %s * :%s", who->nick, who->fakeident, who->fakehost, who->info);
+    else if (IsFakeIdent(who) && IsHiddenHost(who))
+        irc_numeric(from, RPL_WHOISUSER, "%s %s %s * :%s", who->nick, who->fakeident, who->hostname, who->info);
+    else if (IsFakeHost(who) && IsHiddenHost(who))
         irc_numeric(from, RPL_WHOISUSER, "%s %s %s * :%s", who->nick, who->ident, who->fakehost, who->info);
     else if (IsHiddenHost(who) && who->handle_info && hidden_host_suffix)
         irc_numeric(from, RPL_WHOISUSER, "%s %s %s.%s * :%s", who->nick, who->ident, who->handle_info->handle, hidden_host_suffix, who->info);
@@ -1205,12 +1259,22 @@ static CMD_FUNC(cmd_account)
 static CMD_FUNC(cmd_fakehost)
 {
     struct userNode *user;
+    const char *host, *ident;
 
     if ((argc < 3) || !origin || !GetServerH(origin))
         return 0;
     if (!(user = GetUserN(argv[1])))
         return 1;
-    assign_fakehost(user, argv[2], 0);
+
+    if (argc > 3) {
+        ident = argv[2];
+        host = argv[3];
+    } else {
+        ident = NULL;
+        host = argv[2];
+    }
+
+    assign_fakehost(user, host, ident, 0, 0);
     return 1;
 }
 
@@ -1223,7 +1287,7 @@ static CMD_FUNC(cmd_burst)
     struct userNode *un;
     struct modeNode *mNode;
     long mode;
-    int oplevel = -1;
+    int oplevel = 0;
     char *user, *end, sep;
     unsigned long in_timestamp;
 
@@ -1267,16 +1331,13 @@ static CMD_FUNC(cmd_burst)
             while ((sep = *end++)) {
                 if (sep == 'o') {
                     mode |= MODE_CHANOP;
-                    oplevel = -1;
+                    oplevel = MAXOPLEVEL;
                 } else if (sep == 'v') {
                     mode |= MODE_VOICE;
-                    oplevel = -1;
+                    oplevel = 0;
                 } else if (isdigit(sep)) {
                     mode |= MODE_CHANOP;
-                    if (oplevel >= 0)
-                        oplevel += parse_oplevel(end);
-                    else
-                        oplevel = parse_oplevel(end);
+                    oplevel += parse_oplevel(end - 1);
                     while (isdigit(*end)) end++;
                 } else
                     break;
@@ -1310,6 +1371,7 @@ static CMD_FUNC(cmd_mode)
             log_module(MAIN_LOG, LOG_ERROR, "Unable to find user %s whose mode is changing.", argv[1]);
             return 0;
         }
+        argv[2] = unsplit_string(argv + 2, argc - 2, NULL);
         mod_usermode(un, argv[2]);
         return 1;
     }
@@ -1384,7 +1446,7 @@ static CMD_FUNC(cmd_clearmode)
 static CMD_FUNC(cmd_topic)
 {
     struct chanNode *cn;
-    unsigned long chan_ts, topic_ts;
+    unsigned long topic_ts;
 
     if (argc < 3)
         return 0;
@@ -1394,10 +1456,8 @@ static CMD_FUNC(cmd_topic)
     }
     if (argc >= 5) {
         /* Looks like an Asuka style topic burst. */
-        chan_ts = atoi(argv[2]);
         topic_ts = atoi(argv[3]);
     } else {
-        chan_ts = cn->timestamp;
         topic_ts = now;
     }
     SetChannelTopic(cn, GetUserH(origin), argv[argc-1], 0);
@@ -1444,10 +1504,13 @@ static CMD_FUNC(cmd_num_topic)
 static CMD_FUNC(cmd_num_gline)
 {
     unsigned long lastmod;
+    unsigned long lifetime;
+
     if (argc < 6)
         return 0;
     lastmod = (argc > 5) ? strtoul(argv[5], NULL, 0) : 0;
-    gline_add(origin, argv[3], atoi(argv[4])-now, argv[argc - 1], now, lastmod, 0);
+    lifetime = (argc > 6) ? strtoul(argv[6], NULL, 0) : 0;
+    gline_add(origin, argv[3], atoi(argv[4])-now, argv[argc - 1], now, lastmod, lifetime, 0);
     return 1;
 }
 
@@ -1562,15 +1625,22 @@ static CMD_FUNC(cmd_away)
 
 static CMD_FUNC(cmd_gline)
 {
+#define PASTWATCH (5*365*24*3600)
     unsigned long lastmod;
+    unsigned long lifetime;
+    unsigned long expiration;
 
     if (argc < 3)
         return 0;
     if (argv[2][0] == '+') {
         if (argc < 5)
             return 0;
-        lastmod = (argc > 5) ? strtoul(argv[5], NULL, 0) : 0;
-        gline_add(origin, argv[2]+1, strtoul(argv[3], NULL, 0), argv[argc-1], now, lastmod, 0);
+        expiration = strtoul(argv[3], NULL, 10);
+        if (expiration < now - PASTWATCH)
+            expiration += now;
+        lastmod = (argc > 5) ? strtoul(argv[4], NULL, 10) : 0;
+        lifetime = (argc > 6) ? strtoul(argv[5], NULL, 10) : 0;
+        gline_add(origin, argv[2]+1, expiration - now, argv[argc-1], now, lastmod, lifetime, 0);
         return 1;
     } else if (argv[2][0] == '-') {
         gline_remove(argv[2]+1, 0);
@@ -1591,6 +1661,33 @@ static CMD_FUNC(cmd_svsnick)
     return 1;
 }
 
+static CMD_FUNC(cmd_time)
+{
+    extern int clock_skew;
+    char buf[MAXLEN];
+    struct userNode *who;
+    time_t when;
+
+    who = GetUserH(origin);
+    if (!who)
+        return 0;
+
+    when = time(NULL);
+    strftime(buf, sizeof(buf), "%a %b %d %Y -- %H:%M %z", localtime(&when));
+    irc_numeric(who, 391, "%s %lu %d :%s", self->name, now, clock_skew, buf);
+    return 1;
+}
+
+static CMD_FUNC(cmd_xquery)
+{
+    struct server *source;
+    if ((argc < 4)
+        || !(source = GetServerH(origin)))
+        return 0;
+    call_xquery_funcs(source, argv[2], argv[3]);
+    return 1;
+}
+
 void
 free_user(struct userNode *user)
 {
@@ -1730,6 +1827,10 @@ init_parse(void)
     dict_insert(irc_func_dict, TOK_VERSION, cmd_version);
     dict_insert(irc_func_dict, CMD_ADMIN, cmd_admin);
     dict_insert(irc_func_dict, TOK_ADMIN, cmd_admin);
+    dict_insert(irc_func_dict, CMD_TIME, cmd_time);
+    dict_insert(irc_func_dict, TOK_TIME, cmd_time);
+    /* We don't handle XR or the (not really defined) XQUERY. */
+    dict_insert(irc_func_dict, TOK_XQUERY, cmd_xquery);
 
     /* In P10, DESTRUCT doesn't do anything except be broadcast to servers.
      * Apparently to obliterate channels from any servers that think they
@@ -1784,6 +1885,7 @@ init_parse(void)
     memset(notice_funcs, 0, sizeof(privmsg_func_t)*num_notice_funcs);
 
     userList_init(&dead_users);
+    channelList_init(&dead_channels);
     reg_del_channel_func(remove_unbursted_channel);
     reg_exit_func(parse_cleanup);
 }
@@ -1820,6 +1922,9 @@ parse_line(char *line, int recursive)
         for (i=0; i<dead_users.used; i++)
             free_user(dead_users.list[i]);
         dead_users.used = 0;
+        for (i=0; i<dead_channels.used; i++)
+            UnlockChannel(dead_channels.list[i]);
+        dead_channels.used = 0;
     }
     return res;
 }
@@ -1999,7 +2104,6 @@ AddLocalUser(const char *nick, const char *ident, const char *hostname, const ch
 {
     char numeric[COMBO_NUMERIC_LEN+1];
     int local_num = get_local_numeric();
-    unsigned long timestamp = now;
     struct userNode *old_user = GetUserH(nick);
 
     if (!modes)
@@ -2007,7 +2111,6 @@ AddLocalUser(const char *nick, const char *ident, const char *hostname, const ch
     if (old_user) {
         if (IsLocal(old_user))
             return old_user;
-        timestamp = old_user->timestamp - 1;
     }
     if (local_num == -1) {
         log_module(MAIN_LOG, LOG_ERROR, "Unable to allocate numnick for service %s", nick);
@@ -2251,10 +2354,11 @@ void mod_usermode(struct userNode *user, const char *mode_change) {
                 if (*word == ':') {
                     ts = strtoul(word + 1, &sep, 10);
                     if (*sep == ':') {
-                        id = strtoul(word + 1, &sep, 10);
+                        id = strtoul(sep + 1, &sep, 10);
                     } else if (*sep != ' ' && *sep != '\0') {
                         ts = 0;
                     }
+                    word = sep;
                 }
                 tag[ii] = '\0';
                 while (*word == ' ')
@@ -2262,16 +2366,27 @@ void mod_usermode(struct userNode *user, const char *mode_change) {
                 call_account_func(user, tag, ts, id);
             }
             break;
-        case 'f':
+        case 'h':
             if (*word) {
-                char host[MAXLEN];
+                char mask[MAXLEN];
+                char *host, *ident;
                 unsigned int ii;
+
                 for (ii=0; (*word != ' ') && (*word != '\0'); )
-                    host[ii++] = *word++;
-                host[ii] = 0;
+                    mask[ii++] = *word++;
+                mask[ii] = 0;
                 while (*word == ' ')
                     word++;
-                assign_fakehost(user, host, 0);
+
+                if ((host = strrchr(mask, '@'))) {
+                    ident = mask;
+                    *host++ = '\0';
+                } else {
+                    ident = NULL;
+                    host = mask;
+                }
+                user->modes |= FLAGS_HIDDEN_HOST;
+                assign_fakehost(user, host, ident, 0, 0);
             }
             break;
         }
@@ -2332,12 +2447,8 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
         case 's': do_chan_mode(MODE_SECRET); break;
         case 't': do_chan_mode(MODE_TOPICLIMIT); break;
         case 'z':
-          if (!(flags & MCP_REGISTERED)) {
+          if (!(flags & MCP_REGISTERED))
               do_chan_mode(MODE_REGISTERED);
-          } else {
-              mod_chanmode_free(change);
-              return NULL;
-          }
           break;
 #undef do_chan_mode
         case 'l':
@@ -2367,6 +2478,8 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
             }
             break;
         case 'U':
+            if (flags & MCP_NO_APASS)
+                goto error;
             if (add)
             {
                 if ((in_arg >= argc)
@@ -2383,9 +2496,11 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
             }
             break;
         case 'A':
+            if (flags & MCP_NO_APASS)
+                goto error;
             if (add) {
                 if ((in_arg >= argc)
-                    || keyncpy(change->new_upass, modes[in_arg++], sizeof(change->new_upass)))
+                    || keyncpy(change->new_apass, modes[in_arg++], sizeof(change->new_apass)))
                     goto error;
                 change->modes_set |= MODE_APASS;
             } else {
@@ -2427,7 +2542,7 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
             else if (channel->modes & MODE_UPASS)
                 oplevel = base_oplevel + 1;
             else
-                oplevel = -1;
+                oplevel = MAXOPLEVEL;
 
             /* Check that oplevel is within bounds. */
             if (oplevel > MAXOPLEVEL)
@@ -2448,8 +2563,9 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
                 continue;
             if ((change->args[ch_arg].u.member = GetUserMode(channel, victim)))
             {
-                /* Apply the oplevel change */
-                change->args[ch_arg].u.member->oplevel = oplevel;
+                /* Apply the oplevel change if the user is being (de-)opped */
+                if (modes[0][ii] == 'o')
+                    change->args[ch_arg].u.member->oplevel = oplevel;
                 ch_arg++;
             }
             break;
@@ -2468,6 +2584,11 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
         change->modes_set &= ~(MODE_SECRET);
         change->modes_clear |= MODE_SECRET;
     }
+    if (change->modes_clear & MODE_REGISTERED) {
+        /* Horribly cheat by using the lock/unlock semantics. */
+        LockChannel(channel);
+        channelList_append(&dead_channels, channel);
+    }
     return change;
   error:
     mod_chanmode_free(change);
@@ -2627,6 +2748,9 @@ char *
 mod_chanmode_format(struct mod_chanmode *change, char *outbuff)
 {
     unsigned int used = 0;
+    unsigned int args_used = 0;
+    char args[MAXLEN];
+
     assert(change->argc <= change->alloc_argc);
     if (change->modes_clear) {
         outbuff[used++] = '-';
@@ -2662,61 +2786,14 @@ mod_chanmode_format(struct mod_chanmode *change, char *outbuff)
         DO_MODE_CHAR(NOCOLORS, 'c');
         DO_MODE_CHAR(NOCTCPS, 'C');
         DO_MODE_CHAR(REGISTERED, 'z');
+        DO_MODE_CHAR(LIMIT, 'l'), args_used += sprintf(args + args_used, " %d", change->new_limit);
+        DO_MODE_CHAR(KEY, 'k'), args_used += sprintf(args + args_used, " %s", change->new_key);
+        DO_MODE_CHAR(UPASS, 'U'), args_used += sprintf(args + args_used, " %s", change->new_upass);
+        DO_MODE_CHAR(APASS, 'A'), args_used += sprintf(args + args_used, " %s", change->new_apass);
 #undef DO_MODE_CHAR
-        switch (change->modes_set & (MODE_KEY|MODE_LIMIT|MODE_APASS|MODE_UPASS)) {
-        /* Doing this implementation has been a pain in the arse, I hope I didn't forget a possible combination */
-        case MODE_KEY|MODE_LIMIT|MODE_APASS|MODE_UPASS:
-            used += sprintf(outbuff+used, "lkAU %d %s %s %s", change->new_limit, change->new_key, change->new_apass, change->new_upass);
-            break;
-
-        case MODE_KEY|MODE_LIMIT|MODE_APASS:
-            used += sprintf(outbuff+used, "lkA %d %s %s", change->new_limit, change->new_key, change->new_apass);
-            break;
-        case MODE_KEY|MODE_LIMIT|MODE_UPASS:
-            used += sprintf(outbuff+used, "lkU %d %s %s", change->new_limit, change->new_key, change->new_upass);
-            break;
-        case MODE_KEY|MODE_APASS|MODE_UPASS:
-            used += sprintf(outbuff+used, "kAU %s %s %s", change->new_key, change->new_apass, change->new_upass);
-            break;
-
-        case MODE_KEY|MODE_APASS:
-            used += sprintf(outbuff+used, "kA %s %s", change->new_key, change->new_apass);
-            break;
-        case MODE_KEY|MODE_UPASS:
-            used += sprintf(outbuff+used, "kU %s %s", change->new_key, change->new_upass);
-            break;
-        case MODE_KEY|MODE_LIMIT:
-            used += sprintf(outbuff+used, "lk %d %s", change->new_limit, change->new_key);
-            break;
-        case MODE_LIMIT|MODE_UPASS:
-            used += sprintf(outbuff+used, "lU %d %s", change->new_limit, change->new_upass);
-            break;
-        case MODE_LIMIT|MODE_APASS:
-            used += sprintf(outbuff+used, "lA %d %s", change->new_limit, change->new_apass);
-            break;
-        case MODE_APASS|MODE_UPASS:
-            used += sprintf(outbuff+used, "AU %s %s", change->new_apass, change->new_upass);
-            break;
-
-        case MODE_LIMIT|MODE_APASS|MODE_UPASS:
-            used += sprintf(outbuff+used, "lAU %d %s %s", change->new_limit, change->new_apass, change->new_upass);
-            break;
-
-        case MODE_APASS:
-            used += sprintf(outbuff+used, "A %s", change->new_apass);
-            break;
-        case MODE_UPASS:
-            used += sprintf(outbuff+used, "U %s", change->new_upass);
-            break;
-        case MODE_KEY:
-            used += sprintf(outbuff+used, "k %s", change->new_key);
-            break;
-        case MODE_LIMIT:
-            used += sprintf(outbuff+used, "l %d", change->new_limit);
-            break;
-        }
     }
-    outbuff[used] = 0;
+    args[args_used] = '\0';
+    strcpy(outbuff + used, args);
     return outbuff;
 }
 
@@ -2819,8 +2896,8 @@ reg_notice_func(struct userNode *user, privmsg_func_t handler)
     if (numeric >= num_notice_funcs) {
         int newnum = numeric + 8, ii;
         notice_funcs = realloc(notice_funcs, newnum*sizeof(privmsg_func_t));
-        for (ii = num_privmsg_funcs; ii < newnum; ++ii)
-            privmsg_funcs[ii] = NULL;
+        for (ii = num_notice_funcs; ii < newnum; ++ii)
+            notice_funcs[ii] = NULL;
         num_notice_funcs = newnum;
     }
     if (notice_funcs[numeric])
@@ -2866,15 +2943,3 @@ send_burst(void)
     for (it = dict_first(channels); it; it = iter_next(it))
         dict_insert(unbursted_channels, iter_key(it), iter_data(it));
 }
-
-/*
- * Oplevel parsing
- */
-static int
-parse_oplevel(char *str)
-{
-    int oplevel = 0;
-    while (isdigit(*str))
-        oplevel = oplevel * 10 + *str++ - '0';
-    return oplevel;
-}