implemented set KeepConn time function for later use (relay command)
[srvx.git] / src / proto-p10.c
index 2471cb487fffb5220be8c5fba0537e545a2aabe3..f544f9f019a2ddc3799bb19de4e0dc013e0a982a 100644 (file)
@@ -1,5 +1,5 @@
 /* proto-p10.c - IRC protocol output
- * Copyright 2000-2004 srvx Development Team
+ * Copyright 2000-2008 srvx Development Team
  *
  * This file is part of srvx.
  *
@@ -21,9 +21,9 @@
 #include "proto-common.c"
 
 /* Full commands. */
-#define CMD_ACCOUNT            "ACCOUNT"
+#define CMD_ACCOUNT             "ACCOUNT"
 #define CMD_ADMIN               "ADMIN"
-#define CMD_ASLL               "ASLL"
+#define CMD_ASLL                "ASLL"
 #define CMD_AWAY                "AWAY"
 #define CMD_BURST               "BURST"
 #define CMD_CLEARMODE           "CLEARMODE"
@@ -40,7 +40,8 @@
 #define CMD_EOB_ACK             "EOB_ACK"
 #define CMD_ERROR               "ERROR"
 #define CMD_FAKEHOST            "FAKE"
-#define CMD_GET                        "GET"
+#define CMD_FAKEHOST2           "FAKE2"
+#define CMD_GET                 "GET"
 #define CMD_GLINE               "GLINE"
 #define CMD_HASH                "HASH"
 #define CMD_HELP                "HELP"
 #define CMD_PONG                "PONG"
 #define CMD_POST                "POST"
 #define CMD_PRIVMSG             "PRIVMSG"
-#define CMD_PRIVS              "PRIVS"
+#define CMD_PRIVS               "PRIVS"
 #define CMD_PROTO               "PROTO"
 #define CMD_QUIT                "QUIT"
 #define CMD_REHASH              "REHASH"
-#define CMD_RESET              "RESET"
+#define CMD_RELAY               "RELAY"
+#define CMD_RESET               "RESET"
 #define CMD_RESTART             "RESTART"
 #define CMD_RPING               "RPING"
 #define CMD_RPONG               "RPONG"
 #define CMD_SERVER              "SERVER"
 #define CMD_SERVLIST            "SERVLIST"
 #define CMD_SERVSET             "SERVSET"
-#define CMD_SET                        "SET"
+#define CMD_SET                 "SET"
 #define CMD_SETTIME             "SETTIME"
 #define CMD_SILENCE             "SILENCE"
 #define CMD_SQUERY              "SQUERY"
 #define CMD_SQUIT               "SQUIT"
 #define CMD_STATS               "STATS"
 #define CMD_SVSNICK             "SVSNICK"
+#define CMD_SVSMODE             "SVSMODE"
+#define CMD_SVSJOIN             "SVSJOIN"
 #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_XQUERY              "XQUERY"
+#define CMD_XRESPONSE           "XRESPONSE"
 
 /* Tokenized commands. */
-#define TOK_ACCOUNT            "AC"
+#define TOK_ACCOUNT             "AC"
 #define TOK_ADMIN               "AD"
-#define TOK_ASLL               "LL"
+#define TOK_ASLL                "LL"
 #define TOK_AWAY                "A"
 #define TOK_BURST               "B"
 #define TOK_CLEARMODE           "CM"
 #define TOK_EOB_ACK             "EA"
 #define TOK_ERROR               "Y"
 #define TOK_FAKEHOST            "FA"
-#define TOK_GET                        "GET"
+#define TOK_FAKEHOST2           "NFH"
+#define TOK_GET                 "GET"
 #define TOK_GLINE               "GL"
 #define TOK_HASH                "HASH"
 #define TOK_HELP                "HELP"
 #define TOK_PONG                "Z"
 #define TOK_POST                "POST"
 #define TOK_PRIVMSG             "P"
-#define TOK_PRIVS              "PRIVS"
+#define TOK_PRIVS               "PRIVS"
 #define TOK_PROTO               "PROTO"
 #define TOK_QUIT                "Q"
 #define TOK_REHASH              "REHASH"
-#define TOK_RESET              "RESET"
+#define TOK_RELAY               "RL"
+#define TOK_RESET               "RESET"
 #define TOK_RESTART             "RESTART"
 #define TOK_RPING               "RI"
 #define TOK_RPONG               "RO"
 #define TOK_SERVER              "S"
 #define TOK_SERVLIST            "SERVSET"
 #define TOK_SERVSET             "SERVSET"
-#define TOK_SET                        "SET"
+#define TOK_SET                 "SET"
 #define TOK_SETTIME             "SE"
 #define TOK_SILENCE             "U"
 #define TOK_SQUERY              "SQUERY"
 #define TOK_SQUIT               "SQ"
 #define TOK_STATS               "R"
 #define TOK_SVSNICK             "SN"
+#define TOK_SVSMODE             "SM"
+#define TOK_SVSJOIN             "SJ"
 #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_XQUERY              "XQ"
+#define TOK_XRESPONSE           "XR"
 
 /* Protocol messages; aliased to full commands or tokens depending
    on compile-time configuration. ircu prefers tokens WITH THE
 #define TYPE(NAME)              CMD_ ## NAME
 #endif /* ENABLE_TOKENS */
 
-#define P10_ACCOUNT            TYPE(ACCOUNT)
+#define P10_ACCOUNT             TYPE(ACCOUNT)
 #define P10_ADMIN               TYPE(ADMIN)
-#define P10_ASLL               TYPE(ASLL)
+#define P10_ASLL                TYPE(ASLL)
 #define P10_AWAY                TYPE(AWAY)
 #define P10_BURST               TYPE(BURST)
 #define P10_CLEARMODE           TYPE(CLEARMODE)
 #define P10_EOB_ACK             TYPE(EOB_ACK)
 #define P10_ERROR               TYPE(ERROR)
 #define P10_FAKEHOST            TYPE(FAKEHOST)
-#define P10_GET                        TYPE(GET)
+#define P10_FAKEHOST2           TYPE(FAKEHOST2)
+#define P10_GET                 TYPE(GET)
 #define P10_GLINE               TYPE(GLINE)
 #define P10_HASH                TYPE(HASH)
 #define P10_HELP                TYPE(HELP)
 #define P10_PONG                TYPE(PONG)
 #define P10_POST                TYPE(POST)
 #define P10_PRIVMSG             TYPE(PRIVMSG)
-#define P10_PRIVS              TYPE(PRIVS)
+#define P10_PRIVS               TYPE(PRIVS)
 #define P10_PROTO               TYPE(PROTO)
 #define P10_QUIT                TYPE(QUIT)
 #define P10_REHASH              TYPE(REHASH)
-#define P10_RESET              TYPE(RESET)
+#define P10_RELAY               TYPE(RELAY)
+#define P10_RESET               TYPE(RESET)
 #define P10_RESTART             TYPE(RESTART)
 #define P10_RPING               TYPE(RPING)
 #define P10_RPONG               TYPE(RPONG)
 #define P10_SERVER              CMD_SERVER
 #define P10_SERVLIST            TYPE(SERVLIST)
 #define P10_SERVSET             TYPE(SERVSET)
-#define P10_SET                        TYPE(SET)
+#define P10_SET                 TYPE(SET)
 #define P10_SETTIME             TYPE(SETTIME)
 #define P10_SILENCE             TYPE(SILENCE)
 #define P10_SQUERY              TYPE(SQUERY)
 #define P10_SQUIT               TYPE(SQUIT)
 #define P10_STATS               TYPE(STATS)
 #define P10_SVSNICK             TYPE(SVSNICK)
+#define P10_SVSMODE             TYPE(SVSMODE)
+#define P10_SVSJOIN             TYPE(SVSJOIN)
 #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_XQUERY              TYPE(XQUERY)
+#define P10_XRESPONSE           TYPE(XRESPONSE)
 
 /* Servers claiming to have a boot or link time before PREHISTORY
  * trigger errors to the log.  We hope no server has been running
@@ -289,13 +307,29 @@ 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 struct channelList dead_channels;
 
-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);
+/* These correspond to 1 << X:      012345678901234567890123 */
+const char irc_user_mode_chars[] = "o iw dkgn    x   ISDXHst";
+
+static struct userNode *AddUser(struct server* uplink, const char *nick, const char *ident, const char *hostname, const char *modes, const char *numeric, const char *userinfo, unsigned long timestamp, const char *realip);
 
 extern int off_channel;
 
+/*
+ * Oplevel parsing
+ */
+static int
+parse_oplevel(char *str)
+{
+    int oplevel = 0;
+    while (isdigit(*str))
+        oplevel = oplevel * 10 + *str++ - '0';
+    return oplevel;
+}
+
 /* Numerics can be XYY, XYYY, or XXYYY; with X's identifying the
  * server and Y's indentifying the client on that server. */
 struct server*
@@ -366,65 +400,142 @@ 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",
-                srv->name, srv->hops+1, srv->boot, srv->link, srv->numeric, extranum, srv->description);
+        putsock(P10_SERVER " %s %d %lu %lu J10 %s%s +s6 :%s",
+                srv->name, srv->hops+1, srv->boot, srv->link_time, srv->numeric, extranum, srv->description);
+    } else {
+        putsock("%s " P10_SERVER " %s %d %lu %lu %c10 %s%s +s6 :%s",
+                self->numeric, srv->name, srv->hops+1, srv->boot, srv->link_time, (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 - pos; 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;
+                ii += 1;
+            }
+        }
+        *output = '\0';
     } else {
-        putsock("%s " P10_SERVER " %s %d %li %li %c10 %s%s +s :%s",
-                self->numeric, srv->name, srv->hops+1, srv->boot, srv->link, (srv->self_burst ? 'J' : 'P'), srv->numeric, extranum, srv->description);
+        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];
-
-        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';
-        if (IsHelperIrcu(user))
-            modes[modelen++] = 'h';
-        if (IsHiddenHost(user))
-            modes[modelen++] = 'x';
-        modes[modelen] = 0;
-
-        /* 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",
-                user->uplink->numeric, user->nick, user->uplink->hops+1, user->timestamp, user->ident, user->hostname, modes, b64ip, user->numeric, user->info);
+        irc_user_modes(user, modes, sizeof(modes));
+        putsock("%s " P10_NICK " %s %d %lu %s %s +%s %s %s :%s",
+                user->uplink->numeric, user->nick, user->uplink->hops+1, (unsigned long)user->timestamp, user->ident, user->hostname, modes, b64ip, user->numeric, user->info);
     } else {
-        putsock("%s " P10_NICK " %s %d %li %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);
+        putsock("%s " P10_NICK " %s %d %lu %s %s %s %s :%s",
+                user->uplink->numeric, user->nick, user->uplink->hops+1, (unsigned long)user->timestamp, user->ident, user->hostname, b64ip, user->numeric, user->info);
     }
 }
 
 void
-irc_account(struct userNode *user, const char *stamp)
+irc_account(struct userNode *user, const char *stamp, unsigned long timestamp, unsigned long serial)
 {
-    putsock("%s " P10_ACCOUNT " %s %s", self->numeric, user->numeric, stamp);
+    putsock("%s " P10_ACCOUNT " %s %s %lu %lu", self->numeric, user->numeric, stamp, timestamp, serial);
 }
 
 void
-irc_fakehost(struct userNode *user, const char *host)
+irc_fakehost(struct userNode *user, const char *host, const char *ident, int force)
+{
+    /* SRVX added the possibility for FAKE IDENTS
+     * but this is currently *NOT* supported by our IRCu
+     *
+     * edit 24.11.11: 
+     *  NFH (P10_FAKEHOST2) is now supported by our IRCu (git-65-21592a4)
+     */
+    putsock("%s " P10_FAKEHOST2 " %s %s %s%s", self->numeric, user->numeric, ident, host, force ? " FORCE" : "");
+}
+
+void 
+irc_relay(char *message)
+{
+    putsock("%s " P10_RELAY " %s", self->numeric, message);
+}
+
+void 
+irc_simul(struct userNode *target, char *command)
 {
-    putsock("%s " P10_FAKEHOST " %s %s", self->numeric, user->numeric, host);
+    putsock("%s " P10_RELAY " %s SI %s :%s", self->numeric, target->numeric, target->numeric, command);
+}
+
+void 
+irc_keepconn(struct userNode *target, unsigned int timeout)
+{
+    putsock("%s " P10_RELAY " %s KC %s %u", self->numeric, target->numeric, target->numeric, timeout);
 }
 
 void
@@ -436,7 +547,7 @@ irc_regnick(UNUSED_ARG(struct userNode *user))
 void
 irc_nick(struct userNode *user, UNUSED_ARG(const char *old_nick))
 {
-    putsock("%s " P10_NICK " %s "FMT_TIME_T, user->numeric, user->nick, now);
+    putsock("%s " P10_NICK " %s %lu", user->numeric, user->nick, (unsigned long)now);
 }
 
 void
@@ -481,22 +592,66 @@ 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)
 {
-    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
+irc_svsmode(struct userNode *from, struct userNode *user, const char *modes)
+{
+putsock("%s " P10_SVSMODE " %s %s", from->numeric, user->numeric, modes);
+}
+
+void
+irc_svsjoin(struct userNode *from, struct userNode *user, struct chanNode *chan)
+{
+putsock("%s " P10_SVSJOIN " %s %s", from->numeric, user->numeric, chan->name);
+}
+
+void
+irc_svsjoinchan(struct userNode *from, struct userNode *user, const char *chan)
+{
+putsock("%s " P10_SVSJOIN " %s %s", from->numeric, user->numeric, chan);
 }
 
 void
@@ -523,6 +678,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 sys_now;
+    int diff;
+
+    orig.tv_sec = strtoul(orig_ts, &delim, 10);
+    orig.tv_usec = (*delim == '.') ? strtoul(delim + 1, NULL, 10) : 0;
+    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
 irc_pass(const char *passwd)
 {
@@ -544,23 +714,58 @@ 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);
+    //<prefix> GL <target> [!][+|-|>|<]<mask> [<expiration>] [<lastmod>] [<lifetime>] [:<reason>]
+    //expiration = relative time (seconds)
+    //lastmod = timestamp
+    //livetime = timestamp
+    if (gline->lastmod)
+        putsock("%s " P10_GLINE " %s +%s %lu %lu %lu :%s", self->numeric, (srv ? srv->numeric : "*"), 
+                gline->target, gline->expires-now, gline->lastmod, gline->lifetime, gline->reason);
+    else
+        putsock("%s " P10_GLINE " %s +%s %lu :%s", self->numeric, (srv ? srv->numeric : "*"), 
+                gline->target, gline->expires-now, gline->reason);
 }
 
 void
-irc_settime(const char *srv_name_mask, time_t new_time)
+irc_settime(const char *srv_name_mask, unsigned long new_time)
 {
     ioset_set_time(new_time);
     if (!strcmp(srv_name_mask, "*"))
         srv_name_mask = "";
-    putsock("%s " P10_SETTIME " " FMT_TIME_T " %s", self->numeric, new_time, srv_name_mask);
+    putsock("%s " P10_SETTIME " %lu %s", self->numeric, new_time, srv_name_mask);
 }
 
 void
 irc_ungline(const char *mask)
 {
-    putsock("%s " P10_GLINE " * -%s", self->numeric, mask);
+    putsock("%s " P10_GLINE " * -%s %lu", self->numeric, mask, now);
+    //putsock("%s " P10_GLINE " * %s * %lu", self->numeric, mask, now);
+}
+
+/* Return negative if *(struct modeNode**)pa is "less than" pb,
+ * positive if pa is "larger than" pb.  Comparison is based on sorting
+ * so that non-voiced/non-opped users are first, voiced-only users are
+ * next, and the "strongest" oplevels are before "weaker" oplevels.
+ * Within those sets, ordering is arbitrary.
+ */
+static int
+modeNode_sort_p10(const void *pa, const void *pb)
+{
+        struct modeNode *a = *(struct modeNode**)pa;
+        struct modeNode *b = *(struct modeNode**)pb;
+
+        if (a->modes & MODE_CHANOP) {
+            if (!(b->modes & MODE_CHANOP))
+                return 1;
+            else if ((a->modes & MODE_VOICE) != (b->modes & MODE_VOICE))
+                return (a->modes & MODE_VOICE) - (b->modes & MODE_VOICE);
+            else if (a->oplevel != b->oplevel)
+                return a->oplevel - b->oplevel;
+        } else if (b->modes & MODE_CHANOP)
+            return -1;
+        else if ((a->modes & MODE_VOICE) != (b->modes & MODE_VOICE))
+            return (a->modes & MODE_VOICE) - (b->modes & MODE_VOICE);
+        return (a < b) ? -1 : 1;
 }
 
 static void
@@ -570,16 +775,23 @@ irc_burst(struct chanNode *chan)
     int pos, base_len, len;
     struct modeNode *mn;
     struct banNode *bn;
-    long last_mode=-1;
+    int last_oplevel = 0;
+    int last_mode = 0;
+    int new_modes;
+    unsigned int first_ban;
     unsigned int n;
 
-    base_len = sprintf(burst_line, "%s " P10_BURST " %s " FMT_TIME_T " ",
-                       self->numeric, chan->name, chan->timestamp);
+    base_len = sprintf(burst_line, "%s " P10_BURST " %s %lu ",
+                       self->numeric, chan->name,
+                       (unsigned long)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++] = ' ';
 
+    /* sort the users for oplevel-sending purposes */
+    qsort(chan->members.list, chan->members.used, sizeof(chan->members.list[0]), modeNode_sort_p10);
+
     /* dump the users */
     for (n=0; n<chan->members.used; n++) {
         mn = chan->members.list[n];
@@ -587,47 +799,66 @@ irc_burst(struct chanNode *chan)
             burst_line[pos-1] = 0; /* -1 to back up over the space or comma */
             putsock("%s", burst_line);
             pos = base_len;
-            last_mode = -1;
+            last_mode = 0;
+            last_oplevel = 0;
         }
         memcpy(burst_line+pos, mn->user->numeric, strlen(mn->user->numeric));
         pos += strlen(mn->user->numeric);
-        if (mn->modes && (mn->modes != last_mode)) {
-            last_mode = mn->modes;
+        new_modes = mn->modes & (MODE_CHANOP | MODE_VOICE);
+        if (new_modes != last_mode) {
+            last_mode = new_modes;
             burst_line[pos++] = ':';
-            if (last_mode & MODE_CHANOP)
-                burst_line[pos++] = 'o';
-            if (last_mode & MODE_VOICE)
+            if (new_modes & MODE_VOICE)
                 burst_line[pos++] = 'v';
+            /* Note: :vNNN (oplevel NNN with voice) resets the
+             * implicit oplevel back to zero, so we always use the raw
+             * oplevel value here.  Read ircu's m_burst.c for more
+             * examples.
+             */
+            if (new_modes & MODE_CHANOP) {
+                last_oplevel = mn->oplevel;
+                if (mn->oplevel < MAXOPLEVEL)
+                    pos += sprintf(burst_line + pos, "%u", mn->oplevel);
+                else
+                    burst_line[pos++] = 'o';
+            }
+        } else if ((last_mode & MODE_CHANOP) && (mn->oplevel != last_oplevel)) {
+            pos += sprintf(burst_line + pos, ":%u", mn->oplevel - last_oplevel);
+            last_oplevel = mn->oplevel;
         }
         if ((n+1)<chan->members.used)
             burst_line[pos++] = ',';
     }
+
+    /* 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);
@@ -653,8 +884,8 @@ void
 irc_kill(struct userNode *from, struct userNode *target, const char *message)
 {
     if (from) {
-        putsock("%s " P10_KILL " %s :%s!%s (%s)",
-                from->numeric, target->numeric, self->name, from->nick, message);
+        putsock("%s " P10_KILL " %s :%s (%s)",
+                from->numeric, target->numeric, from->nick, message);
     } else {
         putsock("%s " P10_KILL " %s :%s (%s)",
                 self->numeric, target->numeric, self->name, message);
@@ -664,9 +895,9 @@ irc_kill(struct userNode *from, struct userNode *target, const char *message)
 void
 irc_mode(struct userNode *from, struct chanNode *target, const char *modes)
 {
-    putsock("%s " P10_MODE " %s %s "FMT_TIME_T,
+    putsock("%s " P10_MODE " %s %s %lu",
             (from ? from->numeric : self->numeric),
-            target->name, modes, target->timestamp);
+            target->name, modes, (unsigned long)target->timestamp);
 }
 
 void
@@ -680,9 +911,10 @@ irc_join(struct userNode *who, struct chanNode *what)
 {
     if (what->members.used == 1) {
         putsock("%s " P10_CREATE " %s %lu",
-                who->numeric, what->name, what->timestamp);
+                who->numeric, what->name, (unsigned long)what->timestamp);
     } else {
-        putsock("%s " P10_JOIN " %s %lu", who->numeric, what->name, what->timestamp);
+        putsock("%s " P10_JOIN " %s %lu", who->numeric, what->name,
+                (unsigned long)what->timestamp);
     }
 }
 
@@ -705,7 +937,7 @@ irc_stats(struct userNode *from, struct server *target, char type)
 void
 irc_svsnick(struct userNode *from, struct userNode *target, const char *newnick)
 {
-    putsock("%s " P10_SVSNICK " %s %s "FMT_TIME_T, from->uplink->numeric, target->numeric, newnick, now);
+    putsock("%s " P10_SVSNICK " %s %s %lu", from->uplink->numeric, target->numeric, newnick, (unsigned long)now);
 }
 
 void
@@ -741,6 +973,12 @@ irc_numeric(struct userNode *user, unsigned int num, const char *format, ...)
     putsock(":%s %03d %s %s", self->name, num, user->nick, buffer);
 }
 
+void
+irc_xresponse(struct server *target, const char *routing, const char *response)
+{
+    putsock("%s " P10_XRESPONSE " %s %s :%s", self->numeric, target->numeric, routing, response);
+}
+
 static void send_burst(void);
 
 static void
@@ -765,6 +1003,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;
@@ -772,21 +1012,77 @@ 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) && IsFakeIdent(who) && IsHiddenHost(who))
+        irc_numeric(from, RPL_WHOISUSER, "%s %s %s * :%s", who->nick, who->fakeident, who->fakehost, who->info);
+    else if (IsFakeIdent(who) && IsHiddenHost(who))
+        irc_numeric(from, RPL_WHOISUSER, "%s %s %s * :%s", who->nick, who->fakeident, who->hostname, who->info);
+    else if (IsFakeHost(who) && IsHiddenHost(who))
+        irc_numeric(from, RPL_WHOISUSER, "%s %s %s * :%s", who->nick, who->ident, who->fakehost, who->info);
+    else if (IsHiddenHost(who) && who->handle_info && hidden_host_suffix)
+        irc_numeric(from, RPL_WHOISUSER, "%s %s %s.%s * :%s", who->nick, who->ident, who->handle_info->handle, hidden_host_suffix, who->info);
+    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 = 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_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) && (!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;
 }
@@ -822,7 +1118,7 @@ static CMD_FUNC(cmd_server)
     if (srv->boot <= PREHISTORY) {
         /* Server from the mists of time.. */
         if (srv->hops == 1) {
-            log_module(MAIN_LOG, LOG_ERROR, "Server %s claims to have booted at time "FMT_TIME_T".  This is absurd.", srv->name, srv->boot);
+            log_module(MAIN_LOG, LOG_ERROR, "Server %s claims to have booted at time %lu.  This is absurd.", srv->name, (unsigned long)srv->boot);
         }
     } else if ((str = conf_get_data("server/reliable_clock", RECDB_QSTRING))
                && enabled_string(str)) {
@@ -833,11 +1129,11 @@ 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) {
-        extern time_t burst_begin;
+        extern unsigned long burst_begin;
         burst_begin = now;
     }
     return 1;
@@ -869,7 +1165,7 @@ static CMD_FUNC(cmd_eob)
 
 static CMD_FUNC(cmd_eob_ack)
 {
-    extern time_t burst_begin;
+    extern unsigned long burst_begin;
 
     if (GetServerH(origin) == self->uplink) {
         burst_length = now - burst_begin;
@@ -884,8 +1180,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)))
@@ -910,7 +1206,7 @@ static CMD_FUNC(cmd_error_nick)
 
 struct create_desc {
     struct userNode *user;
-    time_t when;
+    unsigned long when;
 };
 
 static void
@@ -931,6 +1227,8 @@ create_helper(char *name, void *data)
         return;
     }
 
+    handle_new_channel_created(name, cd->user);
+
     AddChannelUser(cd->user, AddChannel(name, cd->when, NULL, NULL));
 }
 
@@ -1003,25 +1301,41 @@ static CMD_FUNC(cmd_nick)
 static CMD_FUNC(cmd_account)
 {
     struct userNode *user;
+    unsigned long timestamp = 0;
+    unsigned long serial = 0;
 
     if ((argc < 3) || !origin || !GetServerH(origin))
         return 0; /* Origin must be server. */
     user = GetUserN(argv[1]);
     if (!user)
         return 1; /* A QUIT probably passed the ACCOUNT. */
-    call_account_func(user, argv[2]);
+    if (argc > 3)
+        timestamp = strtoul(argv[3], NULL, 10);
+    if (argc > 4)
+        serial = strtoul(argv[4], NULL, 10);
+    call_account_func(user, argv[2], timestamp, serial);
     return 1;
 }
 
 static CMD_FUNC(cmd_fakehost)
 {
     struct userNode *user;
+    const char *host, *ident;
 
     if ((argc < 3) || !origin || !GetServerH(origin))
         return 0;
     if (!(user = GetUserN(argv[1])))
         return 1;
-    assign_fakehost(user, argv[2], 0);
+
+    if (argc > 3) {
+        ident = argv[2];
+        host = argv[3];
+    } else {
+        ident = NULL;
+        host = argv[2];
+    }
+
+    assign_fakehost(user, host, ident, 0, 0);
     return 1;
 }
 
@@ -1034,8 +1348,9 @@ static CMD_FUNC(cmd_burst)
     struct userNode *un;
     struct modeNode *mNode;
     long mode;
+    int oplevel = 0;
     char *user, *end, sep;
-    time_t in_timestamp;
+    unsigned long in_timestamp;
 
     if (argc < 3)
         return 0;
@@ -1046,8 +1361,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') || (*pos == 'a') || (*pos == 'F'))
                     n_modes++;
+            if (next + n_modes > argc)
+                n_modes = argc - next;
             unsplit_string(argv+next, n_modes, modes);
             next += n_modes;
             break;
@@ -1072,11 +1390,17 @@ 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 = MAXOPLEVEL;
+                } else if (sep == 'v') {
                     mode |= MODE_VOICE;
-                else
+                    oplevel = 0;
+                } else if (isdigit(sep)) {
+                    mode |= MODE_CHANOP;
+                    oplevel += parse_oplevel(end - 1);
+                    while (isdigit(*end)) end++;
+                } else
                     break;
             }
             if (rel_age < 0)
@@ -1086,8 +1410,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;
@@ -1106,6 +1432,7 @@ static CMD_FUNC(cmd_mode)
             log_module(MAIN_LOG, LOG_ERROR, "Unable to find user %s whose mode is changing.", argv[1]);
             return 0;
         }
+        argv[2] = unsplit_string(argv + 2, argc - 2, NULL);
         mod_usermode(un, argv[2]);
         return 1;
     }
@@ -1180,7 +1507,7 @@ static CMD_FUNC(cmd_clearmode)
 static CMD_FUNC(cmd_topic)
 {
     struct chanNode *cn;
-    time_t chan_ts, topic_ts;
+    unsigned long topic_ts;
 
     if (argc < 3)
         return 0;
@@ -1190,10 +1517,8 @@ static CMD_FUNC(cmd_topic)
     }
     if (argc >= 5) {
         /* Looks like an Asuka style topic burst. */
-        chan_ts = atoi(argv[2]);
         topic_ts = atoi(argv[3]);
     } else {
-        chan_ts = cn->timestamp;
         topic_ts = now;
     }
     SetChannelTopic(cn, GetUserH(origin), argv[argc-1], 0);
@@ -1239,9 +1564,14 @@ static CMD_FUNC(cmd_num_topic)
 
 static CMD_FUNC(cmd_num_gline)
 {
+    unsigned long lastmod;
+    unsigned long lifetime;
+
     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;
+    lifetime = (argc > 6) ? strtoul(argv[6], NULL, 0) : 0;
+    gline_add(origin, argv[3], atoi(argv[4])-now, argv[argc - 1], now, lastmod, lifetime, 0);
     return 1;
 }
 
@@ -1283,23 +1613,12 @@ 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)
         return 0;
+    if (GetUserN(argv[2]) && IsOper(GetUserN(argv[2])))
+        operpart(GetChannel(argv[1]), GetUserN(argv[2]));
     ChannelUserKicked(GetUserH(origin), GetUserN(argv[2]), GetChannel(argv[1]));
     return 1;
 }
@@ -1328,13 +1647,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;
 }
@@ -1342,13 +1661,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;
 }
@@ -1369,12 +1688,22 @@ static CMD_FUNC(cmd_away)
 
 static CMD_FUNC(cmd_gline)
 {
+#define PASTWATCH (5*365*24*3600)
+    unsigned long lastmod;
+    unsigned long lifetime;
+    unsigned long expiration;
+
     if (argc < 3)
         return 0;
     if (argv[2][0] == '+') {
         if (argc < 5)
             return 0;
-        gline_add(origin, argv[2]+1, strtoul(argv[3], NULL, 0), argv[argc-1], now, 0);
+        expiration = strtoul(argv[3], NULL, 10);
+        if (expiration < now - PASTWATCH)
+            expiration += now;
+        lastmod = (argc > 5) ? strtoul(argv[4], NULL, 10) : 0;
+        lifetime = (argc > 6) ? strtoul(argv[5], NULL, 10) : 0;
+        gline_add(origin, argv[2]+1, expiration - now, argv[argc-1], now, lastmod, lifetime, 0);
         return 1;
     } else if (argv[2][0] == '-') {
         gline_remove(argv[2]+1, 0);
@@ -1395,8 +1724,131 @@ static CMD_FUNC(cmd_svsnick)
     return 1;
 }
 
-static oper_func_t *of_list;
-static unsigned int of_size = 0, of_used = 0;
+static CMD_FUNC(cmd_time)
+{
+    extern int clock_skew;
+    char buf[MAXLEN];
+    struct userNode *who;
+    time_t when;
+
+    who = GetUserH(origin);
+    if (!who)
+        return 0;
+
+    when = time(NULL);
+    strftime(buf, sizeof(buf), "%a %b %d %Y -- %H:%M %z", localtime(&when));
+    irc_numeric(who, 391, "%s %lu %d :%s", self->name, now, clock_skew, buf);
+    return 1;
+}
+
+static CMD_FUNC(cmd_relay)
+{
+    struct server *sNode;
+    unsigned int len;
+    char buf[3];
+    //<sender> RELAY <destination> <command>
+    len = strlen(argv[1]);
+    buf[2] = 0;
+    switch(len) {
+        case 2:
+            sNode = GetServerN(argv[1]);
+            break;
+        case 5:
+            buf[0] = argv[1][0];
+            buf[1] = argv[1][1];
+            sNode = GetServerN(buf);
+            break;
+        case 6:
+            buf[0] = argv[1][1];
+            buf[1] = argv[1][2];
+            sNode = GetServerN(buf);
+            break;
+        default:
+            /* Invalid destination. Ignore. */
+            return 0;
+    }
+    if(sNode->numeric == self->numeric) {
+        //ok  someone relayed something to us!
+        if(strcmp("LQ", argv[2]) == 0) {
+            //oooh thats exciting - we've got a LOC Query! :D
+            //LQ !ABADE pk910 80.153.5.212 server.zoelle1.de ~watchcat :test
+            //ok  let's check the login datas
+            struct handle_info *hi;
+            char tmp[MAXLEN], tmp2[MAXLEN];
+            sprintf(tmp, "%s@%s",argv[7],argv[6]);
+            sprintf(tmp2, "%s@%s",argv[7],argv[5]);
+            if((hi = checklogin(argv[4],argv[argc-1],&argv[3][1],tmp,tmp2))) {
+             //login ok
+             struct devnull_class *th;
+             char devnull[512];
+             if(hi->devnull && (th = devnull_get(hi->devnull))) {
+                const char *devnull_modes = DEVNULL_MODES;
+                int ii, flen;
+                char flags[50];
+                for (ii=flen=0; devnull_modes[ii]; ++ii)
+                    if (th->modes & (1 << ii))
+                        flags[flen++] = devnull_modes[ii];
+                flags[flen] = 0;
+                sprintf(devnull, "+%s %s %lu %lu",flags,th->name,th->maxchan,th->maxsendq);
+             } else {
+                devnull[0] = 0;
+             }
+             if(!HANDLE_FLAGGED(hi, AUTOHIDE)) {
+                sprintf(tmp,"%s LA %s 0 %s\n",argv[3],hi->handle,devnull);
+             } else if(getfakehost(argv[4])) {
+                sprintf(tmp,"%s LA %s %s %s\n",argv[3],hi->handle,getfakehost(argv[4]),devnull);
+             } else {
+                extern const char *hidden_host_suffix;
+                sprintf(tmp,"%s LA %s %s.%s %s\n",argv[3],hi->handle,hi->handle,hidden_host_suffix,devnull);
+             }
+             irc_relay(tmp);
+            } else {
+             //login rejected
+             sprintf(tmp,"%s LR\n",argv[3]);
+             irc_relay(tmp);
+            }
+        } else if(strcmp("UC", argv[2]) == 0) {
+            char tmp[MAXLEN];
+            sprintf(tmp,"%s UC %s %s",argv[3],argv[3],argv[4]);
+            irc_relay(tmp);
+        } else if(strcmp("JA", argv[2]) == 0) {
+            struct userData *uData;
+            struct chanNode *cn;
+            struct userNode *user;
+            char tmp[MAXLEN];
+            cn = GetChannel(argv[4]);
+            if (!cn) return 0;
+            if (!(user = GetUserN(argv[3]))) return 0;
+            if(!cn->channel_info) {
+                //channel not registered
+                sprintf(tmp,"%s JAA %s %s\n",argv[3],cn->name,argv[6]);
+            } else if((uData = GetChannelUser(cn->channel_info, user->handle_info))) {
+                if(uData->access >= atoi(argv[5])) {
+                    //we can join
+                    sprintf(tmp,"%s JAA %s %s\n",argv[3],cn->name,argv[6]);
+                } else {
+                    //access too low
+                    sprintf(tmp,"%s JAR %s %i %i\n",argv[3],cn->name,uData->access,uData->access);
+                }
+            } else {
+                //0 access
+                sprintf(tmp,"%s JAR %s %s %s\n",argv[3],cn->name,"0","0");
+            }
+            irc_relay(tmp);
+        }
+    }
+    return 1;
+}
+
+static CMD_FUNC(cmd_xquery)
+{
+    struct server *source;
+    if ((argc < 4)
+        || !(source = GetServerH(origin)))
+        return 0;
+    call_xquery_funcs(source, argv[2], argv[3]);
+    return 1;
+}
 
 void
 free_user(struct userNode *user)
@@ -1411,7 +1863,9 @@ 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++)
@@ -1422,6 +1876,8 @@ 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
@@ -1459,11 +1915,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) {
@@ -1526,6 +1977,8 @@ init_parse(void)
     dict_insert(irc_func_dict, TOK_STATS, cmd_stats);
     dict_insert(irc_func_dict, CMD_SVSNICK, cmd_svsnick);
     dict_insert(irc_func_dict, TOK_SVSNICK, cmd_svsnick);
+    dict_insert(irc_func_dict, CMD_RELAY, cmd_relay);
+    dict_insert(irc_func_dict, TOK_RELAY, cmd_relay);
     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);
@@ -1538,6 +1991,10 @@ init_parse(void)
     dict_insert(irc_func_dict, TOK_VERSION, cmd_version);
     dict_insert(irc_func_dict, CMD_ADMIN, cmd_admin);
     dict_insert(irc_func_dict, TOK_ADMIN, cmd_admin);
+    dict_insert(irc_func_dict, CMD_TIME, cmd_time);
+    dict_insert(irc_func_dict, TOK_TIME, cmd_time);
+    /* We don't handle XR or the (not really defined) XQUERY. */
+    dict_insert(irc_func_dict, TOK_XQUERY, cmd_xquery);
 
     /* In P10, DESTRUCT doesn't do anything except be broadcast to servers.
      * Apparently to obliterate channels from any servers that think they
@@ -1576,6 +2033,7 @@ 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?) */
@@ -1591,6 +2049,7 @@ init_parse(void)
     memset(notice_funcs, 0, sizeof(privmsg_func_t)*num_notice_funcs);
 
     userList_init(&dead_users);
+    channelList_init(&dead_channels);
     reg_del_channel_func(remove_unbursted_channel);
     reg_exit_func(parse_cleanup);
 }
@@ -1627,6 +2086,9 @@ parse_line(char *line, int recursive)
         for (i=0; i<dead_users.used; i++)
             free_user(dead_users.list[i]);
         dead_users.used = 0;
+        for (i=0; i<dead_channels.used; i++)
+            UnlockChannel(dead_channels.list[i]);
+        dead_channels.used = 0;
     }
     return res;
 }
@@ -1635,15 +2097,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);
@@ -1716,7 +2181,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, unsigned long boot, unsigned long link_time, const char *numeric, const char *description)
 {
     struct server* sNode;
     int slen, mlen;
@@ -1748,7 +2213,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));
@@ -1799,17 +2264,17 @@ 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;
-        timestamp = old_user->timestamp - 1;
     }
     if (local_num == -1) {
         log_module(MAIN_LOG, LOG_ERROR, "Unable to allocate numnick for service %s", nick);
@@ -1818,7 +2283,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);
-    return AddUser(self, nick, nick, hostname, modes ? modes : "+oik", numeric, desc, now, "AAAAAA");
+    return AddUser(self, nick, ident, hostname, modes, numeric, desc, now, "AAAAAA");
 }
 
 struct userNode *
@@ -1826,7 +2291,7 @@ AddClone(const char *nick, const char *ident, const char *hostname, const char *
 {
     char numeric[COMBO_NUMERIC_LEN+1];
     int local_num = get_local_numeric();
-    time_t timestamp = now;
+    unsigned long timestamp = now;
     struct userNode *old_user = GetUserH(nick);
 
     if (old_user) {
@@ -1844,12 +2309,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;
@@ -1857,40 +2323,49 @@ is_valid_nick(const char *nick) {
 }
 
 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)
+AddUser(struct server* uplink, const char *nick, const char *ident, const char *hostname, const char *modes, const char *numeric, const char *userinfo, unsigned long timestamp, const char *realip)
 {
     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);
+        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) {
-            /* "Old" user is really newer; remove them */
+            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 {
             /* User being added is too new; do not add them to
@@ -1907,8 +2382,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) {
@@ -1917,6 +2393,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;
 
@@ -1927,9 +2405,8 @@ 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;
+    for (n=0; (n<nuf_used) && !uNode->dead; n++)
+        nuf_list[n](uNode);
     return uNode;
 }
 
@@ -1939,12 +2416,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. */
@@ -1969,6 +2448,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
@@ -1980,8 +2467,9 @@ 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;
 
@@ -1991,64 +2479,115 @@ void mod_usermode(struct userNode *user, const char *mode_change) {
     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++) {
-       case 0: case ' ': return;
-       case '+': add = 1; break;
-       case '-': add = 0; break;
-       case 'o':
-           do_user_mode(FLAGS_OPER);
-           if (add) {
-               userList_append(&curr_opers, user);
-               call_oper_funcs(user);
-           } else {
-               userList_remove(&curr_opers, user);
-           }
-           break;
-       case 'O': do_user_mode(FLAGS_LOCOP); break;
-       case 'i': do_user_mode(FLAGS_INVISIBLE);
-           if (add)
+        switch (*mode_change++) {
+        case 0: case ' ': return;
+        case '+': add = 1; break;
+        case '-': add = 0; break;
+        case 'o':
+            do_user_mode(FLAGS_OPER);
+            if (!add) {
+                operdel(user);
+                userList_remove(&curr_opers, user);
+            } else if (!userList_contains(&curr_opers, user)) {
+                operadd(user);
+                userList_append(&curr_opers, user);
+                call_oper_funcs(user);
+            }
+            break;
+        case 'i': do_user_mode(FLAGS_INVISIBLE);
+            if (add)
                 invis_clients++;
             else
                 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;
+            break;
+        case 'w': do_user_mode(FLAGS_WALLOP); 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 'n': do_user_mode(FLAGS_NOCHAN); break;
+        case 'I': do_user_mode(FLAGS_NOIDLE); break;
+        case 'S': do_user_mode(FLAGS_NETSERV); break;
+        case 'D': do_user_mode(FLAGS_SECURITYSERV); break;
+        case 'X': do_user_mode(FLAGS_XTRAOP); break;
+        case 's': do_user_mode(FLAGS_SERVERNOTICE); break;
+        case 'H': do_user_mode(FLAGS_HIDDENOPER); break;
+        case 't': do_user_mode(FLAGS_SEENOIDLE); break;
         case 'x': do_user_mode(FLAGS_HIDDEN_HOST); break;
         case 'r':
             if (*word) {
                 char tag[MAXLEN];
+                char *sep;
                 unsigned int ii;
-                for (ii=0; (*word != ' ') && (*word != '\0'); )
+                unsigned long ts = 0;
+                unsigned long id = 0;
+
+                for (ii=0; (*word != ' ') && (*word != '\0') && (*word != ':'); )
                     tag[ii++] = *word++;
-                tag[ii] = 0;
+                if (*word == ':') {
+                    ts = strtoul(word + 1, &sep, 10);
+                    if (*sep == ':') {
+                        id = strtoul(sep + 1, &sep, 10);
+                    } else if (*sep != ' ' && *sep != '\0') {
+                        ts = 0;
+                    }
+                    word = sep;
+                }
+                tag[ii] = '\0';
                 while (*word == ' ')
                     word++;
-                call_account_func(user, tag);
+                call_account_func(user, tag, ts, id);
             }
             break;
-        case 'f':
+        case 'h':
             if (*word) {
-                char host[MAXLEN];
+                char mask[MAXLEN];
+                char *host, *ident;
                 unsigned int ii;
+
                 for (ii=0; (*word != ' ') && (*word != '\0'); )
-                    host[ii++] = *word++;
-                host[ii] = 0;
+                    mask[ii++] = *word++;
+                mask[ii] = 0;
                 while (*word == ' ')
                     word++;
-                assign_fakehost(user, host, 0);
+
+                if ((host = strrchr(mask, '@'))) {
+                    ident = mask;
+                    *host++ = '\0';
+                } else {
+                    ident = NULL;
+                    host = mask;
+                }
+                user->modes |= FLAGS_HIDDEN_HOST;
+                assign_fakehost(user, host, ident, 0, 0);
             }
             break;
-       }
+        }
 #undef do_user_mode
     }
 }
 
+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 *
-mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags)
+mod_chanmode_parse(struct chanNode *channel, struct userNode *user, char **modes, unsigned int argc, unsigned int flags, short base_oplevel)
 {
     struct mod_chanmode *change;
     unsigned int ii, in_arg, ch_arg, add;
@@ -2072,6 +2611,10 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
         case 'C': do_chan_mode(MODE_NOCTCPS); break;
         case 'D': do_chan_mode(MODE_DELAYJOINS); break;
         case 'c': do_chan_mode(MODE_NOCOLORS); break;
+        case 'M': do_chan_mode(MODE_NOAMSGS); break;
+        case 'N': do_chan_mode(MODE_NONOTICES); break;
+        case 'u': do_chan_mode(MODE_AUDITORIUM); break;
+        case 'S': do_chan_mode(MODE_SSLCHAN); break;
         case 'i': do_chan_mode(MODE_INVITEONLY); break;
         case 'm': do_chan_mode(MODE_MODERATED); break;
         case 'n': do_chan_mode(MODE_NOPRIVMSGS); break;
@@ -2079,6 +2622,16 @@ 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) && (!(flags & MCP_IGN_REGISTERED) || add)) {
+            do_chan_mode(MODE_REGISTERED);
+          } else if (flags & MCP_IGN_REGISTERED) {
+            /* ignore the modechange but continue parsing */
+          } else {
+            mod_chanmode_free(change);
+            return NULL;
+          }
+          break;
 #undef do_chan_mode
         case 'l':
             if (add) {
@@ -2091,12 +2644,23 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
                 change->modes_clear |= MODE_LIMIT;
             }
             break;
-        case 'k':
+        case 'a':
             if (add) {
                 if (in_arg >= argc)
                     goto error;
+                change->modes_set |= MODE_ACCESS;
+                change->new_access = atoi(modes[in_arg++]);
+            } else {
+                change->modes_set &= ~MODE_ACCESS;
+                change->modes_clear |= MODE_ACCESS;
+            }
+            break;
+        case 'k':
+            if (add) {
+                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)) {
@@ -2106,6 +2670,105 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
                 }
             }
             break;
+        case 'f':
+                   if (add) {
+                           if (in_arg >= argc)
+                    goto error;
+                char *mode = modes[in_arg++];
+                if(mode[0] == '!' && !(flags & MCP_OPERMODE)) //noflood flag also for overriders
+                   goto error;//only allow opers
+                else if(mode[0] == '!')
+                   mode++;
+                
+                if(mode[0] == '+' || mode[0] == '@') {
+                    mode++;
+                }
+                char *p;
+                int count = 0, time = 0;
+                for(p = mode; p[0]; p++) {
+                    if(p[0] == ':') {
+                        char tmpchar = p[0];
+                        p[0] = '\0';
+                        count = strtoul(mode,0,10);
+                        p[0] = tmpchar;
+                                               p++;
+                                               time = strtoul(p,0,10);
+                                               break;
+                               }
+                }
+                if(count <= 0 || time <= 0 || count > 100 || time > 600)
+                                   goto error;
+                               change->modes_set |= MODE_NOFLOOD;
+                               safestrncpy(change->new_noflood, modes[in_arg - 1], sizeof(change->new_noflood));
+                       } else {
+                           change->modes_clear |= MODE_NOFLOOD;
+                       }
+            break;
+        case 'F':
+            if (add) {
+                if (in_arg >= argc)
+                    goto error;
+                char *altchan = modes[in_arg++];
+                struct chanNode *target;
+                if(!IsChannelName(altchan) || !(target = GetChannel(altchan)))
+                    goto error;
+                if(!(flags & MCP_OPERMODE)) {
+                    //check if the user has the permissions to use this channel as target
+                    struct modeNode *mn;
+                    struct userData *uData;
+                    struct chanData *cData;
+                    if(user && (mn = GetUserMode(target, user)) && (mn->modes & MODE_CHANOP)) {
+                        //allow - user is opped on target channel
+                    } else if(user && user->handle_info && 
+                              (uData = GetChannelUser(channel->channel_info, user->handle_info)) && 
+                              (cData = uData->channel) && 
+                              uData->access >= cData->lvlOpts[lvlGiveOps]
+                             ) {
+                        //allow - user has access to get op on the channel
+                    } else 
+                        goto error;
+                }
+                change->modes_set |= MODE_ALTCHAN;
+                safestrncpy(change->new_altchan, altchan, sizeof(change->new_altchan));
+            } else {
+                change->modes_clear |= MODE_ALTCHAN;
+            }
+            break;
+        case 'U':
+            if (flags & MCP_NO_APASS)
+                goto error;
+            if (add)
+            {
+                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)) {
+                    if (in_arg >= argc)
+                        goto error;
+                    in_arg++;
+                }
+            }
+            break;
+        case 'A':
+            if (flags & MCP_NO_APASS)
+                goto error;
+            if (add) {
+                if ((in_arg >= argc)
+                    || keyncpy(change->new_apass, modes[in_arg++], sizeof(change->new_apass)))
+                    goto error;
+                change->modes_set |= MODE_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;
@@ -2119,6 +2782,29 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
         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 = MAXOPLEVEL;
+
+            /* Check that oplevel is within bounds. */
+            if (oplevel > MAXOPLEVEL)
+                oplevel = MAXOPLEVEL;
+
             if (!(flags & MCP_ALLOW_OVB))
                 goto error;
             if (in_arg >= argc)
@@ -2133,7 +2819,12 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
             if (!victim)
                 continue;
             if ((change->args[ch_arg].u.member = GetUserMode(channel, victim)))
+            {
+                /* Apply the oplevel change if the user is being (de-)opped */
+                if (modes[0][ii] == 'o')
+                    change->args[ch_arg].u.member->oplevel = oplevel;
                 ch_arg++;
+            }
             break;
         }
         default:
@@ -2150,6 +2841,11 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
         change->modes_set &= ~(MODE_SECRET);
         change->modes_clear |= MODE_SECRET;
     }
+    if (change->modes_clear & MODE_REGISTERED) {
+        /* Horribly cheat by using the lock/unlock semantics. */
+        LockChannel(channel);
+        channelList_append(&dead_channels, channel);
+    }
     return change;
   error:
     mod_chanmode_free(change);
@@ -2218,13 +2914,25 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod
         DO_MODE_CHAR(INVITEONLY, 'i');
         DO_MODE_CHAR(NOPRIVMSGS, 'n');
         DO_MODE_CHAR(LIMIT, 'l');
+        DO_MODE_CHAR(ACCESS, 'a');
+        DO_MODE_CHAR(ALTCHAN, 'F');
+        DO_MODE_CHAR(NOFLOOD, 'f');
         DO_MODE_CHAR(DELAYJOINS, 'D');
         DO_MODE_CHAR(REGONLY, 'r');
         DO_MODE_CHAR(NOCOLORS, 'c');
         DO_MODE_CHAR(NOCTCPS, 'C');
+        DO_MODE_CHAR(NONOTICES, 'N');
+        DO_MODE_CHAR(NOAMSGS, 'M');
+        DO_MODE_CHAR(AUDITORIUM, 'u');
+        DO_MODE_CHAR(SSLCHAN, 'S');
+        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))
@@ -2260,13 +2968,30 @@ 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(NONOTICES, 'N');
+        DO_MODE_CHAR(NOAMSGS, 'M');
+        DO_MODE_CHAR(AUDITORIUM, 'u');
+        DO_MODE_CHAR(SSLCHAN, 'S');
+        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);
         }
+        if(change->modes_set & MODE_ACCESS) {
+            sprintf(int_buff, "%d", change->new_access);
+            mod_chanmode_append(&chbuf, 'a', int_buff);
+        }
+        if (change->modes_set & MODE_ALTCHAN)
+            mod_chanmode_append(&chbuf, 'F', change->new_altchan);
+        if (change->modes_set & MODE_NOFLOOD)
+            mod_chanmode_append(&chbuf, 'f', change->new_noflood);
     }
     for (arg = 0; arg < change->argc; ++arg) {
         if (change->args[arg].mode & MODE_REMOVE)
@@ -2299,6 +3024,9 @@ char *
 mod_chanmode_format(struct mod_chanmode *change, char *outbuff)
 {
     unsigned int used = 0;
+    unsigned int args_used = 0;
+    char args[MAXLEN];
+
     assert(change->argc <= change->alloc_argc);
     if (change->modes_clear) {
         outbuff[used++] = '-';
@@ -2310,11 +3038,21 @@ mod_chanmode_format(struct mod_chanmode *change, char *outbuff)
         DO_MODE_CHAR(INVITEONLY, 'i');
         DO_MODE_CHAR(NOPRIVMSGS, 'n');
         DO_MODE_CHAR(LIMIT, 'l');
+        DO_MODE_CHAR(ACCESS, 'a');
+        DO_MODE_CHAR(ALTCHAN, 'F');
+        DO_MODE_CHAR(NOFLOOD, 'f');
         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(NONOTICES, 'N');
+        DO_MODE_CHAR(NOAMSGS, 'M');
+        DO_MODE_CHAR(AUDITORIUM, 'u');
+        DO_MODE_CHAR(SSLCHAN, 'S');
+        DO_MODE_CHAR(REGISTERED, 'z');
 #undef DO_MODE_CHAR
     }
     if (change->modes_set) {
@@ -2330,62 +3068,89 @@ 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(NONOTICES, 'N');
+               DO_MODE_CHAR(NOAMSGS, 'M');
+        DO_MODE_CHAR(AUDITORIUM, 'u');
+        DO_MODE_CHAR(SSLCHAN, 'S');
+        DO_MODE_CHAR(REGISTERED, 'z');
+        DO_MODE_CHAR(LIMIT, 'l'), args_used += sprintf(args + args_used, " %d", change->new_limit);
+        DO_MODE_CHAR(KEY, 'k'), args_used += sprintf(args + args_used, " %s", change->new_key);
+        DO_MODE_CHAR(ACCESS, 'a'), args_used += sprintf(args + args_used, " %d", change->new_access);
+        DO_MODE_CHAR(ALTCHAN, 'F'), args_used += sprintf(args + args_used, " %s", change->new_altchan);
+        DO_MODE_CHAR(NOFLOOD, 'f'), args_used += sprintf(args + args_used, " %s", change->new_noflood);
+        DO_MODE_CHAR(UPASS, 'U'), args_used += sprintf(args + args_used, " %s", change->new_upass);
+        DO_MODE_CHAR(APASS, 'A'), args_used += sprintf(args + args_used, " %s", change->new_apass);
 #undef DO_MODE_CHAR
-        switch (change->modes_set & (MODE_KEY|MODE_LIMIT)) {
-        case MODE_KEY|MODE_LIMIT:
-            used += sprintf(outbuff+used, "lk %d %s", change->new_limit, change->new_key);
-            break;
-        case MODE_KEY:
-            used += sprintf(outbuff+used, "k %s", change->new_key);
-            break;
-        case MODE_LIMIT:
-            used += sprintf(outbuff+used, "l %d", change->new_limit);
-            break;
-        }
     }
-    outbuff[used] = 0;
+    args[args_used] = '\0';
+    strcpy(outbuff + used, args);
     return outbuff;
 }
 
 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 '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 '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 'F':
+            cleared |= MODE_ALTCHAN;
+            channel->altchan[0] = '\0';
+            break;
+        case 'f':
+            cleared |= MODE_NOFLOOD;
+            channel->noflood[0] = '\0';
+            break;
         case 'k':
-            remove |= MODE_KEY;
+            cleared |= MODE_KEY;
             channel->key[0] = '\0';
             break;
+        case 'A':
+            cleared |= MODE_APASS;
+            channel->apass[0] = '\0';
+            break;
+        case 'U':
+            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 'D': remove |= MODE_DELAYJOINS; break;
-        case 'r': remove |= MODE_REGONLY; break;
-        case 'c': remove |= MODE_NOCOLORS;
-        case 'C': remove |= MODE_NOCTCPS; break;
+        case 'a':
+            cleared |= MODE_ACCESS;
+            channel->access = 0;
+            break;
+        case 'b': cleared |= MODE_BAN; 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 'M': cleared |= MODE_NOAMSGS; break;
+        case 'u': cleared |= MODE_AUDITORIUM; break;
+        case 'S': cleared |= MODE_SSLCHAN; break;
+        case 'N': cleared |= MODE_NONOTICES; break;
+        case 'z': cleared |= MODE_REGISTERED; 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]);
@@ -2393,8 +3158,8 @@ clear_chanmode(struct chanNode *channel, const char *modes)
     }
 
     /* Remove member modes. */
-    if ((remove & (MODE_CHANOP | MODE_VOICE)) && channel->members.used) {
-        int mask = ~(remove & (MODE_CHANOP | MODE_VOICE));
+    if ((cleared & (MODE_CHANOP | MODE_VOICE)) && channel->members.used) {
+        int mask = ~(cleared & (MODE_CHANOP | MODE_VOICE));
         unsigned int i;
 
         for (i = 0; i < channel->members.used; i++)
@@ -2409,9 +3174,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])
@@ -2420,20 +3186,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;
 }
 
 
@@ -2442,9 +3200,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_notice_funcs; ii < newnum; ++ii)
+            notice_funcs[ii] = NULL;
         num_notice_funcs = newnum;
     }
     if (notice_funcs[numeric])
@@ -2453,46 +3212,12 @@ reg_notice_func(struct userNode *user, privmsg_func_t handler)
 }
 
 void
-unreg_notice_func(struct userNode *user, privmsg_func_t handler)
+unreg_notice_func(struct userNode *user)
 {
-    unsigned int x;
-
-    if (!user || handler)
-          return; /* this really only works with users */
-
-    memset(notice_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_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)
-{
-    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;
-}
-
-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