Avoid crashing when BURST has missing modes.
[srvx.git] / src / proto-p10.c
index 565b828b87d4ff443e4c5517a99fe2a89bdfbf1d..003bbe62999cf23eb3a7312a99dd9c1f145cba88 100644 (file)
@@ -1,5 +1,5 @@
 /* proto-p10.c - IRC protocol output
- * Copyright 2000-2004 srvx Development Team
+ * Copyright 2000-2006 srvx Development Team
  *
  * This file is part of srvx.
  *
@@ -39,6 +39,7 @@
 #define CMD_EOB                 "END_OF_BURST"
 #define CMD_EOB_ACK             "EOB_ACK"
 #define CMD_ERROR               "ERROR"
+#define CMD_FAKEHOST            "FAKE"
 #define CMD_GET                        "GET"
 #define CMD_GLINE               "GLINE"
 #define CMD_HASH                "HASH"
 #define TOK_EOB                 "EB"
 #define TOK_EOB_ACK             "EA"
 #define TOK_ERROR               "Y"
+#define TOK_FAKEHOST            "FA"
 #define TOK_GET                        "GET"
 #define TOK_GLINE               "GL"
 #define TOK_HASH                "HASH"
 #define P10_EOB                 TYPE(EOB)
 #define P10_EOB_ACK             TYPE(EOB_ACK)
 #define P10_ERROR               TYPE(ERROR)
+#define P10_FAKEHOST            TYPE(FAKEHOST)
 #define P10_GET                        TYPE(GET)
 #define P10_GLINE               TYPE(GLINE)
 #define P10_HASH                TYPE(HASH)
@@ -286,9 +289,15 @@ static unsigned int num_privmsg_funcs;
 static privmsg_func_t *notice_funcs;
 static unsigned int num_notice_funcs;
 static struct dict *unbursted_channels;
+static char *his_servername;
+static char *his_servercomment;
 
 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, time_t timestamp, const char *realip);
 
+extern int off_channel;
+
+static int parse_oplevel(char *str);
+
 /* Numerics can be XYY, XYYY, or XXYYY; with X's identifying the
  * server and Y's indentifying the client on that server. */
 struct server*
@@ -359,21 +368,96 @@ irc_server(struct server *srv)
     inttobase64(extranum, srv->num_mask, (srv->numeric[1] || (srv->num_mask >= 64*64)) ? 3 : 2);
     if (srv == self) {
         /* The +s, ignored by Run's ircu, means "service" to Undernet's ircu */
-        putsock(P10_SERVER " %s %d %li %li J10 %s%s +s :%s",
+        putsock(P10_SERVER " %s %d %li %li J10 %s%s +s6 :%s",
                 srv->name, srv->hops+1, srv->boot, srv->link, srv->numeric, extranum, srv->description);
     } else {
-        putsock("%s " P10_SERVER " %s %d %li %li %c10 %s%s +s :%s",
+        putsock("%s " P10_SERVER " %s %d %li %li %c10 %s%s +s6 :%s",
                 self->numeric, srv->name, srv->hops+1, srv->boot, srv->link, (srv->self_burst ? 'J' : 'P'), srv->numeric, extranum, srv->description);
     }
 }
 
+static void
+irc_p10_pton(irc_in_addr_t *ip, const char *input)
+{
+    if (strlen(input) == 6) {
+        unsigned int value;
+        memset(ip, 0, 6 * sizeof(ip->in6[0]));
+        value = base64toint(input, 6);
+        if (value)
+            ip->in6[5] = htons(65535);
+        ip->in6[6] = htons(value >> 16);
+        ip->in6[7] = htons(value & 65535);
+    } else {
+        unsigned int pos = 0;
+        do {
+            if (*input == '_') {
+                unsigned int left;
+                for (left = (25 - strlen(input)) / 3; left; left--)
+                    ip->in6[pos++] = 0;
+                input++;
+            } else {
+                ip->in6[pos++] = ntohs(base64toint(input, 3));
+                input += 3;
+            }
+        } while (pos < 8);
+    }
+}
+
+static void
+irc_p10_ntop(char *output, const irc_in_addr_t *ip)
+{
+    if (!irc_in_addr_is_valid(*ip)) {
+        strcpy(output, "AAAAAA");
+    } else if (irc_in_addr_is_ipv4(*ip)) {
+        unsigned int in4;
+        in4 = (ntohs(ip->in6[6]) << 16) | ntohs(ip->in6[7]);
+        inttobase64(output, in4, 6);
+        output[6] = '\0';
+    } else if (irc_in_addr_is_ipv6(*ip)) {
+        unsigned int max_start, max_zeros, curr_zeros, zero, ii;
+        /* Can start by printing out the leading non-zero parts. */
+        for (ii = 0; (ip->in6[ii]) && (ii < 8); ++ii) {
+            inttobase64(output, ntohs(ip->in6[ii]), 3);
+            output += 3;
+        }
+        /* Find the longest run of zeros. */
+        for (max_start = zero = ii, max_zeros = curr_zeros = 0; ii < 8; ++ii) {
+            if (!ip->in6[ii])
+                curr_zeros++;
+            else if (curr_zeros > max_zeros) {
+                max_start = ii - curr_zeros;
+                max_zeros = curr_zeros;
+                curr_zeros = 0;
+            }
+        }
+        if (curr_zeros > max_zeros) {
+            max_start = ii - curr_zeros;
+            max_zeros = curr_zeros;
+            curr_zeros = 0;
+        }
+        /* Print the rest of the address */
+        for (ii = zero; ii < 8; ) {
+            if ((ii == max_start) && max_zeros) {
+                *output++ = '_';
+                ii += max_zeros;
+            } else {
+                inttobase64(output, ntohs(ip->in6[ii]), 3);
+                output += 3;
+            }
+        }
+        *output = '\0';
+    } else {
+        strcpy(output, "???");
+    }
+}
+
 void
 irc_user(struct userNode *user)
 {
-    char b64ip[7];
-    if (!user)
+    char b64ip[25];
+    if (!user || IsDummy(user))
         return;
-    inttobase64(b64ip, ntohl(user->ip.s_addr), 6);
+    irc_p10_ntop(b64ip, &user->ip);
     if (user->modes) {
         int modelen;
         char modes[32];
@@ -387,16 +471,16 @@ irc_user(struct userNode *user)
             modes[modelen++] = 'w';
         if (IsService(user))
             modes[modelen++] = 'k';
-        if (IsServNotice(user))
-            modes[modelen++] = 's';
         if (IsDeaf(user))
             modes[modelen++] = 'd';
         if (IsGlobal(user))
             modes[modelen++] = 'g';
-        if (IsHelperIrcu(user))
-            modes[modelen++] = 'h';
+        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. */
@@ -414,6 +498,12 @@ irc_account(struct userNode *user, const char *stamp)
     putsock("%s " P10_ACCOUNT " %s %s", self->numeric, user->numeric, stamp);
 }
 
+void
+irc_fakehost(struct userNode *user, const char *host)
+{
+    putsock("%s " P10_FAKEHOST " %s %s", self->numeric, user->numeric, host);
+}
+
 void
 irc_regnick(UNUSED_ARG(struct userNode *user))
 {
@@ -468,16 +558,48 @@ irc_wallchops(struct userNode *from, const char *to, const char *message)
     putsock("%s " P10_WALLCHOPS " %s :%s", from->numeric, to, message);
 }
 
+static int
+deliver_to_dummy(struct userNode *source, struct userNode *dest, const char *message, int type)
+{
+    unsigned int num;
+
+    if (!dest || !IsDummy(dest) || !IsLocal(dest))
+        return 0;
+    num = dest->num_local;
+    switch (type) {
+    default:
+        if ((num < num_notice_funcs) && notice_funcs[num])
+            notice_funcs[num](source, dest, message, 0);
+        break;
+    case 1:
+        if ((num < num_privmsg_funcs) && privmsg_funcs[num])
+            privmsg_funcs[num](source, dest, message, 0);
+        break;
+    }
+    return 1;
+}
+
 void
 irc_notice(struct userNode *from, const char *to, const char *message)
 {
-    putsock("%s " P10_NOTICE " %s :%s", from->numeric, to, message);
+    if (to[0] == '#' || to[0] == '$'
+        || !deliver_to_dummy(from, GetUserN(to), message, 0))
+        putsock("%s " P10_NOTICE " %s :%s", from->numeric, to, message);
+}
+
+void
+irc_notice_user(struct userNode *from, struct userNode *to, const char *message)
+{
+    if (!deliver_to_dummy(from, to, message, 0))
+        putsock("%s " P10_NOTICE " %s :%s", from->numeric, to->numeric, message);
 }
 
 void
 irc_privmsg(struct userNode *from, const char *to, const char *message)
 {
-    putsock("%s " P10_PRIVMSG " %s :%s", from->numeric, to, message);
+    if (to[0] == '#' || to[0] == '$'
+        || !deliver_to_dummy(from, GetUserN(to), message, 1))
+        putsock("%s " P10_PRIVMSG " %s :%s", from->numeric, to, message);
 }
 
 void
@@ -504,6 +626,21 @@ irc_pong(const char *who, const char *data)
     putsock("%s " P10_PONG " %s :%s", self->numeric, who, data);
 }
 
+void
+irc_pong_asll(const char *who, const char *orig_ts)
+{
+    char *delim;
+    struct timeval orig;
+    struct timeval now;
+    int diff;
+
+    orig.tv_sec = strtoul(orig_ts, &delim, 10);
+    orig.tv_usec = (*delim == '.') ? strtoul(delim + 1, NULL, 10) : 0;
+    gettimeofday(&now, NULL);
+    diff = (now.tv_sec - orig.tv_sec) * 1000 + (now.tv_usec - orig.tv_usec) / 1000;
+    putsock("%s " P10_PONG " %s %s %d " FMT_TIME_T ".%06u", self->numeric, who, orig_ts, diff, now.tv_sec, (unsigned)now.tv_usec);
+}
+
 void
 irc_pass(const char *passwd)
 {
@@ -525,8 +662,12 @@ irc_introduce(const char *passwd)
 void
 irc_gline(struct server *srv, struct gline *gline)
 {
-    putsock("%s " P10_GLINE " %s +%s %ld :%s",
-            self->numeric, (srv ? srv->numeric : "*"), gline->target, gline->expires-now, gline->reason);
+    if (gline->lastmod)
+        putsock("%s " P10_GLINE " %s +%s %ld %ld :%s",
+                self->numeric, (srv ? srv->numeric : "*"), gline->target, gline->expires-now, gline->lastmod, gline->reason);
+    else
+        putsock("%s " P10_GLINE " %s +%s %ld :%s",
+                self->numeric, (srv ? srv->numeric : "*"), gline->target, gline->expires-now, gline->reason);
 }
 
 void
@@ -552,15 +693,14 @@ irc_burst(struct chanNode *chan)
     struct modeNode *mn;
     struct banNode *bn;
     long last_mode=-1;
+    unsigned int first_ban;
     unsigned int n;
 
-    if (!chan->members.used)
-        return;
     base_len = sprintf(burst_line, "%s " P10_BURST " %s " FMT_TIME_T " ",
                        self->numeric, chan->name, chan->timestamp);
     len = irc_make_chanmode(chan, burst_line+base_len);
     pos = base_len + len;
-    if (len)
+    if (len > 0 && chan->members.used > 0)
         burst_line[pos++] = ' ';
 
     /* dump the users */
@@ -585,32 +725,36 @@ irc_burst(struct chanNode *chan)
         if ((n+1)<chan->members.used)
             burst_line[pos++] = ',';
     }
+
+    /* dump the bans */
     if (chan->banlist.used) {
-        /* dump the bans */
-        if (pos+2+strlen(chan->banlist.list[0]->ban) > 505) {
-            burst_line[pos-1] = 0;
-            putsock("%s", burst_line);
-            pos = base_len;
-        } else {
+        first_ban = 1;
+
+        if (chan->members.used > 0)
             burst_line[pos++] = ' ';
-        }
 
-        burst_line[pos++] = ':';
-        burst_line[pos++] = '%';
-        base_len = pos;
-        for (n=0; n<chan->banlist.used; n++) {
+        for (n=0; n<chan->banlist.used; ) {
+            if (first_ban && (pos < 500)) {
+                burst_line[pos++] = ':';
+                burst_line[pos++] = '%';
+            }
             bn = chan->banlist.list[n];
             len = strlen(bn->ban);
-            if (pos+len+1 > 510) {
-                burst_line[pos-1] = 0; /* -1 to back up over the space or comma */
+            if (pos + 2 + len < 505) {
+                memcpy(burst_line + pos, bn->ban, len);
+                pos += len;
+                burst_line[pos++] = ' ';
+                first_ban = 0;
+                n++;
+            } else {
+                burst_line[pos-1] = 0;
                 putsock("%s", burst_line);
                 pos = base_len;
+                first_ban = 1;
             }
-            memcpy(burst_line+pos, bn->ban, len);
-            pos += len;
-            burst_line[pos++] = ' ';
         }
     }
+
     /* print the last line */
     burst_line[pos] = 0;
     putsock("%s", burst_line);
@@ -674,7 +818,7 @@ irc_kick(struct userNode *who, struct userNode *target, struct chanNode *channel
 {
     const char *numeric;
     struct modeNode *mn = GetUserMode(channel, who);
-    numeric = (mn && (mn->modes & MODE_CHANOP)) ? who->numeric : self->numeric;
+    numeric = ((mn && (mn->modes & MODE_CHANOP)) || off_channel) ? who->numeric : self->numeric;
     putsock("%s " P10_KICK " %s %s :%s",
             numeric, channel->name, target->numeric, msg);
 }
@@ -744,6 +888,90 @@ change_nicklen(int new_nicklen)
     }
 }
 
+static CMD_FUNC(cmd_whois)
+{
+    struct userNode *from;
+    struct userNode *who;
+    char buf[MAXLEN];
+    unsigned int i, mlen, len;
+
+    if (argc < 3)
+        return 0;
+    if (!(from = GetUserH(origin))) {
+        log_module(MAIN_LOG, LOG_ERROR, "Could not find WHOIS origin user %s", origin);
+        return 0;
+    }
+    if(!(who = GetUserH(argv[2]))) {
+        irc_numeric(from, ERR_NOSUCHNICK, "%s@%s :No such nick", argv[2], self->name);
+        return 1;
+    }
+
+    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);
+    else
+        irc_numeric(from, RPL_WHOISUSER, "%s %s %s * :%s", who->nick, who->ident, who->hostname, who->info);
+
+    if ((!IsService(who) && !IsNoChan(who)) || (from == who)) {
+        struct modeNode *mn;
+        mlen = strlen(self->name) + strlen(from->nick) + 12 + strlen(who->nick);
+        len = 0;
+        *buf = '\0';
+        for (i = 0; i < who->channels.used; i++)
+        {
+            mn = who->channels.list[i];
+
+            if (!IsOper(from) && (mn->channel->modes & (MODE_PRIVATE | MODE_SECRET)) && !GetUserMode(mn->channel, from))
+                continue;
+
+            if (len + strlen(mn->channel->name) + mlen > MAXLEN - 5)
+            {
+                irc_numeric(from, RPL_WHOISCHANNELS, "%s :%s", who->nick, buf);
+                *buf = '\0';
+                len = 0;
+            }
+
+            if (IsDeaf(who))
+                *(buf + len++) = '-';
+            if ((mn->channel->modes & (MODE_PRIVATE | MODE_SECRET)) && !GetUserMode(mn->channel, from))
+                *(buf + len++) = '*';
+            if (mn->modes & MODE_CHANOP)
+                *(buf + len++) = '@';
+            else if (mn->modes & MODE_VOICE)
+                *(buf + len++) = '+';
+
+            if (len)
+                *(buf + len) = '\0';
+            strcpy(buf + len, mn->channel->name);
+            len += strlen(mn->channel->name);
+            strcat(buf + len, " ");
+            len++;
+        }
+        if (buf[0] != '\0')
+            irc_numeric(from, RPL_WHOISCHANNELS, "%s :%s", who->nick, buf);
+    }
+
+    if (his_servername && his_servercomment && !IsOper(from) && from != who)
+        irc_numeric(from, RPL_WHOISSERVER, "%s %s :%s", who->nick, his_servername, his_servercomment);
+    else
+        irc_numeric(from, RPL_WHOISSERVER, "%s %s :%s", who->nick, who->uplink->name, who->uplink->description);
+
+    if (IsAway(who))
+        irc_numeric(from, RPL_AWAY, "%s :Away", who->nick);
+    if (IsOper(who))
+        irc_numeric(from, RPL_WHOISOPERATOR, "%s :%s", who->nick, IsLocal(who) ? "is a megalomaniacal power hungry tyrant" : "is an IRC Operator");
+    if (who->handle_info)
+        irc_numeric(from, RPL_WHOISACCOUNT, "%s %s :is logged in as", who->nick, who->handle_info->handle);
+    if (IsHiddenHost(who) && who->handle_info && (IsOper(from) || from == who))
+        irc_numeric(from, RPL_WHOISACTUALLY, "%s %s@%s %s :Actual user@host, Actual IP", who->nick, who->ident, who->hostname, irc_ntoa(&who->ip));
+    if (IsLocal(who) && !IsService(who) && (!IsNoIdle(who) || IsOper(from) || from == who))
+        irc_numeric(from, RPL_WHOISIDLE, "%s %ld %ld :seconds idle, signon time", who->nick, now - who->idle_since, who->timestamp);
+
+    irc_numeric(from, RPL_ENDOFWHOIS, "%s :End of /WHOIS list", who->nick);
+    return 1;
+}
+
 static CMD_FUNC(cmd_server)
 {
     struct server *srv;
@@ -751,7 +979,7 @@ static CMD_FUNC(cmd_server)
 
     if (argc < 8)
         return 0;
-    if (origin) {
+    if (self->uplink) {
         /* another server introduced us */
         srv = AddServer(GetServerH(origin), argv[1], atoi(argv[2]), atoi(argv[3]), atoi(argv[4]), argv[6], argv[argc-1]);
         if (!srv)
@@ -837,8 +1065,8 @@ static CMD_FUNC(cmd_ping)
     struct server *srv;
     struct userNode *un;
 
-    if(argc > 3)
-        irc_pong(argv[2], argv[1]);
+    if (argc > 3)
+        irc_pong_asll(argv[2], argv[3]);
     else if ((srv = GetServerH(origin)))
         irc_pong(self->name, srv->numeric);
     else if ((un = GetUserH(origin)))
@@ -966,6 +1194,18 @@ static CMD_FUNC(cmd_account)
     return 1;
 }
 
+static CMD_FUNC(cmd_fakehost)
+{
+    struct userNode *user;
+
+    if ((argc < 3) || !origin || !GetServerH(origin))
+        return 0;
+    if (!(user = GetUserN(argv[1])))
+        return 1;
+    assign_fakehost(user, argv[2], 0);
+    return 1;
+}
+
 static CMD_FUNC(cmd_burst)
 {
     extern int rel_age;
@@ -975,6 +1215,7 @@ static CMD_FUNC(cmd_burst)
     struct userNode *un;
     struct modeNode *mNode;
     long mode;
+    int oplevel = -1;
     char *user, *end, sep;
     time_t in_timestamp;
 
@@ -987,8 +1228,11 @@ static CMD_FUNC(cmd_burst)
             const char *pos;
             int n_modes;
             for (pos=argv[next], n_modes = 1; *pos; pos++)
-                if ((*pos == 'k') || (*pos == 'l'))
+                if ((*pos == 'k') || (*pos == 'l') || (*pos == 'A')
+                    || (*pos == 'U'))
                     n_modes++;
+            if (next + n_modes > argc)
+                n_modes = argc - next;
             unsplit_string(argv+next, n_modes, modes);
             next += n_modes;
             break;
@@ -1013,11 +1257,20 @@ static CMD_FUNC(cmd_burst)
         if (sep == ':') {
             mode = 0;
             while ((sep = *end++)) {
-                if (sep == 'o')
+                if (sep == 'o') {
                     mode |= MODE_CHANOP;
-                else if (sep == 'v')
+                    oplevel = -1;
+                } else if (sep == 'v') {
                     mode |= MODE_VOICE;
-                else
+                    oplevel = -1;
+                } else if (isdigit(sep)) {
+                    mode |= MODE_CHANOP;
+                    if (oplevel >= 0)
+                        oplevel += parse_oplevel(end);
+                    else
+                        oplevel = parse_oplevel(end);
+                    while (isdigit(*end)) end++;
+                } else
                     break;
             }
             if (rel_age < 0)
@@ -1027,8 +1280,10 @@ static CMD_FUNC(cmd_burst)
             res = 0;
             continue;
         }
-        if ((mNode = AddChannelUser(un, cNode)))
+        if ((mNode = AddChannelUser(un, cNode))) {
             mNode->modes = mode;
+            mNode->oplevel = oplevel;
+        }
     }
 
     return res;
@@ -1144,7 +1399,7 @@ static CMD_FUNC(cmd_topic)
 
 static CMD_FUNC(cmd_num_topic)
 {
-    static struct chanNode *cn;
+    struct chanNode *cn;
 
     if (!argv[0])
         return 0; /* huh? */
@@ -1180,9 +1435,11 @@ static CMD_FUNC(cmd_num_topic)
 
 static CMD_FUNC(cmd_num_gline)
 {
+    time_t lastmod;
     if (argc < 6)
         return 0;
-    gline_add(origin, argv[3], atoi(argv[4])-now, argv[5], now, 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);
     return 1;
 }
 
@@ -1224,19 +1481,6 @@ static CMD_FUNC(cmd_kill)
     return 1;
 }
 
-static CMD_FUNC(cmd_part)
-{
-    struct userNode *user;
-
-    if (argc < 2)
-        return 0;
-    user = GetUserH(origin);
-    if (!user)
-        return 0;
-    parse_foreach(argv[1], part_helper, NULL, NULL, NULL, user);
-    return 1;
-}
-
 static CMD_FUNC(cmd_kick)
 {
     if (argc < 3)
@@ -1269,13 +1513,13 @@ static CMD_FUNC(cmd_squit)
 static CMD_FUNC(cmd_privmsg)
 {
     struct privmsg_desc pd;
-    if (argc != 3)
+    if (argc < 3)
         return 0;
     pd.user = GetUserH(origin);
     if (!pd.user || (IsGagged(pd.user) && !IsOper(pd.user)))
         return 1;
     pd.is_notice = 0;
-    pd.text = argv[2];
+    pd.text = argv[argc - 1];
     parse_foreach(argv[1], privmsg_chan_helper, NULL, privmsg_user_helper, privmsg_invalid, &pd);
     return 1;
 }
@@ -1283,13 +1527,13 @@ static CMD_FUNC(cmd_privmsg)
 static CMD_FUNC(cmd_notice)
 {
     struct privmsg_desc pd;
-    if (argc != 3)
+    if (argc < 3)
         return 0;
     pd.user = GetUserH(origin);
     if (!pd.user || (IsGagged(pd.user) && !IsOper(pd.user)))
         return 1;
     pd.is_notice = 1;
-    pd.text = argv[2];
+    pd.text = argv[argc - 1];
     parse_foreach(argv[1], privmsg_chan_helper, NULL, privmsg_user_helper, privmsg_invalid, &pd);
     return 1;
 }
@@ -1310,12 +1554,15 @@ static CMD_FUNC(cmd_away)
 
 static CMD_FUNC(cmd_gline)
 {
+    time_t lastmod;
+
     if (argc < 3)
         return 0;
     if (argv[2][0] == '+') {
         if (argc < 5)
             return 0;
-        gline_add(origin, argv[2]+1, strtoul(argv[3], NULL, 0), argv[argc-1], now, 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);
         return 1;
     } else if (argv[2][0] == '-') {
         gline_remove(argv[2]+1, 0);
@@ -1352,12 +1599,16 @@ parse_cleanup(void)
     unsigned int nn;
     free(of_list);
     free(privmsg_funcs);
+    num_privmsg_funcs = 0;
     free(notice_funcs);
+    num_notice_funcs = 0;
     free(mcf_list);
     dict_delete(irc_func_dict);
     for (nn=0; nn<dead_users.used; nn++)
         free_user(dead_users.list[nn]);
     userList_clean(&dead_users);
+    free(his_servername);
+    free(his_servercomment);
 }
 
 static void
@@ -1399,6 +1650,12 @@ init_parse(void)
         inttobase64(numer, (numnick << 12) + (usermask & 0x00fff), 3);
     else
         inttobase64(numer, (numnick << 18) + (usermask & 0x3ffff), 5);
+
+    str = conf_get_data("server/his_servername", RECDB_QSTRING);
+    his_servername = str ? strdup(str) : NULL;
+    str = conf_get_data("server/his_servercomment", RECDB_QSTRING);
+    his_servercomment = str ? strdup(str) : NULL;
+
     str = conf_get_data("server/hostname", RECDB_QSTRING);
     desc = conf_get_data("server/description", RECDB_QSTRING);
     if (!str || !desc) {
@@ -1423,6 +1680,8 @@ init_parse(void)
     dict_insert(irc_func_dict, TOK_NICK, cmd_nick);
     dict_insert(irc_func_dict, CMD_ACCOUNT, cmd_account);
     dict_insert(irc_func_dict, TOK_ACCOUNT, cmd_account);
+    dict_insert(irc_func_dict, CMD_FAKEHOST, cmd_fakehost);
+    dict_insert(irc_func_dict, TOK_FAKEHOST, cmd_fakehost);
     dict_insert(irc_func_dict, CMD_PASS, cmd_pass);
     dict_insert(irc_func_dict, TOK_PASS, cmd_pass);
     dict_insert(irc_func_dict, CMD_PING, cmd_ping);
@@ -1499,6 +1758,7 @@ init_parse(void)
     dict_insert(irc_func_dict, "331", cmd_num_topic);
     dict_insert(irc_func_dict, "332", cmd_num_topic);
     dict_insert(irc_func_dict, "333", cmd_num_topic);
+    dict_insert(irc_func_dict, "345", cmd_dummy); /* blah has been invited to blah */
     dict_insert(irc_func_dict, "432", cmd_error_nick); /* Erroneus [sic] nickname */
     /* ban list resetting */
     /* "stats g" responses */
@@ -1508,10 +1768,12 @@ init_parse(void)
     dict_insert(irc_func_dict, "401", cmd_dummy); /* target left network */
     dict_insert(irc_func_dict, "403", cmd_dummy); /* no such channel */
     dict_insert(irc_func_dict, "404", cmd_dummy); /* cannot send to channel */
+    dict_insert(irc_func_dict, "439", cmd_dummy); /* target change too fast */
     dict_insert(irc_func_dict, "441", cmd_dummy); /* target isn't on that channel */
     dict_insert(irc_func_dict, "442", cmd_dummy); /* you aren't on that channel */
     dict_insert(irc_func_dict, "443", cmd_dummy); /* is already on channel (after invite?) */
     dict_insert(irc_func_dict, "461", cmd_dummy); /* Not enough parameters (after TOPIC w/ 0 args) */
+    dict_insert(irc_func_dict, "467", cmd_dummy); /* Channel key already set */
 
     num_privmsg_funcs = 16;
     privmsg_funcs = malloc(sizeof(privmsg_func_t)*num_privmsg_funcs);
@@ -1566,15 +1828,18 @@ static void
 parse_foreach(char *target_list, foreach_chanfunc cf, foreach_nonchan nc, foreach_userfunc uf, foreach_nonuser nu, void *data)
 {
     char *j, old;
+
     do {
         j = target_list;
         while (*j != 0 && *j != ',')
             j++;
         old = *j;
         *j = 0;
+
         if (IsChannelName(target_list)
             || (target_list[0] == '0' && target_list[1] == '\0')) {
             struct chanNode *chan = GetChannel(target_list);
+
             if (chan) {
                 if (cf)
                     cf(chan, data);
@@ -1730,13 +1995,15 @@ void DelServer(struct server* serv, int announce, const char *message)
 }
 
 struct userNode *
-AddService(const char *nick, const char *desc)
+AddLocalUser(const char *nick, const char *ident, const char *hostname, const char *desc, const char *modes)
 {
     char numeric[COMBO_NUMERIC_LEN+1];
     int local_num = get_local_numeric();
     time_t timestamp = now;
     struct userNode *old_user = GetUserH(nick);
 
+    if (!modes)
+        modes = "+oik";
     if (old_user) {
         if (IsLocal(old_user))
             return old_user;
@@ -1746,8 +2013,10 @@ AddService(const char *nick, const char *desc)
         log_module(MAIN_LOG, LOG_ERROR, "Unable to allocate numnick for service %s", nick);
         return 0;
     }
+    if (!hostname)
+        hostname = self->name;
     make_numeric(self, local_num, numeric);
-    return AddUser(self, nick, nick, self->name, "+oik", numeric, desc, now, "AAAAAA");
+    return AddUser(self, nick, ident, hostname, modes, numeric, desc, now, "AAAAAA");
 }
 
 struct userNode *
@@ -1773,12 +2042,13 @@ AddClone(const char *nick, const char *ident, const char *hostname, const char *
 
 int
 is_valid_nick(const char *nick) {
+    unsigned int ii;
     /* IRC has some of The Most Fucked-Up ideas about character sets
      * in the world.. */
     if (!isalpha(*nick) && !strchr("{|}~[\\]^_`", *nick))
         return 0;
-    for (++nick; *nick; ++nick)
-        if (!isalnum(*nick) && !strchr("{|}~[\\]^-_`", *nick))
+    for (ii = 0; nick[ii]; ++ii)
+        if (!isalnum(nick[ii]) && !strchr("{|}~[\\]^-_`", nick[ii]))
             return 0;
     if (strlen(nick) > nicklen)
         return 0;
@@ -1789,7 +2059,7 @@ 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, time_t timestamp, const char *realip)
 {
     struct userNode *oldUser, *uNode;
-    unsigned int n, ignore_user;
+    unsigned int n, ignore_user, dummy;
 
     if ((strlen(numeric) < 3) || (strlen(numeric) > 5)) {
         log_module(MAIN_LOG, LOG_WARNING, "AddUser(%p, %s, ...): numeric %s wrong length!", uplink, nick, numeric);
@@ -1806,7 +2076,10 @@ AddUser(struct server* uplink, const char *nick, const char *ident, const char *
         return NULL;
     }
 
-    if (!is_valid_nick(nick)) {
+    dummy = modes && modes[0] == '*';
+    if (dummy) {
+        ++modes;
+    } else if (!is_valid_nick(nick)) {
         log_module(MAIN_LOG, LOG_WARNING, "AddUser(%p, %s, ...): invalid nickname detected.", uplink, nick);
         return NULL;
     }
@@ -1836,8 +2109,9 @@ AddUser(struct server* uplink, const char *nick, const char *ident, const char *
     safestrncpy(uNode->info, userinfo, sizeof(uNode->info));
     safestrncpy(uNode->hostname, hostname, sizeof(uNode->hostname));
     safestrncpy(uNode->numeric, numeric, sizeof(uNode->numeric));
-    uNode->ip.s_addr = htonl(base64toint(realip, 6));
+    irc_p10_pton(&uNode->ip, realip);
     uNode->timestamp = timestamp;
+    uNode->idle_since = timestamp;
     modeList_init(&uNode->channels);
     uNode->uplink = uplink;
     if (++uNode->uplink->clients > uNode->uplink->max_clients) {
@@ -1846,6 +2120,8 @@ AddUser(struct server* uplink, const char *nick, const char *ident, const char *
     uNode->num_local = base64toint(numeric+strlen(uNode->uplink->numeric), 3) & uNode->uplink->num_mask;
     uNode->uplink->users[uNode->num_local] = uNode;
     mod_usermode(uNode, modes);
+    if (dummy)
+        uNode->modes |= FLAGS_DUMMY;
     if (ignore_user)
         return uNode;
 
@@ -1868,12 +2144,14 @@ DelUser(struct userNode* user, struct userNode *killer, int announce, const char
 {
     unsigned int n;
 
+    verify(user);
+
     /* mark them as dead, in case anybody cares */
     user->dead = 1;
 
     /* remove user from all channels */
     while (user->channels.used > 0)
-        DelChannelUser(user, user->channels.list[user->channels.used-1]->channel, false, 0);
+        DelChannelUser(user, user->channels.list[user->channels.used-1]->channel, NULL, false);
 
     /* Call these in reverse order so ChanServ can update presence
        information before NickServ nukes the handle_info. */
@@ -1898,6 +2176,14 @@ DelUser(struct userNode* user, struct userNode *killer, int announce, const char
             irc_kill(killer, user, why);
     }
 
+    if (IsLocal(user)) {
+        unsigned int num = user->num_local;
+        if (num < num_privmsg_funcs)
+            privmsg_funcs[num] = NULL;
+        if (num < num_notice_funcs)
+            notice_funcs[num] = NULL;
+    }
+
     modeList_clean(&user->channels);
     /* We don't free them, in case we try to privmsg them or something
      * (like when a stupid oper kills themself).  We just put them onto
@@ -1909,15 +2195,16 @@ DelUser(struct userNode* user, struct userNode *killer, int announce, const char
         free_user(user);
 }
 
+static void call_oper_funcs(struct userNode *user);
+
 void mod_usermode(struct userNode *user, const char *mode_change) {
-    static void call_oper_funcs(struct userNode *user);
     int add = 1;
     const char *word = mode_change;
 
     if (!user || !mode_change)
         return;
-    while (*word != ' ' && *word) word++;\
-    while (*word == ' ') word++; \
+    while (*word != ' ' && *word) word++;
+    while (*word == ' ') word++;
     while (1) {
 #define do_user_mode(FLAG) do { if (add) user->modes |= FLAG; else user->modes &= ~FLAG; } while (0)
        switch (*mode_change++) {
@@ -1933,7 +2220,6 @@ void mod_usermode(struct userNode *user, const char *mode_change) {
                userList_remove(&curr_opers, user);
            }
            break;
-       case 'O': do_user_mode(FLAGS_LOCOP); break;
        case 'i': do_user_mode(FLAGS_INVISIBLE);
            if (add)
                 invis_clients++;
@@ -1941,11 +2227,11 @@ void mod_usermode(struct userNode *user, const char *mode_change) {
                 invis_clients--;
            break;
        case 'w': do_user_mode(FLAGS_WALLOP); break;
-       case 's': do_user_mode(FLAGS_SERVNOTICE); break;
        case 'd': do_user_mode(FLAGS_DEAF); break;
        case 'k': do_user_mode(FLAGS_SERVICE); break;
        case 'g': do_user_mode(FLAGS_GLOBAL); break;
-       case 'h': do_user_mode(FLAGS_HELPER); break;
+       case 'n': do_user_mode(FLAGS_NOCHAN); break;
+        case 'I': do_user_mode(FLAGS_NOIDLE); break;
         case 'x': do_user_mode(FLAGS_HIDDEN_HOST); break;
         case 'r':
             if (*word) {
@@ -1959,13 +2245,25 @@ void mod_usermode(struct userNode *user, const char *mode_change) {
                 call_account_func(user, tag);
             }
             break;
+        case 'f':
+            if (*word) {
+                char host[MAXLEN];
+                unsigned int ii;
+                for (ii=0; (*word != ' ') && (*word != '\0'); )
+                    host[ii++] = *word++;
+                host[ii] = 0;
+                while (*word == ' ')
+                    word++;
+                assign_fakehost(user, host, 0);
+            }
+            break;
        }
 #undef do_user_mode
     }
 }
 
 struct mod_chanmode *
-mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags)
+mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags, short base_oplevel)
 {
     struct mod_chanmode *change;
     unsigned int ii, in_arg, ch_arg, add;
@@ -1996,6 +2294,14 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
         case 'r': do_chan_mode(MODE_REGONLY); break;
         case 's': do_chan_mode(MODE_SECRET); break;
         case 't': do_chan_mode(MODE_TOPICLIMIT); break;
+       case 'z':
+         if (!(flags & MCP_REGISTERED)) {
+          do_chan_mode(MODE_REGISTERED);
+         } else {
+          mod_chanmode_free(change);
+          return NULL;
+         }
+         break;
 #undef do_chan_mode
         case 'l':
             if (add) {
@@ -2023,6 +2329,37 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
                 }
             }
             break;
+        case 'U':
+            if (add)
+            {
+              if (in_arg >= argc)
+                  goto error;
+              change->modes_set |= MODE_UPASS;
+              safestrncpy(change->new_upass, modes[in_arg++], sizeof(change->new_upass));
+            } else {
+                change->modes_clear |= MODE_UPASS;
+                if (!(flags & MCP_UPASS_FREE)) {
+                    if (in_arg >= argc)
+                        goto error;
+                    in_arg++;
+                }
+            }
+            break;
+        case 'A':
+            if (add) {
+                if (in_arg >= argc)
+                    goto error;
+                change->modes_set |= MODE_APASS;
+                safestrncpy(change->new_apass, modes[in_arg++], sizeof(change->new_apass));
+            } else {
+                change->modes_clear |= MODE_APASS;
+                if (!(flags & MCP_APASS_FREE)) {
+                    if (in_arg >= argc)
+                      goto error;
+                    in_arg++;
+                }
+            }
+            break;
         case 'b':
             if (!(flags & MCP_ALLOW_OVB))
                 goto error;
@@ -2031,11 +2368,34 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
             change->args[ch_arg].mode = MODE_BAN;
             if (!add)
                 change->args[ch_arg].mode |= MODE_REMOVE;
-            change->args[ch_arg++].hostmask = modes[in_arg++];
+            change->args[ch_arg++].u.hostmask = modes[in_arg++];
             break;
         case 'o': case 'v':
         {
             struct userNode *victim;
+            char *oplevel_str;
+            int oplevel;
+
+            if (in_arg >= argc)
+                goto error;
+            oplevel_str = strchr(modes[in_arg], ':');
+            if (oplevel_str)
+            {
+                /* XXYYY M #channel +o XXYYY:<oplevel> */
+                *oplevel_str++ = '\0';
+                oplevel = parse_oplevel(oplevel_str);
+                if (oplevel <= base_oplevel && !(flags & MCP_FROM_SERVER))
+                    oplevel = base_oplevel + 1;
+            }
+            else if (channel->modes & MODE_UPASS)
+                oplevel = base_oplevel + 1;
+            else
+                oplevel = -1;
+
+            /* Check that oplevel is within bounds. */
+            if (oplevel > MAXOPLEVEL)
+                oplevel = MAXOPLEVEL;
+
             if (!(flags & MCP_ALLOW_OVB))
                 goto error;
             if (in_arg >= argc)
@@ -2049,10 +2409,18 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
                 victim = GetUserH(modes[in_arg++]);
             if (!victim)
                 continue;
-            if ((change->args[ch_arg].member = GetUserMode(channel, victim)))
+            if ((change->args[ch_arg].u.member = GetUserMode(channel, victim)))
+            {
+                /* Apply the oplevel change */
+                change->args[ch_arg].u.member->oplevel = oplevel;
                 ch_arg++;
+            }
             break;
         }
+        default:
+            if (!(flags & MCP_FROM_SERVER))
+                goto error;
+            break;
         }
     }
     change->argc = ch_arg; /* in case any turned out to be ignored */
@@ -2113,7 +2481,9 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod
     chbuf.channel = channel;
     chbuf.actor = who;
     chbuf.chname_len = strlen(channel->name);
-    if ((mn = GetUserMode(channel, who)) && (mn->modes & MODE_CHANOP))
+
+    mn = GetUserMode(channel, who);
+    if ((mn && (mn->modes & MODE_CHANOP)) || off_channel)
         chbuf.is_chanop = 1;
 
     /* First remove modes */
@@ -2133,9 +2503,14 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod
         DO_MODE_CHAR(REGONLY, 'r');
         DO_MODE_CHAR(NOCOLORS, 'c');
         DO_MODE_CHAR(NOCTCPS, 'C');
+        DO_MODE_CHAR(REGISTERED, 'z');
 #undef DO_MODE_CHAR
         if (change->modes_clear & channel->modes & MODE_KEY)
             mod_chanmode_append(&chbuf, 'k', channel->key);
+        if (change->modes_clear & channel->modes & MODE_UPASS)
+            mod_chanmode_append(&chbuf, 'U', channel->upass);
+        if (change->modes_clear & channel->modes & MODE_APASS)
+            mod_chanmode_append(&chbuf, 'A', channel->apass);
     }
     for (arg = 0; arg < change->argc; ++arg) {
         if (!(change->args[arg].mode & MODE_REMOVE))
@@ -2144,13 +2519,13 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod
             chbuf.modes[chbuf.modes_used++] = mode = '-';
         switch (change->args[arg].mode & ~MODE_REMOVE) {
         case MODE_BAN:
-            mod_chanmode_append(&chbuf, 'b', change->args[arg].hostmask);
+            mod_chanmode_append(&chbuf, 'b', change->args[arg].u.hostmask);
             break;
         default:
             if (change->args[arg].mode & MODE_CHANOP)
-                mod_chanmode_append(&chbuf, 'o', change->args[arg].member->user->numeric);
+                mod_chanmode_append(&chbuf, 'o', change->args[arg].u.member->user->numeric);
             if (change->args[arg].mode & MODE_VOICE)
-                mod_chanmode_append(&chbuf, 'v', change->args[arg].member->user->numeric);
+                mod_chanmode_append(&chbuf, 'v', change->args[arg].u.member->user->numeric);
             break;
         }
     }
@@ -2171,9 +2546,14 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod
         DO_MODE_CHAR(REGONLY, 'r');
         DO_MODE_CHAR(NOCOLORS, 'c');
         DO_MODE_CHAR(NOCTCPS, 'C');
+        DO_MODE_CHAR(REGISTERED, 'z');
 #undef DO_MODE_CHAR
         if(change->modes_set & MODE_KEY)
             mod_chanmode_append(&chbuf, 'k', change->new_key);
+        if (change->modes_set & MODE_UPASS)
+            mod_chanmode_append(&chbuf, 'U', change->new_upass);
+        if (change->modes_set & MODE_APASS)
+            mod_chanmode_append(&chbuf, 'A', change->new_apass);
         if(change->modes_set & MODE_LIMIT) {
             sprintf(int_buff, "%d", change->new_limit);
             mod_chanmode_append(&chbuf, 'l', int_buff);
@@ -2186,13 +2566,13 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod
             chbuf.modes[chbuf.modes_used++] = mode = '+';
         switch (change->args[arg].mode) {
         case MODE_BAN:
-            mod_chanmode_append(&chbuf, 'b', change->args[arg].hostmask);
+            mod_chanmode_append(&chbuf, 'b', change->args[arg].u.hostmask);
             break;
         default:
             if (change->args[arg].mode & MODE_CHANOP)
-                mod_chanmode_append(&chbuf, 'o', change->args[arg].member->user->numeric);
+                mod_chanmode_append(&chbuf, 'o', change->args[arg].u.member->user->numeric);
             if (change->args[arg].mode & MODE_VOICE)
-                mod_chanmode_append(&chbuf, 'v', change->args[arg].member->user->numeric);
+                mod_chanmode_append(&chbuf, 'v', change->args[arg].u.member->user->numeric);
             break;
         }
     }
@@ -2222,10 +2602,13 @@ mod_chanmode_format(struct mod_chanmode *change, char *outbuff)
         DO_MODE_CHAR(NOPRIVMSGS, 'n');
         DO_MODE_CHAR(LIMIT, 'l');
         DO_MODE_CHAR(KEY, 'k');
+        DO_MODE_CHAR(UPASS, 'U');
+        DO_MODE_CHAR(APASS, 'A');
         DO_MODE_CHAR(DELAYJOINS, 'D');
         DO_MODE_CHAR(REGONLY, 'r');
         DO_MODE_CHAR(NOCOLORS, 'c');
         DO_MODE_CHAR(NOCTCPS, 'C');
+        DO_MODE_CHAR(REGISTERED, 'z');
 #undef DO_MODE_CHAR
     }
     if (change->modes_set) {
@@ -2241,11 +2624,53 @@ mod_chanmode_format(struct mod_chanmode *change, char *outbuff)
         DO_MODE_CHAR(REGONLY, 'r');
         DO_MODE_CHAR(NOCOLORS, 'c');
         DO_MODE_CHAR(NOCTCPS, 'C');
+        DO_MODE_CHAR(REGISTERED, 'z');
 #undef DO_MODE_CHAR
-        switch (change->modes_set & (MODE_KEY|MODE_LIMIT)) {
+        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;
@@ -2277,6 +2702,14 @@ clear_chanmode(struct chanNode *channel, const char *modes)
             remove |= MODE_KEY;
             channel->key[0] = '\0';
             break;
+        case 'A':
+            remove |= MODE_APASS;
+            channel->apass[0] = '\0';
+            break;
+        case 'U':
+            remove |= MODE_UPASS;
+            channel->upass[0] = '\0';
+            break;
         case 'l':
             remove |= MODE_LIMIT;
             channel->limit = 0;
@@ -2284,8 +2717,9 @@ clear_chanmode(struct chanNode *channel, const char *modes)
         case 'b': remove |= MODE_BAN; break;
         case 'D': remove |= MODE_DELAYJOINS; break;
         case 'r': remove |= MODE_REGONLY; break;
-        case 'c': remove |= MODE_NOCOLORS;
+        case 'c': remove |= MODE_NOCOLORS; break;
         case 'C': remove |= MODE_NOCTCPS; break;
+        case 'z': remove |= MODE_REGISTERED; break;
         }
     }
 
@@ -2320,9 +2754,10 @@ reg_privmsg_func(struct userNode *user, privmsg_func_t handler)
 {
     unsigned int numeric = user->num_local;
     if (numeric >= num_privmsg_funcs) {
-        int newnum = numeric + 8;
+        int newnum = numeric + 8, ii;
         privmsg_funcs = realloc(privmsg_funcs, newnum*sizeof(privmsg_func_t));
-        memset(privmsg_funcs+num_privmsg_funcs, 0, (newnum-num_privmsg_funcs)*sizeof(privmsg_func_t));
+        for (ii = num_privmsg_funcs; ii < newnum; ++ii)
+            privmsg_funcs[ii] = NULL;
         num_privmsg_funcs = newnum;
     }
     if (privmsg_funcs[numeric])
@@ -2330,14 +2765,25 @@ reg_privmsg_func(struct userNode *user, privmsg_func_t handler)
     privmsg_funcs[numeric] = handler;
 }
 
+void
+unreg_privmsg_func(struct userNode *user)
+{
+    if (!IsLocal(user) || user->num_local >= num_privmsg_funcs)
+        return; /* this really only works with users */
+
+    privmsg_funcs[user->num_local] = NULL;
+}
+
+
 void
 reg_notice_func(struct userNode *user, privmsg_func_t handler)
 {
     unsigned int numeric = user->num_local;
     if (numeric >= num_notice_funcs) {
-        int newnum = numeric + 8;
+        int newnum = numeric + 8, ii;
         notice_funcs = realloc(notice_funcs, newnum*sizeof(privmsg_func_t));
-        memset(notice_funcs+num_notice_funcs, 0, (newnum-num_notice_funcs)*sizeof(privmsg_func_t));
+        for (ii = num_privmsg_funcs; ii < newnum; ++ii)
+            privmsg_funcs[ii] = NULL;
         num_notice_funcs = newnum;
     }
     if (notice_funcs[numeric])
@@ -2345,6 +2791,15 @@ reg_notice_func(struct userNode *user, privmsg_func_t handler)
     notice_funcs[numeric] = handler;
 }
 
+void
+unreg_notice_func(struct userNode *user)
+{
+    if (!IsLocal(user) || user->num_local >= num_privmsg_funcs)
+        return; /* this really only works with users */
+
+    notice_funcs[user->num_local] = NULL;
+}
+
 void
 reg_oper_func(oper_func_t handler)
 {
@@ -2399,3 +2854,15 @@ 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;
+}