Added feigling's patch to allow multiple 'channel' criteria in trace/addalert.
[srvx.git] / src / opserv.c
index a5ffb401ef327ead6d214a041d66b3ca2e61ec05..1f51bf5b9433b03ff28706963ad69e5dff00ade4 100644 (file)
@@ -91,6 +91,9 @@ static const struct message_entry msgtab[] = {
     { "OSMSG_NEED_CHANNEL", "You must specify a channel for $b%s$b." },
     { "OSMSG_INVALID_IRCMASK", "$b%s$b is an invalid IRC hostmask." },
     { "OSMSG_ADDED_BAN", "I have banned $b%s$b from $b%s$b." },
+    { "OSMSG_NO_GLINE_CMD", "The GLINE command is not bound so you can only block with the default duration." },
+    { "OSMSG_BLOCK_TRUSTED", "$b%s$b is on a trusted ip. If you really want to G-line him, use the GLINE command." },
+    { "OSMSG_BLOCK_OPER" , "G-lining $b%s$b (*@%s) would also hit the IRC operator $b%s$b." },
     { "OSMSG_GLINE_ISSUED", "G-line issued for $b%s$b." },
     { "OSMSG_GLINE_REMOVED", "G-line removed for $b%s$b." },
     { "OSMSG_GLINE_FORCE_REMOVED", "Unknown/expired G-line removed for $b%s$b." },
@@ -248,6 +251,7 @@ static const struct message_entry msgtab[] = {
     { "OSMSG_CHANINFO_MANY_USERS", "%d users (\"/msg $S %s %s users\" for the list)" },
     { "OSMSG_CHANINFO_USER_COUNT", "Users (%d):" },
     { "OSMSG_CSEARCH_CHANNEL_INFO", "%s [%d users] %s %s" },
+    { "OSMSG_TRACE_MAX_CHANNELS", "You may not use the 'channel' criterion more than %d times." },
     { NULL, NULL }
 };
 
@@ -266,6 +270,7 @@ static dict_t opserv_hostinfo_dict; /* data is struct opserv_hostinfo* */
 static dict_t opserv_user_alerts; /* data is struct opserv_user_alert* */
 static dict_t opserv_nick_based_alerts; /* data is struct opserv_user_alert* */
 static dict_t opserv_channel_alerts; /* data is struct opserv_user_alert* */
+static dict_t opserv_account_alerts; /* data is struct opserv_user_alert* */
 static struct module *opserv_module;
 static struct log_type *OS_LOG;
 static unsigned int new_user_flood;
@@ -317,16 +322,19 @@ opserv_free_hostinfo(void *data)
     free(ohi);
 }
 
+#define DISCRIM_MAX_CHANS 20
+
 typedef struct opservDiscrim {
-    struct chanNode *channel;
+    struct chanNode *channels[DISCRIM_MAX_CHANS];
+    unsigned int channel_count;
     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;
+    unsigned int match_opers : 1, match_trusted : 1, option_log : 1;
+    unsigned int chan_req_modes[DISCRIM_MAX_CHANS], chan_no_modes[DISCRIM_MAX_CHANS];
     int authed : 2, info_space : 2;
 } *discrim_t;
 
@@ -360,8 +368,9 @@ static void
 opserv_free_user_alert(void *data)
 {
     struct opserv_user_alert *alert = data;
-    if (alert->discrim->channel)
-        UnlockChannel(alert->discrim->channel);
+       unsigned int i;
+       for(i = 0; i < alert->discrim->channel_count; i++)
+        UnlockChannel(alert->discrim->channels[i]);
     free(alert->owner);
     free(alert->text_discrim);
     free(alert->split_discrim);
@@ -767,18 +776,48 @@ static MODCMD_FUNC(cmd_block)
     struct userNode *target;
     struct gline *gline;
     char *reason;
+    unsigned long duration = 0;
+    unsigned int offset = 2;
+    unsigned int nn;
+    struct svccmd *gline_cmd;
 
     target = GetUserH(argv[1]);
     if (!target) {
-       reply("MSG_NICK_UNKNOWN", argv[1]);
-       return 0;
+        reply("MSG_NICK_UNKNOWN", argv[1]);
+        return 0;
     }
     if (IsService(target)) {
-       reply("MSG_SERVICE_IMMUNE", target->nick);
-       return 0;
+        reply("MSG_SERVICE_IMMUNE", target->nick);
+        return 0;
+    }
+    if (dict_find(opserv_trusted_hosts, irc_ntoa(&target->ip), NULL)) {
+        reply("OSMSG_BLOCK_TRUSTED", target->nick);
+        return 0;
+    }
+
+    for(nn = 0; nn < curr_opers.used; nn++) {
+        if(memcmp(&curr_opers.list[nn]->ip, &target->ip, sizeof(irc_in_addr_t)) == 0) {
+            reply("OSMSG_BLOCK_OPER", target->nick, irc_ntoa(&target->ip), curr_opers.list[nn]->nick);
+            return 0;
+        }
     }
-    reason = (argc > 2) ? unsplit_string(argv+2, argc-2, NULL) : NULL;
-    gline = opserv_block(target, user->handle_info->handle, reason, 0);
+
+    if(argc > 2 && (duration = ParseInterval(argv[2]))) {
+        offset = 3;
+    }
+    if(duration && duration != opserv_conf.block_gline_duration) {
+        // We require more access when the duration is not the default block duration.
+        gline_cmd = dict_find(cmd->parent->commands, "gline", NULL);
+        if(!gline_cmd)
+        {
+            reply("OSMSG_NO_GLINE_CMD");
+            return 0;
+        }
+        if(!svccmd_can_invoke(user, cmd->parent->bot, gline_cmd, channel, SVCCMD_NOISY))
+            return 0;
+    }
+    reason = (argc > offset) ? unsplit_string(argv+offset, argc-offset, NULL) : NULL;
+    gline = opserv_block(target, user->handle_info->handle, reason, duration);
     reply("OSMSG_GLINE_ISSUED", gline->target);
     return 1;
 }
@@ -916,10 +955,13 @@ static MODCMD_FUNC(cmd_join)
 {
     struct userNode *bot = cmd->parent->bot;
 
-    if (!IsChannelName(argv[1])) {
-        reply("MSG_NOT_CHANNEL_NAME");
-        return 0;
-    } else if (!(channel = GetChannel(argv[1]))) {
+    if (!channel) {
+        if((argc < 2) || !IsChannelName(argv[1]))
+        {
+            reply("MSG_NOT_CHANNEL_NAME");
+            return 0;
+        }
+
         channel = AddChannel(argv[1], now, NULL, NULL);
         AddChannelUser(bot, channel)->modes |= MODE_CHANOP;
     } else if (GetUserMode(channel, bot)) {
@@ -933,6 +975,7 @@ static MODCMD_FUNC(cmd_join)
         change.args[0].u.member = AddChannelUser(bot, channel);
         modcmd_chanmode_announce(&change);
     }
+
     irc_fetchtopic(bot, channel->name);
     reply("OSMSG_JOIN_DONE", channel->name);
     return 1;
@@ -1082,19 +1125,13 @@ static MODCMD_FUNC(cmd_part)
 {
     char *reason;
 
-    if (!IsChannelName(argv[1])) {
-        reply("MSG_NOT_CHANNEL_NAME");
+    if (!GetUserMode(channel, cmd->parent->bot)) {
+        reply("OSMSG_NOT_ON_CHANNEL", cmd->parent->bot->nick, channel->name);
         return 0;
     }
-    if ((channel = GetChannel(argv[1]))) {
-        if (!GetUserMode(channel, cmd->parent->bot)) {
-            reply("OSMSG_NOT_ON_CHANNEL", cmd->parent->bot->nick, channel->name);
-            return 0;
-        }
-        reason = (argc < 3) ? "Leaving." : unsplit_string(argv+2, argc-2, NULL);
-        reply("OSMSG_LEAVING", channel->name);
-        DelChannelUser(cmd->parent->bot, channel, reason, 0);
-    }
+    reason = (argc < 3) ? "Leaving." : unsplit_string(argv+2, argc-2, NULL);
+    reply("OSMSG_LEAVING", channel->name);
+    DelChannelUser(cmd->parent->bot, channel, reason, 0);
     return 1;
 }
 
@@ -1480,11 +1517,15 @@ static MODCMD_FUNC(cmd_stats_uplink) {
 }
 
 static MODCMD_FUNC(cmd_stats_uptime) {
+    extern int lines_processed;
+    extern time_t boot_time;
+    double kernel_time;
+    double user_time;
     char uptime[INTERVALLEN];
+
+#if defined(HAVE_TIMES)
+    static double clocks_per_sec;
     struct tms buf;
-    extern time_t boot_time;
-    extern int lines_processed;
-    static long clocks_per_sec;
 
     if (!clocks_per_sec) {
 #if defined(HAVE_SYSCONF) && defined(_SC_CLK_TCK)
@@ -1496,12 +1537,27 @@ static MODCMD_FUNC(cmd_stats_uptime) {
             clocks_per_sec = CLOCKS_PER_SEC;
         }
     }
-    intervalString(uptime, time(NULL)-boot_time, user->handle_info);
     times(&buf);
-    reply("OSMSG_UPTIME_STATS",
-          uptime, lines_processed,
-          buf.tms_utime/(double)clocks_per_sec,
-          buf.tms_stime/(double)clocks_per_sec);
+    user_time = buf.tms_utime / clocks_per_sec;
+    kernel_time = buf.tms_stime / clocks_per_sec;
+#elif defined(HAVE_GETPROCESSTIMES)
+    FILETIME times[4];
+    LARGE_INTEGER li[2];
+
+    GetProcessTimes(GetCurrentProcess(), &times[0], &times[1], &times[2], &times[3]);
+    li[0].LowPart = times[2].dwLowDateTime;
+    li[0].HighPart = times[2].dwHighDateTime;
+    kernel_time = li[0].QuadPart * 1e-7;
+    li[1].LowPart = times[3].dwLowDateTime;
+    li[1].HighPart = times[3].dwHighDateTime;
+    user_time = li[1].QuadPart * 1e-7;
+#else
+    user_time = NAN;
+    system_time = NAN;
+#endif
+
+    intervalString(uptime, time(NULL)-boot_time, user->handle_info);
+    reply("OSMSG_UPTIME_STATS", uptime, lines_processed, user_time, kernel_time);
     return 1;
 }
 
@@ -2567,10 +2623,12 @@ opserv_add_user_alert(struct userNode *req, const char *name, opserv_alert_react
      * 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)
+    if (alert->discrim->channel_count || alert->discrim->min_channels)
         dict_insert(opserv_channel_alerts, name_dup, alert);
     if (alert->discrim->mask_nick)
         dict_insert(opserv_nick_based_alerts, name_dup, alert);
+    if (alert->discrim->accountmask || alert->discrim->authed != -1)
+        dict_insert(opserv_account_alerts, name_dup, alert);
     return alert;
 }
 
@@ -2979,43 +3037,50 @@ opserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[], in
         }
     } else if (irccasecmp(argv[i], "duration") == 0) {
         discrim->duration = ParseInterval(argv[++i]);
-       } else if (irccasecmp(argv[i], "channel") == 0) {
-            for (j=0, i++; ; j++) {
-                switch (argv[i][j]) {
-                case '#':
-                    goto find_channel;
-                case '-':
-                    discrim->chan_no_modes  |= MODE_CHANOP | MODE_VOICE;
-                    break;
-                case '+':
-                    discrim->chan_req_modes |= MODE_VOICE;
-                    discrim->chan_no_modes  |= MODE_CHANOP;
-                    break;
-                case '@':
-                    discrim->chan_req_modes |= MODE_CHANOP;
-                    break;
-                case '\0':
-                    send_message(user, opserv, "MSG_NOT_CHANNEL_NAME");
-                    goto fail;
-                }
+    } else if (irccasecmp(argv[i], "channel") == 0) {
+        if(discrim->channel_count == DISCRIM_MAX_CHANS)
+        {
+            send_message(user, opserv, "OSMSG_TRACE_MAX_CHANNELS", DISCRIM_MAX_CHANS);
+            goto fail;
+        }
+
+        for (j=0, i++; ; j++) {
+            switch (argv[i][j]) {
+            case '#':
+                goto find_channel;
+            case '-':
+                discrim->chan_no_modes[discrim->channel_count]  |= MODE_CHANOP | MODE_VOICE;
+                break;
+            case '+':
+                discrim->chan_req_modes[discrim->channel_count] |= MODE_VOICE;
+                discrim->chan_no_modes[discrim->channel_count]  |= MODE_CHANOP;
+                break;
+            case '@':
+                discrim->chan_req_modes[discrim->channel_count] |= MODE_CHANOP;
+                break;
+            case '\0':
+                send_message(user, opserv, "MSG_NOT_CHANNEL_NAME");
+                goto fail;
             }
-          find_channel:
-            discrim->chan_no_modes &= ~discrim->chan_req_modes;
-           if (!(discrim->channel = GetChannel(argv[i]+j))) {
-                /* secretly "allow_channel" now means "if a channel name is
-                 * specified, require that it currently exist" */
-                if (allow_channel) {
-                    send_message(user, opserv, "MSG_CHANNEL_UNKNOWN", argv[i]);
-                    goto fail;
-                } else {
-                    discrim->channel = AddChannel(argv[i]+j, now, NULL, NULL);
-                }
-           }
-            LockChannel(discrim->channel);
-        } else if (irccasecmp(argv[i], "numchannels") == 0) {
-            discrim->min_channels = discrim->max_channels = strtoul(argv[++i], NULL, 10);
-       } else if (irccasecmp(argv[i], "limit") == 0) {
-           discrim->limit = strtoul(argv[++i], NULL, 10);
+        }
+        find_channel:
+        discrim->chan_no_modes[discrim->channel_count] &= ~discrim->chan_req_modes[discrim->channel_count];
+        if (!(discrim->channels[discrim->channel_count] = GetChannel(argv[i]+j))) {
+            /* secretly "allow_channel" now means "if a channel name is
+             * specified, require that it currently exist" */
+            if (allow_channel) {
+                send_message(user, opserv, "MSG_CHANNEL_UNKNOWN", argv[i]);
+                goto fail;
+            } else {
+                discrim->channels[discrim->channel_count] = AddChannel(argv[i]+j, now, NULL, NULL);
+            }
+        }
+        LockChannel(discrim->channels[discrim->channel_count]);
+        discrim->channel_count++;
+    } else if (irccasecmp(argv[i], "numchannels") == 0) {
+        discrim->min_channels = discrim->max_channels = strtoul(argv[++i], NULL, 10);
+    } else if (irccasecmp(argv[i], "limit") == 0) {
+        discrim->limit = strtoul(argv[++i], NULL, 10);
         } else if (irccasecmp(argv[i], "reason") == 0) {
             discrim->reason = strdup(unsplit_string(argv+i+1, argc-i-1, NULL));
             i = argc;
@@ -3037,7 +3102,7 @@ opserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[], in
                     discrim->max_ts = now - (ParseInterval(cmp+1) - 1);
                 }
             } else {
-                discrim->min_ts = now - ParseInterval(cmp+2);
+                discrim->min_ts = now - ParseInterval(cmp);
             }
         } else if (irccasecmp(argv[i], "access") == 0) {
             const char *cmp = argv[++i];
@@ -3057,11 +3122,15 @@ opserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[], in
                     discrim->min_level = strtoul(cmp+1, NULL, 0) + 1;
                 }
             } else {
-                discrim->min_level = strtoul(cmp+2, NULL, 0);
+                discrim->min_level = strtoul(cmp, NULL, 0);
+            }
+        } else if (irccasecmp(argv[i], "abuse") == 0) {
+            const char *abuse_what = argv[++i];
+            if (irccasecmp(abuse_what, "opers") == 0) {
+                discrim->match_opers = 1;
+            } else if (irccasecmp(abuse_what, "trusted") == 0) {
+                discrim->match_trusted = 1;
             }
-        } else if ((irccasecmp(argv[i], "abuse") == 0)
-                   && (irccasecmp(argv[++i], "opers") == 0)) {
-            discrim->match_opers = 1;
         } else if (irccasecmp(argv[i], "depth") == 0) {
             discrim->domain_depth = strtoul(argv[++i], NULL, 0);
         } else if (irccasecmp(argv[i], "clones") == 0) {
@@ -3093,7 +3162,7 @@ opserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[], in
 static int
 discrim_match(discrim_t discrim, struct userNode *user)
 {
-    unsigned int access;
+    unsigned int access, i;
 
     if ((user->timestamp < discrim->min_ts)
         || (user->timestamp > discrim->max_ts)
@@ -3112,8 +3181,9 @@ discrim_match(discrim_t discrim, struct userNode *user)
         || (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;
+       for(i = 0; i < discrim->channel_count; i++)
+               if (!GetUserMode(discrim->channels[i], user))
+                       return 0;
     access = user->handle_info ? user->handle_info->opserv_level : 0;
     if ((access < discrim->min_level)
         || (access > discrim->max_level)) {
@@ -3130,23 +3200,39 @@ discrim_match(discrim_t discrim, struct userNode *user)
 static unsigned int
 opserv_discrim_search(discrim_t discrim, discrim_search_func dsf, void *data)
 {
-    unsigned int nn, count;
+    unsigned int nn, count, match;
     struct userList matched;
 
     userList_init(&matched);
     /* Try most optimized search methods first */
-    if (discrim->channel) {
-        for (nn=0;
-                (nn < discrim->channel->members.used)
-                && (matched.used < discrim->limit);
-                nn++) {
-            struct modeNode *mn = discrim->channel->members.list[nn];
-            if (((mn->modes & discrim->chan_req_modes) != discrim->chan_req_modes)
-                    || ((mn->modes & discrim->chan_no_modes) != 0)) {
+    if (discrim->channel_count)
+    {
+        for (nn=0; (nn < discrim->channels[0]->members.used)
+             && (matched.used < discrim->limit);
+             nn++) {
+            struct modeNode *mn = discrim->channels[0]->members.list[nn];
+
+            if (((mn->modes & discrim->chan_req_modes[0]) != discrim->chan_req_modes[0])
+               || ((mn->modes & discrim->chan_no_modes[0]) != 0)) {
                 continue;
             }
-            if (discrim_match(discrim, mn->user)) {
-                userList_append(&matched, mn->user);
+
+            if ((match = discrim_match(discrim, mn->user)))
+            {
+                unsigned int i;
+
+                for (i = 1; i < discrim->channel_count; i++) {
+                    struct modeNode *mn2 = GetUserMode(discrim->channels[i], mn->user);
+
+                    if (((mn2->modes & discrim->chan_req_modes[i]) != discrim->chan_req_modes[i])
+                        || ((mn2->modes & discrim->chan_no_modes[i]) != 0)) {
+                        match = 0;
+                        break;
+                    }
+                }
+
+                if (match)
+                    userList_append(&matched, mn->user);
             }
         }
     } else if (discrim->ip_mask_bits == 128) {
@@ -3215,12 +3301,32 @@ trace_count_func(UNUSED_ARG(struct userNode *match), UNUSED_ARG(void *extra))
 }
 
 static int
-is_oper_victim(struct userNode *user, struct userNode *target, int match_opers)
+is_oper_victim(struct userNode *user, struct userNode *target, int match_opers, int check_ip)
 {
-    return !(IsService(target)
-             || (!match_opers && IsOper(target))
-             || (target->handle_info
-                 && target->handle_info->opserv_level > user->handle_info->opserv_level));
+    unsigned char is_victim;
+    unsigned int nn;
+
+    is_victim = !(IsService(target)
+                  || (!match_opers && IsOper(target))
+                  || (target->handle_info
+                      && target->handle_info->opserv_level > user->handle_info->opserv_level));
+
+    // If we don't need an ip check or want to hit opers or the the "cheap" check already disqualified the target, we are done.
+    if (!check_ip || match_opers || !is_victim)
+        return is_victim;
+
+    for(nn = 0; nn < curr_opers.used; nn++) {
+        if(memcmp(&curr_opers.list[nn]->ip, &target->ip, sizeof(irc_in_addr_t)) == 0)
+            return 0;
+    }
+
+    return 1;
+}
+
+static int
+is_trust_victim(struct userNode *target, int match_trusted)
+{
+    return (match_trusted || !dict_find(opserv_trusted_hosts, irc_ntoa(&target->ip), NULL));
 }
 
 static int
@@ -3228,7 +3334,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)) {
+    if (is_oper_victim(das->source, match, das->discrim->match_opers, 1) && is_trust_victim(match, das->discrim->match_trusted)) {
         opserv_block(match, das->source->handle_info->handle, das->discrim->reason, das->discrim->duration);
     }
 
@@ -3240,7 +3346,7 @@ trace_kill_func(struct userNode *match, void *extra)
 {
     struct discrim_and_source *das = extra;
 
-    if (is_oper_victim(das->source, match, das->discrim->match_opers)) {
+    if (is_oper_victim(das->source, match, das->discrim->match_opers, 0) && is_trust_victim(match, das->discrim->match_trusted)) {
        char *reason;
         if (das->discrim->reason) {
             reason = das->discrim->reason;
@@ -3270,7 +3376,7 @@ trace_gag_func(struct userNode *match, void *extra)
 {
     struct discrim_and_source *das = extra;
 
-    if (is_oper_victim(das->source, match, das->discrim->match_opers)) {
+    if (is_oper_victim(das->source, match, das->discrim->match_opers, 1) && is_trust_victim(match, das->discrim->match_trusted)) {
         char *reason, *mask;
         int masksize;
         if (das->discrim->reason) {
@@ -3360,7 +3466,7 @@ static MODCMD_FUNC(cmd_trace)
 {
     struct discrim_and_source das;
     discrim_search_func action;
-    unsigned int matches;
+    unsigned int matches, i;
     struct svccmd *subcmd;
     char buf[MAXLEN];
 
@@ -3422,8 +3528,8 @@ static MODCMD_FUNC(cmd_trace)
     else
        reply("MSG_NO_MATCHES");
 
-    if (das.discrim->channel)
-        UnlockChannel(das.discrim->channel);
+       for (i = 0; i < das.discrim->channel_count; i++)
+        UnlockChannel(das.discrim->channels[i]);
     free(das.discrim->reason);
     free(das.discrim);
     dict_delete(das.dict);
@@ -3488,7 +3594,7 @@ opserv_cdiscrim_create(struct userNode *user, unsigned int argc, char *argv[])
                 else
                     discrim->min_users = strtoul(cmp+1, NULL, 0) + 1;
             } else {
-                discrim->min_users = strtoul(cmp+2, NULL, 0);
+                discrim->min_users = strtoul(cmp, NULL, 0);
             }
        } else if (!irccasecmp(argv[i], "timestamp")) {
            const char *cmp = argv[++i];
@@ -3753,6 +3859,11 @@ alert_check_user(const char *key, void *data, void *extra)
         return 0;
     }
 
+    if ((alert->reaction != REACT_NOTICE)
+        && !is_trust_victim(user, alert->discrim->match_trusted)) {
+        return 0;
+    }
+
     /* The user matches the alert criteria, so trigger the reaction. */
     if (alert->discrim->option_log)
         log_module(OS_LOG, LOG_INFO, "Alert %s triggered by user %s!%s@%s (%s).", key, user->nick, user->ident, user->hostname, alert->discrim->reason);
@@ -3813,6 +3924,8 @@ opserv_staff_alert(struct userNode *user, UNUSED_ARG(struct handle_info *old_han
         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);
+
+    dict_foreach(opserv_account_alerts, alert_check_user, user);
 }
 
 static MODCMD_FUNC(cmd_log)
@@ -3948,10 +4061,11 @@ static MODCMD_FUNC(cmd_delalert)
     for (i=1; i<argc; i++) {
         dict_remove(opserv_nick_based_alerts, argv[i]);
         dict_remove(opserv_channel_alerts, argv[i]);
-       if (dict_remove(opserv_user_alerts, argv[i]))
-           reply("OSMSG_REMOVED_ALERT", argv[i]);
+        dict_remove(opserv_account_alerts, argv[i]);
+        if (dict_remove(opserv_user_alerts, argv[i]))
+            reply("OSMSG_REMOVED_ALERT", argv[i]);
         else
-           reply("OSMSG_NO_SUCH_ALERT", argv[i]);
+            reply("OSMSG_NO_SUCH_ALERT", argv[i]);
     }
     return 1;
 }
@@ -4051,6 +4165,8 @@ opserv_db_init(void) {
     dict_set_free_keys(opserv_chan_warn, free);
     dict_set_free_data(opserv_chan_warn, free);
     /* set up opserv_user_alerts */
+    dict_delete(opserv_account_alerts);
+    opserv_account_alerts = dict_new();
     dict_delete(opserv_channel_alerts);
     opserv_channel_alerts = dict_new();
     dict_delete(opserv_nick_based_alerts);
@@ -4081,6 +4197,7 @@ opserv_db_cleanup(void)
     unreg_del_user_func(opserv_user_cleanup);
     dict_delete(opserv_hostinfo_dict);
     dict_delete(opserv_nick_based_alerts);
+    dict_delete(opserv_account_alerts);
     dict_delete(opserv_channel_alerts);
     dict_delete(opserv_user_alerts);
     for (nn=0; nn<ArrayLength(level_strings); ++nn)
@@ -4139,7 +4256,7 @@ init_opserv(const char *nick)
     opserv_define_func("GTRACE PRINT", NULL, 0, 0, 0);
     opserv_define_func("INVITE", cmd_invite, 100, 2, 0);
     opserv_define_func("INVITEME", cmd_inviteme, 100, 0, 0);
-    opserv_define_func("JOIN", cmd_join, 601, 0, 2);
+    opserv_define_func("JOIN", cmd_join, 601, 1, 0);
     opserv_define_func("JUMP", cmd_jump, 900, 0, 2);
     opserv_define_func("JUPE", cmd_jupe, 900, 0, 4);
     opserv_define_func("KICK", cmd_kick, 100, 2, 2);
@@ -4150,7 +4267,7 @@ init_opserv(const char *nick)
     opserv_define_func("MODE", cmd_mode, 100, 2, 2);
     opserv_define_func("OP", cmd_op, 100, 2, 2);
     opserv_define_func("OPALL", cmd_opall, 400, 2, 0);
-    opserv_define_func("PART", cmd_part, 601, 0, 2);
+    opserv_define_func("PART", cmd_part, 601, 2, 0);
     opserv_define_func("QUERY", cmd_query, 0, 0, 0);
     opserv_define_func("RAW", cmd_raw, 999, 0, 2);
     opserv_define_func("RECONNECT", cmd_reconnect, 900, 0, 0);