]> jfr.im git - irc/evilnet/x3.git/blobdiff - src/opserv.c
whoops
[irc/evilnet/x3.git] / src / opserv.c
index 88fb61d6c7154b8796bb61e1f23c7c310591d68c..62a5de40933ab8125b483f85aaa24eed2cc55e58 100644 (file)
@@ -3,7 +3,7 @@
  *
  * This file is part of x3.
  *
- * srvx is free software; you can redistribute it and/or modify
+ * x3 is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version.
@@ -23,6 +23,7 @@
 #include "global.h"
 #include "nickserv.h"
 #include "modcmd.h"
+#include "modules.h"
 #include "opserv.h"
 #include "timeq.h"
 #include "saxdb.h"
@@ -54,6 +55,7 @@
 #define KEY_NICK "nick"
 #define KEY_JOIN_POLICER "join_policer"
 #define KEY_NEW_USER_POLICER "new_user_policer"
+#define KEY_AUTOJOIN_CHANNELS "autojoin_channels"
 #define KEY_REASON "reason"
 #define KEY_RESERVES "reserves"
 #define KEY_IDENT "username" /* for compatibility with 1.0 DBs */
 #define KEY_ISSUER "issuer"
 #define KEY_ISSUED "issued"
 #define KEY_ADMIN_LEVEL "admin_level"
+#define KEY_SILENT_LEVEL "silent_level"
 
 #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."
@@ -128,19 +131,21 @@ static const struct message_entry msgtab[] = {
     { "OSMSG_OPALL_DONE", "Opped everyone on $b%s$b." },
     { "OSMSG_HOP_DONE", "Halfopped the requested lusers." },
     { "OSMSG_HOPALL_DONE", "Halfopped everyone on $b%s$b." },
-    { "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" },
-    { "OSMSG_WHOIS_NUMERIC", "Numnick : %s" },
-    { "OSMSG_WHOIS_SERVER", "Server  : %s" },
-    { "OSMSG_WHOIS_NICK_AGE", "Nick Age: %s" },
-    { "OSMSG_WHOIS_ACCOUNT", "Account : %s" },
-    { "OSMSG_WHOIS_CHANNELS", "Channels: %s" },
-    { "OSMSG_WHOIS_HIDECHANS", "Channel list omitted for your sanity." },
+    { "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_CRYPT_HOST", "Crypt Host : %s" },
+    { "OSMSG_WHOIS_CRYPT_IP",   "Crypt IP   : %s" },
+    { "OSMSG_WHOIS_IP",         "Real IP    : %s" },
+    { "OSMSG_WHOIS_MODES",      "Modes      : +%s " },
+    { "OSMSG_WHOIS_INFO",       "Info       : %s" },
+    { "OSMSG_WHOIS_NUMERIC",    "Numnick    : %s" },
+    { "OSMSG_WHOIS_SERVER",     "Server     : %s" },
+    { "OSMSG_WHOIS_NICK_AGE",   "Nick Age   : %s" },
+    { "OSMSG_WHOIS_ACCOUNT",    "Account    : %s" },
+    { "OSMSG_WHOIS_CHANNELS",   "Channels   : %s" },
+    { "OSMSG_WHOIS_HIDECHANS",  "Channel list omitted for your sanity." },
     { "OSMSG_UNBAN_DONE", "Ban(s) removed from channel %s." },
     { "OSMSG_CHANNEL_VOICED", "All users on %s voiced." },
     { "OSMSG_CHANNEL_DEVOICED", "All voiced users on %s de-voiced." },
@@ -286,11 +291,14 @@ static const struct message_entry msgtab[] = {
     { "OSMSG_CHANINFO_USER_COUNT", "Users (%d):" },
     { "OSMSG_CSEARCH_CHANNEL_INFO", "%s [%d users] %s %s" },
     { "OSMSG_INVALID_REGEX", "Invalid regex: %s: %s (%d)" },
+    { "OSMSG_TRACK_DISABLED", "Tracking is not currently compiled into X3" },
+    { "OSMSG_MAXUSERS_RESET", "Max clients has been reset to $b%d$b" },
     { NULL, NULL }
 };
 
 #define OPSERV_SYNTAX() svccmd_send_help_brief(user, opserv, cmd)
 
+extern void add_track_user(struct userNode *user);
 typedef int (*discrim_search_func)(struct userNode *match, void *extra);
 
 struct userNode *opserv;
@@ -309,6 +317,7 @@ static struct module *opserv_module;
 static struct log_type *OS_LOG;
 static unsigned int new_user_flood;
 static char *level_strings[1001];
+struct string_list *autojoin_channels;
 
 static struct {
     struct chanNode *debug_channel;
@@ -324,6 +333,7 @@ static struct {
     unsigned long join_flood_moderate;
     unsigned long join_flood_moderate_threshold;
     unsigned long admin_level;
+    unsigned long silent_level;
 } opserv_conf;
 
 struct trusted_host {
@@ -360,17 +370,20 @@ 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;
+    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;
     regex_t regex_nick, regex_ident, regex_host, regex_info;
     unsigned int has_regex_nick : 1, has_regex_ident : 1, has_regex_host : 1, has_regex_info : 1;
-    unsigned long limit, ip_mask;
-    struct in_addr ip_addr;
     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;
+    unsigned int intra_scmp : 2, intra_dcmp : 2;
     unsigned int use_regex : 1;
+    unsigned int silent : 1;
 } *discrim_t;
 
 struct discrim_and_source {
@@ -389,7 +402,9 @@ static int ungag_helper_func(struct userNode *match, void *extra);
 typedef enum {
     REACT_NOTICE,
     REACT_KILL,
+//    REACT_SILENT,
     REACT_GLINE,
+    REACT_TRACK,
     REACT_SHUN
 } opserv_alert_reaction;
 
@@ -426,6 +441,7 @@ opserv_free_user_alert(void *data)
 #define opserv_debug(format...) do { if (opserv_conf.debug_channel) send_channel_notice(opserv_conf.debug_channel , opserv , ## format); } while (0)
 #define opserv_alert(format...) do { if (opserv_conf.alert_channel) send_channel_notice(opserv_conf.alert_channel , opserv , ## format); } while (0)
 
+
 /* A lot of these commands are very similar to what ChanServ can do,
  * but OpServ can do them even on channels that aren't registered.
  */
@@ -522,8 +538,12 @@ 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);
+        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];
@@ -545,7 +565,7 @@ static MODCMD_FUNC(cmd_chaninfo)
 
 /* This command has been replaced by 'alert notice channel #foo' */
 /*
-static MODCMD_FUNC(cmd_warn) 
+static MODCMD_FUNC(cmd_warn)
 {
     char *reason, *message;
 
@@ -714,6 +734,14 @@ static MODCMD_FUNC(cmd_dehopall)
     return 1;
 }
 
+static MODCMD_FUNC(cmd_resetmax)
+{
+    max_clients = dict_size(clients);
+    max_clients_time = now;
+    reply("OSMSG_MAXUSERS_RESET", max_clients);
+    return 1;
+}
+
 static MODCMD_FUNC(cmd_rehash)
 {
     extern char *services_config;
@@ -864,17 +892,16 @@ static MODCMD_FUNC(cmd_restart)
 }
 
 static struct gline *
-opserv_block(struct userNode *target, char *src_handle, char *reason, unsigned long duration)
+opserv_block(struct userNode *target, char *src_handle, char *reason, unsigned long duration, int silent)
 {
-    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;
-    return gline_add(src_handle, mask, duration, reason, now, 1);
+    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, silent ? 1 : 0);
 }
 
 static MODCMD_FUNC(cmd_block)
@@ -893,7 +920,7 @@ static MODCMD_FUNC(cmd_block)
         return 0;
     }
     reason = (argc > 2) ? unsplit_string(argv+2, argc-2, NULL) : NULL;
-    gline = opserv_block(target, user->handle_info->handle, reason, 0);
+    gline = opserv_block(target, user->handle_info->handle, reason, 0, 0);
     reply("OSMSG_GLINE_ISSUED", gline->target);
     return 1;
 }
@@ -918,7 +945,7 @@ static MODCMD_FUNC(cmd_gline)
         reply("MSG_INVALID_DURATION", argv[2]);
         return 0;
     }
-    gline = gline_add(user->handle_info->handle, argv[1], duration, reason, now, 1);
+    gline = gline_add(user->handle_info->handle, argv[1], duration, reason, now, 1, 0);
     reply("OSMSG_GLINE_ISSUED", gline->target);
     return 1;
 }
@@ -1438,7 +1465,9 @@ static MODCMD_FUNC(cmd_whois)
     reply("OSMSG_WHOIS_HOST", target->ident, target->hostname);
     if (IsFakeHost(target))
         reply("OSMSG_WHOIS_FAKEHOST", target->fakehost);
-    reply("OSMSG_WHOIS_IP", inet_ntoa(target->ip));
+    reply("OSMSG_WHOIS_CRYPT_HOST", target->crypthost);
+    reply("OSMSG_WHOIS_CRYPT_IP", target->cryptip);
+    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)
@@ -1801,7 +1830,9 @@ static MODCMD_FUNC(cmd_stats_alerts) {
         switch (alert->reaction) {
         case REACT_NOTICE: reaction = "notice"; break;
         case REACT_KILL: reaction = "kill"; break;
+//        case REACT_SILENT: reaction = "silent"; break;
         case REACT_GLINE: reaction = "gline"; break;
+        case REACT_TRACK: reaction = "track"; break;
         case REACT_SHUN: reaction = "shun"; break;
         default: reaction = "<unknown>"; break;
         }
@@ -1885,9 +1916,9 @@ static MODCMD_FUNC(cmd_stats_memory) {
     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,
+    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
@@ -2012,6 +2043,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))
@@ -2023,16 +2055,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);
@@ -2050,8 +2083,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 */
@@ -2060,9 +2095,9 @@ 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));
-            gline_add(opserv->nick, target, opserv_conf.clone_gline_duration, "AUTO Excessive connections from a single host.", now, 1);
+            char target[IRC_NTOP_MAX_SIZE + 3] = { '*', '@', '\0' };
+            strcpy(target + 2, addr);
+            gline_add(opserv->nick, target, opserv_conf.clone_gline_duration, "Excessive connections from a single host.", now, 1, 1);
         }
     }
 
@@ -2073,6 +2108,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
@@ -2081,9 +2117,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);
     }
 }
 
@@ -2411,7 +2449,7 @@ static MODCMD_FUNC(cmd_addtrust)
 {
     unsigned long interval;
     char *reason, *tmp;
-    struct in_addr tmpaddr;
+    irc_in_addr_t tmpaddr;
     unsigned int count;
 
     if (dict_find(opserv_trusted_hosts, argv[1], NULL)) {
@@ -2419,7 +2457,7 @@ static MODCMD_FUNC(cmd_addtrust)
         return 0;
     }
 
-    if (!inet_aton(argv[1], &tmpaddr)) {
+    if (!irc_pton(&tmpaddr, NULL, argv[1])) {
         reply("OSMSG_BAD_IP", argv[1]);
         return 0;
     }
@@ -2715,8 +2753,10 @@ opserv_define_func(const char *name, modcmd_func_t *func, int min_level, int req
 
 int add_reserved(const char *key, void *data, void *extra)
 {
+    struct chanNode *chan;
     struct record_data *rd = data;
     const char *ident, *hostname, *desc;
+    unsigned int i;
     struct userNode *reserve;
     ident = database_get_data(rd->d.object, KEY_IDENT, RECDB_QSTRING);
     if (!ident) {
@@ -2737,6 +2777,14 @@ int add_reserved(const char *key, void *data, void *extra)
         reserve->modes |= FLAGS_PERSISTENT;
         dict_insert(extra, reserve->nick, reserve);
     }
+
+    if (autojoin_channels && reserve) {
+        for (i = 0; i < autojoin_channels->used; i++) {
+            chan = AddChannel(autojoin_channels->list[i], now, "+nt", NULL, NULL);
+            AddChannelUser(reserve, chan)->modes |= MODE_VOICE;
+        }
+    }
+
     return 0;
 }
 
@@ -2755,18 +2803,14 @@ foreach_matching_user(const char *hostmask, discrim_search_func func, void *extr
     discrim->max_channels = INT_MAX;
     discrim->authed = -1;
     discrim->info_space = -1;
+    discrim->intra_scmp = 0;
+    discrim->intra_dcmp = 0;
     discrim->use_regex = 0;
+    discrim->silent = 0;
     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);
@@ -2920,8 +2964,14 @@ add_user_alert(const char *key, void *data, UNUSED_ARG(void *extra))
         reaction = REACT_NOTICE;
     else if (!irccasecmp(react, "kill"))
         reaction = REACT_KILL;
+    /*
+    else if (!irccasecmp(react, "silent"))
+        reaction = REACT_SILENT;
+    */
     else if (!irccasecmp(react, "gline"))
         reaction = REACT_GLINE;
+    else if (!irccasecmp(react, "track"))
+        reaction = REACT_TRACK;
     else if (!irccasecmp(react, "shun"))
         reaction = REACT_SHUN;
     else {
@@ -3110,7 +3160,9 @@ opserv_saxdb_write(struct saxdb_context *ctx)
             switch (alert->reaction) {
             case REACT_NOTICE: reaction = "notice"; break;
             case REACT_KILL: reaction = "kill"; break;
+//            case REACT_SILENT: reaction = "silent"; break;
             case REACT_GLINE: reaction = "gline"; break;
+            case REACT_TRACK: reaction = "track"; break;
             case REACT_SHUN: reaction = "shun"; break;
             default:
                 reaction = NULL;
@@ -3226,6 +3278,10 @@ opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int
     discrim->max_channels = INT_MAX;
     discrim->authed = -1;
     discrim->info_space = -1;
+    discrim->intra_dcmp = 0;
+    discrim->intra_scmp = 0;
+    discrim->use_regex = 0;
+    discrim->silent = 0;
 
     for (i=0; i<argc; i++) {
         if (irccasecmp(argv[i], "log") == 0) {
@@ -3237,6 +3293,18 @@ opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int
             send_message(user, bot, "MSG_MISSING_PARAMS", argv[i]);
             goto fail;
         }
+        if (argv[i+1][0] == '&') {
+            /* Looking for intra-userNode matches */
+            char *tmp = &(argv[i+1][1]);
+            if (strcasecmp(tmp, argv[i]) != 0) { /* Don't allow "nick &nick" etc */
+                if (!strcasecmp(tmp, "nick"))
+                    discrim->intra_dcmp = 1;
+                else if (!strcasecmp(tmp, "ident"))
+                    discrim->intra_dcmp = 2;
+                else if (!strcasecmp(tmp, "info"))
+                    discrim->intra_dcmp = 3;
+            }
+        }
         if (irccasecmp(argv[i], "mask") == 0) {
             if (!is_ircmask(argv[++i])) {
                 send_message(user, bot, "OSMSG_INVALID_IRCMASK", argv[i]);
@@ -3250,18 +3318,33 @@ opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int
                 goto fail;
             }
         } else if (irccasecmp(argv[i], "nick") == 0) {
-            discrim->mask_nick = argv[++i];
+            i++;
+            if (discrim->intra_dcmp > 0)
+                discrim->intra_scmp = 1;
+           else
+                discrim->mask_nick = argv[i];
         } else if (irccasecmp(argv[i], "ident") == 0) {
-            discrim->mask_ident = argv[++i];
+            i++;
+            if (discrim->intra_dcmp > 0)
+                discrim->intra_scmp = 2;
+           else
+                discrim->mask_ident = argv[i];
         } else if (irccasecmp(argv[i], "host") == 0) {
             discrim->mask_host = argv[++i];
         } else if (irccasecmp(argv[i], "info") == 0) {
-            discrim->mask_info = argv[++i];
+            i++;
+            if (discrim->intra_dcmp > 0)
+                discrim->intra_scmp = 3;
+           else
+                discrim->mask_info = argv[i];
         } 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, bot, "OSMSG_ACCOUNTMASK_AUTHED");
@@ -3305,6 +3388,18 @@ opserv_discrim_create(struct userNode *user, struct userNode *bot, unsigned int
             send_message(user, opserv, "MSG_INVALID_BINARY", argv[i]);
             goto fail;
         }
+    } else if (irccasecmp(argv[i], "silent") == 0) {
+        i++;
+        if(!oper_has_access(user, opserv, opserv_conf.silent_level, 0)) {
+            goto fail;
+        } else if (true_string(argv[i])) {
+            discrim->silent = 1;
+        } else if (false_string(argv[i])) {
+            discrim->silent = 0;
+        } else {
+            send_message(user, opserv, "MSG_INVALID_BINARY", argv[i]);
+            goto fail;
+        }
     } else if (irccasecmp(argv[i], "duration") == 0) {
         discrim->duration = ParseInterval(argv[++i]);
         } else if (irccasecmp(argv[i], "channel") == 0) {
@@ -3502,6 +3597,7 @@ static int
 discrim_match(discrim_t discrim, struct userNode *user)
 {
     unsigned int access;
+    char *scmp=NULL, *dcmp=NULL;
 
     if ((user->timestamp < discrim->min_ts)
         || (user->timestamp > discrim->max_ts)
@@ -3513,9 +3609,12 @@ discrim_match(discrim_t discrim, struct userNode *user)
         || (discrim->info_space == 1 && user->info[0] != ' ')
         || (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->use_regex)
     {
@@ -3536,18 +3635,40 @@ discrim_match(discrim_t discrim, struct userNode *user)
         }
     }
 
-    if (discrim->channel && !GetUserMode(discrim->channel, user)) return 0;
+    if ((discrim->intra_scmp > 0 && discrim->intra_dcmp > 0)) {
+       switch(discrim->intra_scmp) {
+            case 1: scmp=user->nick; break;
+            case 2: scmp=user->ident; break;
+            case 3: 
+                scmp=user->info; 
+                if (discrim->info_space == 1) scmp++;
+                break;
+        }
+        switch(discrim->intra_dcmp) {
+            case 1: dcmp=user->nick; break;
+            case 2: dcmp=user->ident; break;
+            case 3: /* When checking INFO, and info_space is enabled
+                     * ignore the first character in a search 
+                     * XXX: Should we ignore ALL leading whitespace?
+                     *      Also, what about ignoring ~ in ident?
+                     */
+                dcmp=user->info; 
+                if (discrim->info_space == 1) dcmp++;
+                break;
+        }
+        if (irccasecmp(scmp,dcmp))
+            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;
 }
@@ -3574,8 +3695,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;
@@ -3654,7 +3775,7 @@ trace_gline_func(struct userNode *match, void *extra)
     struct discrim_and_source *das = extra;
 
     if (is_oper_victim(das->source, match, das->discrim->match_opers)) {
-        opserv_block(match, das->source->handle_info->handle, das->discrim->reason, das->discrim->duration);
+        opserv_block(match, das->source->handle_info->handle, das->discrim->reason, das->discrim->duration, das->discrim->silent);
     }
 
     return 0;
@@ -3732,29 +3853,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--;
@@ -4283,8 +4417,13 @@ alert_check_user(const char *key, void *data, void *extra)
     case REACT_KILL:
         DelUser(user, opserv, 1, alert->discrim->reason);
         return 1;
+/*
+    case REACT_SILENT:
+        opserv_block(user, alert->owner, alert->discrim->reason, alert->discrim->duration, 1);
+        return 1;
+*/
     case REACT_GLINE:
-        opserv_block(user, alert->owner, alert->discrim->reason, alert->discrim->duration);
+        opserv_block(user, alert->owner, alert->discrim->reason, alert->discrim->duration, alert->discrim->silent);
         return 1;
     case REACT_SHUN:
         opserv_shun(user, alert->owner, alert->discrim->reason, alert->discrim->duration);
@@ -4295,6 +4434,12 @@ alert_check_user(const char *key, void *data, void *extra)
     case REACT_NOTICE:
         opserv_alert("Alert $b%s$b triggered by user $b%s$b!%s@%s (%s).", key, user->nick, user->ident, user->hostname, alert->discrim->reason);
         break;
+    case REACT_TRACK:
+#ifdef HAVE_TRACK
+        opserv_alert("Alert $b%s$b triggered by user $b%s$b!%s@%s (%s) (Tracking).", key, user->nick, user->ident, user->hostname, alert->discrim->reason);
+       add_track_user(user);
+#endif
+       break;
     }
     return 0;
 }
@@ -4307,7 +4452,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;
         }
@@ -4332,7 +4477,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);
@@ -4452,9 +4597,20 @@ static MODCMD_FUNC(cmd_addalert)
         reaction = REACT_NOTICE;
     else if (!irccasecmp(argv[2], "kill"))
         reaction = REACT_KILL;
+/*
+    else if (!irccasecmp(argv[2], "silent"))
+        reaction = REACT_SILENT;
+*/
     else if (!irccasecmp(argv[2], "gline"))
         reaction = REACT_GLINE;
-    else if (!irccasecmp(argv[2], "shun"))
+    else if (!irccasecmp(argv[2], "track")) {
+#ifndef HAVE_TRACK
+        send_message(user, opserv, "OSMSG_TRACK_DISABLED");
+        return 0;
+#else
+        reaction = REACT_TRACK;
+#endif
+    } else if (!irccasecmp(argv[2], "shun"))
         reaction = REACT_SHUN;
     else {
         reply("OSMSG_UNKNOWN_REACTION", argv[2]);
@@ -4484,6 +4640,8 @@ static MODCMD_FUNC(cmd_delalert)
 static void
 opserv_conf_read(void)
 {
+    struct chanNode *chan;
+    unsigned int i;
     struct record_data *rd;
     dict_t conf_node, child;
     const char *str, *str2;
@@ -4530,6 +4688,9 @@ opserv_conf_read(void)
     str = database_get_data(conf_node, KEY_ADMIN_LEVEL, RECDB_QSTRING);
     opserv_conf.admin_level = str ? strtoul(str, NULL, 0): 800;
 
+    str = database_get_data(conf_node, KEY_SILENT_LEVEL, RECDB_QSTRING);
+    opserv_conf.silent_level = str ? strtoul(str, NULL, 0): 700;
+
     str = database_get_data(conf_node, KEY_UNTRUSTED_MAX, RECDB_QSTRING);
     opserv_conf.untrusted_max = str ? strtoul(str, NULL, 0) : 5;
     str = database_get_data(conf_node, KEY_PURGE_LOCK_DELAY, RECDB_QSTRING);
@@ -4547,6 +4708,19 @@ opserv_conf_read(void)
     str = database_get_data(conf_node, KEY_BLOCK_GLINE_DURATION, RECDB_QSTRING);
     opserv_conf.block_gline_duration = str ? ParseInterval(str) : 3600;
 
+    free_string_list(autojoin_channels);
+    autojoin_channels = database_get_data(conf_node, KEY_AUTOJOIN_CHANNELS, RECDB_STRING_LIST);
+
+    if(autojoin_channels)
+        autojoin_channels = string_list_copy(autojoin_channels);
+
+    if (autojoin_channels && opserv) {
+        for (i = 0; i < autojoin_channels->used; i++) {
+            chan = AddChannel(autojoin_channels->list[i], now, "+nt", NULL, NULL);
+            AddChannelUser(opserv, chan)->modes |= MODE_CHANOP;
+        }
+    }
+
     str = database_get_data(conf_node, KEY_BLOCK_SHUN_DURATION, RECDB_QSTRING);
     opserv_conf.block_shun_duration = str ? ParseInterval(str) : 3600;
 
@@ -4649,8 +4823,10 @@ init_opserv(const char *nick)
     opserv_define_func("ACCESS", cmd_access, 0, 0, 0);
     opserv_define_func("ADDALERT", cmd_addalert, 800, 0, 4);
     opserv_define_func("ADDALERT NOTICE", NULL, 0, 0, 0);
+    opserv_define_func("ADDALERT SILENT", NULL, 900, 0, 0);
     opserv_define_func("ADDALERT GLINE", NULL, 900, 0, 0);
     opserv_define_func("ADDALERT SHUN", NULL, 900, 0, 0);
+    opserv_define_func("ADDALERT TRACK", NULL, 900, 0, 0);
     opserv_define_func("ADDALERT KILL", NULL, 900, 0, 0);
     opserv_define_func("ADDBAD", cmd_addbad, 800, 0, 2);
     opserv_define_func("ADDEXEMPT", cmd_addexempt, 800, 0, 2);
@@ -4712,6 +4888,7 @@ init_opserv(const char *nick)
     opserv_define_func("REFRESHS", cmd_refreshs, 600, 0, 0);
     opserv_define_func("REHASH", cmd_rehash, 900, 0, 0);
     opserv_define_func("REOPEN", cmd_reopen, 900, 0, 0);
+    opserv_define_func("RESETMAX", cmd_resetmax, 900, 0, 0);
     opserv_define_func("RESERVE", cmd_reserve, 800, 0, 5);
     opserv_define_func("RESTART", cmd_restart, 900, 0, 2);
     opserv_define_func("SET", cmd_set, 900, 0, 3);