Fixes for LP64 models; announce IPv6 support.
[srvx.git] / src / opserv.c
index 74a4380998bf6aa75f0c0cfd4fe890a5d0a2ba60..2496b8d3129c618a5a633e66da6b4530d3258721 100644 (file)
@@ -78,7 +78,7 @@
 #define KEY_ISSUED "issued"
 
 #define IDENT_FORMAT           "%s [%s@%s/%s]"
-#define IDENT_DATA(user)       user->nick, user->ident, user->hostname, inet_ntoa(user->ip)
+#define IDENT_DATA(user)       user->nick, user->ident, user->hostname, irc_ntoa(&user->ip)
 #define MAX_CHANNELS_WHOIS     50
 #define OSMSG_PART_REASON       "%s has no reason."
 #define OSMSG_KICK_REQUESTED    "Kick requested by %s."
@@ -115,6 +115,7 @@ static const struct message_entry msgtab[] = {
     { "OSMSG_WHOIS_IDENT", "%s (%s@%s) from %d.%d.%d.%d" },
     { "OSMSG_WHOIS_NICK", "Nick    : %s" },
     { "OSMSG_WHOIS_HOST", "Host    : %s@%s" },
+    { "OSMSG_WHOIS_FAKEHOST", "Fakehost: %s" },
     { "OSMSG_WHOIS_IP",   "Real IP : %s" },
     { "OSMSG_WHOIS_MODES", "Modes   : +%s " },
     { "OSMSG_WHOIS_INFO", "Info    : %s" },
@@ -317,14 +318,15 @@ opserv_free_hostinfo(void *data)
 
 typedef struct opservDiscrim {
     struct chanNode *channel;
-    char *mask_nick, *mask_ident, *mask_host, *mask_info, *server, *ip_mask_str, *reason, *accountmask;
-    unsigned long limit, ip_mask;
-    struct in_addr ip_addr;
+    char *mask_nick, *mask_ident, *mask_host, *mask_info, *server, *reason, *accountmask;
+    irc_in_addr_t ip_mask;
+    unsigned long limit;
+    time_t min_ts, max_ts;
     unsigned int min_level, max_level, domain_depth, duration, min_clones, min_channels, max_channels;
+    unsigned char ip_mask_bits;
     unsigned int match_opers : 1, option_log : 1;
     unsigned int chan_req_modes : 2, chan_no_modes : 2;
     int authed : 2, info_space : 2;
-    time_t min_ts, max_ts;
 } *discrim_t;
 
 struct discrim_and_source {
@@ -397,20 +399,20 @@ static MODCMD_FUNC(cmd_ban)
     struct mod_chanmode change;
     struct userNode *victim;
 
-    change.modes_set = change.modes_clear = 0;
+    mod_chanmode_init(&change);
     change.argc = 1;
     change.args[0].mode = MODE_BAN;
     if (is_ircmask(argv[1]))
-        change.args[0].hostmask = strdup(argv[1]);
+        change.args[0].u.hostmask = strdup(argv[1]);
     else if ((victim = GetUserH(argv[1])))
-        change.args[0].hostmask = generate_hostmask(victim, 0);
+        change.args[0].u.hostmask = generate_hostmask(victim, 0);
     else {
        reply("OSMSG_INVALID_IRCMASK", argv[1]);
        return 0;
     }
     modcmd_chanmode_announce(&change);
-    reply("OSMSG_ADDED_BAN", change.args[0].hostmask, channel->name);
-    free((char*)change.args[0].hostmask);
+    reply("OSMSG_ADDED_BAN", change.args[0].u.hostmask, channel->name);
+    free((char*)change.args[0].u.hostmask);
     return 1;
 }
 
@@ -455,9 +457,13 @@ static MODCMD_FUNC(cmd_chaninfo)
     }
     reply("OSMSG_CHANINFO_USER_COUNT", channel->members.used);
     for (n=0; n<channel->members.used; n++) {
-       moden = channel->members.list[n];
-       if (moden->modes & MODE_CHANOP)
-            send_message_type(4, user, cmd->parent->bot, " @%s (%s@%s)", moden->user->nick, moden->user->ident, moden->user->hostname);
+        moden = channel->members.list[n];
+        if (moden->modes & MODE_CHANOP) {
+            if (moden->oplevel >= 0)
+                send_message_type(4, user, cmd->parent->bot, " @%s:%d (%s@%s)", moden->user->nick, moden->oplevel, moden->user->ident, moden->user->hostname);
+            else
+                send_message_type(4, user, cmd->parent->bot, " @%s (%s@%s)", moden->user->nick, moden->user->ident, moden->user->hostname);
+        }
     }
     for (n=0; n<channel->members.used; n++) {
        moden = channel->members.list[n];
@@ -472,7 +478,7 @@ static MODCMD_FUNC(cmd_chaninfo)
     return 1;
 }
 
-static MODCMD_FUNC(cmd_warn) 
+static MODCMD_FUNC(cmd_warn)
 {
     char *reason, *message;
 
@@ -521,9 +527,11 @@ static MODCMD_FUNC(cmd_clearbans)
     change = mod_chanmode_alloc(channel->banlist.used);
     for (ii=0; ii<channel->banlist.used; ii++) {
         change->args[ii].mode = MODE_REMOVE | MODE_BAN;
-        change->args[ii].hostmask = channel->banlist.list[ii]->ban;
+        change->args[ii].u.hostmask = strdup(channel->banlist.list[ii]->ban);
     }
     modcmd_chanmode_announce(change);
+    for (ii=0; ii<change->argc; ++ii)
+        free((char*)change->args[ii].u.hostmask);
     mod_chanmode_free(change);
     reply("OSMSG_CLEARBANS_DONE", channel->name);
     return 1;
@@ -537,9 +545,8 @@ static MODCMD_FUNC(cmd_clearmodes)
        reply("OSMSG_NO_CHANNEL_MODES", channel->name);
         return 0;
     }
-    change.modes_set = 0;
+    mod_chanmode_init(&change);
     change.modes_clear = channel->modes;
-    change.argc = 0;
     modcmd_chanmode_announce(&change);
     reply("OSMSG_CLEARMODES_DONE", channel->name);
     return 1;
@@ -559,7 +566,7 @@ static MODCMD_FUNC(cmd_deop)
             || !(mn->modes & MODE_CHANOP))
             continue;
         change->args[count].mode = MODE_REMOVE | MODE_CHANOP;
-        change->args[count++].member = mn;
+        change->args[count++].u.member = mn;
     }
     if (count) {
         change->argc = count;
@@ -581,7 +588,7 @@ static MODCMD_FUNC(cmd_deopall)
        if (IsService(mn->user) || !(mn->modes & MODE_CHANOP))
             continue;
         change->args[count].mode = MODE_REMOVE | MODE_CHANOP;
-        change->args[count++].member = mn;
+        change->args[count++].u.member = mn;
     }
     if (count) {
         change->argc = count;
@@ -744,14 +751,13 @@ static MODCMD_FUNC(cmd_restart)
 static struct gline *
 opserv_block(struct userNode *target, char *src_handle, char *reason, unsigned long duration)
 {
-    char *mask;
-    mask = alloca(MAXLEN);
-    snprintf(mask, MAXLEN, "*@%s", target->hostname);
-    if (!reason) {
-        reason = alloca(MAXLEN);
-        snprintf(reason, MAXLEN, "G-line requested by %s.", src_handle);
-    }
-    if (!duration) duration = opserv_conf.block_gline_duration;
+    char mask[IRC_NTOP_MAX_SIZE+3] = { '*', '@', '\0' };
+    irc_ntop(mask + 2, sizeof(mask) - 2, &target->ip);
+    if (!reason)
+        snprintf(reason = alloca(MAXLEN), MAXLEN,
+                 "G-line requested by %s.", src_handle);
+    if (!duration)
+        duration = opserv_conf.block_gline_duration;
     return gline_add(src_handle, mask, duration, reason, now, 1);
 }
 
@@ -886,7 +892,7 @@ static MODCMD_FUNC(cmd_inviteme)
        return 0;
     }
     if (GetUserMode(opserv_conf.debug_channel, user)) {
-        reply("OSMSG_ALREADY_THERE", channel->name);
+        reply("OSMSG_ALREADY_THERE", opserv_conf.debug_channel->name);
         return 0;
     }
     irc_invite(cmd->parent->bot, target, opserv_conf.debug_channel);
@@ -920,10 +926,10 @@ static MODCMD_FUNC(cmd_join)
         return 0;
     } else {
         struct mod_chanmode change;
-        change.modes_set = change.modes_clear = 0;
+        mod_chanmode_init(&change);
         change.argc = 1;
         change.args[0].mode = MODE_CHANOP;
-        change.args[0].member = AddChannelUser(bot, channel);
+        change.args[0].u.member = AddChannelUser(bot, channel);
         modcmd_chanmode_announce(&change);
     }
     irc_fetchtopic(bot, channel->name);
@@ -966,9 +972,9 @@ static MODCMD_FUNC(cmd_kickall)
      * channel, we have to join it in temporarily. */
     if (!(inchan = GetUserMode(channel, bot) ? 1 : 0)) {
         struct mod_chanmode change;
-        memset(&change, 0, sizeof(change));
+        mod_chanmode_init(&change);
         change.args[0].mode = MODE_CHANOP;
-        change.args[0].member = AddChannelUser(bot, channel);
+        change.args[0].u.member = AddChannelUser(bot, channel);
         modcmd_chanmode_announce(&change);
     }
     if (argc < 2) {
@@ -1015,10 +1021,10 @@ static MODCMD_FUNC(cmd_kickban)
        reply("OSMSG_NOT_ON_CHANNEL", target->nick, channel->name);
        return 0;
     }
-    change.modes_set = change.modes_clear = 0;
+    mod_chanmode_init(&change);
     change.argc = 1;
     change.args[0].mode = MODE_BAN;
-    change.args[0].hostmask = mask = generate_hostmask(target, 0);
+    change.args[0].u.hostmask = mask = generate_hostmask(target, 0);
     modcmd_chanmode_announce(&change);
     KickChannelUser(target, channel, cmd->parent->bot, reason);
     free(mask);
@@ -1038,15 +1044,16 @@ static MODCMD_FUNC(cmd_kickbanall)
     if (!(inchan = GetUserMode(channel, bot) ? 1 : 0)) {
         change = mod_chanmode_alloc(2);
         change->args[0].mode = MODE_CHANOP;
-        change->args[0].member = AddChannelUser(bot, channel);
+        change->args[0].u.member = AddChannelUser(bot, channel);
         change->args[1].mode = MODE_BAN;
-        change->args[1].hostmask = "*!*@*";
+        change->args[1].u.hostmask = "*!*@*";
     } else {
         change = mod_chanmode_alloc(1);
         change->args[0].mode = MODE_BAN;
-        change->args[0].hostmask = "*!*@*";
+        change->args[0].u.hostmask = "*!*@*";
     }
     modcmd_chanmode_announce(change);
+    mod_chanmode_free(change);
     if (argc < 2) {
        reason = alloca(strlen(OSMSG_KICK_REQUESTED)+strlen(user->nick)+1);
        sprintf(reason, OSMSG_KICK_REQUESTED, user->nick);
@@ -1067,7 +1074,7 @@ static MODCMD_FUNC(cmd_kickbanall)
     if (!inchan)
         DelChannelUser(bot, channel, "My work here is done", 0);
     reply("OSMSG_KICKALL_DONE", channel->name);
-    return 1;    
+    return 1;
 }
 
 static MODCMD_FUNC(cmd_part)
@@ -1116,7 +1123,7 @@ static MODCMD_FUNC(cmd_op)
         if (mn->modes & MODE_CHANOP)
             continue;
         change->args[count].mode = MODE_CHANOP;
-        change->args[count++].member = mn;
+        change->args[count++].u.member = mn;
     }
     if (count) {
         change->argc = count;
@@ -1138,7 +1145,7 @@ static MODCMD_FUNC(cmd_opall)
        if (mn->modes & MODE_CHANOP)
             continue;
         change->args[count].mode = MODE_CHANOP;
-        change->args[count++].member = mn;
+        change->args[count++].u.member = mn;
     }
     if (count) {
         change->argc = count;
@@ -1159,17 +1166,17 @@ static MODCMD_FUNC(cmd_whois)
     if (argv[1][0] == '*')
         target = GetUserN(argv[1]+1);
     else
-        target = GetUserH(argv[1]);
-#else
-    target = GetUserH(argv[1]);
 #endif
+    target = GetUserH(argv[1]);
     if (!target) {
         reply("MSG_NICK_UNKNOWN", argv[1]);
         return 0;
     }
     reply("OSMSG_WHOIS_NICK", target->nick);
     reply("OSMSG_WHOIS_HOST", target->ident, target->hostname);
-    reply("OSMSG_WHOIS_IP", inet_ntoa(target->ip));
+    if (IsFakeHost(target))
+        reply("OSMSG_WHOIS_FAKEHOST", target->fakehost);
+    reply("OSMSG_WHOIS_IP", irc_ntoa(&target->ip));
     if (target->modes) {
        bpos = 0;
 #define buffer_cat(str) (herelen = strlen(str), memcpy(buffer+bpos, str, herelen), bpos += herelen)
@@ -1183,6 +1190,7 @@ static MODCMD_FUNC(cmd_whois)
        if (IsDeaf(target)) buffer[bpos++] = 'd';
         if (IsHiddenHost(target)) buffer[bpos++] = 'x';
         if (IsGagged(target)) buffer_cat(" (gagged)");
+       if (IsRegistering(target)) buffer_cat(" (registered account)");
        buffer[bpos] = 0;
        if (bpos > 0)
             reply("OSMSG_WHOIS_MODES", buffer);
@@ -1205,10 +1213,10 @@ static MODCMD_FUNC(cmd_whois)
 static MODCMD_FUNC(cmd_unban)
 {
     struct mod_chanmode change;
-    change.modes_set = change.modes_clear = 0;
+    mod_chanmode_init(&change);
     change.argc = 1;
     change.args[0].mode = MODE_REMOVE | MODE_BAN;
-    change.args[0].hostmask = argv[1];
+    change.args[0].u.hostmask = argv[1];
     modcmd_chanmode_announce(&change);
     reply("OSMSG_UNBAN_DONE", channel->name);
     return 1;
@@ -1225,7 +1233,7 @@ static MODCMD_FUNC(cmd_voiceall)
        if (mn->modes & (MODE_CHANOP|MODE_VOICE))
             continue;
         change->args[count].mode = MODE_VOICE;
-        change->args[count++].member = mn;
+        change->args[count++].u.member = mn;
     }
     if (count) {
         change->argc = count;
@@ -1244,10 +1252,10 @@ static MODCMD_FUNC(cmd_devoiceall)
     change = mod_chanmode_alloc(channel->members.used);
     for (ii = count = 0; ii < channel->members.used; ++ii) {
        struct modeNode *mn = channel->members.list[ii];
-       if (mn->modes & MODE_VOICE)
+       if (!(mn->modes & MODE_VOICE))
             continue;
         change->args[count].mode = MODE_REMOVE | MODE_VOICE;
-        change->args[count++].member = mn;
+        change->args[count++].u.member = mn;
     }
     if (count) {
         change->argc = count;
@@ -1576,11 +1584,33 @@ static MODCMD_FUNC(cmd_stats_warn) {
     return 1;
 }
 
+#if defined(WITH_MALLOC_SRVX)
+static MODCMD_FUNC(cmd_stats_memory) {
+    extern unsigned long alloc_count, alloc_size;
+    send_message_type(MSG_TYPE_NOXLATE, user, cmd->parent->bot,
+                      "%u allocations totalling %u bytes.",
+                      alloc_count, alloc_size);
+    return 1;
+}
+#elif defined(WITH_MALLOC_SLAB)
+static MODCMD_FUNC(cmd_stats_memory) {
+    extern unsigned long slab_alloc_count, slab_count, slab_alloc_size;
+    extern unsigned long big_alloc_count, big_alloc_size;
+    send_message_type(MSG_TYPE_NOXLATE, user, cmd->parent->bot,
+                      "%u allocations in %u slabs totalling %u bytes.",
+                      slab_alloc_count, slab_count, slab_alloc_size);
+    send_message_type(MSG_TYPE_NOXLATE, user, cmd->parent->bot,
+                      "%u big allocations totalling %u bytes.",
+                      big_alloc_count, big_alloc_size);
+    return 1;
+}
+#endif
+
 static MODCMD_FUNC(cmd_dump)
 {
-    char linedup[MAXLEN], *original;
+    char linedup[MAXLEN], original[MAXLEN];
 
-    original = unsplit_string(argv+1, argc-1, NULL);
+    unsplit_string(argv+1, argc-1, original);
     safestrncpy(linedup, original, sizeof(linedup));
     /* assume it's only valid IRC if we can parse it */
     if (parse_line(linedup, 1)) {
@@ -1593,9 +1623,9 @@ static MODCMD_FUNC(cmd_dump)
 
 static MODCMD_FUNC(cmd_raw)
 {
-    char linedup[MAXLEN], *original;
+    char linedup[MAXLEN], original[MAXLEN];
 
-    original = unsplit_string(argv+1, argc-1, NULL);
+    unsplit_string(argv+1, argc-1, original);
     safestrncpy(linedup, original, sizeof(linedup));
     /* Try to parse the line before sending it; if it's too wrong,
      * maybe it will core us instead of our uplink. */
@@ -1696,6 +1726,7 @@ opserv_new_user_check(struct userNode *user)
 {
     struct opserv_hostinfo *ohi;
     struct gag_entry *gag;
+    char addr[IRC_NTOP_MAX_SIZE];
 
     /* Check to see if we should ignore them entirely. */
     if (IsLocal(user) || IsService(user))
@@ -1707,16 +1738,17 @@ opserv_new_user_check(struct userNode *user)
 
     /* Gag them if appropriate. */
     for (gag = gagList; gag; gag = gag->next) {
-        if (user_matches_glob(user, gag->mask, 1)) {
+        if (user_matches_glob(user, gag->mask, MATCH_USENICK)) {
             gag_helper_func(user, NULL);
             break;
         }
     }
 
     /* Add to host info struct */
-    if (!(ohi = dict_find(opserv_hostinfo_dict, inet_ntoa(user->ip), NULL))) {
+    irc_ntop(addr, sizeof(addr), &user->ip);
+    if (!(ohi = dict_find(opserv_hostinfo_dict, addr, NULL))) {
         ohi = calloc(1, sizeof(*ohi));
-        dict_insert(opserv_hostinfo_dict, strdup(inet_ntoa(user->ip)), ohi);
+        dict_insert(opserv_hostinfo_dict, strdup(addr), ohi);
         userList_init(&ohi->clients);
     }
     userList_append(&ohi->clients, user);
@@ -1734,8 +1766,10 @@ opserv_new_user_check(struct userNode *user)
     }
 
     /* Only warn or G-line if there's an untrusted max and their IP is sane. */
-    if (opserv_conf.untrusted_max && user->ip.s_addr && (ntohl(user->ip.s_addr) != INADDR_LOOPBACK)) {
-        struct trusted_host *th = dict_find(opserv_trusted_hosts, inet_ntoa(user->ip), NULL);
+    if (opserv_conf.untrusted_max
+        && irc_in_addr_is_valid(user->ip)
+        && !irc_in_addr_is_loopback(user->ip)) {
+        struct trusted_host *th = dict_find(opserv_trusted_hosts, addr, NULL);
         unsigned int limit = th ? th->limit : opserv_conf.untrusted_max;
         if (!limit) {
             /* 0 means unlimited hosts */
@@ -1744,8 +1778,8 @@ opserv_new_user_check(struct userNode *user)
             for (nn=0; nn<ohi->clients.used; nn++)
                 send_message(ohi->clients.list[nn], opserv, "OSMSG_CLONE_WARNING");
         } else if (ohi->clients.used > limit) {
-            char target[18];
-            sprintf(target, "*@%s", inet_ntoa(user->ip));
+            char target[IRC_NTOP_MAX_SIZE + 3] = { '*', '@', '\0' };
+            strcpy(target + 2, addr);
             gline_add(opserv->nick, target, opserv_conf.clone_gline_duration, "AUTO Excessive connections from a single host.", now, 1);
         }
     }
@@ -1757,6 +1791,7 @@ static void
 opserv_user_cleanup(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why))
 {
     struct opserv_hostinfo *ohi;
+    char addr[IRC_NTOP_MAX_SIZE];
 
     if (IsLocal(user)) {
         /* Try to remove it from the reserved nick dict without
@@ -1765,9 +1800,11 @@ opserv_user_cleanup(struct userNode *user, UNUSED_ARG(struct userNode *killer),
         dict_remove(opserv_reserved_nick_dict, user->nick);
         return;
     }
-    if ((ohi = dict_find(opserv_hostinfo_dict, inet_ntoa(user->ip), NULL))) {
+    irc_ntop(addr, sizeof(addr), &user->ip);
+    if ((ohi = dict_find(opserv_hostinfo_dict, addr, NULL))) {
         userList_remove(&ohi->clients, user);
-        if (ohi->clients.used == 0) dict_remove(opserv_hostinfo_dict, inet_ntoa(user->ip));
+        if (ohi->clients.used == 0)
+            dict_remove(opserv_hostinfo_dict, addr);
     }
 }
 
@@ -1775,9 +1812,10 @@ int
 opserv_bad_channel(const char *name)
 {
     unsigned int found;
+    int present;
 
-    dict_find(opserv_exempt_channels, name, &found);
-    if (found)
+    dict_find(opserv_exempt_channels, name, &present);
+    if (present)
         return 0;
 
     if (gline_find(name))
@@ -1799,9 +1837,9 @@ opserv_shutdown_channel(struct chanNode *channel, const char *reason)
     change = mod_chanmode_alloc(2);
     change->modes_set = MODE_SECRET | MODE_INVITEONLY;
     change->args[0].mode = MODE_CHANOP;
-    change->args[0].member = AddChannelUser(opserv, channel);
+    change->args[0].u.member = AddChannelUser(opserv, channel);
     change->args[1].mode = MODE_BAN;
-    change->args[1].hostmask = "*!*@*";
+    change->args[1].u.hostmask = "*!*@*";
     mod_chanmode_announce(opserv, channel, change);
     mod_chanmode_free(change);
     for (nn=channel->members.used; nn>0; ) {
@@ -1874,17 +1912,16 @@ opserv_join_check(struct modeNode *mNode)
         /* Don't moderate the channel unless it is activated and
            the number of users in the channel is over the threshold. */
         struct mod_chanmode change;
-        change.modes_set = change.modes_clear = change.argc = 0;
+        mod_chanmode_init(&change);
         channel->join_flooded = 1;
         if (opserv_conf.join_flood_moderate && (channel->members.used > opserv_conf.join_flood_moderate_threshold)) {
             if (!GetUserMode(channel, opserv)) {
                 /* If we aren't in the channel, join it. */
                 change.args[0].mode = MODE_CHANOP;
-                change.args[0].member = AddChannelUser(opserv, channel);
+                change.args[0].u.member = AddChannelUser(opserv, channel);
                 change.argc++;
             }
-            if (!(channel->modes & MODE_MODERATED))
-                change.modes_set |= MODE_MODERATED;
+            change.modes_set = (MODE_MODERATED | MODE_DELAYJOINS) & ~channel->modes;
             if (change.modes_set || change.argc)
                 mod_chanmode_announce(opserv, channel, &change);
             send_target_message(0, channel->name, opserv, "OSMSG_FLOOD_MODERATE");
@@ -2092,21 +2129,21 @@ static MODCMD_FUNC(cmd_addtrust)
 {
     unsigned long interval;
     char *reason, *tmp;
-    struct in_addr tmpaddr;
-    int count;
+    irc_in_addr_t tmpaddr;
+    unsigned int count;
 
     if (dict_find(opserv_trusted_hosts, argv[1], NULL)) {
         reply("OSMSG_ALREADY_TRUSTED", argv[1]);
         return 0;
     }
 
-    if (!inet_aton(argv[1], &tmpaddr)) {
+    if (!irc_pton(&tmpaddr, NULL, argv[1])) {
         reply("OSMSG_BAD_IP", argv[1]);
         return 0;
     }
 
     count = strtoul(argv[2], &tmp, 10);
-    if (!count || *tmp != '\0') {
+    if (*tmp != '\0') {
         reply("OSMSG_BAD_NUMBER", argv[2]);
         return 0;
     }
@@ -2128,7 +2165,7 @@ static MODCMD_FUNC(cmd_edittrust)
     unsigned long interval;
     struct trusted_host *th;
     char *reason, *tmp;
-    int count;
+    unsigned int count;
 
     th = dict_find(opserv_trusted_hosts, argv[1], NULL);
     if (!th) {
@@ -2279,11 +2316,11 @@ static MODCMD_FUNC(cmd_clone)
            reply("MSG_CHANNEL_UNKNOWN", argv[3]);
            return 0;
        }
-        change.modes_set = change.modes_clear = 0;
+        mod_chanmode_init(&change);
         change.argc = 1;
         change.args[0].mode = MODE_CHANOP;
-        change.args[0].member = GetUserMode(channel, clone);
-        if (!change.args[0].member) {
+        change.args[0].u.member = GetUserMode(channel, clone);
+        if (!change.args[0].u.member) {
             reply("OSMSG_NOT_ON_CHANNEL", clone->nick, channel->name);
             return 0;
        }
@@ -2420,15 +2457,8 @@ foreach_matching_user(const char *hostmask, discrim_search_func func, void *extr
     discrim->info_space = -1;
     dupmask = strdup(hostmask);
     if (split_ircmask(dupmask, &discrim->mask_nick, &discrim->mask_ident, &discrim->mask_host)) {
-        if (discrim->mask_host && !discrim->mask_host[strspn(discrim->mask_host, "0123456789.?*")]) {
-            if (!parse_ipmask(discrim->mask_host, &discrim->ip_addr, &discrim->ip_mask)) {
-                log_module(OS_LOG, LOG_ERROR, "Couldn't parse %s as an IP mask!", discrim->mask_host);
-                free(discrim);
-                free(dupmask);
-                return 0;
-            }
-            discrim->mask_host = 0;
-        }
+        if (!irc_pton(&discrim->ip_mask, &discrim->ip_mask_bits, discrim->mask_host))
+            discrim->ip_mask_bits = 0;
         matched = opserv_discrim_search(discrim, func, extra);
     } else {
        log_module(OS_LOG, LOG_ERROR, "Couldn't split IRC mask for gag %s!", hostmask);
@@ -2536,9 +2566,14 @@ opserv_add_user_alert(struct userNode *req, const char *name, opserv_alert_react
         alert->discrim->reason = strdup(name);
     alert->reaction = reaction;
     dict_insert(opserv_user_alerts, name_dup, alert);
-    if (alert->discrim->channel)
+    /* Stick the alert into the appropriate additional alert dict(s).
+     * For channel alerts, we only use channels and min_channels;
+     * max_channels would have to be checked on /part, which we do not
+     * yet do, and which seems of questionable value.
+     */
+    if (alert->discrim->channel || alert->discrim->min_channels)
         dict_insert(opserv_channel_alerts, name_dup, alert);
-    else if (alert->discrim->mask_nick)
+    if (alert->discrim->mask_nick)
         dict_insert(opserv_nick_based_alerts, name_dup, alert);
     return alert;
 }
@@ -2908,8 +2943,11 @@ opserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[], in
        } else if (irccasecmp(argv[i], "server") == 0) {
            discrim->server = argv[++i];
        } else if (irccasecmp(argv[i], "ip") == 0) {
-           j = parse_ipmask(argv[++i], &discrim->ip_addr, &discrim->ip_mask);
-           if (!j) discrim->ip_mask_str = argv[i];
+            j = irc_pton(&discrim->ip_mask, &discrim->ip_mask_bits, argv[++i]);
+            if (!j) {
+                send_message(user, opserv, "OSMSG_BAD_IP", argv[i]);
+                goto fail;
+            }
     } else if (irccasecmp(argv[i], "account") == 0) {
         if (discrim->authed == 0) {
             send_message(user, opserv, "OSMSG_ACCOUNTMASK_AUTHED");
@@ -3075,21 +3113,20 @@ discrim_match(discrim_t discrim, struct userNode *user)
         || (discrim->mask_info && !match_ircglob(user->info, discrim->mask_info))
         || (discrim->server && !match_ircglob(user->uplink->name, discrim->server))
         || (discrim->accountmask && (!user->handle_info || !match_ircglob(user->handle_info->handle, discrim->accountmask)))
-        || (discrim->ip_mask && !MATCH_IPMASK(user->ip, discrim->ip_addr, discrim->ip_mask))) {
+        || (discrim->ip_mask_bits && !irc_check_mask(&user->ip, &discrim->ip_mask, discrim->ip_mask_bits))
+        )
+        return 0;
+    if (discrim->channel && !GetUserMode(discrim->channel, user))
         return 0;
-    }
-    if (discrim->channel && !GetUserMode(discrim->channel, user)) return 0;
     access = user->handle_info ? user->handle_info->opserv_level : 0;
     if ((access < discrim->min_level)
         || (access > discrim->max_level)) {
         return 0;
     }
-    if (discrim->ip_mask_str) {
-        if (!match_ircglob(inet_ntoa(user->ip), discrim->ip_mask_str)) return 0;
-    }
     if (discrim->min_clones > 1) {
-        struct opserv_hostinfo *ohi = dict_find(opserv_hostinfo_dict, inet_ntoa(user->ip), NULL);
-        if (!ohi || (ohi->clients.used < discrim->min_clones)) return 0;
+        struct opserv_hostinfo *ohi = dict_find(opserv_hostinfo_dict, irc_ntoa(&user->ip), NULL);
+        if (!ohi || (ohi->clients.used < discrim->min_clones))
+            return 0;
     }
     return 1;
 }
@@ -3116,8 +3153,8 @@ opserv_discrim_search(discrim_t discrim, discrim_search_func dsf, void *data)
                 userList_append(&matched, mn->user);
             }
         }
-    } else if (discrim->ip_mask_str && !discrim->ip_mask_str[strcspn(discrim->ip_mask_str, "?*")]) {
-        struct opserv_hostinfo *ohi = dict_find(opserv_hostinfo_dict, discrim->ip_mask_str, NULL);
+    } else if (discrim->ip_mask_bits == 128) {
+        struct opserv_hostinfo *ohi = dict_find(opserv_hostinfo_dict, irc_ntoa(&discrim->ip_mask), NULL);
         if (!ohi) {
             userList_clean(&matched);
             return 0;
@@ -3262,29 +3299,42 @@ static int
 trace_domains_func(struct userNode *match, void *extra)
 {
     struct discrim_and_source *das = extra;
+    irc_in_addr_t ip;
     unsigned long *count;
     unsigned int depth;
     char *hostname;
+    char ipmask[IRC_NTOP_MASK_MAX_SIZE];
 
-    if (!match->hostname[strspn(match->hostname, "0123456789.")]) {
-        char ipmask[16];
-        unsigned long matchip = ntohl(match->ip.s_addr);
-        /* raw IP address.. use up to first three octets of IP */
-        switch (das->discrim->domain_depth) {
-        default:
-            snprintf(ipmask, sizeof(ipmask), "%lu.%lu.%lu.*", (matchip>>24)&255, (matchip>>16)&255, (matchip>>8)&255);
-            break;
-        case 2:
-            snprintf(ipmask, sizeof(ipmask), "%lu.%lu.*", (matchip>>24)&255, (matchip>>16)&255);
-            break;
-        case 1:
-            snprintf(ipmask, sizeof(ipmask), "%lu.*", (matchip>>24)&255);
-            break;
-        }
+    if (irc_pton(&ip, NULL, match->hostname)) {
+        if (irc_in_addr_is_ipv4(ip)) {
+            unsigned long matchip = ntohl(ip.in6_32[3]);
+            /* raw IP address.. use up to first three octets of IP */
+            switch (das->discrim->domain_depth) {
+            default:
+                snprintf(ipmask, sizeof(ipmask), "%lu.%lu.%lu.*", (matchip>>24)&255, (matchip>>16)&255, (matchip>>8)&255);
+                break;
+            case 2:
+                snprintf(ipmask, sizeof(ipmask), "%lu.%lu.*", (matchip>>24)&255, (matchip>>16)&255);
+                break;
+            case 1:
+                snprintf(ipmask, sizeof(ipmask), "%lu.*", (matchip>>24)&255);
+                break;
+            }
+        } else if (irc_in_addr_is_ipv6(ip)) {
+            switch (das->discrim->domain_depth) {
+            case 1:  depth = 16; goto ipv6_pfx;
+            case 2:  depth = 24; goto ipv6_pfx;
+            case 3:  depth = 32; goto ipv6_pfx;
+            default: depth = das->discrim->domain_depth;
+            ipv6_pfx:
+                irc_ntop_mask(ipmask, sizeof(ipmask), &ip, depth);
+            }
+        } else safestrncpy(ipmask, match->hostname, sizeof(ipmask));
+        ipmask[sizeof(ipmask) - 1] = '\0';
         hostname = ipmask;
     } else {
         hostname = match->hostname + strlen(match->hostname);
-        for (depth=das->discrim->domain_depth; 
+        for (depth=das->discrim->domain_depth;
              depth && (hostname > match->hostname);
              depth--) {
             hostname--;
@@ -3708,7 +3758,7 @@ opserv_alert_check_nick(struct userNode *user, UNUSED_ARG(const char *old_nick))
     /* Gag them if appropriate (and only if). */
     user->modes &= ~FLAGS_GAGGED;
     for (gag = gagList; gag; gag = gag->next) {
-        if (user_matches_glob(user, gag->mask, 1)) {
+        if (user_matches_glob(user, gag->mask, MATCH_USENICK)) {
             gag_helper_func(user, NULL);
             break;
         }
@@ -3733,7 +3783,7 @@ opserv_staff_alert(struct userNode *user, UNUSED_ARG(struct handle_info *old_han
     else
         return;
 
-    if (user->ip.s_addr)
+    if (irc_in_addr_is_valid(user->ip))
         send_channel_notice(opserv_conf.staff_auth_channel, opserv, IDENT_FORMAT" authed to %s account %s", IDENT_DATA(user), type, user->handle_info->handle);
     else
         send_channel_notice(opserv_conf.staff_auth_channel, opserv, "%s [%s@%s] authed to %s account %s", user->nick, user->ident, user->hostname, type, user->handle_info->handle);
@@ -4019,8 +4069,10 @@ void
 init_opserv(const char *nick)
 {
     OS_LOG = log_register_type("OpServ", "file:opserv.log");
-    if (nick)
-        opserv = AddService(nick, "Oper Services");
+    if (nick) {
+        const char *modes = conf_get_data("services/opserv/modes", RECDB_QSTRING);
+        opserv = AddService(nick, modes ? modes : NULL, "Oper Services", NULL);
+    }
     conf_register_reload(opserv_conf_read);
 
     memset(level_strings, 0, sizeof(level_strings));
@@ -4097,6 +4149,9 @@ init_opserv(const char *nick)
     opserv_define_func("STATS UPLINK", cmd_stats_uplink, 0, 0, 0);
     opserv_define_func("STATS UPTIME", cmd_stats_uptime, 0, 0, 0);
     opserv_define_func("STATS WARN", cmd_stats_warn, 0, 0, 0);
+#if defined(WITH_MALLOC_SRVX) || defined(WITH_MALLOC_SLAB)
+    opserv_define_func("STATS MEMORY", cmd_stats_memory, 0, 0, 0);
+#endif
     opserv_define_func("TRACE", cmd_trace, 100, 0, 3);
     opserv_define_func("TRACE PRINT", NULL, 0, 0, 0);
     opserv_define_func("TRACE COUNT", NULL, 0, 0, 0);
@@ -4131,7 +4186,7 @@ init_opserv(const char *nick)
     opserv_db_init();
     saxdb_register("OpServ", opserv_saxdb_read, opserv_saxdb_write);
     if (nick)
-        service_register(opserv, '?');
+        service_register(opserv)->trigger = '?';
 
     reg_exit_func(opserv_db_cleanup);
     message_register_table(msgtab);