]> jfr.im git - irc/evilnet/x3.git/blobdiff - src/proto-p10.c
Changed "sslfp" to "certfp" and "SSL fingerprint" to "client certificate fingerprint...
[irc/evilnet/x3.git] / src / proto-p10.c
index d9944d7bd835b8586ae945397a65f4785a2c9753..731b1cf7d477028158f0f6fba94e2e17b64600cb 100644 (file)
 #define CMD_PROTO               "PROTO"
 #define CMD_QUIT                "QUIT"
 #define CMD_REHASH              "REHASH"
+#define CMD_REMOVE             "REMOVE"
 #define CMD_RESET              "RESET"
 #define CMD_RESTART             "RESTART"
 #define CMD_RPING               "RPING"
 #define CMD_RPONG               "RPONG"
+#define CMD_SASL                "SASL"
 #define CMD_SERVER              "SERVER"
 #define CMD_SERVLIST            "SERVLIST"
 #define CMD_SERVSET             "SERVSET"
@@ -95,6 +97,7 @@
 #define CMD_SMO                 "SMO"
 #define CMD_SNO                 "SNO"
 #define CMD_SSHUN              "SSHUN"
+#define CMD_SPAMFILTER          "SPAMFILTER"
 #define CMD_SQUERY              "SQUERY"
 #define CMD_SQUIT               "SQUIT"
 #define CMD_STATS               "STATS"
 #define CMD_SVSPART             "SVSPART"
 #define CMD_SVSQUIT             "SVSQUIT"
 #define CMD_SWHOIS              "SWHOIS"
+#define CMD_TEMPSHUN            "TEMPSHUN"
 #define CMD_TIME                "TIME"
 #define CMD_TOPIC               "TOPIC"
 #define CMD_TRACE               "TRACE"
 #define CMD_WHO                 "WHO"
 #define CMD_WHOIS               "WHOIS"
 #define CMD_WHOWAS              "WHOWAS"
+#define CMD_ZLINE              "ZLINE"
 
 /* Tokenized commands. */
 #define TOK_ACCOUNT            "AC"
 #define TOK_PROTO               "PROTO"
 #define TOK_QUIT                "Q"
 #define TOK_REHASH              "REHASH"
+#define TOK_REMOVE             "RM"
 #define TOK_RESET              "RESET"
 #define TOK_RESTART             "RESTART"
 #define TOK_RPING               "RI"
 #define TOK_RPONG               "RO"
+#define TOK_SASL                "SASL"
 #define TOK_SERVER              "S"
 #define TOK_SERVLIST            "SERVSET"
 #define TOK_SERVSET             "SERVSET"
 #define TOK_SMO                 "SMO"
 #define TOK_SNO                 "SNO"
 #define TOK_SSHUN              "SSU"
+#define TOK_SPAMFILTER          "SF"
 #define TOK_SQUERY              "SQUERY"
 #define TOK_SQUIT               "SQ"
 #define TOK_STATS               "R"
 #define TOK_SVSPART             "SP"
 #define TOK_SVSQUIT             "SX"
 #define TOK_SWHOIS              "SW"
+#define TOK_TEMPSHUN            "TS"
 #define TOK_TIME                "TI"
 #define TOK_TOPIC               "T"
 #define TOK_TRACE               "TR"
 #define TOK_WHO                 "H"
 #define TOK_WHOIS               "W"
 #define TOK_WHOWAS              "X"
+#define TOK_ZLINE              "ZL"
 
 /* Protocol messages; aliased to full commands or tokens depending
    on compile-time configuration. ircu prefers tokens WITH THE
 #define P10_PROTO               TYPE(PROTO)
 #define P10_QUIT                TYPE(QUIT)
 #define P10_REHASH              TYPE(REHASH)
+#define P10_REMOVE             TYPE(REMOVE)
 #define P10_RESET              TYPE(RESET)
 #define P10_RESTART             TYPE(RESTART)
 #define P10_RPING               TYPE(RPING)
 #define P10_RPONG               TYPE(RPONG)
+#define P10_SASL                TYPE(SASL)
 #define P10_SERVER              CMD_SERVER
 #define P10_SERVLIST            TYPE(SERVLIST)
 #define P10_SERVSET             TYPE(SERVSET)
 #define P10_SMO                 TYPE(SMO)
 #define P10_SNO                 TYPE(SNO)
 #define P10_SSHUN              TYPE(SSHUN)
+#define P10_SPAMFILTER          TYPE(SPAMFILTER)
 #define P10_SQUERY              TYPE(SQUERY)
 #define P10_SQUIT               TYPE(SQUIT)
 #define P10_STATS               TYPE(STATS)
 #define P10_SVSPART             TYPE(SVSPART)
 #define P10_SVSQUIT            TYPE(SVSQUIT)
 #define P10_SWHOIS              TYPE(SWHOIS)
+#define P10_TEMPSHUN            TYPE(TEMPSHUN)
 #define P10_TIME                TYPE(TIME)
 #define P10_TOPIC               TYPE(TOPIC)
 #define P10_TRACE               TYPE(TRACE)
 #define P10_WHO                 TYPE(WHO)
 #define P10_WHOIS               TYPE(WHOIS)
 #define P10_WHOWAS              TYPE(WHOWAS)
+#define P10_ZLINE              TYPE(ZLINE)
 #define P10_EXEMPT             TYPE(EXEMPT)
 
 /* Servers claiming to have a boot or link time before PREHISTORY
@@ -336,10 +351,13 @@ 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 const char *his_servername;
+static const char *his_servercomment;
 static int extended_accounts;
 
+/* These correspond to 1 << X:      01234567890123456789012345 */
+const char irc_user_mode_chars[] = "oOiw dkgh    x  BnIX  azDRWHLq";
+
 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;
@@ -484,11 +502,11 @@ irc_server(struct server *srv)
 
     inttobase64(extranum, srv->num_mask, (srv->numeric[1] || (srv->num_mask >= 64*64)) ? 3 : 2);
     if (srv == self) {
-        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);
+        putsock(P10_SERVER " %s %d " FMT_TIME_T " " FMT_TIME_T " J10 %s%s +s6o :%s",
+                srv->name, srv->hops+1, srv->boot, srv->link_time, srv->numeric, extranum, srv->description);
     } else {
-        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);
+        putsock("%s " P10_SERVER " %s %d " FMT_TIME_T " " FMT_TIME_T " %c10 %s%s +s6o :%s",
+                self->numeric, srv->name, srv->hops+1, srv->boot, srv->link_time, (srv->self_burst ? 'J' : 'P'), srv->numeric, extranum, srv->description);
     }
 }
 
@@ -508,7 +526,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 {
@@ -571,52 +589,18 @@ void
 irc_user(struct userNode *user)
 {
     char b64ip[25];
-    if (!user)
+    if (!user || IsDummy(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 (IsServNotice(user))
-            modes[modelen++] = 's';
-        if (IsDeaf(user))
-            modes[modelen++] = 'd';
-        if (IsGlobal(user))
-            modes[modelen++] = 'g';
-        // sethost - reed/apples
-        // if (IsHelperIrcu(user))
-        if (IsSetHost(user))
-            modes[modelen++] = 'h';
-        if (IsFakeHost(user))
-            modes[modelen++] = 'f';
-        if (IsHiddenHost(user))
-            modes[modelen++] = 'x';
-        if (IsBotM(user))
-            modes[modelen++] = 'B';
-        if (IsHideChans(user))
-            modes[modelen++] = 'n';
-        if (IsHideIdle(user))
-            modes[modelen++] = 'I';
-        if (IsXtraOp(user))
-            modes[modelen++] = 'X';
-
-        modes[modelen] = 0;
-
+        irc_user_modes(user, modes, sizeof(modes));
         /* we don't need to put the + in modes because it's in the format string. */
-        putsock("%s " P10_NICK " %s %d %li %s %s +%s %s %s :%s",
+        putsock("%s " P10_NICK " %s %d " FMT_TIME_T " %s %s +%s %s %s :%s",
                 user->uplink->numeric, user->nick, user->uplink->hops+1, user->timestamp, user->ident, user->hostname, modes, b64ip, user->numeric, user->info);
     } else {
-        putsock("%s " P10_NICK " %s %d %li %s %s %s %s :%s",
+        putsock("%s " P10_NICK " %s %d " FMT_TIME_T " %s %s %s %s :%s",
                 user->uplink->numeric, user->nick, user->uplink->hops+1, user->timestamp, user->ident, user->hostname, b64ip, user->numeric, user->info);
     }
 }
@@ -639,9 +623,9 @@ void
 irc_account(struct userNode *user, const char *stamp, time_t timestamp)
 {
     if(extended_accounts)
-        putsock("%s " P10_ACCOUNT " %s R %s %lu", self->numeric, user->numeric, stamp, timestamp); 
+        putsock("%s " P10_ACCOUNT " %s R %s "FMT_TIME_T, self->numeric, user->numeric, stamp, timestamp); 
     else
-        putsock("%s " P10_ACCOUNT " %s %s %lu", self->numeric, user->numeric, stamp, timestamp);
+        putsock("%s " P10_ACCOUNT " %s %s "FMT_TIME_T, self->numeric, user->numeric, stamp, timestamp);
 }
 
 void
@@ -715,23 +699,48 @@ irc_wallops(const char *format, ...)
     putsock("%s " P10_WALLOPS " :%s", self->numeric, buffer);
 }
 
+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)
 {
-    putsock("%s " P10_NOTICE " %s :%s", from->numeric, to->numeric, 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
@@ -793,14 +802,14 @@ irc_pong_asll(const char *who, const char *orig_ts)
 {
     char *delim;
     struct timeval orig;
-    struct timeval now;
+    struct timeval sys_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);
+    gettimeofday(&sys_now, NULL);
+    diff = (sys_now.tv_sec - orig.tv_sec) * 1000 + (sys_now.tv_usec - orig.tv_usec) / 1000;
+    putsock("%s " P10_PONG " %s %s %d %lu.%06lu", self->numeric, who, orig_ts, diff, (unsigned long)sys_now.tv_sec, (unsigned long)sys_now.tv_usec);
 }
 
 void
@@ -824,15 +833,15 @@ irc_introduce(const char *passwd)
 void
 irc_gline(struct server *srv, struct gline *gline, int silent)
 {
-    putsock("%s " P10_GLINE " %s +%s %ld %ld :%s<%s> %s",
+    putsock("%s " P10_GLINE " %s +%s " FMT_TIME_T " " FMT_TIME_T " :%s<%s> %s",
             self->numeric, (srv ? srv->numeric : "*"), gline->target, gline->expires-now, now, silent ? "AUTO " : "", gline->issuer, gline->reason);
 }
 
 void
 irc_shun(struct server *srv, struct shun *shun)
 {
-    putsock("%s " P10_SHUN " %s +%s %ld :<%s> %s",
-            self->numeric, (srv ? srv->numeric : "*"), shun->target, shun->expires-now, shun->issuer, shun->reason);
+    putsock("%s " P10_SHUN " %s +%s " FMT_TIME_T " " FMT_TIME_T " :<%s> %s",
+            self->numeric, (srv ? srv->numeric : "*"), shun->target, shun->expires-now, now, shun->issuer, shun->reason);
 }
 
 void
@@ -847,16 +856,16 @@ irc_settime(const char *srv_name_mask, time_t new_time)
 void
 irc_ungline(const char *mask)
 {
-    putsock("%s " P10_GLINE " * -%s", self->numeric, mask);
+    putsock("%s " P10_GLINE " * -%s " FMT_TIME_T, self->numeric, mask, now);
 }
 
 void
 irc_unshun(const char *mask)
 {
-    putsock("%s " P10_SHUN " * -%s", self->numeric, mask);
+    putsock("%s " P10_SHUN " * -%s " FMT_TIME_T, self->numeric, mask, now);
 }
 
-static void
+void
 irc_burst(struct chanNode *chan)
 {
     char burst_line[512];
@@ -865,13 +874,15 @@ irc_burst(struct chanNode *chan)
     struct banNode *bn;
     struct exemptNode *en;
     long last_mode=-1;
+    unsigned int first_ban;
+    unsigned int first_exempt;
     unsigned int n;
 
     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++] = ' ';
 
     if(chan->members.used < 1)
@@ -901,57 +912,64 @@ irc_burst(struct chanNode *chan)
         if ((n+1)<chan->members.used)
             burst_line[pos++] = ',';
     }
+
+    if (len > 0 && (chan->banlist.used > 0 || chan->exemptlist.used > 0))
+        burst_line[pos++] = ' ';
+
     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 {
-            burst_line[pos++] = ' ';
-        }
+        first_ban = 1;
 
-        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++] = ' ';
         }
     }
-    if (chan->exemptlist.used) {
-        /* dump the exempt */
-        if (pos+2+strlen(chan->exemptlist.list[0]->exempt) > 505) {
-            burst_line[pos-1] = 0;
-            putsock("%s", burst_line);
-            pos = base_len;
-        } else {
-            burst_line[pos++] = ' ';
-        }
 
-        burst_line[pos++] = ' ';
-        burst_line[pos++] = '~';
-        burst_line[pos++] = ' ';
-        base_len = pos;
-        for (n=0; n<chan->exemptlist.used; n++) {
+    if (chan->exemptlist.used) {
+        /* dump the exempts */
+        first_exempt = 1;
+
+        for (n=0; n<chan->exemptlist.used; ) {
+            if (first_exempt && (pos < 500)) {
+                if (chan->banlist.used < 1) {
+                    burst_line[pos++] = ':';
+                    burst_line[pos++] = '%';
+                    burst_line[pos++] = ' ';
+                }
+                burst_line[pos++] = '~';
+                burst_line[pos++] = ' ';
+            }
             en = chan->exemptlist.list[n];
             len = strlen(en->exempt);
-            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, en->exempt, len);
+                pos += len;
+                burst_line[pos++] = ' ';
+                first_exempt = 0;
+                n++;
+            } else {
+                burst_line[pos-1] = 0;
                 putsock("%s", burst_line);
                 pos = base_len;
+                first_exempt = 1;
             }
-            memcpy(burst_line+pos, en->exempt, len);
-            pos += len;
-            burst_line[pos++] = ' ';
         }
     }
     /* print the last line */
@@ -1022,10 +1040,10 @@ void
 irc_join(struct userNode *who, struct chanNode *what)
 {
     if (what->members.used == 1) {
-        putsock("%s " P10_CREATE " %s %lu",
+        putsock("%s " P10_CREATE " %s "FMT_TIME_T,
                 who->numeric, what->name, what->timestamp);
     } else {
-        putsock("%s " P10_JOIN " %s %lu", who->numeric, what->name, what->timestamp);
+        putsock("%s " P10_JOIN " %s "FMT_TIME_T, who->numeric, what->name, what->timestamp);
     }
 }
 
@@ -1042,7 +1060,7 @@ irc_svspart(struct userNode *from, struct userNode *who, struct chanNode *to)
 }
 
 void
-irc_svsquit(struct userNode *from, struct userNode *who, char *reason)
+irc_svsquit(struct userNode *from, struct userNode *who, char const *reason)
 {
     putsock("%s " P10_SVSQUIT " %s :%s", from->uplink->numeric, who->numeric, reason);
 }
@@ -1077,6 +1095,12 @@ irc_swhois(struct userNode *from, struct userNode *target, const char *message)
 
 }
 
+void
+irc_tempshun(struct userNode *from, struct userNode *target, int remove, const char *reason)
+{
+    putsock("%s " P10_TEMPSHUN " %s %s :%s", from->numeric, (remove ? "-" : "+"), target->numeric, reason);
+}
+
 void
 irc_part(struct userNode *who, struct chanNode *what, const char *reason)
 {
@@ -1091,32 +1115,44 @@ void
 irc_topic(struct userNode *service, struct userNode *who, struct chanNode *what, const char *topic)
 {
 
-   int type = 4;
-   int host_in_topic = 0;
-   const char *hstr, *tstr;
+   int type = 4, host_in_topic = 0, hasident = 0, hhtype = 0;
+   const char *hstr, *tstr, *hhstr, *htstr;
    char *host, *hostmask;
    char shost[MAXLEN];
    char sident[MAXLEN];
 
    tstr = conf_get_data("server/type", RECDB_QSTRING);
    hstr = conf_get_data("server/host_in_topic", RECDB_QSTRING);
+   hhstr = conf_get_data("server/hidden_host", RECDB_QSTRING);
+   htstr = conf_get_data("server/hidden_host_type", RECDB_QSTRING);
    if(tstr)
      type = atoi(tstr);
    else
      type = 4;/* default to 040 style topics */
+   if (htstr)
+     hhtype = atoi(htstr);
 
    if (hstr) {
-      if (IsFakeHost(who))
+      if (IsHiddenHost(who) && IsFakeHost(who))
           safestrncpy(shost, who->fakehost, sizeof(shost));
-      else if (IsSetHost(who)) {
+      else if (IsHiddenHost(who) && IsSetHost(who)) {
           hostmask = strdup(who->sethost);
-          if ((host = (strrchr(hostmask, '@'))))
+          if ((host = (strrchr(hostmask, '@')))) {
+              hasident = 1;
               *host++ = '\0';
-          else
+          else
               host = hostmask;
 
-          safestrncpy(sident, hostmask, sizeof(shost));
+          if (hasident)
+              safestrncpy(sident, hostmask, sizeof(shost));
+          else
+              safestrncpy(sident, who->ident, sizeof(shost));
+
           safestrncpy(shost, host, sizeof(shost));
+      } else if (IsHiddenHost(who) && ((hhtype == 1) || (hhtype == 3)) && who->handle_info && hhstr) {
+          snprintf(shost, sizeof(shost), "%s.%s", who->handle_info->handle, hhstr);
+      } else if (IsHiddenHost(who) && ((hhtype == 2) || (hhtype == 3)) && who->crypthost[0]) {
+          safestrncpy(shost, who->crypthost, sizeof(shost));
       } else
           safestrncpy(shost, who->hostname, sizeof(shost));
 
@@ -1154,6 +1190,34 @@ void
 irc_mark(struct userNode *user, char *mark)
 {
     char *host = user->hostname;
+    int type = 4;
+    const char *tstr = NULL;
+    unsigned int ii = 0;
+    int markfound = 0;
+
+    tstr = conf_get_data("server/type", RECDB_QSTRING);
+    if(tstr)
+        type = atoi(tstr);
+    else
+        type = 4;
+
+    if (user->marks)
+        for (ii=0; ii<user->marks->used; ii++)
+            if (!irccasecmp(user->marks->list[ii], mark))
+                markfound = 1;
+
+    if (!markfound)
+    {
+        if (!user->marks)
+            user->marks = alloc_string_list(1);
+        string_list_append(user->marks, strdup(mark));
+    }
+
+    if (type >= 9)
+    {
+        putsock("%s " CMD_MARK " %s MARK %s", self->numeric, user->nick, mark);
+        return;
+    }
 
     /* TODO: Allow mark overwrite. If they are marked, and their fakehost is oldmark.hostname, update it to newmark.hostname so mark can be called multiple times. Probably requires ircd modification also */
     if(user->mark)
@@ -1192,6 +1256,21 @@ irc_mark(struct userNode *user, char *mark)
     }
 }
 
+void irc_sno(unsigned int mask, char const* format, ...) {
+    va_list arg_list;
+    char buffer[MAXLEN];
+    va_start(arg_list, format);
+    vsnprintf(buffer, MAXLEN-2, format, arg_list);
+    buffer[MAXLEN-1] = 0;
+    putsock("%s " CMD_SNO " %d :%s", self->numeric, mask, buffer);
+}
+
+void
+irc_sasl(struct server* dest, const char *identifier, const char *subcmd, const char *data)
+{
+    putsock("%s " P10_SASL " %s %s %s %s", self->numeric, dest->numeric, identifier, subcmd, data);
+}
+
 static void send_burst(void);
 
 static void
@@ -1216,6 +1295,8 @@ static CMD_FUNC(cmd_whois)
 {
     struct userNode *from;
     struct userNode *who;
+    char buf[MAXLEN];
+    unsigned int i, mlen, len;
 
     if (argc < 3)
         return 0;
@@ -1223,21 +1304,75 @@ static CMD_FUNC(cmd_whois)
         log_module(MAIN_LOG, LOG_ERROR, "Could not find WHOIS origin user %s", origin);
         return 0;
     }
-    if(!(who = GetUserH(argv[2]))) {
+    if (!(who = GetUserH(argv[2]))) {
         irc_numeric(from, ERR_NOSUCHNICK, "%s@%s :No such nick", argv[2], self->name);
         return 1;
     }
-    if (IsHiddenHost(who) && !IsOper(from)) {
-        /* Just stay quiet. */
-        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) || (from == who)) {
+        struct modeNode *mn;
+        mlen = strlen(self->name) + strlen(from->nick) + 12 + strlen(who->nick);
+        len = 0;
+        *buf = '\0';
+        for (i = who->channels.used; i > 0; )
+        {
+            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_HALFOP)
+                *(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);
     }
-    irc_numeric(from, RPL_WHOISUSER, "%s %s %s * :%s", who->nick, who->ident, who->hostname, who->info);
-    if (his_servername && his_servercomment)
+
+    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 :is a megalomaniacal power hungry tyrant", who->nick);
+        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) && (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;
 }
@@ -1284,7 +1419,8 @@ static CMD_FUNC(cmd_server)
              * Alternately, we are same age, but we accept their time
              * since we are linking to them. */
             self->boot = srv->boot;
-            ioset_set_time(srv->link);
+            ioset_set_time(srv->link_time
+);
         }
     }
     if (srv == self->uplink) {
@@ -1299,7 +1435,6 @@ static CMD_FUNC(cmd_eob)
 {
     struct server *sender;
     dict_iterator_t it;
-    unsigned int ii;
 
     if (!(sender = GetServerH(origin)))
         return 0;
@@ -1320,8 +1455,7 @@ static CMD_FUNC(cmd_eob)
     }
     sender->self_burst = 0;
     recalc_bursts(sender);
-    for (ii=0; ii<slf_used; ii++)
-        slf_list[ii](sender);
+    call_server_link_funcs(sender);
     /* let auto-routing figure out if we were
      * wating on this server to link a child to it */
     /* DONT call this if uplink is _US_ */
@@ -1355,6 +1489,22 @@ static CMD_FUNC(cmd_rping)
     return 1;
 }
 
+static CMD_FUNC(cmd_sasl)
+{
+    struct server *serv;
+
+    if (argc < 5)
+        return 0;
+
+    serv = GetServerH(origin);
+    if (!serv)
+      return 0;
+
+    call_sasl_input_func(serv, argv[2], argv[3], argv[4], (argc>5 ? argv[argc-1] : NULL));
+
+    return 1;
+}
+
 static CMD_FUNC(cmd_ping)
 {
     struct server *srv;
@@ -1512,12 +1662,13 @@ static CMD_FUNC(cmd_account)
     
     if(!strcmp(argv[2],"C"))
     {
-        if((hi = loc_auth(argv[4], argv[5], NULL)))
+        if((hi = loc_auth(NULL, argv[4], argv[5], NULL)))
         {
             /* Return a AC A */
-            putsock("%s " P10_ACCOUNT " %s A %s %lu", self->numeric, server->numeric , argv[3], hi->registered);
+            putsock("%s " P10_ACCOUNT " %s A %s "FMT_TIME_T, self->numeric, server->numeric , argv[3], hi->registered);
 
-        }
+                       log_module(MAIN_LOG, LOG_DEBUG, "loc_auth: %s\n", user->nick);
+         }
         else
         {
             /* Return a AC D */
@@ -1527,11 +1678,24 @@ static CMD_FUNC(cmd_account)
     }
     else if(!strcmp(argv[2],"H")) /* New enhanced (host) version of C */
     {
-        if((hi = loc_auth(argv[5], argv[6], argv[4] )))
+        if((hi = loc_auth(NULL, argv[5], argv[6], argv[4] )))
         {
             /* Return a AC A */
-            putsock("%s " P10_ACCOUNT " %s A %s %lu", self->numeric, server->numeric , argv[3], hi->registered);
-
+            putsock("%s " P10_ACCOUNT " %s A %s "FMT_TIME_T, self->numeric, server->numeric , argv[3], hi->registered);
+        }
+        else
+        {
+            /* Return a AC D */
+            putsock("%s " P10_ACCOUNT " %s D %s", self->numeric, server->numeric , argv[3]);
+        }
+        return 1;
+    }
+    else if(!strcmp(argv[2],"S"))
+    {
+        if((hi = loc_auth(argv[5], argv[6], argv[7], argv[4])))
+        {
+            /* Return a AC A */
+            putsock("%s " P10_ACCOUNT " %s A %s "FMT_TIME_T, self->numeric, server->numeric , argv[3], hi->registered);
         }
         else
         {
@@ -1574,7 +1738,9 @@ static struct {
   P(FORCE_LOCAL_OPMODE), P(REMOTEREHASH), P(CHECK), P(SEE_SECRET_CHAN),
   P(SHUN),           P(LOCAL_SHUN),     P(WIDE_SHUN),     P(ZLINE),
   P(LOCAL_ZLINE),    P(WIDE_ZLINE),     P(LIST_CHAN),     P(WHOIS_NOTICE),
-  P(HIDE_IDLE),      P(XTRAOP),         P(HIDE_CHANNELS),
+  P(HIDE_IDLE),      P(XTRAOP),         P(HIDE_CHANNELS), P(DISPLAY_MODE),
+  P(FREEFORM),       P(REMOVE),         P(SPAMFILTER),    P(ADMIN),
+  P(APASS_OPMODE),   P(HIDE_OPER),      P(REMOTE),        P(SERVICE),
 #undef P
   { 0, 0 }
 };
@@ -1596,6 +1762,16 @@ char *client_report_privs(struct userNode *client)
   return privbuf;
 }
 
+int clear_privs(struct userNode *who) {
+  int i = 0;
+  assert(0 != who);
+
+  for (i = 0; privtab[i].name; i++)
+    RevokePriv(who, privtab[i].priv);
+
+  return 0;
+}
+
 int client_modify_priv_by_name(struct userNode *who, char *priv, int what) {
  int i = 0;
  assert(0 != priv);
@@ -1632,45 +1808,64 @@ irc_privs(struct userNode *target, char *flag, int add)
     putsock("%s " P10_PRIVS " %s %s%s", self->numeric, target->numeric, (add == PRIV_ADD) ? "+" : "-", flag);
 }
 
+void
+irc_raw_privs(struct userNode *target, const char *privs)
+{
+    putsock("%s " P10_PRIVS " %s %s", self->numeric, target->numeric,  privs);
+}
+
 static CMD_FUNC(cmd_privs)
 {
-  char *tstr = NULL;
-  int type = 0;
+    char *tstr = NULL;
+    char buf[512] = "";
+    char *p = 0;
+    char *tmp;
 
-  tstr = conf_get_data("server/type", RECDB_QSTRING);
-  if(tstr)
-    type = atoi(tstr);
+    int what = PRIV_ADD;
+    int type = 0;
 
-  if (type < 6)
-    return 1; /* silently ignore */
+    unsigned int i;
 
-  struct userNode *user = argc > 1 ? GetUserN(argv[1]) : NULL;
-  char buf[512] = "";
-  int what = PRIV_ADD;
-  char *p = 0;
-  char *tmp;
-  unsigned int i;
+    struct server *sender;
+    struct userNode *user;
 
-  if (argc < 3)
-    return 0;
 
-  if (!user)
-    return 0;
+    tstr = conf_get_data("server/type", RECDB_QSTRING);
+    if(tstr)
+        type = atoi(tstr);
 
-  for (i=1; i<argc; i++) {
-    strcat(buf, argv[i]);
-    strcat(buf, " ");
-  }
 
-  for (i = 2; i < argc; i++) {
-    if (*argv[i] == '+') { what = PRIV_ADD; argv[i]++; }
-    if (*argv[i] == '-') { what = PRIV_DEL; argv[i]++; }
-    for (tmp = x3_strtok(&p, argv[i], ","); tmp;
-         tmp = x3_strtok(&p, NULL, ",")) {
-      client_modify_priv_by_name(user, tmp, what);
+    if (!(sender = GetServerH(origin))) { /* from oper */
+        return 1; /* ignore as no services have privs set */
+    } else { /* from server */
+        if (type < 5)
+            return 1; /* silently ignore */
+
+        user = argc > 1 ? GetUserN(argv[1]) : NULL;
+
+        if (!user)
+            return 0;
+
+        for (i=1; i<argc; i++) {
+            strcat(buf, argv[i]);
+            strcat(buf, " ");
+        }
+
+        for (i = 2; i < argc; i++) {
+            if (*argv[i] == '+') { what = PRIV_ADD; argv[i]++; }
+            if (*argv[i] == '-') { what = PRIV_DEL; argv[i]++; }
+            for (tmp = x3_strtok(&p, argv[i], ","); tmp;
+                 tmp = x3_strtok(&p, NULL, ",")) {
+                if (!strcmp(tmp, "PRIV_NONE")) {
+                    if (now > user->timestamp+5)
+                        clear_privs(user);
+                    break;
+                } else
+                    client_modify_priv_by_name(user, tmp, what);
+            }
+        }
     }
-  }
-  return 1;
+    return 1;
 }
 
 static CMD_FUNC(cmd_burst)
@@ -1686,7 +1881,9 @@ static CMD_FUNC(cmd_burst)
     struct modeNode *mNode;
     long mode;
     int oplevel = -1;
+    int type = 0;
     char *user, *end, sep;
+    char *tstr;
     time_t in_timestamp;
     char* parm = NULL;
 
@@ -1694,6 +1891,11 @@ static CMD_FUNC(cmd_burst)
         return 0;
     modes[0] = 0;
 
+    tstr = conf_get_data("server/type", RECDB_QSTRING);
+    if(tstr) {
+        type = atoi(tstr);
+    }
+
     exemptlist[0] = 0;
     banlist[0] = 0;
 
@@ -1704,31 +1906,23 @@ static CMD_FUNC(cmd_burst)
             int n_modes;
             for (pos=argv[next], n_modes = 1; *pos; pos++)
                 if ((*pos == 'k') || (*pos == 'l') || (*pos == 'A')
-                    || (*pos == 'U'))
+                    || (*pos == 'U') || ((type > 7) && (*pos == 'L')))
                     n_modes++;
+            if (next + n_modes > argc)
+                n_modes = argc - next;
             unsplit_string(argv+next, n_modes, modes);
             next += n_modes;
             break;
         }
         case '%': {
+            ctype = 1;
             for(parm = mysep(&argv[next], " "); /* parm = first param */
                   parm;   /* While param is not null */
                   parm = mysep(&argv[next], " ")  /* parm = next param */
                )
             {
-              switch (parm[0]) {
-                case '%': {
-                  ctype = 1;
-                  break;
-                }
-                case '~': {
-                  ctype = 2;
-                  break;
-                }
-                default: {
-                  break;
-                }
-              }
+              if (0 == strcmp("~", parm))
+                ctype = 2;
               if (ctype == 1) {
                 if (bcheck == 0) {
                   /* strip % char off start of very first ban */
@@ -1816,36 +2010,58 @@ static CMD_FUNC(cmd_burst)
     return res;
 }
 
-/* TODO: 
+/* TODO:
  * This is a stub that doesn't actually do anything. It should be completed
  * so that bans on *!*@markname.* match users as it does in nefarious
  */
 static CMD_FUNC(cmd_mark)
 {
+    const char *tstr;
+    int type = 4;
     struct userNode *target;
-    /* 
+    /*
      * log_module(MAIN_LOG, LOG_ERROR, "DEBUG: mark, user %s, type %s, arg %s", argv[1], argv[2], argv[3]);
      */
 
+    tstr = conf_get_data("server/type", RECDB_QSTRING);
+    if(tstr)
+       type = atoi(tstr);
+    else
+       type = 4;/* default to 040 style topics */
+
     if(argc < 4)
         return 0;
     if(!strcasecmp(argv[2], "DNSBL")) {
         /* DNSBL <modes> */
         return 1;
     }
-    else if(!strcasecmp(argv[2], "DNSBL_DATA")) {
+    else if(!strcasecmp(argv[2], "DNSBL_DATA") || !strcasecmp(argv[2], "MARK")) {
+        int markfound = 0;
+        unsigned int ii = 0;
         /* DNSBL_DATA name */
         target = GetUserH(argv[1]);
         if(!target) {
             log_module(MAIN_LOG, LOG_ERROR, "Unable to find user %s whose dnsbl mark is changing.", argv[1]);
             return 0;
         }
-        target->mark = strdup(argv[3]);
+        if (type >= 9) {
+            if (target->marks)
+                for (ii=0; ii<target->marks->used; ii++)
+                    if (!irccasecmp(target->marks->list[ii], argv[3]))
+                         markfound = 1;
+            if (!markfound)
+            {
+                if (!target->marks)
+                    target->marks = alloc_string_list(1);
+                string_list_append(target->marks, strdup(argv[3]));
+            }
+        } else
+            target->mark = strdup(argv[3]);
         return 1;
-        
+
     }
     else if(!strcasecmp(argv[2], "CVERSION")) {
-        /* DNSBL_DATA name */
+        /* CTCP VERSION mark */
         target = GetUserH(argv[1]);
         if(!target) {
             log_module(MAIN_LOG, LOG_ERROR, "Unable to find user %s whose version mark is changing.", argv[1]);
@@ -1863,6 +2079,22 @@ static CMD_FUNC(cmd_mark)
 
         return 1;
     }
+    else if(!strcasecmp(argv[2], "SSLCLIFP")) {
+        /* SSL fingerprint mark */
+        target = GetUserH(argv[1]);
+        if(!target) {
+            log_module(MAIN_LOG, LOG_ERROR, "Unable to find user %s whose SSL fingerprint mark is changing.", argv[1]);
+            return 0;
+        }
+
+        char *sslfp = unsplit_string(argv + 3, argc - 3, NULL);
+        if(!sslfp)
+            sslfp = "";
+
+        target->sslfp = strdup(sslfp);
+
+        return 1;
+    }
     /* unknown type of mark */
     return 1;
 }
@@ -2058,7 +2290,7 @@ static CMD_FUNC(cmd_num_gline)
         else
             gline_add(origin, argv[3], atoi(argv[4])-now, argv[5], now, 0, 0);
     } else {
-        if (argv[5] == "+")
+        if (!strcmp(argv[5], "+"))
           gline_add(origin, argv[3], atoi(argv[4])-now, argv[6], now, 0, 0);
     }
     return 1;
@@ -2072,7 +2304,7 @@ static CMD_FUNC(cmd_num_shun)
         else
             shun_add(origin, argv[3], atoi(argv[4])-now, argv[5], now, 0);
     } else {
-        if (argv[5] == "+")
+        if (!strcmp(argv[5], "+"))
             shun_add(origin, argv[3], atoi(argv[4])-now, argv[6], now, 0);
     }
     return 1;
@@ -2101,9 +2333,11 @@ static CMD_FUNC(cmd_kill)
          * Ghost response to a KILL we sent out earlier.  So we only
          * whine if the target is local.
          */
-        if (!strncmp(argv[1], self->numeric, strlen(self->numeric)))
+        if (!strncmp(argv[1], self->numeric, strlen(self->numeric))) {
             log_module(MAIN_LOG, LOG_ERROR, "Unable to find kill victim %s", argv[1]);
-        return 0;
+            return 0;
+        }
+        return 1;
     }
 
     if (IsLocal(user) && IsService(user)) {
@@ -2116,19 +2350,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_svspart)
 {
     struct userNode *user;
@@ -2152,7 +2373,15 @@ static CMD_FUNC(cmd_silence)
     if (argc < 2)
         return 0;
 
-    user = GetUserN(argv[1]);
+    if (strncmp(argv[1], "*", 2) == 0)
+    {
+        if (!(user = GetUserH(origin))) {
+            log_module(MAIN_LOG, LOG_ERROR, "Could not find SILENCE origin user %s", origin);
+            return 0;
+        }
+    }
+    else
+        user = GetUserN(argv[1]);
 
     /* Sanity, go nuts if this happens */
     if (!user)
@@ -2224,7 +2453,7 @@ 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)))
@@ -2242,7 +2471,7 @@ static CMD_FUNC(cmd_privmsg)
         return 1; /* Silently Ignore */
 
     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;
@@ -2380,9 +2609,6 @@ static CMD_FUNC(cmd_svsnick)
     return 1;
 }
 
-static oper_func_t *of_list;
-static unsigned int of_size = 0, of_used = 0;
-
 void
 free_user(struct userNode *user)
 {
@@ -2391,12 +2617,15 @@ free_user(struct userNode *user)
 }
 
 static void
-parse_cleanup(void)
+parse_cleanup(UNUSED_ARG(void *extra))
 {
     unsigned int nn;
     free(of_list);
+    free(of_list_extra);
     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++)
@@ -2407,10 +2636,12 @@ parse_cleanup(void)
 static void
 p10_conf_reload(void) {
     hidden_host_suffix = conf_get_data("server/hidden_host", RECDB_QSTRING);
+    his_servername = conf_get_data("server/his_servername", RECDB_QSTRING);
+    his_servercomment = conf_get_data("server/his_servercomment", RECDB_QSTRING);
 }
 
 static void
-remove_unbursted_channel(struct chanNode *cNode) {
+remove_unbursted_channel(struct chanNode *cNode, UNUSED_ARG(void *extra)) {
     if (unbursted_channels)
         dict_remove(unbursted_channels, cNode->name);
 }
@@ -2446,11 +2677,6 @@ init_parse(void)
     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) {
@@ -2521,6 +2747,8 @@ init_parse(void)
     dict_insert(irc_func_dict, TOK_SVSPART, cmd_svspart);
     dict_insert(irc_func_dict, CMD_SWHOIS, cmd_dummy);
     dict_insert(irc_func_dict, TOK_SWHOIS, cmd_dummy);
+    dict_insert(irc_func_dict, CMD_TEMPSHUN, cmd_dummy);
+    dict_insert(irc_func_dict, TOK_TEMPSHUN, cmd_dummy);
     dict_insert(irc_func_dict, CMD_WHOIS, cmd_whois);
     dict_insert(irc_func_dict, TOK_WHOIS, cmd_whois);
     dict_insert(irc_func_dict, CMD_GLINE, cmd_gline);
@@ -2549,6 +2777,9 @@ init_parse(void)
     dict_insert(irc_func_dict, CMD_RPONG, cmd_dummy);
     dict_insert(irc_func_dict, TOK_RPONG, cmd_dummy);
 
+    dict_insert(irc_func_dict, CMD_SASL, cmd_sasl);
+    dict_insert(irc_func_dict, TOK_SASL, cmd_sasl);
+
     /* In P10, DESTRUCT doesn't do anything except be broadcast to servers.
      * Apparently to obliterate channels from any servers that think they
      * exist?
@@ -2581,11 +2812,19 @@ init_parse(void)
     /* ignore ALIST for now */
     dict_insert(irc_func_dict, TOK_ALIST, cmd_dummy);
     dict_insert(irc_func_dict, CMD_ALIST, cmd_dummy);
+    /* ignore SPAMFILTER */
+    dict_insert(irc_func_dict, TOK_SPAMFILTER, cmd_dummy);
+    dict_insert(irc_func_dict, CMD_SPAMFILTER, cmd_dummy);
     /* Ignore remote luser */
     dict_insert(irc_func_dict, TOK_LUSERS, cmd_dummy);
     /* We have reliable clock!  Always!  Wraaa! */
     dict_insert(irc_func_dict, CMD_SETTIME, cmd_dummy);
     dict_insert(irc_func_dict, TOK_SETTIME, cmd_dummy);
+    /* Ignore ZLINE/ZL/REMOVE/RM */
+    dict_insert(irc_func_dict, CMD_ZLINE, cmd_dummy);
+    dict_insert(irc_func_dict, TOK_ZLINE, cmd_dummy);
+    dict_insert(irc_func_dict, CMD_REMOVE, cmd_dummy);
+    dict_insert(irc_func_dict, TOK_REMOVE, cmd_dummy);
 
     /* ignore /trace and /motd commands targetted at us */
     dict_insert(irc_func_dict, TOK_TRACE, cmd_dummy);
@@ -2600,6 +2839,7 @@ init_parse(void)
     dict_insert(irc_func_dict, "432", cmd_error_nick); /* Erroneus [sic] nickname */
     /* ban list resetting */
     /* "stats g" responses */
+    dict_insert(irc_func_dict, "230", cmd_dummy); /* ignore stats headers */
     dict_insert(irc_func_dict, "247", cmd_num_gline);
     dict_insert(irc_func_dict, "542", cmd_num_shun);
     dict_insert(irc_func_dict, "219", cmd_dummy); /* "End of /STATS report" */
@@ -2623,8 +2863,8 @@ init_parse(void)
     memset(notice_funcs, 0, sizeof(privmsg_func_t)*num_notice_funcs);
 
     userList_init(&dead_users);
-    reg_del_channel_func(remove_unbursted_channel);
-    reg_exit_func(parse_cleanup);
+    reg_del_channel_func(remove_unbursted_channel, NULL);
+    reg_exit_func(parse_cleanup, NULL);
     // reg_notice_func(opserv, check_ctcp);
 }
 
@@ -2752,7 +2992,7 @@ make_numeric(struct server *svr, int local_num, char *outbuf)
 }
 
 struct server *
-AddServer(struct server *uplink, const char *name, int hops, time_t boot, time_t link, const char *numeric, const char *description)
+AddServer(struct server *uplink, const char *name, int hops, time_t boot, time_t link_time, const char *numeric, const char *description)
 {
     struct server* sNode;
     int slen, mlen;
@@ -2784,7 +3024,7 @@ AddServer(struct server *uplink, const char *name, int hops, time_t boot, time_t
     sNode->num_mask = base64toint(numeric+slen, mlen);
     sNode->hops = hops;
     sNode->boot = boot;
-    sNode->link = link;
+    sNode->link_time = link_time;
     strncpy(sNode->numeric, numeric, slen);
     safestrncpy(sNode->description, description, sizeof(sNode->description));
     sNode->users = calloc(sNode->num_mask+1, sizeof(*sNode->users));
@@ -2835,13 +3075,16 @@ void DelServer(struct server* serv, int announce, const char *message)
 }
 
 struct userNode *
-AddService(const char *nick, const char *modes, const char *desc, const char *hostname)
+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;
@@ -2854,8 +3097,7 @@ AddService(const char *nick, const char *modes, const char *desc, const char *ho
     if (!hostname)
         hostname = self->name;
     make_numeric(self, local_num, numeric);
-    /* TODO: Make these modes part of the conf file */
-    return AddUser(self, nick, nick, hostname, modes ? modes : "+oik", numeric, desc, now, "AAAAAA");
+    return AddUser(self, nick, ident, hostname, modes, numeric, desc, timestamp, "AAAAAA");
 }
 
 struct userNode *
@@ -2898,38 +3140,47 @@ 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 ignore_user, dummy;
     char *tstr;
     int type;
 
     if ((strlen(numeric) < 3) || (strlen(numeric) > 5)) {
-        log_module(MAIN_LOG, LOG_WARNING, "AddUser(%p, %s, ...): numeric %s wrong length!", uplink, nick, numeric);
+        log_module(MAIN_LOG, LOG_WARNING, "AddUser(%p, %s, ...): numeric %s wrong length!", (void*)uplink, nick, numeric);
         return NULL;
     }
 
     if (!uplink) {
-        log_module(MAIN_LOG, LOG_WARNING, "AddUser(%p, %s, ...): server for numeric %s doesn't exist!", uplink, nick, numeric);
+        log_module(MAIN_LOG, LOG_WARNING, "AddUser(%p, %s, ...): server for numeric %s doesn't exist!", (void*)uplink, nick, numeric);
         return NULL;
     }
 
     if (uplink != GetServerN(numeric)) {
-        log_module(MAIN_LOG, LOG_WARNING, "AddUser(%p, %s, ...): server for numeric %s differs from nominal uplink %s.", uplink, nick, numeric, uplink->name);
+        log_module(MAIN_LOG, LOG_WARNING, "AddUser(%p, %s, ...): server for numeric %s differs from nominal uplink %s.", (void*)uplink, nick, numeric, uplink->name);
         return NULL;
     }
 
-    if (!is_valid_nick(nick)) {
-        log_module(MAIN_LOG, LOG_WARNING, "AddUser(%p, %s, ...): invalid nickname detected.", uplink, 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.", (void*)uplink, nick);
         return NULL;
     }
 
     ignore_user = 0;
     if ((oldUser = GetUserH(nick))) {
-        if (IsLocal(oldUser) && (IsService(oldUser) || IsPersistent(oldUser))) {
-            /* The service should collide the new user off. */
+        if (IsLocal(oldUser)
+            && (IsService(oldUser) || IsPersistent(oldUser))) {
+            /* The service should collide the new user off - but not
+             * if the new user is coming in during a burst.  (During a
+             * burst, the bursting server will kill either our user --
+             * triggering a ReintroduceUser() -- or its own.)
+             */
             oldUser->timestamp = timestamp - 1;
-            irc_user(oldUser);
-        }
-        if (oldUser->timestamp > timestamp) {
+            ignore_user = 1;
+            if (!uplink->burst)
+                irc_user(oldUser);
+        } else if (oldUser->timestamp > timestamp) {
             /* "Old" user is really newer; remove them */
             DelUser(oldUser, 0, 1, "Overruled by older nick");
         } else {
@@ -2951,7 +3202,7 @@ AddUser(struct server* uplink, const char *nick, const char *ident, const char *
 
     tstr = conf_get_data("server/type", RECDB_QSTRING);
     type = atoi(tstr);
-    if (type > 6) {
+    if (type == 7) {
       if (irc_in_addr_is_ipv4(uNode->ip)) {
         make_virtip((char*)irc_ntoa(&uNode->ip), (char*)irc_ntoa(&uNode->ip), uNode->cryptip);
         make_virthost((char*)irc_ntoa(&uNode->ip), uNode->hostname, uNode->crypthost);
@@ -2962,7 +3213,7 @@ AddUser(struct server* uplink, const char *nick, const char *ident, const char *
 
     if (!uNode->crypthost && uNode->cryptip)
         snprintf(uNode->crypthost, sizeof(uNode->crypthost), "%s", strdup(uNode->cryptip));
-
+    uNode->idle_since = timestamp;
     uNode->timestamp = timestamp;
     modeList_init(&uNode->channels);
     uNode->uplink = uplink;
@@ -2972,6 +3223,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;
 
     set_geoip_info(uNode);
 
@@ -2985,9 +3238,7 @@ AddUser(struct server* uplink, const char *nick, const char *ident, const char *
     }
     if (IsLocal(uNode))
         irc_user(uNode);
-    for (n=0; n<nuf_used; n++)
-        if (nuf_list[n](uNode))
-            break;
+    call_new_user_funcs(uNode);
 
     if ((uNode->loc == 1) && (uNode->handle_info))
         send_func_list(uNode);
@@ -2999,7 +3250,6 @@ AddUser(struct server* uplink, const char *nick, const char *ident, const char *
 void
 DelUser(struct userNode* user, struct userNode *killer, int announce, const char *why)
 {
-    unsigned int n;
 
     verify(user);
 
@@ -3011,17 +3261,19 @@ DelUser(struct userNode* user, struct userNode *killer, int announce, const char
 
     /* 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, 0);
 
     /* Call these in reverse order so ChanServ can update presence
        information before NickServ nukes the handle_info. */
-    for (n = duf_used; n > 0; )
-        duf_list[--n](user, killer, why);
+    call_del_user_funcs(user, killer, why);
 
     user->uplink->clients--;
     user->uplink->users[user->num_local] = NULL;
-    if (IsOper(user))
+    if (IsOper(user)) {
         userList_remove(&curr_opers, user);
+        if (count_opers > 0 && !IsBotM(user) && !IsService(user) && !IsHideOper(user))
+          count_opers--;
+    }
     /* remove from global dictionary, but not if after a collide */
     if (user == dict_find(clients, user->nick, NULL))
         dict_remove(clients, user->nick);
@@ -3036,6 +3288,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);
 
     /* Clean up version data */
@@ -3044,11 +3304,19 @@ DelUser(struct userNode* user, struct userNode *killer, int announce, const char
         user->version_reply = NULL;
     }
 
+    /* Clean up SSL fingerprint data */
+    if(user->sslfp) {
+        free(user->sslfp);
+        user->sslfp = NULL;
+    }
+
     /* clean up mark */
     if(user->mark) {
         free(user->mark);
         user->mark = NULL;
     }
+    free_string_list(user->marks);
+    user->marks = NULL;
 
     /* clean up geoip data if any */
     if(user->country_code) free(user->country_code);
@@ -3070,6 +3338,8 @@ static void call_oper_funcs(struct userNode *user);
 
 void mod_usermode(struct userNode *user, const char *mode_change) {
     int add = 1;
+    long setmodes = 0;
+    int donemodes = 0;
     const char *word = mode_change;
 
     if (!user || !mode_change)
@@ -3077,24 +3347,26 @@ void mod_usermode(struct userNode *user, const char *mode_change) {
 
     call_user_mode_funcs(user, mode_change);
 
+    setmodes = user->modes;
+
     while (*word != ' ' && *word) word++;
     while (*word == ' ') word++;
-    while (1) {
+    while (!donemodes) {
 #define do_user_mode(FLAG) do { if (add) user->modes |= FLAG; else user->modes &= ~FLAG; } while (0)
        switch (*mode_change++) {
-       case 0: case ' ': return;
+       case 0: case ' ':
+            donemodes = 1;
+            break;
        case '+': add = 1; break;
        case '-': add = 0; break;
        case 'o':
-           if (add) {
-                if(!IsOper(user)) { /* Dont re-oper an oper */
-                    userList_append(&curr_opers, user);
-                    call_oper_funcs(user);
-                }
-           } else {
-               userList_remove(&curr_opers, user);
-           }
            do_user_mode(FLAGS_OPER);
+            if (!add) {
+                userList_remove(&curr_opers, user);
+            } else if (!userList_contains(&curr_opers, user)) {
+                userList_append(&curr_opers, user);
+                call_oper_funcs(user);
+           }
            break;
        case 'O': do_user_mode(FLAGS_LOCOP); break;
        case 'i': do_user_mode(FLAGS_INVISIBLE);
@@ -3104,12 +3376,12 @@ 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 'B': do_user_mode(FLAGS_BOT); break;
        case 'n': do_user_mode(FLAGS_HIDECHANS); break;
+    case 'p': do_user_mode(FLAGS_HIDECHANS); break;
        case 'I': do_user_mode(FLAGS_HIDEIDLE); break;
        case 'X': do_user_mode(FLAGS_XTRAOP); break;
        case 'C': do_user_mode(FLAGS_CLOAKHOST);
@@ -3177,9 +3449,68 @@ void mod_usermode(struct userNode *user, const char *mode_change) {
                 assign_fakehost(user, host, 0);
             }
             break;
+        case 'a': do_user_mode(FLAGS_ADMIN); break;
+        case 'z': do_user_mode(FLAGS_SSL); break;
+        case 'D': do_user_mode(FLAGS_PRIVDEAF); break;
+        case 'R': do_user_mode(FLAGS_ACCOUNTONLY); break;
+        case 'W': do_user_mode(FLAGS_WHOIS); break;
+        case 'H': do_user_mode(FLAGS_HIDEOPER); break;
+        case 'L': do_user_mode(FLAGS_NOLINK); break;
+        case 'q': do_user_mode(FLAGS_COMMONCHANSONLY); break;
        }
 #undef do_user_mode
     }
+
+    // Set user mode +o
+    if (!(setmodes & FLAGS_OPER) && IsOper(user)) {
+        if (!IsBotM(user) && !IsService(user) && !IsHideOper(user))
+            count_opers++;
+    }
+
+    // Set user mode -o
+    if ((setmodes & FLAGS_OPER) && !IsOper(user)) {
+        if (count_opers > 1 && !(setmodes & FLAGS_BOT) &&
+            !(setmodes & FLAGS_SERVICE) && !(setmodes & FLAGS_HIDEOPER))
+            count_opers--;
+    }
+
+    // Set +H, +k or +B
+    if (!(setmodes & FLAGS_HIDEOPER) &&
+        !(setmodes & FLAGS_SERVICE) &&
+        !(setmodes & FLAGS_BOT) &&
+        (IsHideOper(user) || IsService(user) || IsBotM(user))) {
+        if ((setmodes & FLAGS_OPER) && IsOper(user) && count_opers > 0)
+              count_opers--;
+    }
+
+    // Set -H, -k or -B
+    if (((setmodes & FLAGS_HIDEOPER) ||
+         (setmodes & FLAGS_SERVICE) ||
+         (setmodes & FLAGS_BOT)) &&
+        !IsHideOper(user) && !IsService(user) && !IsBotM(user)) {
+        if ((setmodes & FLAGS_OPER) && IsOper(user))
+            count_opers++;
+    }
+}
+
+static int
+keyncpy(char output[], char input[], size_t output_size)
+{
+    size_t ii;
+
+    if (input[0] == ':')
+    {
+        output[0] = '\0';
+        return 1;
+    }
+
+    for (ii = 0; (ii + 1 < output_size) && (input[ii] != '\0'); ++ii)
+    {
+        output[ii] = input[ii];
+    }
+
+    output[ii] = '\0';
+    return 0;
 }
 
 struct mod_chanmode *
@@ -3220,14 +3551,15 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
         case 'Q': do_chan_mode(MODE_NOQUITMSGS); break;
         case 'T': do_chan_mode(MODE_NOAMSG); break;
         case 'O': do_chan_mode(MODE_OPERSONLY); break;
+        case 'a': do_chan_mode(MODE_ADMINSONLY); break;
         case 'Z': do_chan_mode(MODE_SSLONLY); break;
        case 'L': do_chan_mode(MODE_HIDEMODE); break;
        case 'z':
          if (!(flags & MCP_REGISTERED)) {
-          do_chan_mode(MODE_REGISTERED);
+              do_chan_mode(MODE_REGISTERED);
          } else {
-          mod_chanmode_free(change);
-          return NULL;
+              mod_chanmode_free(change);
+              return NULL;
          }
          break;
 #undef do_chan_mode
@@ -3244,10 +3576,10 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
             break;
         case 'k':
             if (add) {
-                if (in_arg >= argc)
+                if ((in_arg >= argc)
+                    || keyncpy(change->new_key, modes[in_arg++], sizeof(change->new_key)))
                     goto error;
                 change->modes_set |= MODE_KEY;
-                safestrncpy(change->new_key, modes[in_arg++], sizeof(change->new_key));
             } else {
                 change->modes_clear |= MODE_KEY;
                 if (!(flags & MCP_KEY_FREE)) {
@@ -3260,10 +3592,10 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
         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));
+                if ((in_arg >= argc)
+                    || keyncpy(change->new_upass, modes[in_arg++], sizeof(change->new_upass)))
+                    goto error;
+                change->modes_set |= MODE_UPASS;
             } else {
                 change->modes_clear |= MODE_UPASS;
                 if (!(flags & MCP_UPASS_FREE)) {
@@ -3275,10 +3607,10 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
             break;
         case 'A':
             if (add) {
-                if (in_arg >= argc)
+                if ((in_arg >= argc)
+                    || keyncpy(change->new_apass, modes[in_arg++], sizeof(change->new_apass)))
                     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)) {
@@ -3454,6 +3786,7 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod
         DO_MODE_CHAR(NOQUITMSGS, 'Q');
         DO_MODE_CHAR(NOAMSG, 'T');
         DO_MODE_CHAR(OPERSONLY, 'O');
+        DO_MODE_CHAR(ADMINSONLY, 'a');
         DO_MODE_CHAR(REGISTERED, 'z');
         DO_MODE_CHAR(SSLONLY, 'Z');
        DO_MODE_CHAR(HIDEMODE, 'L');
@@ -3462,7 +3795,7 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod
             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)
+        if (change->modes_clear & channel->modes & MODE_APASS)
             mod_chanmode_append(&chbuf, 'A', channel->apass);
     }
     for (arg = 0; arg < change->argc; ++arg) {
@@ -3510,6 +3843,7 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod
         DO_MODE_CHAR(NOQUITMSGS, 'Q');
         DO_MODE_CHAR(NOAMSG, 'T');
         DO_MODE_CHAR(OPERSONLY, 'O');
+        DO_MODE_CHAR(ADMINSONLY, 'a');
         DO_MODE_CHAR(REGISTERED, 'z');
         DO_MODE_CHAR(SSLONLY, 'Z');
        DO_MODE_CHAR(HIDEMODE, 'L');
@@ -3585,6 +3919,7 @@ mod_chanmode_format(struct mod_chanmode *change, char *outbuff)
         DO_MODE_CHAR(NOQUITMSGS, 'Q');
         DO_MODE_CHAR(NOAMSG, 'T');
         DO_MODE_CHAR(OPERSONLY, 'O');
+        DO_MODE_CHAR(ADMINSONLY, 'a');
         DO_MODE_CHAR(REGISTERED, 'z');
         DO_MODE_CHAR(SSLONLY, 'Z');
        DO_MODE_CHAR(HIDEMODE, 'L');
@@ -3609,62 +3944,23 @@ mod_chanmode_format(struct mod_chanmode *change, char *outbuff)
         DO_MODE_CHAR(NOQUITMSGS, 'Q');
         DO_MODE_CHAR(NOAMSG, 'T');
         DO_MODE_CHAR(OPERSONLY, 'O');
+        DO_MODE_CHAR(ADMINSONLY, 'a');
         DO_MODE_CHAR(REGISTERED, 'z');
         DO_MODE_CHAR(SSLONLY, 'Z');
        DO_MODE_CHAR(HIDEMODE, 'L');
-#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);
-
-        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;
+       DO_MODE_CHAR(KEY, 'k');
+       DO_MODE_CHAR(LIMIT, 'l');
+       DO_MODE_CHAR(APASS, 'A');
+       DO_MODE_CHAR(UPASS, 'U');
+#undef DO_MODE_CHAR
 
-        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;
-        }
+#define DO_MODE_PARM(BIT, PARM) if (change->modes_set & MODE_##BIT) used += sprintf(outbuff+used, " %s", PARM);
+       DO_MODE_PARM(KEY, change->new_key);
+       if (change->modes_set & MODE_LIMIT) used += sprintf(outbuff+used, " %d", change->new_limit);
+       DO_MODE_PARM(APASS, change->new_apass);
+       DO_MODE_PARM(UPASS, change->new_upass);
+#undef DO_MODE_PARM
     }
     outbuff[used] = 0;
     return outbuff;
@@ -3673,61 +3969,62 @@ mod_chanmode_format(struct mod_chanmode *change, char *outbuff)
 static int
 clear_chanmode(struct chanNode *channel, const char *modes)
 {
-    unsigned int remove;
+    unsigned int cleared;
 
-    for (remove = 0; *modes; modes++) {
+    for (cleared = 0; *modes; modes++) {
         switch (*modes) {
-        case 'o': remove |= MODE_CHANOP; break;
-        case 'h': remove |= MODE_HALFOP; break;
-        case 'v': remove |= MODE_VOICE; break;
-        case 'p': remove |= MODE_PRIVATE; break;
-        case 's': remove |= MODE_SECRET; break;
-        case 'm': remove |= MODE_MODERATED; break;
-        case 't': remove |= MODE_TOPICLIMIT; break;
-        case 'i': remove |= MODE_INVITEONLY; break;
-        case 'n': remove |= MODE_NOPRIVMSGS; break;
+        case 'o': cleared |= MODE_CHANOP; break;
+        case 'h': cleared |= MODE_HALFOP; break;
+        case 'v': cleared |= MODE_VOICE; break;
+        case 'p': cleared |= MODE_PRIVATE; break;
+        case 's': cleared |= MODE_SECRET; break;
+        case 'm': cleared |= MODE_MODERATED; break;
+        case 't': cleared |= MODE_TOPICLIMIT; break;
+        case 'i': cleared |= MODE_INVITEONLY; break;
+        case 'n': cleared |= MODE_NOPRIVMSGS; break;
         case 'k':
-            remove |= MODE_KEY;
+            cleared |= MODE_KEY;
             channel->key[0] = '\0';
             break;
         case 'A':
-            remove |= MODE_APASS;
+            cleared |= MODE_APASS;
             channel->apass[0] = '\0';
             break;
         case 'U':
-            remove |= MODE_UPASS;
+            cleared |= MODE_UPASS;
             channel->upass[0] = '\0';
             break;
         case 'l':
-            remove |= MODE_LIMIT;
+            cleared |= MODE_LIMIT;
             channel->limit = 0;
             break;
-        case 'b': remove |= MODE_BAN; break;
-        case 'e': remove |= MODE_EXEMPT; break;
-        case 'D': remove |= MODE_DELAYJOINS; break;
-        case 'r': remove |= MODE_REGONLY; break;
-        case 'c': remove |= MODE_NOCOLORS; break;
-        case 'C': remove |= MODE_NOCTCPS; break;
-        case 'S': remove |= MODE_STRIPCOLOR; break;
-        case 'M': remove |= MODE_MODUNREG; break;
-        case 'N': remove |= MODE_NONOTICE; break;
-        case 'Q': remove |= MODE_NOQUITMSGS; break;
-        case 'T': remove |= MODE_NOAMSG; break;
-        case 'O': remove |= MODE_OPERSONLY; break;
-        case 'z': remove |= MODE_REGISTERED; break;
-        case 'Z': remove |= MODE_SSLONLY; break;
-       case 'L': remove |= MODE_HIDEMODE; break;
+        case 'b': cleared |= MODE_BAN; break;
+        case 'e': cleared |= MODE_EXEMPT; break;
+        case 'D': cleared |= MODE_DELAYJOINS; break;
+        case 'r': cleared |= MODE_REGONLY; break;
+        case 'c': cleared |= MODE_NOCOLORS; break;
+        case 'C': cleared |= MODE_NOCTCPS; break;
+        case 'S': cleared |= MODE_STRIPCOLOR; break;
+        case 'M': cleared |= MODE_MODUNREG; break;
+        case 'N': cleared |= MODE_NONOTICE; break;
+        case 'Q': cleared |= MODE_NOQUITMSGS; break;
+        case 'T': cleared |= MODE_NOAMSG; break;
+        case 'O': cleared |= MODE_OPERSONLY; break;
+        case 'a': cleared |= MODE_ADMINSONLY; break;
+        case 'z': cleared |= MODE_REGISTERED; break;
+        case 'Z': cleared |= MODE_SSLONLY; break;
+       case 'L': cleared |= MODE_HIDEMODE; break;
         }
     }
 
-    if (!remove)
+    if (!cleared)
         return 1;
 
     /* Remove simple modes. */
-    channel->modes &= ~remove;
+    channel->modes &= ~cleared;
 
     /* If removing bans, kill 'em all. */
-    if ((remove & MODE_BAN) && channel->banlist.used) {
+    if ((cleared & MODE_BAN) && channel->banlist.used) {
         unsigned int i;
         for (i=0; i<channel->banlist.used; i++)
             free(channel->banlist.list[i]);
@@ -3735,16 +4032,16 @@ clear_chanmode(struct chanNode *channel, const char *modes)
     }
 
     /* If removing exempts, kill 'em all. */
-    if ((remove & MODE_EXEMPT) && channel->exemptlist.used) {
+    if ((cleared & MODE_EXEMPT) && channel->exemptlist.used) {
         unsigned int i;
         for (i=0; i<channel->exemptlist.used; i++)
             free(channel->exemptlist.list[i]);
         channel->exemptlist.used = 0;
     }
 
-    /* Remove member modes. */
-    if ((remove & (MODE_CHANOP | MODE_HALFOP | MODE_VOICE)) && channel->members.used) {
-        int mask = ~(remove & (MODE_CHANOP | MODE_HALFOP | MODE_VOICE));
+    /* Removed member modes. */
+    if ((cleared & (MODE_CHANOP | MODE_HALFOP | MODE_VOICE)) && channel->members.used) {
+        int mask = ~(cleared & (MODE_CHANOP | MODE_HALFOP | MODE_VOICE));
         unsigned int i;
 
         for (i = 0; i < channel->members.used; i++)
@@ -3759,9 +4056,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])
@@ -3770,20 +4068,12 @@ reg_privmsg_func(struct userNode *user, privmsg_func_t handler)
 }
 
 void
-unreg_privmsg_func(struct userNode *user, privmsg_func_t handler)
+unreg_privmsg_func(struct userNode *user)
 {
-    unsigned int x;
-
-    if (!user || handler)
-      return; /* this really only works with users */
-
-    memset(privmsg_funcs+user->num_local, 0, sizeof(privmsg_func_t));
+    if (!IsLocal(user) || user->num_local >= num_privmsg_funcs)
+        return; /* this really only works with users */
 
-    for (x = user->num_local+1; x < num_privmsg_funcs; x++) 
-       memmove(privmsg_funcs+x-1, privmsg_funcs+x, sizeof(privmsg_func_t));
-    
-    privmsg_funcs = realloc(privmsg_funcs, num_privmsg_funcs*sizeof(privmsg_func_t)); 
-    num_privmsg_funcs--;
+    privmsg_funcs[user->num_local] = NULL;
 }
 
 
@@ -3792,9 +4082,10 @@ 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])
@@ -3803,46 +4094,12 @@ reg_notice_func(struct userNode *user, privmsg_func_t handler)
 }
 
 void
-unreg_notice_func(struct userNode *user, privmsg_func_t handler)
-{
-    unsigned int x;
-
-    if (!user || handler)
-          return; /* this really only works with users */
-
-    memset(notice_funcs+user->num_local, 0, sizeof(privmsg_func_t));
-
-    for (x = user->num_local+1; x < num_notice_funcs; x++)
-       memmove(notice_funcs+x-1, notice_funcs+x, sizeof(privmsg_func_t));
-
-    memset(notice_funcs+user->num_local, 0, sizeof(privmsg_func_t));
-    notice_funcs = realloc(notice_funcs, num_notice_funcs*sizeof(privmsg_func_t));
-    num_notice_funcs--;
-}
-
-void
-reg_oper_func(oper_func_t handler)
+unreg_notice_func(struct userNode *user)
 {
-    if (of_used == of_size) {
-       if (of_size) {
-           of_size <<= 1;
-           of_list = realloc(of_list, of_size*sizeof(oper_func_t));
-       } else {
-           of_size = 8;
-           of_list = malloc(of_size*sizeof(oper_func_t));
-       }
-    }
-    of_list[of_used++] = handler;
-}
+    if (!IsLocal(user) || user->num_local >= num_privmsg_funcs)
+        return; /* this really only works with users */
 
-static void
-call_oper_funcs(struct userNode *user)
-{
-    unsigned int n;
-    if (IsLocal(user))
-        return;
-    for (n=0; n<of_used; n++)
-       of_list[n](user);
+    notice_funcs[user->num_local] = NULL;
 }
 
 static void