]> jfr.im git - irc/evilnet/x3.git/blobdiff - src/spamserv.c
Minor typo in previous commit where returning 0 when it should have been 1 from opser...
[irc/evilnet/x3.git] / src / spamserv.c
index 0819acdc50e1ed8ee132114b4f320607287237d4..b8b48544311bc8ff29ed24dbe3282919b4143afe 100644 (file)
@@ -3,7 +3,7 @@
  *
  * This program 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
+ * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.  Important limitations are
  * listed in the COPYING file that accompanies this software.
  *
@@ -28,6 +28,8 @@
 #include "timeq.h"
 #include "gline.h"
 
+#include <ctype.h>
+
 #define SPAMSERV_CONF_NAME           "services/spamserv"
 
 #define KEY_EXCEPTIONS               "exceptions"
 #define KEY_FLAGS                    "flags"
 #define KEY_INFO                     "info"
 #define KEY_EXPIRY                   "expiry"
-
+#define KEY_TRUSTED_HOSTS           "trusted"
+#define KEY_CHANNELS                "channel"
+#define KEY_ISSUER                  "issuer"
+#define KEY_ISSUED                  "issued"
+#define KEY_TRUSTED_ACCOUNTS        "trusted"
 #define KEY_DEBUG_CHANNEL            "debug_channel"
 #define KEY_GLOBAL_EXCEPTIONS        "global_exceptions"
 #define KEY_GLOBAL_BADWORDS          "global_badwords"
 #define KEY_ADV_CHAN_MUST_EXIST      "adv_chan_must_exist"
 #define KEY_STRIP_MIRC_CODES         "strip_mirc_codes"
 #define KEY_ALLOW_MOVE_MERGE         "allow_move_merge"
+#define KEY_CAPSMIN                  "capsmin"
+#define KEY_CAPSPERCENT              "capspercent"
+#define KEY_EXCEPTLEVEL              "exceptlevel"
+#define KEY_EXCEPTSPAMLEVEL          "exceptspamlevel"
+#define KEY_EXCEPTFLOODLEVEL         "exceptfloodlevel"
+#define KEY_EXCEPTADVLEVEL           "exceptadvlevel"
+#define KEY_EXCEPTBADWORDLEVEL       "exceptbadwordlevel"
+#define KEY_EXCEPTCAPSLEVEL          "exceptcapslevel"
 
 #define SPAMSERV_FUNC(NAME)    MODCMD_FUNC(NAME)
 #define SPAMSERV_SYNTAX()      svccmd_send_help(user, spamserv, cmd)
@@ -73,13 +87,15 @@ dict_t registered_channels_dict;
 dict_t connected_users_dict;
 dict_t killed_users_dict;
 
+#define SSFUNC_ARGS             user, channel, argc, argv, cmd
+
 #define spamserv_notice(target, format...) send_message(target , spamserv , ## format)
 #define spamserv_debug(format...) do { if(spamserv_conf.debug_channel) send_channel_notice(spamserv_conf.debug_channel , spamserv , ## format); } while(0)
 #define ss_reply(format...)    send_message(user , spamserv , ## format)
 
-#define SET_SUBCMDS_SIZE 13
+#define SET_SUBCMDS_SIZE 20
 
-const char *set_subcommands[SET_SUBCMDS_SIZE] = {"SPAMLIMIT", "BADREACTION", "ADVREACTION", "WARNREACTION", "ADVSCAN", "SPAMSCAN", "BADWORDSCAN", "CHANFLOODSCAN", "JOINFLOODSCAN", "SCANCHANOPS", "SCANHALFOPS", "SCANVOICED"};
+const char *set_subcommands[SET_SUBCMDS_SIZE] = {"EXCEPTLEVEL", "EXCEPTADVLEVEL", "EXCEPTBADWORDLEVEL", "EXCEPTCAPSLEVEL", "EXCEPTFLOODLEVEL", "EXCEPTSPAMLEVEL", "SPAMLIMIT", "BADREACTION", "CAPSREACTION", "ADVREACTION", "WARNREACTION", "ADVSCAN", "CAPSSCAN", "SPAMSCAN", "BADWORDSCAN", "CHANFLOODSCAN", "JOINFLOODSCAN", "CAPSMIN", "CAPSPERCENT"};
 
 extern struct string_list *autojoin_channels;
 static void spamserv_clear_spamNodes(struct chanNode *channel);
@@ -142,6 +158,30 @@ static const struct message_entry msgtab[] = {
     { "SSMSG_WARNING_RULES_T",          "%s is against the network rules. Read the network rules at %s" },
     { "SSMSG_WARNING_RULES_2_T",        "You are violating the network rules. Read the network rules at %s" },
 
+    { "SSMSG_ALREADY_TRUSTED", "Account $b%s$b is already trusted." },
+    { "SSMSG_NOT_TRUSTED", "Account $b%s$b is not trusted." },
+    { "SSMSG_ADDED_TRUSTED", "Added %s to the global trusted-accounts list" },
+    { "SSMSG_ADDED_TRUSTED_CHANNEL", "Added %s to the trusted-accounts list for channel %s." },
+    { "SSMSG_REMOVED_TRUSTED", "Removed %s from the global trusted-accounts list." },
+    { "SSMSG_REMOVED_TRUSTED_CHANNEL", "Removed %s from channel %s trusted-account list." },
+    { "SSMSG_TRUSTED_LIST", "$bTrusted Accounts$b" },
+    { "SSMSG_TRUSTED_LIST_HEADER", "Account         Added By   Time" },
+    { "SSMSG_HOST_IS_TRUSTED",      "%-15s %-10s set %s ago" },
+    { "SSMSG_TRUSTED_LIST_BAR", "----------------------------------------" },
+    { "SSMSG_TRUSTED_LIST_END", "---------End of Trusted Accounts--------" },
+    { "SSMSG_HOST_NOT_TRUSTED", "%s does not have a special trust." },
+
+    { "SSMSG_MUST_BE_HELPING", "You must have security override (helping mode) on to use this command." },
+
+    { "SSMSG_SET_CAPSMIN",            "$bCapsMin$b            %d - atleast this min caps and atleast CapsPercent of the total line." },
+    { "SSMSG_SET_CAPSPERCENT",        "$bCapsPercent$b        %d - atleast CapsPercent of the total line." },
+    { "SSMSG_SET_EXCEPTLEVEL"   ,     "$bExceptLevel$b        %d - level and above will be excepted from all checks." },
+    { "SSMSG_SET_EXCEPTADVLEVEL",     "$bExceptAdvLevel$b     %d - and above will be excepted from advertising checks." },
+    { "SSMSG_SET_EXCEPTBADWORDLEVEL", "$bExceptBadWordLevel$b %d - and above will be excepted from badword checks." },
+    { "SSMSG_SET_EXCEPTCAPSLEVEL"   , "$bExceptCapsLevel$b    %d - and above will be excepted from caps checks." },
+    { "SSMSG_SET_EXCEPTFLOODLEVEL",   "$bExceptFloodLevel$b   %d - and above will be excepted from flood checks." },
+    { "SSMSG_SET_EXCEPTSPAMLEVEL",    "$bExceptSpamLevel$b    %d - and above will be excepted from spam checks." },
+
     { NULL, NULL }
 };
 
@@ -155,6 +195,7 @@ static const struct message_entry msgtab[] = {
 #define SSMSG_FLOOD                   "Flooding the channel/network"
 #define SSMSG_ADV                     "Advertising"
 #define SSMSG_BAD                     "Badwords"
+#define SSMSG_CAPS                    "Caps"
 #define SSMSG_JOINFLOOD               "Join flooding the channel"
 
 #define SSMSG_WARNING                  "%s is against the network rules"
@@ -169,6 +210,8 @@ static const struct message_entry msgtab[] = {
 #define SSMSG_WARNING_RULES_2         "SSMSG_WARNING_RULES_2_T"
 */
 
+static dict_t spamserv_trusted_accounts;
+
 static struct
 {
        struct chanNode *debug_channel;
@@ -188,8 +231,17 @@ static struct
        unsigned int adv_chan_must_exist : 1;
        unsigned int strip_mirc_codes : 1;
        unsigned int allow_move_merge : 1;
+       unsigned long untrusted_max;
 } spamserv_conf;
 
+struct trusted_account {
+    char *account;
+    struct string_list *channel;
+    char *issuer;
+    unsigned long limit;
+    time_t issued;
+};
+
 /***********************************************/
 /*                   Channel                   */
 /***********************************************/
@@ -234,9 +286,24 @@ spamserv_register_channel(struct chanNode *channel, struct string_list *exceptio
        cInfo->exceptions = exceptions ? string_list_copy(exceptions) : alloc_string_list(1);
        cInfo->badwords = badwords ? string_list_copy(badwords) : alloc_string_list(1);
        cInfo->flags = flags;
+       cInfo->exceptlevel = 300;
+       cInfo->exceptspamlevel = 100;
+       cInfo->exceptadvlevel = 100;
+       cInfo->exceptbadwordlevel = 100;
+       cInfo->exceptcapslevel = 100;
+       cInfo->exceptfloodlevel = 100;
+        cInfo->capsmin = 10;
+        cInfo->capspercent = 25;
+
+        /* XXX Rewrite the flag system */
+        if (strlen(info) < 5)
+            strcat(info, "s");
+        if (strlen(info) < 6)
+            strcat(info, "s");
+
        safestrncpy(cInfo->info, info, sizeof(cInfo->info));
        cInfo->suspend_expiry = 0;
-       dict_insert(registered_channels_dict, cInfo->channel->name, cInfo);
+       dict_insert(registered_channels_dict, strdup(cInfo->channel->name), cInfo);
 
        return cInfo;
 }
@@ -247,9 +314,9 @@ spamserv_unregister_channel(struct chanInfo *cInfo)
        if(!cInfo)
                return;
 
-       dict_remove(registered_channels_dict, cInfo->channel->name);
        free_string_list(cInfo->exceptions);
        free_string_list(cInfo->badwords);
+       dict_remove(registered_channels_dict, cInfo->channel->name);
        free(cInfo);
 }
 
@@ -300,7 +367,7 @@ spamserv_cs_move_merge(struct userNode *user, struct chanNode *channel, struct c
                cInfo->channel = target;
 
                dict_remove(registered_channels_dict, channel->name);
-               dict_insert(registered_channels_dict, target->name, cInfo);
+               dict_insert(registered_channels_dict, strdup(target->name), cInfo);
 
                if(move)
                {
@@ -523,13 +590,14 @@ spamserv_create_user(struct userNode *user)
        uInfo->warnlevel = kNode ? kNode->warnlevel : 0;
        uInfo->lastadv = 0;
        uInfo->lastbad = 0;
+       uInfo->lastcaps = 0;
 
-       dict_insert(connected_users_dict, user->nick, uInfo);
+       dict_insert(connected_users_dict, strdup(user->nick), uInfo);
 
        if(kNode)
        {
+               /* free(kNode); dict_remove does this */
                dict_remove(killed_users_dict, irc_ntoa(&user->ip));
-               free(kNode);
        }
 }
 
@@ -556,7 +624,7 @@ spamserv_delete_user(struct userInfo *uInfo)
 }
 
 static int
-spamserv_new_user_func(struct userNode *user)
+spamserv_new_user_func(struct userNode *user, UNUSED_ARG(void *extra))
 {
        if(!IsLocal(user))
                spamserv_create_user(user);
@@ -565,7 +633,7 @@ spamserv_new_user_func(struct userNode *user)
 }
 
 static void
-spamserv_del_user_func(struct userNode *user, struct userNode *killer, UNUSED_ARG(const char *why))
+spamserv_del_user_func(struct userNode *user, struct userNode *killer, UNUSED_ARG(const char *why), UNUSED_ARG(void *extra))
 {
        struct userInfo *uInfo = get_userInfo(user->nick);
        struct killNode *kNode;
@@ -588,23 +656,25 @@ spamserv_del_user_func(struct userNode *user, struct userNode *killer, UNUSED_AR
 
                kNode->time = now;
 
-               dict_insert(killed_users_dict, irc_ntoa(&user->ip), kNode);
+               dict_insert(killed_users_dict, strdup(irc_ntoa(&user->ip)), kNode);
        }
 
        spamserv_delete_user(uInfo);    
 }
 
 static void
-spamserv_nick_change_func(struct userNode *user, const char *old_nick)
+spamserv_nick_change_func(struct userNode *user, const char *old_nick, UNUSED_ARG(void *extra))
 {
        struct userInfo *uInfo = get_userInfo(old_nick);
 
-       dict_remove(connected_users_dict, old_nick);
-       dict_insert(connected_users_dict, user->nick, uInfo);
+        if(uInfo) {
+            dict_remove(connected_users_dict, old_nick);
+            dict_insert(connected_users_dict, strdup(user->nick), uInfo);
+        }
 }
 
 static int
-spamserv_user_join(struct modeNode *mNode)
+spamserv_user_join(struct modeNode *mNode, UNUSED_ARG(void *extra))
 {
        struct chanNode *channel = mNode->channel;
        struct userNode *user = mNode->user;    
@@ -649,7 +719,7 @@ spamserv_user_join(struct modeNode *mNode)
 }
 
 static void
-spamserv_user_part(struct modeNode *mn, UNUSED_ARG(const char *reason))
+spamserv_user_part(struct modeNode *mn, UNUSED_ARG(const char *reason), UNUSED_ARG(void *extra))
 {
        struct userNode *user = mn->user;
        struct chanNode *channel = mn->channel;
@@ -758,7 +828,7 @@ timeq_joinflood(UNUSED_ARG(void *data))
 {
        dict_iterator_t it;
        struct userInfo *uInfo;
-       struct floodNode *fNode;
+       struct floodNode *fNode, *nextnode;
 
        for(it = dict_first(connected_users_dict); it; it = iter_next(it))
        {
@@ -767,8 +837,9 @@ timeq_joinflood(UNUSED_ARG(void *data))
                if(!(fNode = uInfo->joinflood))
                        continue;
 
-               for(; fNode; fNode = fNode->next)
+               for(; fNode; fNode = nextnode)
                {
+                        nextnode = fNode->next;
                        if(now - fNode->time > JOINFLOOD_EXPIRE)
                        {
                                if(!(--fNode->count))
@@ -800,6 +871,26 @@ timeq_bad(UNUSED_ARG(void *data))
        timeq_add(now + BAD_TIMEQ_FREQ, timeq_bad, NULL);
 }
 
+static void
+timeq_caps(UNUSED_ARG(void *data))
+{
+       dict_iterator_t it;
+       struct userInfo *uInfo;
+
+       for(it = dict_first(connected_users_dict); it; it = iter_next(it))
+       {
+               uInfo = iter_data(it);
+
+               if(uInfo->lastcaps && uInfo->lastcaps - now > CAPS_EXPIRE)
+               {
+                       uInfo->lastcaps = 0;
+                       uInfo->flags &= ~USER_CAPS_WARNED;
+               }
+       }
+
+       timeq_add(now + CAPS_TIMEQ_FREQ, timeq_caps, NULL);
+}
+
 static void
 timeq_adv(UNUSED_ARG(void *data))
 {
@@ -843,13 +934,21 @@ timeq_kill(UNUSED_ARG(void *data))
        dict_iterator_t it;
        struct killNode *kNode;
 
-       for(it = dict_first(killed_users_dict); it; it = iter_next(it))
-       {
-               kNode = iter_data(it);
-
-               if(kNode->time - now > KILL_EXPIRE)
-                       free(kNode);
-       }
+        while(1) {
+            for(it = dict_first(killed_users_dict); it; it = iter_next(it))
+            {
+                    kNode = iter_data(it);
+
+                    if(now - kNode->time > KILL_EXPIRE) {
+                            dict_remove(killed_users_dict, iter_key(it));
+                             /* have to restart the loop because next is
+                              * now invalid. FIXME: how could we do this better? */
+                             break; /* out of for() loop */
+                    }
+            }
+            /* no more killed_users to delete, so stop while loop */
+            break; /* out of while() loop */ 
+        }
 
        timeq_add(now + KILL_TIMEQ_FREQ, timeq_kill, NULL);
 }
@@ -1515,6 +1614,327 @@ SPAMSERV_FUNC(cmd_set)
        return subcmd->command->func(user, channel, argc - 1, argv + 1, subcmd);
 }
 
+int ss_check_user_level(struct chanNode *channel, struct userNode *user, unsigned int minimum, int allow_override, int exempt_owner)
+{
+    struct userData *uData;
+    struct chanData *cData = channel->channel_info;
+    if(!minimum)
+        return 1;
+    uData = _GetChannelUser(cData, user->handle_info, allow_override, 0);
+    if(!uData)
+        return 0;
+    if(minimum <= uData->access)
+        return 1;
+    if((minimum > UL_OWNER) && (uData->access == UL_OWNER) && exempt_owner)
+        return 1;
+    return 0;
+}
+
+
+static int
+channel_except_level(struct userNode *user, struct chanNode *channel, int argc, char *argv[], struct svccmd *cmd)
+{
+    struct chanData *cData = channel->channel_info;
+    struct chanInfo *cInfo;
+    struct userData *uData;
+    unsigned short value;
+
+    cInfo = get_chanInfo(channel->name);
+
+    if(argc > 1)
+    {
+        if(!ss_check_user_level(channel, user, cInfo->exceptlevel, 1, 1))
+        {
+            reply("SSMSG_CANNOT_SET");
+            return 0;
+        }
+        value = user_level_from_name(argv[1], UL_OWNER+1);
+        if(!value && strcmp(argv[1], "0"))
+       {
+           reply("SSMSG_INVALID_ACCESS", argv[1]);
+            return 0;
+        }
+        uData = GetChannelUser(cData, user->handle_info);
+        if(!uData || ((uData->access < UL_OWNER) && (value > uData->access)))
+        {
+            reply("SSMSG_BAD_SETLEVEL");
+            return 0;
+        }
+        cInfo->exceptlevel = value;
+    }
+    reply("SSMSG_SET_EXCEPTLEVEL", cInfo->exceptlevel);
+    return 0;
+}
+
+static int
+channel_except_adv_level(struct userNode *user, struct chanNode *channel, int argc, char *argv[], struct svccmd *cmd)
+{
+    struct chanData *cData = channel->channel_info;
+    struct chanInfo *cInfo;
+    struct userData *uData;
+    unsigned short value;
+
+    cInfo = get_chanInfo(channel->name);
+
+    if(argc > 1)
+    {
+        if(!ss_check_user_level(channel, user, cInfo->exceptadvlevel, 1, 1))
+        {
+            reply("SSMSG_CANNOT_SET");
+            return 0;
+        }
+        value = user_level_from_name(argv[1], UL_OWNER+1);
+        if(!value && strcmp(argv[1], "0"))
+       {
+           reply("SSMSG_INVALID_ACCESS", argv[1]);
+            return 0;
+        }
+        uData = GetChannelUser(cData, user->handle_info);
+        if(!uData || ((uData->access < UL_OWNER) && (value > uData->access)))
+        {
+            reply("SSMSG_BAD_SETLEVEL");
+            return 0;
+        }
+        cInfo->exceptadvlevel = value;
+    }
+    reply("SSMSG_SET_EXCEPTADVLEVEL", cInfo->exceptadvlevel);
+    return 0;
+}
+
+static int
+channel_except_badword_level(struct userNode *user, struct chanNode *channel, int argc, char *argv[], struct svccmd *cmd)
+{
+    struct chanData *cData = channel->channel_info;
+    struct chanInfo *cInfo;
+    struct userData *uData;
+    unsigned short value;
+
+    cInfo = get_chanInfo(channel->name);
+
+    if(argc > 1)
+    {
+        if(!ss_check_user_level(channel, user, cInfo->exceptbadwordlevel, 1, 1))
+        {
+            reply("SSMSG_CANNOT_SET");
+            return 0;
+        }
+        value = user_level_from_name(argv[1], UL_OWNER+1);
+        if(!value && strcmp(argv[1], "0"))
+       {
+           reply("SSMSG_INVALID_ACCESS", argv[1]);
+            return 0;
+        }
+        uData = GetChannelUser(cData, user->handle_info);
+        if(!uData || ((uData->access < UL_OWNER) && (value > uData->access)))
+        {
+            reply("SSMSG_BAD_SETLEVEL");
+            return 0;
+        }
+        cInfo->exceptbadwordlevel = value;
+    }
+    reply("SSMSG_SET_EXCEPTBADWORDLEVEL", cInfo->exceptbadwordlevel);
+    return 0;
+}
+
+static int
+channel_except_caps_level(struct userNode *user, struct chanNode *channel, int argc, char *argv[], struct svccmd *cmd)
+{
+    struct chanData *cData = channel->channel_info;
+    struct chanInfo *cInfo;
+    struct userData *uData;
+    unsigned short value;
+
+    cInfo = get_chanInfo(channel->name);
+
+    if(argc > 1)
+    {
+        if(!ss_check_user_level(channel, user, cInfo->exceptcapslevel, 1, 1))
+        {
+            reply("SSMSG_CANNOT_SET");
+            return 0;
+        }
+        value = user_level_from_name(argv[1], UL_OWNER+1);
+        if(!value && strcmp(argv[1], "0"))
+       {
+           reply("SSMSG_INVALID_ACCESS", argv[1]);
+            return 0;
+        }
+        uData = GetChannelUser(cData, user->handle_info);
+        if(!uData || ((uData->access < UL_OWNER) && (value > uData->access)))
+        {
+            reply("SSMSG_BAD_SETLEVEL");
+            return 0;
+        }
+        cInfo->exceptcapslevel = value;
+    }
+    reply("SSMSG_SET_EXCEPTCAPSLEVEL", cInfo->exceptcapslevel);
+    return 0;
+}
+
+static int
+channel_except_flood_level(struct userNode *user, struct chanNode *channel, int argc, char *argv[], struct svccmd *cmd)
+{
+    struct chanData *cData = channel->channel_info;
+    struct chanInfo *cInfo;
+    struct userData *uData;
+    unsigned short value;
+
+    cInfo = get_chanInfo(channel->name);
+
+    if(argc > 1)
+    {
+        if(!ss_check_user_level(channel, user, cInfo->exceptfloodlevel, 1, 1))
+        {
+            reply("SSMSG_CANNOT_SET");
+            return 0;
+        }
+        value = user_level_from_name(argv[1], UL_OWNER+1);
+        if(!value && strcmp(argv[1], "0"))
+       {
+           reply("SSMSG_INVALID_ACCESS", argv[1]);
+            return 0;
+        }
+        uData = GetChannelUser(cData, user->handle_info);
+        if(!uData || ((uData->access < UL_OWNER) && (value > uData->access)))
+        {
+            reply("SSMSG_BAD_SETLEVEL");
+            return 0;
+        }
+        cInfo->exceptfloodlevel = value;
+    }
+    reply("SSMSG_SET_EXCEPTFLOODLEVEL", cInfo->exceptfloodlevel);
+    return 0;
+}
+
+static int
+channel_except_spam_level(struct userNode *user, struct chanNode *channel, int argc, char *argv[], struct svccmd *cmd)
+{
+    struct chanData *cData = channel->channel_info;
+    struct chanInfo *cInfo;
+    struct userData *uData;
+    unsigned short value;
+
+    cInfo = get_chanInfo(channel->name);
+
+    if(argc > 1)
+    {
+        if(!ss_check_user_level(channel, user, cInfo->exceptspamlevel, 1, 1))
+        {
+            reply("SSMSG_CANNOT_SET");
+            return 0;
+        }
+        value = user_level_from_name(argv[1], UL_OWNER+1);
+        if(!value && strcmp(argv[1], "0"))
+       {
+           reply("SSMSG_INVALID_ACCESS", argv[1]);
+            return 0;
+        }
+        uData = GetChannelUser(cData, user->handle_info);
+        if(!uData || ((uData->access < UL_OWNER) && (value > uData->access)))
+        {
+            reply("SSMSG_BAD_SETLEVEL");
+            return 0;
+        }
+        cInfo->exceptspamlevel = value;
+    }
+    reply("SSMSG_SET_EXCEPTSPAMLEVEL", cInfo->exceptspamlevel);
+    return 0;
+}
+
+static
+SPAMSERV_FUNC(opt_capsmin)
+{
+    struct chanInfo *cInfo;
+
+    cInfo = get_chanInfo(channel->name);
+
+    if(argc > 1)
+    {
+        char *mask = strdup(argv[1]);
+        unsigned int old = cInfo->capsmin;
+
+        if (isdigit(*mask) && strspn(mask, "1234567890,-") == strlen(mask)) {
+            cInfo->capsmin = mask ? strtoul(mask, NULL, 0) : 10;
+
+            if (cInfo->capsmin < 0) {
+                cInfo->capsmin = old;
+                reply("SSMSG_BAD_SETLEVEL");
+                return 0;
+            }
+        } else {
+            reply("SSMSG_BAD_SETLEVEL");
+            return 0;
+        }
+    }
+    reply("SSMSG_SET_CAPSMIN", cInfo->capsmin);
+    return 0;
+}
+
+static
+SPAMSERV_FUNC(opt_capspercent)
+{
+    struct chanInfo *cInfo;
+
+    cInfo = get_chanInfo(channel->name);
+
+    if(argc > 1)
+    {
+        char *mask = strdup(argv[1]);
+        unsigned int old = cInfo->capspercent;
+
+        if (isdigit(*mask) && strspn(mask, "1234567890,-") == strlen(mask)) {
+            cInfo->capspercent = mask ? strtoul(mask, NULL, 0) : 10;
+
+            if ((cInfo->capspercent < 0) || (cInfo->capspercent > 100)) {
+                cInfo->capspercent = old;
+                reply("SSMSG_BAD_SETLEVEL");
+                return 0;
+            }
+        } else {
+            reply("SSMSG_BAD_SETLEVEL");
+            return 0;
+        }
+    }
+    reply("SSMSG_SET_CAPSPERCENT", cInfo->capspercent);
+    return 0;
+}
+
+static
+SPAMSERV_FUNC(opt_exceptlevel)
+{
+    return channel_except_level(SSFUNC_ARGS);
+}
+
+static
+SPAMSERV_FUNC(opt_exceptadvlevel)
+{
+    return channel_except_adv_level(SSFUNC_ARGS);
+}
+
+static
+SPAMSERV_FUNC(opt_exceptbadwordlevel)
+{
+    return channel_except_badword_level(SSFUNC_ARGS);
+}
+
+static
+SPAMSERV_FUNC(opt_exceptcapslevel)
+{
+    return channel_except_caps_level(SSFUNC_ARGS);
+}
+
+static
+SPAMSERV_FUNC(opt_exceptfloodlevel)
+{
+    return channel_except_flood_level(SSFUNC_ARGS);
+}
+
+static
+SPAMSERV_FUNC(opt_exceptspamlevel)
+{
+    return channel_except_spam_level(SSFUNC_ARGS);
+}
+
 static 
 SPAMSERV_FUNC(opt_spamlimit)
 {
@@ -1575,12 +1995,33 @@ SPAMSERV_FUNC(opt_badreaction)
        MULTIPLE_OPTION("BadReaction   ", "BadReaction", ci_BadReaction);
 }
 
+static 
+SPAMSERV_FUNC(opt_capsreaction)
+{
+       struct valueData values[] =
+       {
+               {"Kick on disallowed caps.", 'k', 0},
+               {"Kickban on disallowed caps.", 'b', 0},
+               {"Short timed ban on disallowed caps.", 's', 0},
+               {"Long timed ban on disallowed caps.", 'l', 0},
+               {"Kill on disallowed caps.", 'd', 1}
+       };
+
+       MULTIPLE_OPTION("CapsReaction   ", "CapsReaction", ci_CapsReaction);
+}
+
 static 
 SPAMSERV_FUNC(opt_advscan)
 {
        BINARY_OPTION("AdvScan       ", CHAN_ADV_SCAN);
 }
 
+static 
+SPAMSERV_FUNC(opt_capsscan)
+{
+       BINARY_OPTION("CapsScan      ", CHAN_CAPSSCAN);
+}
+
 static 
 SPAMSERV_FUNC(opt_spamscan)
 {
@@ -1605,22 +2046,349 @@ SPAMSERV_FUNC(opt_joinflood)
        BINARY_OPTION("JoinFloodScan ", CHAN_JOINFLOOD);
 }
 
-static 
-SPAMSERV_FUNC(opt_scanops)
+static void
+spamserv_add_trusted_account(const char *account, struct string_list *channel, const char *issuer, time_t issued)
 {
-       BINARY_OPTION("ScanChanOps   ", CHAN_SCAN_CHANOPS);
+    struct trusted_account *ta;
+    ta = calloc(1, sizeof(*ta));
+    if (!ta)
+        return;
+    ta->account = strdup(account);
+    ta->channel = channel ? string_list_copy(channel) : alloc_string_list(1);
+    ta->issuer = strdup(issuer);
+    ta->issued = issued;
+    dict_insert(spamserv_trusted_accounts, strdup(ta->account), ta);
 }
 
-static 
-SPAMSERV_FUNC(opt_scanhalfops)
+/*
+static void
+free_trusted_account(void *data)
 {
-       BINARY_OPTION("ScanHalfOps   ", CHAN_SCAN_HALFOPS);
+    struct trusted_account *ta = data;
+    free(ta->account);
+    free_string_list(ta->channel);
+    free(ta->issuer);
+    free(ta);
 }
+*/
 
-static 
-SPAMSERV_FUNC(opt_scanvoiced)
+static SPAMSERV_FUNC(cmd_addtrust)
+{
+    unsigned int i;
+    struct userData *uData;
+    struct chanData *cData;
+    struct chanInfo *cInfo;
+    struct trusted_account *ta;
+    struct string_list *templist;
+    struct handle_info *hi;
+
+    if (!(channel = GetChannel(argv[2]))) {
+        ss_reply("SSMSG_NOT_REGISTERED", argv[2]);
+        return 0;
+    }
+
+    cInfo = get_chanInfo(channel->name);
+    cData = channel->channel_info;
+    uData = GetChannelUser(cData, user->handle_info);
+
+    if (!cInfo || !channel->channel_info) {
+        ss_reply("SSMSG_NOT_REGISTERED", channel->name);
+        return 0;
+    }
+
+    if (CHECK_SUSPENDED(cInfo)) {
+         ss_reply("SSMSG_SUSPENDED", channel->name);
+         return 0;
+    }
+
+    if (!uData || (uData->access < UL_MANAGER)) {
+        ss_reply("SSMSG_NO_ACCESS");
+        return 0;
+    }
+
+    if (!(hi = modcmd_get_handle_info(user, argv[1]))) {
+        return 0;
+    }
+
+    if ((ta = dict_find(spamserv_trusted_accounts, argv[1], NULL))) {
+        if (ta->channel->used && (argc > 1)) {
+            for (i=0; i < ta->channel->used; i++) {
+                if (!strcmp(ta->channel->list[i], argv[2])) {
+                    ss_reply("SSMSG_ALREADY_TRUSTED", hi->handle);
+                    return 0;
+                }
+            }
+        }
+
+        string_list_append(ta->channel, argv[2]);
+        ss_reply("SSMSG_ADDED_TRUSTED_CHANNEL", hi->handle, argv[2]);
+        return 1;
+    }
+
+    templist = alloc_string_list(sizeof(argv[2])+1);
+//    templist = alloc_string_list(1);
+    string_list_append(templist, argv[2]);
+
+    spamserv_add_trusted_account(hi->handle, templist, user->handle_info->handle, now);
+    ss_reply("SSMSG_ADDED_TRUSTED_CHANNEL", hi->handle, argv[2]);
+    return 1;
+}
+
+static SPAMSERV_FUNC(cmd_oaddtrust)
+{
+    unsigned int i, global = 0;
+    struct chanInfo *cInfo;
+    struct chanData *cData;
+    struct trusted_account *ta;
+    struct string_list *templist;
+    struct handle_info *hi;
+
+    if (!strcmp(argv[2], "global"))
+        global = 1;
+
+    if (!(channel = GetChannel(argv[2])) && (global == 0)) {
+        ss_reply("SSMSG_NOT_REGISTERED", channel ? channel->name : (global ? "global" : ""));
+        return 0;
+    }
+
+    if (channel) {
+        cInfo = get_chanInfo(channel->name);
+        cData = channel->channel_info;
+
+        if (!cInfo || !channel->channel_info) {
+            ss_reply("SSMSG_NOT_REGISTERED", channel->name);
+            return 0;
+        }
+    }
+
+    if (!(hi = modcmd_get_handle_info(user, argv[1]))) {
+        return 0;
+    }
+
+    if ((ta = dict_find(spamserv_trusted_accounts, argv[1], NULL))) {
+        if (ta->channel->used && (argc > 1)) {
+            for (i=0; i < ta->channel->used; i++) {
+                if (!strcmp(ta->channel->list[i], argv[2])) {
+                    ss_reply("SSMSG_ALREADY_TRUSTED", argv[1]);
+                    return 0;
+                }
+            }
+        }
+
+        string_list_append(ta->channel, argv[2]);
+
+        if (global == 1)
+            ss_reply("SSMSG_ADDED_TRUSTED", argv[1]);
+        else
+            ss_reply("SSMSG_ADDED_TRUSTED_CHANNEL", argv[1], argv[2]);
+
+        return 1;
+    }
+
+    templist = alloc_string_list(sizeof(argv[2])+1);
+//    templist = alloc_string_list(1);
+    string_list_append(templist, argv[2]);
+
+    spamserv_add_trusted_account(hi->handle, templist, user->handle_info->handle, now);
+
+    if (global == 1)
+        ss_reply("SSMSG_ADDED_TRUSTED", hi->handle);
+    else
+        ss_reply("SSMSG_ADDED_TRUSTED_CHANNEL", hi->handle, argv[2]);
+
+    return 1;
+}
+
+static SPAMSERV_FUNC(cmd_deltrust)
+{
+    unsigned int i;
+    int rem = 0;
+    struct trusted_account *ta;
+    struct userData *uData;
+    struct chanData *cData;
+    struct chanInfo *cInfo;
+    struct handle_info *hi;
+
+    if (!(channel = GetChannel(argv[2]))) {
+        ss_reply("SSMSG_NOT_REGISTERED", argv[2]);
+        return 0;
+    }
+
+    cInfo = get_chanInfo(channel->name);
+    cData = channel->channel_info;
+    uData = GetChannelUser(cData, user->handle_info);
+
+    if (!cInfo || !channel->channel_info) {
+        ss_reply("SSMSG_NOT_REGISTERED", channel->name);
+        return 0;
+    }
+
+    if (CHECK_SUSPENDED(cInfo)) {
+         ss_reply("SSMSG_SUSPENDED", channel->name);
+         return 0;
+    }
+
+    if (!uData || (uData->access < UL_MANAGER)) {
+        ss_reply("SSMSG_NO_ACCESS");
+        return 0;
+    }
+
+    if (!(hi = modcmd_get_handle_info(user, argv[1]))) {
+        return 0;
+    }
+
+    ta = dict_find(spamserv_trusted_accounts, hi->handle, NULL);
+
+    if (!ta) {
+        ss_reply("SSMSG_NOT_TRUSTED", argv[2]);
+        return 0;
+    }
+
+    if (argc > 1) {
+        if (ta->channel->used) {
+            for (i=0; i < ta->channel->used; i++) {
+                if (!strcmp(ta->channel->list[i], argv[2])) {
+                    string_list_delete(ta->channel, i);
+                    rem = 1;
+                }
+            }
+        }
+
+        if (rem == 1)
+            ss_reply("SSMSG_REMOVED_TRUSTED_CHANNEL", hi->handle, argv[2]);
+        else {
+            ss_reply("SSMSG_NOT_TRUSTED", hi->handle, argv[2]);
+            return 0;
+        }
+    } else {
+        dict_remove(spamserv_trusted_accounts, hi->handle);
+        ss_reply("SSMSG_REMOVED_TRUSTED", hi->handle);
+    }
+
+    return 1;
+}
+
+static SPAMSERV_FUNC(cmd_odeltrust)
 {
-       BINARY_OPTION("ScanVoiced    ", CHAN_SCAN_VOICED);
+    unsigned int i;
+    int rem = 0, global = 0;
+    struct trusted_account *ta;
+    struct chanInfo *cInfo;
+    struct chanData *cData;
+    struct handle_info *hi;
+
+    if (!strcmp(argv[2], "global"))
+        global = 1;
+
+    if (!(channel = GetChannel(argv[2])) && (global == 0)) {
+        ss_reply("SSMSG_NOT_REGISTERED", channel ? channel->name : (global ? "global" : ""));
+        return 0;
+    }
+
+    if (channel) {
+        cInfo = get_chanInfo(channel->name);
+        cData = channel->channel_info;
+
+        if (!cInfo || !channel->channel_info) {
+            ss_reply("SSMSG_NOT_REGISTERED", channel->name);
+            return 0;
+        }
+    }
+
+    if (!(hi = modcmd_get_handle_info(user, argv[1]))) {
+        return 0;
+    }
+
+    ta = dict_find(spamserv_trusted_accounts, hi->handle, NULL);
+
+    if (!ta) {
+        ss_reply("SSMSG_NOT_TRUSTED", argv[2]);
+        return 0;
+    }
+
+    if (argc > 1) {
+        if (ta->channel->used) {
+            for (i=0; i < ta->channel->used; i++) {
+                if (!strcmp(ta->channel->list[i], argv[2])) {
+                    string_list_delete(ta->channel, i);
+                    rem = 1;
+                }
+            }
+        }
+
+        if (rem == 1)
+            ss_reply("SSMSG_REMOVED_TRUSTED_CHANNEL", hi->handle, argv[2]);
+        else {
+            ss_reply("SSMSG_NOT_TRUSTED", argv[2]);
+            return 0;
+        }
+    } else {
+        dict_remove(spamserv_trusted_accounts, hi->handle);
+        ss_reply("SSMSG_REMOVED_TRUSTED", hi->handle);
+    }
+
+    return 1;
+}
+
+static SPAMSERV_FUNC(cmd_listtrust) {
+    dict_iterator_t it;
+    struct trusted_account *ta;
+    char issued[INTERVALLEN];
+    char *chan;
+    unsigned int i;
+
+    if (argc > 0) {
+        if (!strcmp(argv[1], "global")) {
+            if (!IsHelping(user)) {
+                reply("SSMSG_MUST_BE_HELPING");
+                return 0;
+            } else
+                chan = "global";
+        } else {
+            channel = GetChannel(argv[1]);
+            if (channel)
+                chan = strdup(channel->name);
+            else {
+                ss_reply("SSMSG_NOT_REGISTERED", argv[1]);
+                return 0;
+            }
+        }
+    } else {
+        reply("MSG_INVALID_CHANNEL");
+        return 0;
+    }
+
+    reply("SSMSG_TRUSTED_LIST");
+    reply("SSMSG_TRUSTED_LIST_BAR");
+    reply("SSMSG_TRUSTED_LIST_HEADER");
+    reply("SSMSG_TRUSTED_LIST_BAR");
+    for (it = dict_first(spamserv_trusted_accounts); it; it = iter_next(it)) {
+        ta = iter_data(it);
+
+        if (ta->channel->used) {
+            for (i=0; i < ta->channel->used; i++) {
+
+                if (!strcmp(ta->channel->list[i], chan)) {
+                    if (ta->issued)
+                        intervalString(issued, now - ta->issued, user->handle_info);
+
+                    ss_reply("SSMSG_HOST_IS_TRUSTED", iter_key(it),
+                            (ta->issuer ? ta->issuer : "<unknown>"),
+                            (ta->issued ? issued : "some time"));
+
+                } else if (!strcmp(ta->channel->list[i], "global") && (!strcmp(chan, "global"))) {
+                    if (ta->issued)
+                        intervalString(issued, now - ta->issued, user->handle_info);
+
+                    ss_reply("SSMSG_HOST_IS_TRUSTED", iter_key(it),
+                            (ta->issuer ? ta->issuer : "<unknown>"),
+                             (ta->issued ? issued : 0));
+                }
+            }
+        }
+    }
+    ss_reply("SSMSG_TRUSTED_LIST_END");
+    return 1;
 }
 
 static void 
@@ -1707,6 +2475,27 @@ is_in_badword_list(struct chanInfo *cInfo, char *message)
        return 0;
 }
 
+static int
+check_caps(struct chanInfo *cInfo, char *message)
+{
+        int c;
+
+       if ( (c = strlen(message)) >= cInfo->capsmin) {
+               int i = 0;
+               char *s = strdup(message);
+
+               do {
+                       if (isupper(*s))
+                               i++;
+               } while (*s++);
+
+               if (i >= cInfo->capsmin && i * 100 / c >= cInfo->capspercent)
+                       return 1;
+       }
+
+       return 0;
+}
+
 static int
 check_badwords(struct chanInfo *cInfo, char *message)
 {
@@ -1803,43 +2592,83 @@ spamserv_punish(struct chanNode *channel, struct userNode *user, time_t expires,
 void
 spamserv_channel_message(struct chanNode *channel, struct userNode *user, char *text)
 {
+       struct chanData *cData;
        struct chanInfo *cInfo;
        struct userInfo *uInfo;
+       struct userData *uData;
        struct spamNode *sNode;
        struct floodNode *fNode;
+        struct trusted_account *ta;
        unsigned int violation = 0;
        char reason[MAXLEN];
 
        /* make sure: spamserv is not disabled; x3 is running; spamserv is in the chan; chan is regged, user does exist */
-       if(!spamserv || quit_services || !GetUserMode(channel, spamserv) || !(cInfo = get_chanInfo(channel->name)) || !(uInfo = get_userInfo(user->nick)))
+       if(!spamserv || quit_services || !GetUserMode(channel, spamserv) || IsOper(user) || !(cInfo = get_chanInfo(channel->name)) || !(uInfo = get_userInfo(user->nick)))
                return;
 
-       
-       if(!CHECK_CHANOPS(cInfo))
-       {
-               struct modeNode *mn = GetUserMode(channel, user);
-               if (mn && (mn->modes & MODE_CHANOP))
-                       return;
-       }
+       cData = channel->channel_info;
+       uData = GetChannelUser(cData, user->handle_info);
+
+        if (user->handle_info) {
+          ta = dict_find(spamserv_trusted_accounts, user->handle_info->handle, NULL);
+          if (ta) {
+             unsigned int i = 0;
+             for (i=0; i < ta->channel->used; i++) {
+                if (!strcmp(ta->channel->list[i], channel->name))
+                    return;
+
+                if (!strcmp(ta->channel->list[i], "global"))
+                    return;
+              }
+           }
+        }
 
-       if(!CHECK_HALFOPS(cInfo))
-       {
-               struct modeNode *mn = GetUserMode(channel, user);
-               if (mn && (mn->modes & MODE_HALFOP))
-                       return;
-       }
-       
-       if(!CHECK_VOICED(cInfo))
+
+        if(uData && (uData->access >= cInfo->exceptlevel))
+            return;
+
+       if(CHECK_CAPSSCAN(cInfo) && check_caps(cInfo, text))
        {
-               struct modeNode *mn = GetUserMode(channel, user);
-               if (mn && ((mn->modes & MODE_VOICE) && !(mn->modes & MODE_CHANOP) && !(mn->modes & MODE_HALFOP)))
-                       return;
-       }
+                if(uData && (uData->access >= cInfo->exceptcapslevel))
+                    return;
+
+               if(CHECK_CAPS_WARNED(uInfo))
+               {
+                       switch(cInfo->info[ci_CapsReaction])
+                       {
+                               case 'k': uInfo->flags |= USER_KICK; break;
+                               case 'b': uInfo->flags |= USER_KICKBAN; break;
+                               case 's': uInfo->flags |= USER_SHORT_TBAN; break;
+                               case 'l': uInfo->flags |= USER_LONG_TBAN; break;
+                               case 'd': uInfo->flags |= CHECK_KILLED(uInfo) ? USER_GLINE : USER_KILL; break;
+                       }
+
+                       uInfo->warnlevel += CAPS_WARNLEVEL;
+                       violation = 5;
+               }
+               else
+               {               
+                       uInfo->flags |= USER_CAPS_WARNED;
+                       uInfo->lastcaps = now;
+                       uInfo->warnlevel += CAPS_WARNLEVEL;
+
+                       if(uInfo->warnlevel < MAX_WARNLEVEL) {
+                               if (spamserv_conf.network_rules)
+                                       spamserv_notice(user, "SSMSG_WARNING_RULES_T", SSMSG_CAPS, spamserv_conf.network_rules);
+                               else
+                                       spamserv_notice(user, "SSMSG_WARNING_T", SSMSG_CAPS, spamserv_conf.network_rules);
+                       }
+               }
+
+        }
 
        to_lower(text);
 
        if(CHECK_SPAM(cInfo))
        {
+                if(uData && (uData->access >= cInfo->exceptspamlevel))
+                    return;
+
                if(!(sNode = uInfo->spam))
                {
                        spamserv_create_spamNode(channel, uInfo, text);
@@ -1910,6 +2739,9 @@ spamserv_channel_message(struct chanNode *channel, struct userNode *user, char *
 
        if(CHECK_FLOOD(cInfo))
        {
+                if(uData && (uData->access >= cInfo->exceptfloodlevel))
+                    return;
+
                if(!(fNode = uInfo->flood))
                {
                        spamserv_create_floodNode(channel, user, &uInfo->flood);
@@ -1920,31 +2752,25 @@ spamserv_channel_message(struct chanNode *channel, struct userNode *user, char *
                                if(fNode->channel == channel)
                                        break;
                                
-                       if(!fNode)
-                       {
+                       if(!fNode) {
                                spamserv_create_floodNode(channel, user, &uInfo->flood);
-                       }
-                       else
-                       {
-                               if(((now - fNode->time) < FLOOD_EXPIRE))
-                               {
+                       } else {
+                               if(((now - fNode->time) < FLOOD_EXPIRE)) {
                                        fNode->count++;
                                        
-                                       if(fNode->count == FLOOD_MAX_LINES - 1)
-                                       {
-                                               uInfo->warnlevel += FLOOD_WARNLEVEL;
-
-                                               if(uInfo->warnlevel < MAX_WARNLEVEL) {
-                                                       if (spamserv_conf.network_rules)
-                                                               spamserv_notice(user, "SSMSG_WARNING_RULES_T", SSMSG_FLOOD, spamserv_conf.network_rules);
-                                                       else
-                                                               spamserv_notice(user, "SSMSG_WARNING_T", SSMSG_FLOOD, spamserv_conf.network_rules);
-                                               }
+                                       if(fNode->count == FLOOD_MAX_LINES - 1) {
+                                           uInfo->warnlevel += FLOOD_WARNLEVEL;
+
+                                           if(uInfo->warnlevel < MAX_WARNLEVEL) {
+                                               if (spamserv_conf.network_rules)
+                                                   spamserv_notice(user, "SSMSG_WARNING_RULES_T", SSMSG_FLOOD, spamserv_conf.network_rules);
+                                               else
+                                                   spamserv_notice(user, "SSMSG_WARNING_T", SSMSG_FLOOD, spamserv_conf.network_rules);
+                                           }
+                                           fNode->time = now;
                                        }
-                                       else if(fNode->count > FLOOD_MAX_LINES)
-                                       {
-                                               switch(cInfo->info[ci_WarnReaction])
-                                               {
+                                       else if(fNode->count > FLOOD_MAX_LINES) {
+                                               switch(cInfo->info[ci_WarnReaction]) {
                                                        case 'k': uInfo->flags |= USER_KICK; break;
                                                        case 'b': uInfo->flags |= USER_KICKBAN; break;
                                                        case 's': uInfo->flags |= USER_SHORT_TBAN; break;
@@ -1956,15 +2782,18 @@ spamserv_channel_message(struct chanNode *channel, struct userNode *user, char *
                                                uInfo->warnlevel += FLOOD_WARNLEVEL;
                                                violation = 2;                                          
                                        }
-                               }
-
-                               fNode->time = now;
+                               } else {
+                                   fNode->time = now;
+                                }
                        }
                }
        }
 
        if(CHECK_BADWORDSCAN(cInfo) && check_badwords(cInfo, text))
        {
+                if(uData && (uData->access >= cInfo->exceptbadwordlevel))
+                    return;
+
                if(CHECK_BAD_WARNED(uInfo))
                {
                        switch(cInfo->info[ci_BadReaction])
@@ -1996,6 +2825,9 @@ spamserv_channel_message(struct chanNode *channel, struct userNode *user, char *
 
        if(CHECK_ADV(cInfo) && check_advertising(cInfo, text))
        {
+                if(uData && (uData->access >= cInfo->exceptspamlevel))
+                    return;
+
                if(CHECK_ADV_WARNED(uInfo))
                {
                        switch(cInfo->info[ci_AdvReaction])
@@ -2042,7 +2874,7 @@ spamserv_channel_message(struct chanNode *channel, struct userNode *user, char *
                else
                        uInfo->flags |= USER_KILL;
 
-               violation = 5;
+               violation = 6;
        }
 
        if(!violation)
@@ -2055,6 +2887,7 @@ spamserv_channel_message(struct chanNode *channel, struct userNode *user, char *
                case 2: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_FLOOD, spamserv_conf.network_rules); break;
                case 3: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_ADV, spamserv_conf.network_rules); break;
                case 4: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_BAD, spamserv_conf.network_rules); break;
+               case 5: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_CAPS, spamserv_conf.network_rules); break;
                default: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES_2 : SSMSG_WARNING_2, spamserv_conf.network_rules); break;
        }
 
@@ -2089,6 +2922,28 @@ spamserv_channel_message(struct chanNode *channel, struct userNode *user, char *
        }
 }
 
+static int
+trusted_account_read(const char *account, void *data, UNUSED_ARG(void *extra))
+{
+    struct record_data *rd = data;
+    const char *str, *issuer;
+    struct string_list *strlist;
+    time_t issued;
+
+    if (rd->type == RECDB_OBJECT) {
+        dict_t obj = GET_RECORD_OBJECT(rd);
+        /* new style structure */
+        strlist = database_get_data(obj, KEY_CHANNELS, RECDB_STRING_LIST);
+        issuer = database_get_data(obj, KEY_ISSUER, RECDB_QSTRING);
+        str = database_get_data(obj, KEY_ISSUED, RECDB_QSTRING);
+        issued = str ? ParseInterval(str) : 0;
+    } else
+        return 0;
+
+    spamserv_add_trusted_account(account, strlist, issuer, issued);
+    return 0;
+}
+
 static int
 spamserv_saxdb_read(struct dict *database)
 {
@@ -2098,8 +2953,15 @@ spamserv_saxdb_read(struct dict *database)
        struct chanInfo *cInfo;
        struct string_list *strlist, *strlist2;
        unsigned int flags;
+        unsigned int exceptlevel, exceptadvlevel, exceptbadwordlevel;
+        unsigned int exceptfloodlevel, exceptspamlevel, exceptcapslevel;
+        unsigned int capsmin, capspercent;
        char *str, *info;       
        time_t expiry;    
+       dict_t object;
+
+       if ((object = database_get_data(database, KEY_TRUSTED_HOSTS, RECDB_OBJECT)))
+               dict_foreach(object, trusted_account_read, spamserv_trusted_accounts);
 
        for(it = dict_first(database); it; it = iter_next(it))
        {
@@ -2112,6 +2974,8 @@ spamserv_saxdb_read(struct dict *database)
                }
 
                channel = GetChannel(iter_key(it));
+                if (!strcmp("trusted", iter_key(it)))
+                    continue;
 
                strlist = database_get_data(hir->d.object, KEY_EXCEPTIONS, RECDB_STRING_LIST);
                strlist2 = database_get_data(hir->d.object, KEY_BADWORDS, RECDB_STRING_LIST);
@@ -2124,6 +2988,30 @@ spamserv_saxdb_read(struct dict *database)
                str = database_get_data(hir->d.object, KEY_EXPIRY, RECDB_QSTRING);
                expiry = str ? strtoul(str, NULL, 0) : 0;
 
+               str = database_get_data(hir->d.object, KEY_CAPSMIN, RECDB_QSTRING);
+               capsmin = str ? strtoul(str, NULL, 0) : 10;
+
+               str = database_get_data(hir->d.object, KEY_CAPSPERCENT, RECDB_QSTRING);
+               capspercent = str ? strtoul(str, NULL, 0) : 25;
+
+               str = database_get_data(hir->d.object, KEY_EXCEPTLEVEL, RECDB_QSTRING);
+               exceptlevel = str ? strtoul(str, NULL, 0) : UL_MANAGER;
+
+               str = database_get_data(hir->d.object, KEY_EXCEPTADVLEVEL, RECDB_QSTRING);
+               exceptadvlevel = str ? strtoul(str, NULL, 0) : UL_OP;
+
+               str = database_get_data(hir->d.object, KEY_EXCEPTBADWORDLEVEL, RECDB_QSTRING);
+               exceptbadwordlevel = str ? strtoul(str, NULL, 0) : UL_OP;
+
+               str = database_get_data(hir->d.object, KEY_EXCEPTCAPSLEVEL, RECDB_QSTRING);
+               exceptcapslevel = str ? strtoul(str, NULL, 0) : UL_OP;
+
+               str = database_get_data(hir->d.object, KEY_EXCEPTFLOODLEVEL, RECDB_QSTRING);
+               exceptfloodlevel = str ? strtoul(str, NULL, 0) : UL_OP;
+
+               str = database_get_data(hir->d.object, KEY_EXCEPTSPAMLEVEL, RECDB_QSTRING);
+               exceptspamlevel = str ? strtoul(str, NULL, 0) : UL_OP;
+
                if(channel && info)
                {
                        if((cInfo = spamserv_register_channel(channel, strlist, strlist2, flags, info)))
@@ -2138,7 +3026,16 @@ spamserv_saxdb_read(struct dict *database)
                                else if(!CHECK_SUSPENDED(cInfo))
                                        spamserv_join_channel(cInfo->channel);
                                else
-                                       cInfo->suspend_expiry = expiry;                         
+                                       cInfo->suspend_expiry = expiry;
+
+                               cInfo->capsmin      = capsmin;
+                               cInfo->capspercent  = capspercent;
+                               cInfo->exceptlevel        = exceptlevel;
+                               cInfo->exceptadvlevel     = exceptadvlevel;
+                               cInfo->exceptbadwordlevel = exceptbadwordlevel;
+                               cInfo->exceptcapslevel    = exceptcapslevel;
+                               cInfo->exceptfloodlevel   = exceptfloodlevel;
+                               cInfo->exceptspamlevel    = exceptspamlevel;
                        }
                }
                else
@@ -2153,6 +3050,19 @@ spamserv_saxdb_write(struct saxdb_context *ctx)
 {
        dict_iterator_t it;
 
+        if (dict_size(spamserv_trusted_accounts)) {
+            saxdb_start_record(ctx, KEY_TRUSTED_ACCOUNTS, 1);
+            for (it = dict_first(spamserv_trusted_accounts); it; it = iter_next(it)) {
+                struct trusted_account *ta = iter_data(it);
+                saxdb_start_record(ctx, iter_key(it), 0);
+                if (ta->channel) saxdb_write_string_list(ctx, KEY_CHANNELS, ta->channel);
+                if (ta->issued) saxdb_write_int(ctx, KEY_ISSUED, ta->issued);
+                if (ta->issuer) saxdb_write_string(ctx, KEY_ISSUER, ta->issuer);
+                saxdb_end_record(ctx);
+            }
+            saxdb_end_record(ctx);
+        }
+
        for(it = dict_first(registered_channels_dict); it; it = iter_next(it))
        {
                struct chanInfo *cInfo = iter_data(it);
@@ -2166,7 +3076,31 @@ spamserv_saxdb_write(struct saxdb_context *ctx)
                        saxdb_write_string_list(ctx, KEY_BADWORDS, cInfo->badwords);
 
                if(cInfo->flags)
-                       saxdb_write_int(ctx, KEY_FLAGS, cInfo->flags);  
+                       saxdb_write_int(ctx, KEY_FLAGS, cInfo->flags);
+
+               if(cInfo->capsmin)
+                       saxdb_write_int(ctx, KEY_CAPSMIN, cInfo->capsmin);
+
+               if(cInfo->capspercent)
+                       saxdb_write_int(ctx, KEY_CAPSPERCENT, cInfo->capspercent);
+
+               if(cInfo->exceptlevel)
+                       saxdb_write_int(ctx, KEY_EXCEPTLEVEL, cInfo->exceptlevel);
+
+               if(cInfo->exceptadvlevel)
+                       saxdb_write_int(ctx, KEY_EXCEPTADVLEVEL, cInfo->exceptadvlevel);
+
+               if(cInfo->exceptbadwordlevel)
+                       saxdb_write_int(ctx, KEY_EXCEPTBADWORDLEVEL, cInfo->exceptbadwordlevel);
+
+               if(cInfo->exceptcapslevel)
+                       saxdb_write_int(ctx, KEY_EXCEPTCAPSLEVEL, cInfo->exceptcapslevel);
+
+               if(cInfo->exceptfloodlevel)
+                       saxdb_write_int(ctx, KEY_EXCEPTFLOODLEVEL, cInfo->exceptfloodlevel);
+
+               if(cInfo->exceptspamlevel)
+                       saxdb_write_int(ctx, KEY_EXCEPTSPAMLEVEL, cInfo->exceptspamlevel);
 
                saxdb_write_string(ctx, KEY_INFO, cInfo->info);                 
 
@@ -2252,7 +3186,7 @@ spamserv_conf_read(void)
 }
 
 static void
-spamserv_db_cleanup(void)
+spamserv_db_cleanup(UNUSED_ARG(void* extra))
 {
        dict_iterator_t it;
 
@@ -2261,14 +3195,17 @@ spamserv_db_cleanup(void)
                spamserv_unregister_channel(iter_data(it));
        }
 
-       while((it = dict_first(killed_users_dict)))
+/* now handled automatically
+ *     while((it = dict_first(killed_users_dict)))
        {
                free(iter_data(it));
        }
+*/
        
        dict_delete(registered_channels_dict);
        dict_delete(connected_users_dict);
        dict_delete(killed_users_dict);
+       dict_delete(spamserv_trusted_accounts);
 }
 
 void
@@ -2281,33 +3218,50 @@ init_spamserv(const char *nick)
                return;
 
         const char *modes = conf_get_data("services/spamserv/modes", RECDB_QSTRING);
-       spamserv = AddService(nick, modes ? modes : NULL, "Anti Spam Services", NULL);
+        spamserv = AddLocalUser(nick, nick, NULL, "Anti Spam Services", modes);
        spamserv_service = service_register(spamserv);
 
        conf_register_reload(spamserv_conf_read);
 
        SS_LOG = log_register_type("SpamServ", "file:spamserv.log");    
 
+        /* auto-free the keys for these dicts,
+         * and auto-free the keys AND data for killed_users_dict.
+         * other data need free'd manually. */
        registered_channels_dict = dict_new();
+        dict_set_free_keys(registered_channels_dict, free);
        connected_users_dict = dict_new();
+        dict_set_free_keys(connected_users_dict, free);
        killed_users_dict = dict_new();
+        dict_set_free_keys(killed_users_dict, free);
+        dict_set_free_data(killed_users_dict, free);
+        spamserv_trusted_accounts = dict_new();
+        dict_set_free_keys(spamserv_trusted_accounts, free);
+        dict_set_free_data(spamserv_trusted_accounts, free);
 
        saxdb_register("SpamServ", spamserv_saxdb_read, spamserv_saxdb_write);
 
-       reg_new_user_func(spamserv_new_user_func);
-       reg_del_user_func(spamserv_del_user_func);
-       reg_nick_change_func(spamserv_nick_change_func);
-       reg_join_func(spamserv_user_join);
-       reg_part_func(spamserv_user_part);
+       reg_new_user_func(spamserv_new_user_func, NULL);
+       reg_del_user_func(spamserv_del_user_func, NULL);
+       reg_nick_change_func(spamserv_nick_change_func, NULL);
+       reg_join_func(spamserv_user_join, NULL);
+       reg_part_func(spamserv_user_part, NULL);
 
        timeq_add(now + FLOOD_TIMEQ_FREQ, timeq_flood, NULL);
        timeq_add(now + JOINFLOOD_TIMEQ_FREQ, timeq_joinflood, NULL);
        timeq_add(now + ADV_TIMEQ_FREQ, timeq_adv, NULL);
        timeq_add(now + BAD_TIMEQ_FREQ, timeq_bad, NULL);
+       timeq_add(now + CAPS_TIMEQ_FREQ, timeq_caps, NULL);
        timeq_add(now + WARNLEVEL_TIMEQ_FREQ, timeq_warnlevel, NULL);
        timeq_add(now + KILL_TIMEQ_FREQ, timeq_kill, NULL);
 
        spamserv_module = module_register("SpamServ", SS_LOG, "spamserv.help", NULL);
+
+       modcmd_register(spamserv_module, "ADDTRUST", cmd_addtrust, 3, MODCMD_REQUIRE_AUTHED, "flags", "+acceptchan", NULL);
+       modcmd_register(spamserv_module, "DELTRUST", cmd_deltrust, 3, MODCMD_REQUIRE_AUTHED, "flags", "+acceptchan", NULL);
+       modcmd_register(spamserv_module, "OADDTRUST", cmd_oaddtrust, 3, MODCMD_REQUIRE_AUTHED, "flags", "+acceptchan,+helping", NULL);
+       modcmd_register(spamserv_module, "ODELTRUST", cmd_odeltrust, 3, MODCMD_REQUIRE_AUTHED, "flags", "+acceptchan,+helping", NULL);
+       modcmd_register(spamserv_module, "LISTTRUST", cmd_listtrust, 2, MODCMD_REQUIRE_AUTHED, NULL);
        modcmd_register(spamserv_module, "REGISTER", cmd_register, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, "flags", "+acceptchan,+helping", NULL);
        modcmd_register(spamserv_module, "UNREGISTER", cmd_unregister, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, "flags", "+loghostmask", NULL);
        modcmd_register(spamserv_module, "ADDEXCEPTION", cmd_addexception, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
@@ -2316,18 +3270,25 @@ init_spamserv(const char *nick)
        modcmd_register(spamserv_module, "DELBADWORD", cmd_delbadword, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
        modcmd_register(spamserv_module, "STATUS", cmd_status, 1, 0, NULL);
        modcmd_register(spamserv_module, "SET", cmd_set, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+       modcmd_register(spamserv_module, "SET EXCEPTLEVEL", opt_exceptlevel, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+       modcmd_register(spamserv_module, "SET EXCEPTADVLEVEL", opt_exceptadvlevel, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+       modcmd_register(spamserv_module, "SET EXCEPTBADWORDLEVEL", opt_exceptbadwordlevel, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+       modcmd_register(spamserv_module, "SET EXCEPTCAPSLEVEL", opt_exceptcapslevel, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+       modcmd_register(spamserv_module, "SET EXCEPTFLOODLEVEL", opt_exceptfloodlevel, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+       modcmd_register(spamserv_module, "SET EXCEPTSPAMLEVEL", opt_exceptspamlevel, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
        modcmd_register(spamserv_module, "SET SPAMLIMIT", opt_spamlimit, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
        modcmd_register(spamserv_module, "SET BADREACTION", opt_badreaction, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+       modcmd_register(spamserv_module, "SET CAPSREACTION", opt_capsreaction, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
        modcmd_register(spamserv_module, "SET ADVREACTION", opt_advreaction, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
        modcmd_register(spamserv_module, "SET WARNREACTION", opt_warnreaction, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
        modcmd_register(spamserv_module, "SET ADVSCAN", opt_advscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+       modcmd_register(spamserv_module, "SET CAPSSCAN", opt_capsscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
        modcmd_register(spamserv_module, "SET BADWORDSCAN", opt_badwordscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
        modcmd_register(spamserv_module, "SET SPAMSCAN", opt_spamscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
        modcmd_register(spamserv_module, "SET CHANFLOODSCAN", opt_chanfloodscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
        modcmd_register(spamserv_module, "SET JOINFLOODSCAN", opt_joinflood, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
-       modcmd_register(spamserv_module, "SET SCANCHANOPS", opt_scanops, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
-       modcmd_register(spamserv_module, "SET SCANHALFOPS", opt_scanhalfops, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
-       modcmd_register(spamserv_module, "SET SCANVOICED", opt_scanvoiced, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+       modcmd_register(spamserv_module, "SET CAPSMIN", opt_capsmin, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+       modcmd_register(spamserv_module, "SET CAPSPERCENT", opt_capspercent, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
 
        spamserv_service->trigger = spamserv_conf.trigger;
 
@@ -2339,7 +3300,7 @@ init_spamserv(const char *nick)
             }
         }
 
-       reg_exit_func(spamserv_db_cleanup);
+       reg_exit_func(spamserv_db_cleanup, NULL);
        message_register_table(msgtab);
        crc32_init();
 }