Merge branch 'master' into SpamServ
authorNurPech <nurpech@nurpech.de>
Mon, 20 May 2013 20:44:29 +0000 (22:44 +0200)
committerNurPech <nurpech@nurpech.de>
Mon, 20 May 2013 20:44:29 +0000 (22:44 +0200)
Conflicts:
src/spamserv.c

src/spamserv.c
src/spamserv.h
src/spamserv.help

index 7d6d9b3aeddff0e455ae0e0018dd24d2eb8e5ef2..588d076911a397c2cf20f7857d6dcbba0d871b0d 100644 (file)
+<<<<<<< HEAD
+/* spamserv.c - anti spam service\r
+ * Copyright 2004 feigling\r
+ *\r
+ * This program is free software; you can redistribute it and/or modify\r
+ * it under the terms of the GNU General Public License as published by\r
+ * the Free Software Foundation; either version 2 of the License, or\r
+ * (at your option) any later version.  Important limitations are\r
+ * listed in the COPYING file that accompanies this software.\r
+ *\r
+ * This program is distributed in the hope that it will be useful,\r
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+ * GNU General Public License for more details.\r
+ *\r
+ * You should have received a copy of the GNU General Public License\r
+ * along with this program; if not, email srvx-maintainers@srvx.net.\r
+ *\r
+ * $Id: spamserv.c,v 1.08 2004/06/27 22:21:00 feigling Exp $\r
+ */\r
+\r
+#include "conf.h"\r
+#include "spamserv.h"\r
+#include "chanserv.h"\r
+#include "global.h"\r
+#include "modcmd.h"\r
+#include "saxdb.h"\r
+#include "timeq.h"\r
+#include "gline.h"\r
+\r
+#define SPAMSERV_CONF_NAME           "services/spamserv"\r
+\r
+#define KEY_EXCEPTIONS               "exceptions"\r
+#define KEY_FLAGS                    "flags"\r
+#define KEY_INFO                     "info"\r
+#define KEY_EXCEPTLEVEL              "exceptlevel"\r
+#define KEY_EXPIRY                   "expiry"\r
+#define KEY_LASTBADWORDID                       "last_badword_id"\r
+#define KEY_BADWORDS                 "badwords"\r
+\r
+#define KEY_BADWORD_MASK                        "mask"\r
+#define KEY_BADWORD_TRIGGERED           "count"\r
+#define KEY_BADWORD_ACTION                      "action"\r
+#define KEY_BADWORDID                           "badwordid"\r
+\r
+#define KEY_DEBUG_CHANNEL            "debug_channel"\r
+#define KEY_GLOBAL_EXCEPTIONS        "global_exceptions"\r
+#define KEY_NETWORK_RULES            "network_rules"\r
+#define KEY_TRIGGER                  "trigger"\r
+#define KEY_SHORT_BAN_DURATION       "short_ban_duration"\r
+#define KEY_LONG_BAN_DURATION        "long_ban_duration"\r
+#define KEY_GLINE_DURATION           "gline_duration"\r
+#define KEY_EXCEPTION_MAX            "exception_max"\r
+#define KEY_EXCEPTION_MIN_LEN        "exception_min_len"\r
+#define KEY_EXCEPTION_MAX_LEN        "exception_max_len"\r
+#define KEY_ADV_CHAN_MUST_EXIST      "adv_chan_must_exist"\r
+#define KEY_STRIP_MIRC_CODES         "strip_mirc_codes"\r
+#define KEY_ALLOW_MOVE_MERGE         "allow_move_merge"\r
+\r
+#define SPAMSERV_FUNC(NAME)    MODCMD_FUNC(NAME)\r
+#define SPAMSERV_SYNTAX()      svccmd_send_help(user, spamserv, cmd)\r
+#define SPAMSERV_MIN_PARMS(N) do { \\r
+(void)argv; \\r
+  if(argc < N) { \\r
+    ss_reply(MSG_MISSING_PARAMS, argv[0]); \\r
+    SPAMSERV_SYNTAX(); \\r
+    return 0; } } while(0)\r
+\r
+struct userNode                        *spamserv;\r
+static struct module   *spamserv_module;\r
+static struct service  *spamserv_service;\r
+static struct log_type *SS_LOG;\r
+static unsigned long   crc_table[256];\r
+\r
+dict_t registered_channels_dict;\r
+dict_t connected_users_dict;\r
+dict_t killed_users_dict;\r
+\r
+#define spamserv_notice(target, format...) send_message(target , spamserv , ## format)\r
+#define spamserv_debug(format...) do { if(spamserv_conf.debug_channel) send_channel_message(spamserv_conf.debug_channel , spamserv , ## format); } while(0)\r
+#define ss_reply(format...)    send_message(user , spamserv , ## format)\r
+\r
+#define SET_SUBCMDS_SIZE 10\r
+\r
+const char *set_subcommands[SET_SUBCMDS_SIZE] = {"SPAMLIMIT", "ADVREACTION", "WARNREACTION", "ADVSCAN", "SPAMSCAN", "FLOODSCAN", "JOINFLOODSCAN", "EXCEPTLEVEL","SCANCHANOPS", "SCANVOICED"};\r
+\r
+static void spamserv_clear_spamNodes(struct chanNode *channel);\r
+static void spamserv_punish(struct chanNode *channel, struct userNode *user, time_t expires, char *reason, int ban);\r
+static unsigned long crc32(const char *text);\r
+\r
+#define BINARY_OPTION(arguments...)    return binary_option(arguments, user, channel, argc, argv);\r
+#define MULTIPLE_OPTION(arguments...)  return multiple_option(arguments, values, ArrayLength(values), user, channel, argc, argv);\r
+\r
+static const struct message_entry msgtab[] = {\r
+    { "SSMSG_CHANNEL_OPTIONS",         "Channel Options:" },\r
+    { "SSMSG_STRING_VALUE",            "$b%s$b%s" },\r
+    { "SSMSG_NUMERIC_VALUE",           "$b%s$b%d - %s" },\r
+    { "SSMSG_EASYNUMERIC_VALUE",       "$b%s$b%d" },\r
+    { "SSMSG_INVALID_NUM_SET",         "$b'%d'$b is an invalid %s setting." },\r
+    { "SSMSG_INVALID_OPTION",          "$b%s$b is not a valid %s option." },\r
+    { "SSMSG_INVALID_BINARY",          "$b%s$b is an invalid binary value." },\r
+\r
+    { "SSMSG_NOT_REGISTERED",          "$b%s$b has not been registered with $b$X$b." },\r
+    { "SSMSG_NOT_REGISTERED_CS",       "$b%s$b has not been registered with $b$C$b." },\r
+    { "SSMSG_ALREADY_REGISTERED",      "$b%s$b is already registered." },\r
+    { "SSMSG_DEBUG_CHAN",              "You may not register the debug channel." },\r
+    { "SSMSG_SUSPENDED_CS",            "$b$C$b access to $b%s$b has been temporarily suspended, thus you can't %s it." },\r
+    { "SSMSG_SUSPENDED",               "$b$X$b access to $b%s$b has been temporarily suspended." },\r
+    { "SSMSG_NO_REGISTER",             "Due to an error it was not possible to register $b%s$b." },\r
+    { "SSMSG_REG_SUCCESS",             "Channel $b%s$b registered." },\r
+    { "SSMSG_UNREG_SUCCESS",           "$b%s$b has been unregistered." },\r
+    { "SSMSG_NO_ACCESS",               "You lack sufficient access to use this command." },\r
+    { "SSMSG_MUST_BE_OPER",            "You must be an irc operator to set this option." },\r
+    { "SSMSG_CONFIRM_UNREG",           "To confirm this unregistration, you must append 'CONFIRM' to the end of your command. For example, 'unregister CONFIRM'." },\r
+\r
+    { "SSMSG_NO_EXCEPTIONS",           "No words found in the exception list." },\r
+    { "SSMSG_NO_SUCH_EXCEPTION",       "Word $b%s$b not found in the exception list." },\r
+    { "SSMSG_EXCEPTION_LIST",          "The following words are in the exception list:" },\r
+    { "SSMSG_EXCEPTION_ADDED",         "Word $b%s$b added to the exception list." },\r
+    { "SSMSG_EXCEPTION_DELETED",       "Word $b%s$b deleted from the exception list." },\r
+    { "SSMSG_EXCEPTION_IN_LIST",       "The word $b%s$b is already in the exception list." },\r
+    { "SSMSG_EXCEPTION_MAX",           "The exception list has reached the maximum exceptions (max %lu). Delete a word to add another one." },\r
+    { "SSMSG_EXCEPTION_TOO_SHORT",     "The word must be at least %lu characters long." },\r
+    { "SSMSG_EXCEPTION_TOO_LONG",      "The word may not be longer than %lu characters." },\r
+\r
+    { "SSMSG_STATUS",                  "$bStatus:$b" },\r
+    { "SSMSG_STATUS_USERS",            "Total Users Online:  %u" },\r
+    { "SSMSG_STATUS_CHANNELS",         "Registered Channels: %u" },\r
+    { "SSMSG_STATUS_MEMORY",           "$bMemory Information:$b" },\r
+    { "SSMSG_STATUS_CHANNEL_LIST",     "$bRegistered Channels:$b" },\r
+    { "SSMSG_STATUS_NO_CHANNEL",       "No channels registered." },\r
+\r
+    { "SSMSG_BADWORD_ALREADY_ADDED",   "$b%s$b is already added. (ID: %s)" },\r
+    { "SSMSG_BADWORD_ADDED",                      "added '$b%s$b' to the badword list with ID %s." },\r
+    { "SSMSG_BADWORD_SET_DONE",           "Done." },\r
+       { "SSMSG_BADWORD_SET_INVALID",     "Invalid Option for setting %s" },\r
+       { "SSMSG_BADWORD_SET",             "Settings for BadWord entry $b%s$b" },\r
+       { "SSMSG_BADWORD_SET_MASK",        "$bMASK$b:   %s" },\r
+       { "SSMSG_BADWORD_SET_ACTION",      "$bACTION$b: %s" },\r
+       { "SSMSG_BADWORD_NOT_FOUND",       "badword with ID %s does not exist." },\r
+       { "SSMSG_BADWORD_REMOVED",                 "badword ID $b%s$b has been removed (mask: '%s')" },\r
+       { NULL, NULL }\r
+};\r
+\r
+#define SSMSG_DEBUG_KICK              "Kicked user $b%s$b from $b%s$b, reason: %s"\r
+#define SSMSG_DEBUG_BAN               "Banned user $b%s$b from $b%s$b, reason: %s"\r
+#define SSMSG_DEBUG_KILL              "Killed user $b%s$b, last violation in $b%s$b"\r
+#define SSMSG_DEBUG_GLINE             "Glined user $b%s$b, host $b%s$b, last violation in $b%s$b"\r
+#define SSMSG_DEBUG_RECONNECT         "Killed user $b%s$b reconnected to the network"\r
+#define SSMSG_SPAM                    "Spamming"\r
+#define SSMSG_FLOOD                   "Flooding the channel/network"\r
+#define SSMSG_ADV                     "Advertising"\r
+#define SSMSG_JOINFLOOD               "Join flooding the channel"\r
+#define SSMSG_WARNING                 "%s is against the network rules"\r
+#define SSMSG_WARNING_2               "You are violating the network rules"\r
+#define SSMSG_WARNING_RULES           "%s is against the network rules. Read the network rules at %s"\r
+#define SSMSG_WARNING_RULES_2         "You are violating the network rules. Read the network rules at %s"\r
+#define SSMSG_BADWORD_DETECTED           "Your message contained a forbidden word."\r
+\r
+static struct\r
+{\r
+       struct chanNode *debug_channel;\r
+       struct string_list *global_exceptions;\r
+       const char *network_rules;\r
+       unsigned char trigger;\r
+       unsigned long short_ban_duration;\r
+       unsigned long long_ban_duration;\r
+       unsigned long gline_duration;\r
+       unsigned long exception_max;\r
+       unsigned long exception_min_len;\r
+       unsigned long exception_max_len;\r
+       unsigned int adv_chan_must_exist : 1;\r
+       unsigned int strip_mirc_codes : 1;\r
+       unsigned int allow_move_merge : 1;\r
+} spamserv_conf;\r
+\r
+/***********************************************/\r
+/*                   Channel                   */\r
+/***********************************************/\r
+\r
+struct chanInfo*\r
+get_chanInfo(const char *channelname)\r
+{\r
+       return dict_find(registered_channels_dict, channelname, 0);\r
+}\r
+\r
+static void\r
+spamserv_join_channel(struct chanNode *channel)\r
+{\r
+       struct mod_chanmode change;\r
+       mod_chanmode_init(&change);\r
+       change.argc = 1;\r
+       change.args[0].mode = MODE_CHANOP;\r
+       change.args[0].u.member = AddChannelUser(spamserv, channel);\r
+       mod_chanmode_announce(spamserv, channel, &change);\r
+}\r
+\r
+static void\r
+spamserv_part_channel(struct chanNode *channel, char *reason)\r
+{\r
+       /* we only have to clear the spamNodes because every other node expires on it's own */\r
+       spamserv_clear_spamNodes(channel);\r
+       DelChannelUser(spamserv, channel, reason, 0);\r
+}\r
+\r
+static struct chanInfo*\r
+spamserv_register_channel(struct chanNode *channel, struct string_list *exceptions, unsigned int flags, char *info)\r
+{\r
+       struct chanInfo *cInfo = malloc(sizeof(struct chanInfo));\r
+       \r
+       if(!cInfo)\r
+       {\r
+               log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for cInfo; channel: %s", channel->name);\r
+               return NULL;\r
+       }\r
+\r
+       cInfo->channel = channel;\r
+       cInfo->exceptions = exceptions ? string_list_copy(exceptions) : alloc_string_list(1);\r
+       cInfo->flags = flags;\r
+    cInfo->exceptlevel = 400;\r
+       safestrncpy(cInfo->info, info, sizeof(cInfo->info));\r
+       cInfo->suspend_expiry = 0;\r
+       cInfo->badwords = dict_new();\r
+       cInfo->last_badword_id = 0;\r
+       dict_insert(registered_channels_dict, cInfo->channel->name, cInfo);\r
+\r
+       return cInfo;\r
+}\r
+\r
+static void\r
+spamserv_unregister_channel(struct chanInfo *cInfo)\r
+{\r
+       if(!cInfo)\r
+               return;\r
+\r
+       dict_remove(registered_channels_dict, cInfo->channel->name);\r
+       free_string_list(cInfo->exceptions);\r
+       free(cInfo);\r
+}\r
+\r
+void\r
+spamserv_cs_suspend(struct chanNode *channel, time_t expiry, int suspend, char *reason)\r
+{\r
+       struct chanInfo *cInfo = get_chanInfo(channel->name);\r
+\r
+       if(cInfo)\r
+       {\r
+               if(suspend)\r
+               {\r
+                       cInfo->flags |= CHAN_SUSPENDED;\r
+                       cInfo->suspend_expiry = expiry;\r
+                       spamserv_part_channel(channel, reason);\r
+               }\r
+               else\r
+               {\r
+                       if(CHECK_SUSPENDED(cInfo))\r
+                       {\r
+                               cInfo->flags &= ~CHAN_SUSPENDED;\r
+                               cInfo->suspend_expiry = 0;\r
+                       }\r
+               }\r
+       }\r
+}\r
+\r
+int\r
+spamserv_cs_move_merge(struct userNode *user, struct chanNode *channel, struct chanNode *target, int move)\r
+{\r
+       struct chanInfo *cInfo = get_chanInfo(channel->name);\r
+\r
+       if(cInfo)\r
+       {\r
+               char reason[MAXLEN];\r
+\r
+               if(!spamserv_conf.allow_move_merge || get_chanInfo(target->name))\r
+               {\r
+                       if(move)\r
+                               snprintf(reason, sizeof(reason), "unregistered due to a channel move to %s", target->name);\r
+                       else\r
+                               snprintf(reason, sizeof(reason), "unregistered due to a channel merge into %s", target->name);\r
+\r
+                       spamserv_cs_unregister(user, channel, manually, reason);\r
+                       return 0;\r
+               }\r
+\r
+               cInfo->channel = target;\r
+\r
+               dict_remove(registered_channels_dict, channel->name);\r
+               dict_insert(registered_channels_dict, target->name, cInfo);\r
+\r
+               if(move)\r
+               {\r
+                       snprintf(reason, sizeof(reason), "Channel moved to %s by %s.", target->name, user->handle_info->handle);\r
+               }\r
+               else\r
+               {\r
+                       spamserv_join_channel(target);\r
+                       snprintf(reason, sizeof(reason), "%s merged into %s by %s.", channel->name, target->name, user->handle_info->handle);   \r
+               }\r
+\r
+               if(!CHECK_SUSPENDED(cInfo))\r
+                       spamserv_part_channel(channel, reason);\r
+\r
+               if(move)\r
+                       snprintf(reason, sizeof(reason), "$X (channel %s) moved to %s by %s.", channel->name, target->name, user->handle_info->handle);\r
+               else\r
+                       snprintf(reason, sizeof(reason), "$X (channel %s) merged into %s by %s.", channel->name, target->name, user->handle_info->handle);\r
+\r
+               global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);\r
+               return 1;\r
+       }\r
+\r
+       return 0;\r
+}\r
+\r
+void\r
+spamserv_cs_unregister(struct userNode *user, struct chanNode *channel, enum cs_unreg type, char *reason)\r
+{\r
+       struct chanInfo *cInfo = get_chanInfo(channel->name);\r
+\r
+       if(cInfo)\r
+       {\r
+               char global[MAXLEN], partmsg[MAXLEN];\r
+\r
+               switch (type)\r
+               {\r
+               case manually:\r
+                       snprintf(global, sizeof(global), "$X (channel %s) %s by %s.", channel->name, reason, user->handle_info->handle);\r
+                       snprintf(partmsg, sizeof(partmsg), "%s %s by %s.", channel->name, reason, user->handle_info->handle);                   \r
+                       break;\r
+               case expire:\r
+                       snprintf(global, sizeof(global), "$X (channel %s) registration expired.", channel->name);\r
+                       snprintf(partmsg, sizeof(partmsg), "%s registration expired.", channel->name);                  \r
+                       break;\r
+               case lost_all_users:\r
+                       snprintf(global, sizeof(global), "$X (channel %s) lost all users.", channel->name);\r
+                       snprintf(partmsg, sizeof(partmsg), "%s lost all users.", channel->name);                        \r
+                       break;\r
+               }\r
+\r
+               if(!CHECK_SUSPENDED(cInfo))\r
+                       spamserv_part_channel(channel, partmsg);\r
+               \r
+               spamserv_unregister_channel(cInfo);\r
+               global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, global);\r
+       }\r
+}\r
+\r
+/***********************************************/\r
+/*                    User                     */\r
+/***********************************************/\r
+\r
+static struct userInfo*\r
+get_userInfo(const char *nickname)\r
+{\r
+       return dict_find(connected_users_dict, nickname, 0);\r
+}\r
+\r
+static void\r
+spamserv_create_spamNode(struct chanNode *channel, struct userInfo *uInfo, char *text)\r
+{\r
+       struct spamNode *sNode = malloc(sizeof(struct spamNode));\r
+\r
+       if(!sNode)\r
+       {\r
+               log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for sNode; channel: %s; user: %s", channel->name, uInfo->user->nick);\r
+               return;\r
+       }\r
+\r
+       sNode->channel = channel;       \r
+       sNode->crc32 = crc32(text);\r
+       sNode->count = 1;\r
+       sNode->next = NULL;\r
+\r
+       if(uInfo->spam)\r
+       {\r
+               struct spamNode *temp = uInfo->spam;\r
+               \r
+               while(temp->next)\r
+                       temp = temp->next;\r
+\r
+               sNode->prev = temp;\r
+               temp->next = sNode;\r
+       }\r
+       else\r
+       {\r
+               sNode->prev = NULL;\r
+               uInfo->spam = sNode;\r
+       }\r
+}\r
+\r
+static void\r
+spamserv_delete_spamNode(struct userInfo *uInfo, struct spamNode *sNode)\r
+{\r
+       if(!sNode)\r
+               return;\r
+\r
+       if(sNode == uInfo->spam)\r
+               uInfo->spam = sNode->next;\r
+       \r
+       if(sNode->next)\r
+                sNode->next->prev = sNode->prev;\r
+       if(sNode->prev)\r
+                sNode->prev->next = sNode->next;\r
+\r
+       free(sNode);\r
+}\r
+\r
+static void\r
+spamserv_clear_spamNodes(struct chanNode *channel)\r
+{\r
+       struct userInfo *uInfo;\r
+       struct spamNode *sNode;\r
+       unsigned int i;\r
+\r
+       for(i = 0; i < channel->members.used; i++)\r
+       {\r
+               if((uInfo = get_userInfo(channel->members.list[i]->user->nick)))\r
+               {\r
+                       if((sNode = uInfo->spam))\r
+                       {\r
+                               for(; sNode; sNode = sNode->next)\r
+                                       if(sNode->channel == channel)\r
+                                               break;\r
+                                       \r
+                               if(sNode)\r
+                                       spamserv_delete_spamNode(uInfo, sNode);\r
+                       }\r
+               }\r
+       }\r
+}\r
+\r
+static void\r
+spamserv_create_floodNode(struct chanNode *channel, struct userNode *user, struct floodNode **uI_fNode)\r
+{\r
+       struct floodNode *fNode = malloc(sizeof(struct floodNode));\r
+\r
+       if(!fNode)\r
+       {\r
+               log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for fNode; channel: %s; user: %s", channel->name, user->nick);\r
+               return;\r
+       }\r
+\r
+       fNode->channel = channel;\r
+       fNode->owner = user;\r
+       fNode->count = 1;\r
+       fNode->time = now;      \r
+       fNode->next = NULL;\r
+\r
+       if(*uI_fNode)\r
+       {\r
+               struct floodNode *temp = *uI_fNode;\r
+               \r
+               while(temp->next)\r
+                       temp = temp->next;\r
+               \r
+               fNode->prev = temp;\r
+               temp->next = fNode;\r
+       }\r
+       else\r
+       {\r
+               fNode->prev = NULL;\r
+               *uI_fNode = fNode;\r
+       }\r
+}\r
+\r
+static void\r
+spamserv_delete_floodNode(struct floodNode **uI_fNode, struct floodNode *fNode)\r
+{\r
+       if(!fNode)\r
+               return;\r
+\r
+       if(fNode == *uI_fNode)\r
+               *uI_fNode = fNode->next;\r
+       \r
+       if(fNode->next)\r
+                fNode->next->prev = fNode->prev;\r
+       if(fNode->prev)\r
+                fNode->prev->next = fNode->next;\r
+\r
+       free(fNode);\r
+}\r
+\r
+static void\r
+spamserv_create_user(struct userNode *user)\r
+{\r
+       struct userInfo *uInfo = malloc(sizeof(struct userInfo));\r
+       struct killNode *kNode = dict_find(killed_users_dict, irc_ntoa(&user->ip), 0);\r
+\r
+       if(!uInfo)\r
+       {\r
+               log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for uInfo; nick: %s", user->nick);\r
+               return;\r
+       }\r
+\r
+       if(kNode)\r
+               spamserv_debug(SSMSG_DEBUG_RECONNECT, user->nick);\r
+\r
+       uInfo->user = user;\r
+       uInfo->spam = NULL;\r
+       uInfo->flood = NULL;\r
+       uInfo->joinflood = NULL;\r
+       uInfo->flags = kNode ? USER_KILLED : 0;\r
+       uInfo->warnlevel = kNode ? kNode->warnlevel : 0;\r
+       uInfo->lastadv = 0;\r
+\r
+       dict_insert(connected_users_dict, user->nick, uInfo);\r
+\r
+       if(kNode)\r
+       {\r
+               dict_remove(killed_users_dict, irc_ntoa(&user->ip));\r
+               free(kNode);\r
+       }\r
+}\r
+\r
+static void\r
+spamserv_delete_user(struct userInfo *uInfo)\r
+{\r
+       if(!uInfo)\r
+               return;\r
+\r
+       if(uInfo->spam)\r
+               while(uInfo->spam)\r
+                       spamserv_delete_spamNode(uInfo, uInfo->spam);   \r
+\r
+       if(uInfo->flood)\r
+               while(uInfo->flood)\r
+                       spamserv_delete_floodNode(&uInfo->flood, uInfo->flood);\r
+\r
+       if(uInfo->joinflood)\r
+               while(uInfo->joinflood)\r
+                       spamserv_delete_floodNode(&uInfo->joinflood, uInfo->joinflood);\r
+\r
+       dict_remove(connected_users_dict, uInfo->user->nick);\r
+       free(uInfo);\r
+}\r
+\r
+static void\r
+spamserv_new_user_func(struct userNode *user)\r
+{\r
+       if(!IsLocal(user))\r
+               spamserv_create_user(user);\r
+}\r
+\r
+static void\r
+spamserv_del_user_func(struct userNode *user, struct userNode *killer, UNUSED_ARG(const char *why))\r
+{\r
+       struct userInfo *uInfo = get_userInfo(user->nick);\r
+       struct killNode *kNode;\r
+\r
+       if(killer == spamserv)\r
+       {\r
+               kNode = malloc(sizeof(struct killNode));\r
+\r
+               if(!kNode)\r
+               {\r
+                       log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for killNode - nickname %s", user->nick);\r
+                       spamserv_delete_user(uInfo);                    \r
+                       return;\r
+               }\r
+\r
+               if(uInfo->warnlevel > KILL_WARNLEVEL)\r
+                       kNode->warnlevel = uInfo->warnlevel - KILL_WARNLEVEL;\r
+               else\r
+                       kNode->warnlevel = 0;\r
+\r
+               kNode->time = now;\r
+\r
+               dict_insert(killed_users_dict, irc_ntoa(&user->ip), kNode);\r
+       }\r
+\r
+       spamserv_delete_user(uInfo);    \r
+}\r
+\r
+static void\r
+spamserv_nick_change_func(struct userNode *user, const char *old_nick)\r
+{\r
+       struct userInfo *uInfo = get_userInfo(old_nick);\r
+\r
+       dict_remove(connected_users_dict, old_nick);\r
+       dict_insert(connected_users_dict, user->nick, uInfo);\r
+}\r
+\r
+static int\r
+spamserv_user_join(struct modeNode *mNode)\r
+{\r
+       struct chanNode *channel = mNode->channel;\r
+       struct userNode *user = mNode->user;    \r
+       struct chanInfo *cInfo;\r
+       struct userInfo *uInfo;\r
+       struct floodNode *jfNode;\r
+\r
+       if(user->uplink->burst || !(cInfo = get_chanInfo(channel->name)) || !CHECK_JOINFLOOD(cInfo) || !(uInfo = get_userInfo(user->nick)))\r
+               return 0;\r
+        \r
+        \r
+    if(!CHECK_CHANOPS(cInfo))\r
+       {\r
+               //struct modeNode *mn = GetUserMode(channel, user);\r
+               //if(mn->modes & MODE_CHANOP)\r
+               //      return;\r
+        if(check_user_level(channel, user, lvlGiveOps, 1, 0)) \r
+            return 0;\r
+       }\r
+    \r
+    if(!CHECK_VOICED(cInfo))\r
+       {\r
+        if(check_user_level(channel, user, lvlGiveVoice, 1, 0)) \r
+            return 0;\r
+    }\r
+    \r
+    if(cInfo->exceptlevel == 0)\r
+        return 0;\r
+    if(cInfo->exceptlevel < 501) {\r
+      struct userData *uData;\r
+       if((uData = GetChannelUser(channel->channel_info, user->handle_info)) && (uData->access >= cInfo->exceptlevel)) {\r
+        return 0;\r
+       }\r
+    }\r
+\r
+       if(!(jfNode = uInfo->joinflood))\r
+       {\r
+               spamserv_create_floodNode(channel, user, &uInfo->joinflood);\r
+       }\r
+       else\r
+       {\r
+               for(; jfNode; jfNode = jfNode->next)\r
+                       if(jfNode->channel == channel)\r
+                               break;\r
+\r
+               if(!jfNode)\r
+               {\r
+                       spamserv_create_floodNode(channel, user, &uInfo->joinflood);\r
+               }\r
+               else\r
+               {\r
+                       jfNode->count++;\r
+                       jfNode->time = now;             \r
+\r
+                       if(jfNode->count > JOINFLOOD_MAX)\r
+                       {\r
+                               char reason[MAXLEN];\r
+\r
+                               spamserv_delete_floodNode(&uInfo->joinflood, jfNode);\r
+                               snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_JOINFLOOD, spamserv_conf.network_rules);\r
+                               spamserv_punish(channel, user, JOINFLOOD_B_DURATION, reason, 1);\r
+                       }\r
+               }\r
+       }\r
+\r
+       return 0;\r
+}\r
+\r
+static void\r
+spamserv_user_part(struct modeNode *mn, UNUSED_ARG(const char *reason))\r
+{\r
+       struct userNode *user = mn->user;\r
+       struct chanNode *channel = mn->channel;\r
+       struct userInfo *uInfo;\r
+       struct spamNode *sNode;\r
+       struct floodNode *fNode;\r
+\r
+       if(user->dead || !get_chanInfo(channel->name) || !(uInfo = get_userInfo(user->nick)))\r
+               return;\r
+\r
+       if((sNode = uInfo->spam))\r
+       {\r
+               for(; sNode; sNode = sNode->next)\r
+                       if(sNode->channel == channel)\r
+                               break;\r
+\r
+               if(sNode)\r
+                       spamserv_delete_spamNode(uInfo, sNode);\r
+       }\r
+\r
+       if((fNode = uInfo->flood))\r
+       {\r
+               for(; fNode; fNode = fNode->next)\r
+                       if(fNode->channel == channel)\r
+                               break;\r
+\r
+               if(fNode)\r
+                       spamserv_delete_floodNode(&uInfo->flood, fNode);\r
+       }\r
+}\r
+\r
+static struct badword*\r
+add_badword(struct chanInfo *cInfo, const char *badword_mask, unsigned int triggered, unsigned int action, const char *id)\r
+{\r
+    struct badword *badword;\r
+\r
+    badword = calloc(1, sizeof(*badword));\r
+    if (!badword)\r
+        return NULL;\r
+\r
+    if(!id) {\r
+        cInfo->last_badword_id++;\r
+        badword->id = strtab(cInfo->last_badword_id);\r
+    } else\r
+        badword->id = strdup(id);\r
+    badword->badword_mask = strdup(badword_mask);\r
+    badword->triggered = triggered;\r
+    badword->action = action;\r
+    dict_insert(cInfo->badwords, badword->id, badword);\r
+    return badword;\r
+}\r
+\r
+/***********************************************/\r
+/*                 Other Stuff                 */\r
+/***********************************************/\r
+\r
+static void\r
+crc32_init(void)\r
+{\r
+       unsigned long crc;\r
+       int i, j;\r
+\r
+       for(i = 0; i < 256; i++)\r
+       {\r
+               crc = i;\r
+\r
+               for(j = 8; j > 0; j--)\r
+               {\r
+                       if(crc & 1)\r
+                       {\r
+                               crc = (crc >> 1) ^ 0xEDB88320L;\r
+                       }\r
+                       else\r
+                       {\r
+                               crc >>= 1;\r
+                       }\r
+               }\r
+\r
+               crc_table[i] = crc;\r
+       }\r
+}\r
+\r
+static unsigned long\r
+crc32(const char *text)\r
+{\r
+       register unsigned long crc = 0xFFFFFFFF;\r
+       unsigned int c, i = 0;\r
+       \r
+       while((c = (unsigned int)text[i++]) != 0)\r
+               crc = ((crc >> 8) & 0x00FFFFFF) ^ crc_table[(crc^c) & 0xFF];\r
\r
+       return (crc^0xFFFFFFFF);\r
+}\r
+\r
+static void\r
+timeq_flood(UNUSED_ARG(void *data))\r
+{\r
+       dict_iterator_t         it;\r
+       struct userInfo         *uInfo;\r
+       struct floodNode        *fNode;\r
+\r
+       for(it = dict_first(connected_users_dict); it; it = iter_next(it))\r
+       {\r
+               uInfo = iter_data(it);\r
+\r
+               if(!(fNode = uInfo->flood))\r
+                       continue;\r
+\r
+               for(; fNode; fNode = fNode->next)\r
+               {\r
+                       if(now - fNode->time > FLOOD_EXPIRE)\r
+                       {\r
+                               if(!(--fNode->count))\r
+                                       spamserv_delete_floodNode(&uInfo->flood, fNode);\r
+                       }\r
+               }\r
+       }\r
+       \r
+       timeq_add(now + FLOOD_TIMEQ_FREQ, timeq_flood, NULL);\r
+}\r
+\r
+static void\r
+timeq_joinflood(UNUSED_ARG(void *data))\r
+{\r
+       dict_iterator_t it;\r
+       struct userInfo *uInfo;\r
+       struct floodNode *fNode;\r
+\r
+       for(it = dict_first(connected_users_dict); it; it = iter_next(it))\r
+       {\r
+               uInfo = iter_data(it);\r
+\r
+               if(!(fNode = uInfo->joinflood))\r
+                       continue;\r
+\r
+               for(; fNode; fNode = fNode->next)\r
+               {\r
+                       if(now - fNode->time > JOINFLOOD_EXPIRE)\r
+                       {\r
+                               if(!(--fNode->count))\r
+                                       spamserv_delete_floodNode(&uInfo->joinflood, fNode);                            \r
+                       }\r
+               }\r
+       }\r
+\r
+       timeq_add(now + JOINFLOOD_TIMEQ_FREQ, timeq_joinflood, NULL);\r
+}\r
+\r
+static void\r
+timeq_adv(UNUSED_ARG(void *data))\r
+{\r
+       dict_iterator_t it;\r
+       struct userInfo *uInfo;\r
+\r
+       for(it = dict_first(connected_users_dict); it; it = iter_next(it))\r
+       {\r
+               uInfo = iter_data(it);\r
+\r
+               if(uInfo->lastadv && uInfo->lastadv - now > ADV_EXPIRE)\r
+               {\r
+                       uInfo->lastadv = 0;\r
+                       uInfo->flags &= ~USER_ADV_WARNED;\r
+               }\r
+       }\r
+\r
+       timeq_add(now + ADV_TIMEQ_FREQ, timeq_adv, NULL);\r
+}\r
+\r
+static void\r
+timeq_warnlevel(UNUSED_ARG(void *data))\r
+{\r
+       dict_iterator_t it;\r
+       struct userInfo *uInfo;\r
+\r
+       for(it = dict_first(connected_users_dict); it; it = iter_next(it))\r
+       {\r
+               uInfo = iter_data(it);\r
+\r
+               if(uInfo->warnlevel > 0)\r
+                       uInfo->warnlevel--;\r
+       }\r
+\r
+       timeq_add(now + WARNLEVEL_TIMEQ_FREQ, timeq_warnlevel, NULL);\r
+}\r
+\r
+static void\r
+timeq_kill(UNUSED_ARG(void *data))\r
+{\r
+       dict_iterator_t it;\r
+       struct killNode *kNode;\r
+\r
+       for(it = dict_first(killed_users_dict); it; it = iter_next(it))\r
+       {\r
+               kNode = iter_data(it);\r
+\r
+               if(kNode->time - now > KILL_EXPIRE)\r
+                       free(kNode);\r
+       }\r
+\r
+       timeq_add(now + KILL_TIMEQ_FREQ, timeq_kill, NULL);\r
+}\r
+\r
+static int\r
+binary_option(char *name, unsigned long mask, struct userNode *user, struct chanNode *channel, int argc, char *argv[])\r
+{\r
+       struct chanInfo *cInfo = get_chanInfo(channel->name);\r
+       int value;\r
+\r
+       if(argc > 1)\r
+       {\r
+               if(enabled_string(argv[1]))\r
+               {\r
+                       cInfo->flags |= mask;\r
+                       value = 1;\r
+               }\r
+               else if(disabled_string(argv[1]))\r
+               {\r
+                   cInfo->flags &= ~mask;\r
+                   value = 0;\r
+               }\r
+               else\r
+               {\r
+                  spamserv_notice(user, "SSMSG_INVALID_BINARY", argv[1]);\r
+                  return 0;\r
+               }\r
+       }\r
+       else\r
+       {\r
+               value = (cInfo->flags & mask) ? 1 : 0;\r
+       }\r
+\r
+       spamserv_notice(user, "SSMSG_STRING_VALUE", name, value ? "Enabled." : "Disabled.");\r
+       return 1;\r
+}\r
+\r
+struct valueData\r
+{\r
+       char *description;\r
+       char value;\r
+       int  oper_only : 1;\r
+};\r
+\r
+static int\r
+multiple_option(char *name, char *description, enum channelinfo info, struct valueData *values, int count, struct userNode *user, struct chanNode *channel, int argc, char *argv[])\r
+{\r
+       struct chanInfo *cInfo = get_chanInfo(channel->name);\r
+       int index;\r
+\r
+       if(argc > 1)\r
+       {\r
+               index = atoi(argv[1]);\r
+               \r
+               if(index < 0 || index >= count)\r
+               {\r
+                       spamserv_notice(user, "SSMSG_INVALID_NUM_SET", index, description);\r
+\r
+            for(index = 0; index < count; index++)\r
+                spamserv_notice(user, "SSMSG_NUMERIC_VALUE", name, index, values[index].description);\r
+\r
+                       return 0;\r
+               }\r
+\r
+               if(values[index].oper_only && !IsOper(user))\r
+               {\r
+                       spamserv_notice(user, "SSMSG_MUST_BE_OPER");\r
+                       return 0;\r
+               }\r
+               \r
+               cInfo->info[info] = values[index].value;\r
+       }\r
+       else\r
+       {\r
+               for(index = 0; index < count && cInfo->info[info] != values[index].value; index++);\r
+       }\r
+\r
+       spamserv_notice(user, "SSMSG_NUMERIC_VALUE", name, index, values[index].description);\r
+       return 1;\r
+}\r
+\r
+static int\r
+show_exceptions(struct userNode *user, struct chanInfo *cInfo)\r
+{\r
+       struct helpfile_table table;\r
+       unsigned int i;\r
+\r
+       if(!cInfo->exceptions->used)\r
+       {\r
+               spamserv_notice(user, "SSMSG_NO_EXCEPTIONS");\r
+               return 0;\r
+       }\r
+\r
+       spamserv_notice(user, "SSMSG_EXCEPTION_LIST");\r
+\r
+       table.length = 0;\r
+       table.width = 1;\r
+       table.flags = TABLE_REPEAT_ROWS | TABLE_NO_FREE | TABLE_NO_HEADERS;\r
+       table.contents = alloca(cInfo->exceptions->used * sizeof(*table.contents));\r
+\r
+       for(i = 0; i < cInfo->exceptions->used; i++)\r
+       {\r
+               table.contents[table.length] = alloca(table.width * sizeof(**table.contents));\r
+               table.contents[table.length][0] = cInfo->exceptions->list[i];\r
+               table.length++;\r
+       }\r
+       \r
+       table_send(spamserv, user->nick, 0, NULL, table);\r
+\r
+       return 1;\r
+}\r
+\r
+static void\r
+show_memory_usage(struct userNode *user)\r
+{\r
+       dict_iterator_t it;\r
+       struct helpfile_table table;\r
+       struct chanInfo *cInfo;\r
+       struct userInfo *uInfo;\r
+       struct spamNode *sNode;\r
+       struct floodNode *fNode;\r
+       double channel_size = 0, user_size, size;\r
+       unsigned int spamcount = 0, floodcount = 0, i, j;\r
+       char buffer[64];\r
+\r
+       for(it = dict_first(registered_channels_dict); it; it = iter_next(it))\r
+       {\r
+               cInfo = iter_data(it);\r
+\r
+               if(!cInfo->exceptions->used)\r
+                       continue;\r
+\r
+               for(i = 0; i < cInfo->exceptions->used; i++)\r
+                       channel_size += strlen(cInfo->exceptions->list[i]) * sizeof(char);              \r
+       }\r
+\r
+       for(it = dict_first(connected_users_dict); it; it = iter_next(it))\r
+       {\r
+               uInfo = iter_data(it);\r
+\r
+               for(sNode = uInfo->spam; sNode; sNode = sNode->next, spamcount++);\r
+               for(fNode = uInfo->flood; fNode; fNode = fNode->next, floodcount++);\r
+               for(fNode = uInfo->joinflood; fNode; fNode = fNode->next, floodcount++);\r
+       }\r
+\r
+       channel_size += dict_size(registered_channels_dict) * sizeof(struct chanInfo);\r
+       \r
+       user_size = dict_size(connected_users_dict) * sizeof(struct userInfo) +\r
+                               dict_size(killed_users_dict) * sizeof(struct killNode) +\r
+                               spamcount * sizeof(struct spamNode)     +\r
+                               floodcount *  sizeof(struct floodNode);\r
+\r
+       size = channel_size + user_size;\r
+       \r
+       ss_reply("SSMSG_STATUS_MEMORY");\r
+       \r
+       table.length = 3;\r
+       table.width = 4;\r
+       table.flags = TABLE_NO_FREE | TABLE_NO_HEADERS | TABLE_PAD_LEFT;\r
+       table.contents = calloc(table.length, sizeof(char**));\r
+\r
+       // chanInfo\r
+       table.contents[0] = calloc(table.width, sizeof(char*));\r
+       snprintf(buffer, sizeof(buffer), "Channel Memory Usage:");\r
+       table.contents[0][0] = strdup(buffer);\r
+       snprintf(buffer, sizeof(buffer), " %g Byte; ", channel_size);\r
+       table.contents[0][1] = strdup(buffer);\r
+       snprintf(buffer, sizeof(buffer), "%g KiloByte; ", channel_size / 1024);\r
+       table.contents[0][2] = strdup(buffer);\r
+       snprintf(buffer, sizeof(buffer), "%g MegaByte", channel_size / 1024 / 1024);\r
+       table.contents[0][3] = strdup(buffer);\r
+\r
+       // userInfo\r
+       table.contents[1] = calloc(table.width, sizeof(char*));\r
+       snprintf(buffer, sizeof(buffer), "User Memory Usage   :");\r
+       table.contents[1][0] = strdup(buffer);\r
+       snprintf(buffer, sizeof(buffer), " %g Byte; ", user_size);\r
+       table.contents[1][1] = strdup(buffer);\r
+       snprintf(buffer, sizeof(buffer), "%g KiloByte; ", user_size / 1024);\r
+       table.contents[1][2] = strdup(buffer);\r
+       snprintf(buffer, sizeof(buffer), "%g MegaByte", user_size / 1024 / 1024);\r
+       table.contents[1][3] = strdup(buffer);\r
+\r
+       // total memory usage\r
+       table.contents[2] = calloc(table.width, sizeof(char*));\r
+       snprintf(buffer, sizeof(buffer), "Total Memory Usage  :");\r
+       table.contents[2][0] = strdup(buffer);\r
+       snprintf(buffer, sizeof(buffer), " %g Byte; ", size);\r
+       table.contents[2][1] = strdup(buffer);\r
+       snprintf(buffer, sizeof(buffer), "%g KiloByte; ", size / 1024);\r
+       table.contents[2][2] = strdup(buffer);\r
+       snprintf(buffer, sizeof(buffer), "%g MegaByte", size / 1024 / 1024);\r
+       table.contents[2][3] = strdup(buffer);\r
+\r
+       table_send(spamserv, user->nick, 0, NULL, table);\r
+       \r
+       for(i = 0; i < table.length; i++)\r
+       {\r
+               for(j = 0; j < table.width; j++)\r
+                       free((char*)table.contents[i][j]);\r
+\r
+        free(table.contents[i]);\r
+       }\r
+\r
+       free(table.contents);\r
+}\r
+\r
+static void\r
+show_registered_channels(struct userNode *user)\r
+{\r
+       struct helpfile_table table;\r
+       dict_iterator_t it;\r
+\r
+       spamserv_notice(user, "SSMSG_STATUS_CHANNEL_LIST");\r
+\r
+       if(!dict_size(registered_channels_dict))\r
+       {\r
+               spamserv_notice(user, "SSMSG_STATUS_NO_CHANNEL");\r
+               return;\r
+       }\r
+\r
+       table.length = 0;\r
+       table.width = 1;\r
+       table.flags = TABLE_REPEAT_ROWS | TABLE_NO_FREE | TABLE_NO_HEADERS;\r
+       table.contents = alloca(dict_size(registered_channels_dict) * sizeof(*table.contents));\r
+\r
+       for(it = dict_first(registered_channels_dict); it; it = iter_next(it))\r
+       {\r
+               struct chanInfo *cInfo = iter_data(it);\r
+\r
+               table.contents[table.length] = alloca(table.width * sizeof(**table.contents));\r
+               table.contents[table.length][0] = cInfo->channel->name;\r
+               table.length++;\r
+       }\r
+       \r
+       table_send(spamserv, user->nick, 0, NULL, table);\r
+}\r
+\r
+/***********************************************/\r
+/*                SpamServ_Func                */\r
+/***********************************************/\r
+\r
+static \r
+SPAMSERV_FUNC(cmd_register)\r
+{\r
+       struct chanInfo *cInfo;\r
+       char reason[MAXLEN];\r
+\r
+       if(!channel || !channel->channel_info)\r
+       {\r
+               ss_reply("SSMSG_NOT_REGISTERED_CS", channel->name);\r
+               return 0;\r
+       }\r
+\r
+       if(get_chanInfo(channel->name))\r
+       {\r
+               ss_reply("SSMSG_ALREADY_REGISTERED", channel->name);\r
+               return 0;\r
+       }\r
+\r
+       if(IsSuspended(channel->channel_info))\r
+       {\r
+               ss_reply("SSMSG_SUSPENDED_CS", channel->name, "register");\r
+               return 0;\r
+       }\r
+\r
+       if(channel == spamserv_conf.debug_channel)\r
+       {\r
+               ss_reply("SSMSG_DEBUG_CHAN");\r
+               return 0;\r
+       }\r
+\r
+       if(!(cInfo = spamserv_register_channel(channel, spamserv_conf.global_exceptions, CHAN_FLAGS_DEFAULT , CHAN_INFO_DEFAULT)))\r
+       {\r
+               ss_reply("SSMSG_NO_REGISTER", channel->name);\r
+               return 0;\r
+       }\r
+\r
+       spamserv_join_channel(cInfo->channel);\r
+       \r
+       snprintf(reason, sizeof(reason), "%s (channel %s) registered by %s.", spamserv->nick, channel->name, user->handle_info->handle);\r
+       global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);\r
+       ss_reply("SSMSG_REG_SUCCESS", channel->name);\r
+\r
+       return 1;\r
+}\r
+\r
+static \r
+SPAMSERV_FUNC(cmd_unregister)\r
+{\r
+       struct chanInfo *cInfo;\r
+       struct chanData *cData;\r
+       struct userData *uData;\r
+       char reason[MAXLEN];\r
+\r
+       if(!channel || !(cData = channel->channel_info) || !(cInfo = get_chanInfo(channel->name)))\r
+       {\r
+               ss_reply("SSMSG_NOT_REGISTERED", channel->name);\r
+               return 0;\r
+       }\r
+\r
+       if(!(uData = GetChannelUser(cData, user->handle_info)) || (uData->access < UL_OWNER))\r
+       {\r
+        ss_reply("SSMSG_NO_ACCESS");\r
+        return 0;\r
+       }\r
+\r
+       if(!IsHelping(user))\r
+       {\r
+        if(IsSuspended(cData))\r
+        {\r
+            ss_reply("SSMSG_SUSPENDED_CS", channel->name, "unregister");\r
+            return 0;\r
+        }\r
+\r
+               if(argc < 2 || strcasecmp(argv[1], "CONFIRM"))\r
+               {\r
+                       ss_reply("SSMSG_CONFIRM_UNREG");\r
+                       return 0;\r
+               }\r
+        }\r
+\r
+       if(!CHECK_SUSPENDED(cInfo))\r
+       {\r
+               snprintf(reason, sizeof(reason), "%s unregistered by %s.", spamserv->nick, user->handle_info->handle);          \r
+               spamserv_part_channel(channel, reason);\r
+       }\r
+       \r
+       spamserv_unregister_channel(cInfo);     \r
+\r
+       snprintf(reason, sizeof(reason), "%s (channel %s) unregistered by %s.", spamserv->nick, channel->name, user->handle_info->handle);\r
+       global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);\r
+       ss_reply("SSMSG_UNREG_SUCCESS", channel->name);\r
+\r
+       return 1;\r
+}\r
+\r
+static \r
+SPAMSERV_FUNC(cmd_status)\r
+{\r
+       ss_reply("SSMSG_STATUS");\r
+       ss_reply("SSMSG_STATUS_USERS", dict_size(connected_users_dict));\r
+       ss_reply("SSMSG_STATUS_CHANNELS", dict_size(registered_channels_dict));\r
+\r
+       if(IsOper(user) && argc > 1)\r
+       {\r
+               if(!irccasecmp(argv[1], "memory"))\r
+                       show_memory_usage(user);\r
+               else if(!irccasecmp(argv[1], "channels"))\r
+                       show_registered_channels(user);         \r
+       }\r
+       \r
+       return 1;\r
+}\r
+\r
+static \r
+SPAMSERV_FUNC(cmd_addexception)\r
+{\r
+       struct chanInfo *cInfo = get_chanInfo(channel->name);\r
+       struct userData *uData;\r
+       unsigned int i;\r
+\r
+       if(!cInfo || !channel->channel_info)\r
+       {\r
+               ss_reply("SSMSG_NOT_REGISTERED", channel->name);\r
+               return 0;\r
+       }\r
+\r
+       if(CHECK_SUSPENDED(cInfo))\r
+       {\r
+               ss_reply("SSMSG_SUSPENDED", channel->name);\r
+               return 0;\r
+       }\r
+\r
+       if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)) || (uData->access < 400))\r
+       {\r
+               ss_reply("SSMSG_NO_ACCESS");\r
+               return 0;\r
+       }\r
+\r
+       if(argc < 2)\r
+               return show_exceptions(user, cInfo);\r
+\r
+       if(cInfo->exceptions->used == spamserv_conf.exception_max && !IsOper(user))\r
+       {\r
+               ss_reply("SSMSG_EXCEPTION_MAX", spamserv_conf.exception_max);\r
+               return 0;\r
+       }\r
+\r
+       if(strlen(argv[1]) < spamserv_conf.exception_min_len)\r
+       {\r
+               ss_reply("SSMSG_EXCEPTION_TOO_SHORT", spamserv_conf.exception_min_len);\r
+               return 0;\r
+       }\r
+       else if(strlen(argv[1]) > spamserv_conf.exception_max_len)\r
+       {\r
+               ss_reply("SSMSG_EXCEPTION_TOO_LONG", spamserv_conf.exception_max_len);\r
+               return 0;\r
+       }\r
+\r
+       for(i = 0; i < cInfo->exceptions->used; i++)\r
+       {\r
+               if(!irccasecmp(argv[1], cInfo->exceptions->list[i]))\r
+               {\r
+                       ss_reply("SSMSG_EXCEPTION_IN_LIST", argv[1]);\r
+                       return 0;\r
+               }\r
+       }\r
+\r
+       string_list_append(cInfo->exceptions, strdup(argv[1]));\r
+       ss_reply("SSMSG_EXCEPTION_ADDED", argv[1]);\r
+\r
+       return 1;\r
+}\r
+\r
+static \r
+SPAMSERV_FUNC(cmd_delexception)\r
+{\r
+       struct chanInfo *cInfo = get_chanInfo(channel->name);\r
+       struct userData *uData;\r
+       unsigned int i;\r
+       int found = -1;\r
+\r
+       if(!cInfo || !channel->channel_info)\r
+       {\r
+               ss_reply("SSMSG_NOT_REGISTERED", channel->name);\r
+               return 0;\r
+       }\r
+\r
+       if(CHECK_SUSPENDED(cInfo))\r
+       {\r
+               ss_reply("SSMSG_SUSPENDED", channel->name);\r
+               return 0;\r
+       }\r
+\r
+       if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)) || (uData->access < 400))\r
+       {\r
+               ss_reply("SSMSG_NO_ACCESS");\r
+               return 0;\r
+       }\r
+\r
+       if(argc < 2)\r
+               return show_exceptions(user, cInfo);\r
+\r
+       for(i = 0; i < cInfo->exceptions->used; i++)\r
+       {\r
+               if(!irccasecmp(argv[1], cInfo->exceptions->list[i]))\r
+               {\r
+                       found = i;\r
+                       break;\r
+               }\r
+       }\r
+       \r
+       if(found == -1)\r
+       {\r
+               ss_reply("SSMSG_NO_SUCH_EXCEPTION", argv[1]);\r
+               return 0;\r
+       }\r
+\r
+       string_list_delete(cInfo->exceptions, i);\r
+       ss_reply("SSMSG_EXCEPTION_DELETED", argv[1]);\r
+\r
+       return 1;\r
+}\r
+\r
+static \r
+SPAMSERV_FUNC(cmd_set)\r
+{\r
+       struct chanInfo *cInfo = get_chanInfo(channel->name);\r
+       struct svccmd   *subcmd;        \r
+       char cmd_name[MAXLEN];\r
+       unsigned int i;\r
+\r
+       if(!cInfo)\r
+       {\r
+               ss_reply("SSMSG_NOT_REGISTERED", channel->name);\r
+               return 0;\r
+       }\r
+\r
+       if(CHECK_SUSPENDED(cInfo))\r
+       {\r
+               ss_reply("SSMSG_SUSPENDED", channel->name);\r
+               return 0;\r
+       }\r
+\r
+    if(!check_user_level(channel,user,lvlSetters,1,0))\r
+       {\r
+               ss_reply("SSMSG_NO_ACCESS");\r
+               return 0;\r
+       }\r
+       \r
+       if(argc < 2)\r
+       {\r
+               ss_reply("SSMSG_CHANNEL_OPTIONS");\r
+\r
+               for(i = 0; i < SET_SUBCMDS_SIZE; i++)\r
+               {\r
+                       sprintf(cmd_name, "%s %s", cmd->name, set_subcommands[i]);\r
+\r
+                       if((subcmd = dict_find(cmd->parent->commands, cmd_name, NULL)))\r
+                               subcmd->command->func(user, channel, 1, argv + 1, subcmd);\r
+               }\r
+\r
+               return 1;\r
+       }\r
+\r
+       sprintf(cmd_name, "%s %s", cmd->name, argv[1]);\r
+       subcmd = dict_find(cmd->parent->commands, cmd_name, NULL);\r
+\r
+       if(!subcmd)\r
+       {\r
+               reply("SSMSG_INVALID_OPTION", argv[1], argv[0]);\r
+               return 0;\r
+       }\r
+\r
+       return subcmd->command->func(user, channel, argc - 1, argv + 1, subcmd);\r
+}\r
+\r
+static \r
+SPAMSERV_FUNC(opt_spamlimit)\r
+{\r
+       struct valueData values[] =\r
+       {\r
+               {"Users may send the same message $b2$b times.", 'a', 0},\r
+               {"Users may send the same message $b3$b times.", 'b', 0},\r
+               {"Users may send the same message $b4$b times.", 'c', 0},\r
+               {"Users may send the same message $b5$b times.", 'd', 0},\r
+               {"Users may send the same message $b6$b times.", 'e', 0}\r
+       };\r
+\r
+       MULTIPLE_OPTION("SpamLimit     ", "SpamLimit", ci_SpamLimit);\r
+}\r
+\r
+static \r
+SPAMSERV_FUNC(opt_advreaction)\r
+{\r
+       struct valueData values[] =\r
+       {\r
+               {"Kick on disallowed advertising.", 'k', 0},\r
+               {"Kickban on disallowed advertising.", 'b', 0},\r
+               {"Short timed ban on disallowed advertising.", 's', 0},\r
+               {"Long timed ban on disallowed advertising.", 'l', 0},\r
+               {"Kill on disallowed advertising.", 'd', 1}\r
+       };\r
+\r
+       MULTIPLE_OPTION("AdvReaction   ", "AdvReaction", ci_AdvReaction);\r
+}\r
+\r
+static \r
+SPAMSERV_FUNC(opt_warnreaction)\r
+{\r
+       struct valueData values[] =\r
+       {\r
+               {"Kick after warning.", 'k', 0},\r
+               {"Kickban after warning.", 'b', 0},\r
+               {"Short timed ban after warning.", 's', 0},\r
+               {"Long timed ban after warning.", 'l', 0},\r
+               {"Kill after warning.", 'd', 1}\r
+       };\r
+\r
+       MULTIPLE_OPTION("WarnReaction  ", "WarnReaction", ci_WarnReaction);\r
+}\r
+\r
+static \r
+SPAMSERV_FUNC(opt_advscan)\r
+{\r
+       BINARY_OPTION("AdvScan       ", CHAN_ADV_SCAN);\r
+}\r
+\r
+static \r
+SPAMSERV_FUNC(opt_spamscan)\r
+{\r
+       BINARY_OPTION("SpamScan      ", CHAN_SPAMSCAN);\r
+}\r
+\r
+static \r
+SPAMSERV_FUNC(opt_floodscan)\r
+{\r
+       BINARY_OPTION("FloodScan     ", CHAN_FLOODSCAN);\r
+}\r
+\r
+static \r
+SPAMSERV_FUNC(opt_joinflood)\r
+{\r
+       BINARY_OPTION("JoinFloodScan ", CHAN_JOINFLOOD);\r
+}\r
+\r
+static \r
+SPAMSERV_FUNC(opt_scanops)\r
+{\r
+       BINARY_OPTION("ScanChanOps   ", CHAN_SCAN_CHANOPS);\r
+}\r
+\r
+static \r
+SPAMSERV_FUNC(opt_scanvoiced)\r
+{\r
+       BINARY_OPTION("ScanVoiced    ", CHAN_SCAN_VOICED);\r
+}\r
+\r
+static \r
+SPAMSERV_FUNC(opt_exceptlevel)\r
+{\r
+ struct chanInfo *cInfo = get_chanInfo(channel->name);\r
+ struct userData *uData;\r
+ int index;\r
+ if(argc > 1)\r
+       {\r
+               index = atoi(argv[1]);\r
+               if(index < 1 || index > 501)\r
+               {\r
+                       spamserv_notice(user, "SSMSG_INVALID_NUM_SET", index, "ExceptLevel");\r
+                       return 0;\r
+               }\r
+        if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)) || (uData->access < cInfo->exceptlevel || index > uData->access))\r
+        {\r
+            ss_reply("SSMSG_NO_ACCESS");\r
+            return 0;\r
+        }\r
+        cInfo->exceptlevel=index;\r
+    } else {\r
+     index=cInfo->exceptlevel;\r
+    }\r
+    spamserv_notice(user, "SSMSG_EASYNUMERIC_VALUE", "ExceptLevel   ", index);\r
+    return 1;\r
+}\r
+\r
+static SPAMSERV_FUNC(cmd_addbad)\r
+{\r
+        struct chanInfo *cInfo = get_chanInfo(channel->name);\r
+        struct userData *uData;\r
+\r
+        if(!cInfo)\r
+        {\r
+               ss_reply("SSMSG_NOT_REGISTERED", channel->name);\r
+               return 0;\r
+        }\r
+\r
+        if(CHECK_SUSPENDED(cInfo))\r
+        {\r
+                ss_reply("SSMSG_SUSPENDED", channel->name);\r
+            return 0;\r
+        }\r
+\r
+        if(!check_user_level(channel,user,lvlSetters,1,0))\r
+        {\r
+               ss_reply("SSMSG_NO_ACCESS");\r
+               return 0;\r
+        }\r
+\r
+        dict_iterator_t it;\r
+        char *mask = unsplit_string(argv + 1, argc - 1, NULL);\r
+        for (it = dict_first(cInfo->badwords); it; it = iter_next(it)) {\r
+                struct badword *badword = iter_data(it);\r
+                if(match_ircglob(mask,badword->badword_mask)) {\r
+                        reply("SSMSG_BADWORD_ALREADY_ADDED", mask, badword->id);\r
+                        return 1;\r
+                }\r
+        }\r
+\r
+        struct badword *new_badword = add_badword(cInfo, mask, 0, BADACTION_KICK, NULL);\r
+        for (it = dict_first(cInfo->badwords); it; it = iter_next(it)) {\r
+                struct badword *badword = iter_data(it);\r
+                if(match_ircglob(badword->badword_mask, new_badword->badword_mask) && badword != new_badword) {\r
+                        dict_remove(cInfo->badwords, badword->id);\r
+                }\r
+        }\r
+\r
+        reply("SSMSG_BADWORD_ADDED", new_badword->badword_mask, new_badword->id);\r
+        return 1;\r
+}\r
+\r
+static SPAMSERV_FUNC(cmd_delbad)\r
+{\r
+        struct chanInfo *cInfo = get_chanInfo(channel->name);\r
+        struct userData *uData;\r
+        unsigned int n;\r
+\r
+        if(!cInfo)\r
+        {\r
+                ss_reply("SSMSG_NOT_REGISTERED", channel->name);\r
+                return 0;\r
+        }\r
+\r
+        if(CHECK_SUSPENDED(cInfo))\r
+        {\r
+                ss_reply("SSMSG_SUSPENDED", channel->name);\r
+            return 0;\r
+        }\r
+\r
+        if(!check_user_level(channel,user,lvlSetters,1,0))\r
+        {\r
+                ss_reply("SSMSG_NO_ACCESS");\r
+                return 0;\r
+        }\r
+\r
+        for (n=1; n<argc; n++) {\r
+                struct badword *badword = dict_find(cInfo->badwords, argv[n], NULL);\r
+                if (!badword) {\r
+                        reply("SSMSG_BADWORD_NOT_FOUND", argv[n]);\r
+                        continue;\r
+                }\r
+                reply("SSMSG_BADWORD_REMOVED", argv[n], badword->badword_mask);\r
+                dict_remove(cInfo->badwords, argv[n]);\r
+        }\r
+        return 1;\r
+}\r
+\r
+static SPAMSERV_FUNC(cmd_setbad)\r
+{\r
+        struct chanInfo *cInfo = get_chanInfo(channel->name);\r
+        struct userData *uData;\r
+        struct badword *badword;\r
+\r
+        if(!cInfo)\r
+        {\r
+                ss_reply("SSMSG_NOT_REGISTERED", channel->name);\r
+                return 0;\r
+        }\r
+\r
+        if(CHECK_SUSPENDED(cInfo))\r
+        {\r
+                ss_reply("SSMSG_SUSPENDED", channel->name);\r
+                return 0;\r
+        }\r
+\r
+        if(!check_user_level(channel,user,lvlSetters,1,0))\r
+        {\r
+                ss_reply("SSMSG_NO_ACCESS");\r
+                return 0;\r
+        }\r
+\r
+        if ((badword = dict_find(cInfo->badwords, argv[1], NULL))) {\r
+                if (argc > 3) {\r
+                        unsigned int ii;\r
+                        char *setting = argv[2];\r
+                        char *value = argv[3];\r
+                        for( ii = 0; setting[ ii ]; ii++)\r
+                                setting[ ii ] = toupper( setting[ ii ] );\r
+                        for( ii = 0; value[ ii ]; ii++)\r
+                                value[ ii ] = toupper( value[ ii ] );\r
+                        if(!strcmp("MASK",setting)) {\r
+                                  free(badword->badword_mask);\r
+                                  badword->badword_mask = strdup(argv[3]);\r
+                                  badword->triggered = 0;\r
+                                  reply("SSMSG_BADWORD_SET_DONE");\r
+                        }\r
+                        else if(!strcmp("ACTION",setting)) {\r
+                                 if (!strcmp("1",value) || !strcmp("KICK",value)) {\r
+                                        badword->action = BADACTION_KICK;\r
+                                        reply("SSMSG_BADWORD_SET_DONE");\r
+                                 } else if (!strcmp("2",value) || !strcmp("BAN",value)) {\r
+                                        badword->action = BADACTION_BAN;\r
+                                        reply("SSMSG_BADWORD_SET_DONE");\r
+                                 } else if (!strcmp("3",value) || !strcmp("KILL",value)) {\r
+                                        badword->action = BADACTION_KILL;\r
+                                        reply("SSMSG_BADWORD_SET_DONE");\r
+                                 } else if (!strcmp("4",value) || !strcmp("GLINE",value)) {\r
+                                        badword->action = BADACTION_GLINE;\r
+                                        reply("SSMSG_BADWORD_SET_DONE");\r
+                                 } else {\r
+                                        reply("SSMSG_BADWORD_SET_INVALID", setting);\r
+                                 }\r
+                        } else {\r
+                                 reply("SSMSG_BADWORD_SETTING_INVALID", setting);\r
+                        }\r
+\r
+                } else {\r
+                        reply("SSMSG_BADWORD_SET", badword->id);\r
+                        reply("SSMSG_BADWORD_SET_MASK", badword->badword_mask);\r
+                        switch(badword->action) {\r
+                                case BADACTION_KICK:\r
+                                  reply("SSMSG_BADWORD_SET_ACTION", "KICK");\r
+                                  break;\r
+                                case BADACTION_BAN:\r
+                                  reply("SSMSG_BADWORD_SET_ACTION", "BAN");\r
+                                  break;\r
+                                case BADACTION_KILL:\r
+                                  reply("SSMSG_BADWORD_SET_ACTION", "KILL");\r
+                                  break;\r
+                                case BADACTION_GLINE:\r
+                                  reply("SSMSG_BADWORD_SET_ACTION", "GLINE");\r
+                                  break;\r
+                                default:\r
+                                  reply("SSMSG_BADWORD_SET_ACTION", "*undef*");\r
+                        }\r
+                }\r
+        } else {\r
+                reply("SSMSG_BADWORD_NOT_FOUND", argv[1]);\r
+                return 0;\r
+        }\r
+        return 1;\r
+}\r
+\r
+int\r
+ss_badwords_sort(const void *pa, const void *pb)\r
+{\r
+        struct badword *a = *(struct badword**)pa;\r
+        struct badword *b = *(struct badword**)pb;\r
+\r
+        return strtoul(a->id, NULL, 0) - strtoul(b->id, NULL, 0);\r
+}\r
+\r
+static SPAMSERV_FUNC(cmd_listbad)\r
+{\r
+        struct helpfile_table tbl;\r
+        struct chanInfo *cInfo = get_chanInfo(channel->name);\r
+        struct userData *uData;\r
+        struct badword **badwords;\r
+        unsigned int count = 0, ii = 0;\r
+\r
+        if(!cInfo)\r
+        {\r
+            ss_reply("SSMSG_NOT_REGISTERED", channel->name);\r
+                return 0;\r
+        }\r
+\r
+     if(CHECK_SUSPENDED(cInfo))\r
+        {\r
+                ss_reply("SSMSG_SUSPENDED", channel->name);\r
+                return 0;\r
+        }\r
+\r
+        if(!check_user_level(channel,user,lvlSetters,1,0))\r
+        {\r
+                ss_reply("SSMSG_NO_ACCESS");\r
+                return 0;\r
+        }\r
+\r
+        dict_iterator_t it;\r
+        for (it = dict_first(cInfo->badwords); it; it = iter_next(it)) {\r
+                count++;\r
+        }\r
+\r
+        tbl.length = count+1;\r
+        tbl.width = 4;\r
+        tbl.flags = 0;\r
+        tbl.flags = TABLE_NO_FREE;\r
+        tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));\r
+        tbl.contents[0] = malloc(tbl.width * sizeof(tbl.contents[0][0]));\r
+        tbl.contents[0][0] = "#";\r
+        tbl.contents[0][1] = "Badword";\r
+        tbl.contents[0][2] = "Action";\r
+        tbl.contents[0][3] = "(Triggered)";\r
+        if(!count)\r
+        {\r
+                table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);\r
+            reply("MSG_NONE");\r
+            free(tbl.contents[0]);\r
+            free(tbl.contents);\r
+            return 0;\r
+        }\r
+        badwords = alloca(count * sizeof(badwords[0]));\r
+        for (it = dict_first(cInfo->badwords); it; it = iter_next(it)) {\r
+            struct badword *bw = iter_data(it);\r
+            badwords[ii++] = bw;\r
+         }\r
+\r
+        qsort(badwords, count, sizeof(badwords[0]), ss_badwords_sort);\r
+        for (ii = 1; ii <= count; ii++) {\r
+                struct badword *bw = badwords[ii-1];\r
+                tbl.contents[ii] = malloc(tbl.width * sizeof(tbl.contents[0][0]));\r
+                tbl.contents[ii][0] = strdup(bw->id);\r
+                tbl.contents[ii][1] = strdup(bw->badword_mask);\r
+                switch(bw->action) {\r
+                        case BADACTION_KICK:\r
+                          tbl.contents[ii][2] = "KICK";\r
+                          break;\r
+                        case BADACTION_BAN:\r
+                          tbl.contents[ii][2] = "BAN";\r
+                          break;\r
+                        case BADACTION_KILL:\r
+                          tbl.contents[ii][2] = "KILL";\r
+                          break;\r
+                        case BADACTION_GLINE:\r
+                          tbl.contents[ii][2] = "GLINE";\r
+                          break;\r
+                        default:\r
+                          tbl.contents[ii][2] = "*undef*";\r
+                }\r
+                tbl.contents[ii][3] = strtab(bw->triggered);\r
+        }\r
+\r
+        table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);\r
+        for(ii = 1; ii < tbl.length; ++ii)\r
+        {\r
+                free(tbl.contents[ii]);\r
+        }\r
+        free(tbl.contents[0]);\r
+        free(tbl.contents);\r
+        return 1;\r
+}\r
+\r
+static void \r
+to_lower(char *message)\r
+{\r
+       unsigned int i, diff = 'a' - 'A';\r
+\r
+       for(i = 0; i < strlen(message); i++)\r
+       {\r
+               if((message[i] >= 'A') && (message[i] <= 'Z'))\r
+                       message[i] = message[i] + diff;\r
+       }\r
+}\r
+\r
+static char *\r
+strip_mirc_codes(char *text)\r
+{\r
+       // taken from xchat and modified\r
+       int nc = 0, i = 0, col = 0, len = strlen(text);\r
+       static char new_str[MAXLEN];\r
+\r
+       while(len > 0)\r
+       {\r
+               if((col && isdigit(*text) && nc < 2) ||\r
+                       (col && *text == ',' && isdigit(*(text + 1)) && nc < 3))\r
+               {\r
+                       nc++;\r
+\r
+                       if(*text == ',')\r
+                               nc = 0;\r
+               }\r
+               else\r
+               {\r
+                       col = 0;\r
+\r
+                       switch(*text)\r
+                       {\r
+                       case '\003':\r
+                               col = 1;\r
+                               nc = 0;\r
+                               break;\r
+                       case '\002':\r
+                       case '\022':\r
+                       case '\026':                    \r
+                       case '\031':\r
+                       case '\037':\r
+                               break;\r
+                       default:\r
+                               new_str[i] = *text;\r
+                               i++;\r
+                       }\r
+               }\r
+\r
+               text++;\r
+               len--;\r
+       }\r
+\r
+       new_str[i] = '\0';\r
+\r
+       return new_str;\r
+}\r
+\r
+static int\r
+is_in_exception_list(struct chanInfo *cInfo, char *message)\r
+{\r
+       unsigned int i;\r
+\r
+       for(i = 0; i < cInfo->exceptions->used; i++)\r
+               if(strstr(message, cInfo->exceptions->list[i]))\r
+                       return 1;\r
+\r
+       return 0;\r
+}\r
+\r
+static int\r
+check_advertising(struct chanInfo *cInfo, char *message)\r
+{\r
+       unsigned int i = 0;\r
+\r
+       if(spamserv_conf.strip_mirc_codes)\r
+               message = strip_mirc_codes(message);\r
+\r
+       if(is_in_exception_list(cInfo, message))\r
+               return 0;\r
+\r
+       while(message[i] != 0)\r
+       {\r
+               if(message[i] == '#')\r
+               {\r
+                       char channelname[CHANNELLEN];\r
+                       unsigned int j = 0;\r
+\r
+                       if(!spamserv_conf.adv_chan_must_exist)\r
+                               return 1;\r
+\r
+                       /* only return 1, if the channel does exist */  \r
+\r
+                       while((message[i] != 0) && (message[i] != ' '))\r
+                       {\r
+                               channelname[j] = message[i];\r
+                               i++;\r
+                               j++;                            \r
+                       }\r
+\r
+                       channelname[j] = '\0';\r
+\r
+                       if(GetChannel(channelname))\r
+                               return 1;\r
+               }\r
+               else if((message[i] == 'w') && (message[i+1] == 'w') && (message[i+2] == 'w') && (message[i+3] == '.'))\r
+                       return 1;\r
+               else if((message[i] == 'h') && (message[i+1] == 't') && (message[i+2] == 't') && (message[i+3] == 'p') && (message[i+4] == ':'))\r
+                       return 1;\r
+               else if((message[i] == 'f') && (message[i+1] == 't') && (message[i+2] == 'p') && ((message[i+3] == '.') || (message[i+3] == ':')))\r
+                       return 1;\r
+\r
+               i++;\r
+       }\r
+\r
+       return 0;\r
+}\r
+\r
+struct banData *add_channel_ban(struct chanData *channel, const char *mask, char *owner, unsigned long set, unsigned long triggered, unsigned long expires, char *reason);\r
+\r
+static void\r
+spamserv_punish(struct chanNode *channel, struct userNode *user, time_t expires, char *reason, int ban)\r
+{\r
+       if(ban)\r
+       {\r
+               struct mod_chanmode change;\r
+               char *hostmask = generate_hostmask(user, GENMASK_STRICT_HOST | GENMASK_ANY_IDENT);\r
+\r
+               sanitize_ircmask(hostmask);\r
+\r
+               if(expires)\r
+                       add_channel_ban(channel->channel_info, hostmask, spamserv->nick, now, now, now + expires, reason);\r
+\r
+               mod_chanmode_init(&change);\r
+               change.argc = 1;\r
+               change.args[0].mode = MODE_BAN;\r
+      change.args[0].u.hostmask = hostmask;\r
+               mod_chanmode_announce(spamserv, channel, &change);        \r
+\r
+               free(hostmask);\r
+\r
+               spamserv_debug(SSMSG_DEBUG_BAN, user->nick, channel->name, reason);\r
+       }\r
+       else\r
+               spamserv_debug(SSMSG_DEBUG_KICK, user->nick, channel->name, reason);\r
+\r
+       KickChannelUser(user, channel, spamserv, reason);       \r
+}\r
+\r
+static void\r
+spamserv_detected_badword(struct userNode *user, struct chanNode *chan, struct badword *badword)\r
+{\r
+    char *hostmask;\r
+    char *reason = SSMSG_BADWORD_DETECTED;\r
+    char mask[IRC_NTOP_MAX_SIZE+3] = { '*', '@', '\0' };\r
+    switch(badword->action) {\r
+        case BADACTION_BAN:\r
+            hostmask = generate_hostmask(user, GENMASK_STRICT_HOST | GENMASK_ANY_IDENT);\r
+            sanitize_ircmask(hostmask);\r
+            if(chan->channel_info) {\r
+                //registered channel\r
+                add_channel_ban(chan->channel_info, hostmask, spamserv->nick, now, now, now + spamserv_conf.long_ban_duration, reason);\r
+            }\r
+            struct mod_chanmode change;\r
+            mod_chanmode_init(&change);\r
+            change.argc = 1;\r
+            change.args[0].mode = MODE_BAN;\r
+            change.args[0].u.hostmask = hostmask;\r
+            mod_chanmode_announce(spamserv, chan, &change);\r
+            free(hostmask);\r
+\r
+        case BADACTION_KICK:\r
+            if(GetUserMode(chan, user))\r
+                KickChannelUser(user, chan, spamserv, reason);\r
+            break;\r
+        case BADACTION_KILL:\r
+            DelUser(user, spamserv, 1, reason);\r
+            break;\r
+        case BADACTION_GLINE:\r
+            irc_ntop(mask + 2, sizeof(mask) - 2, &user->ip);\r
+            gline_add(spamserv->nick, mask, spamserv_conf.gline_duration, reason, now, now, 0, 1);\r
+            break;\r
+        default:\r
+            //error?\r
+            break;\r
+        }\r
+}\r
+\r
+void\r
+spamserv_channel_message(struct chanNode *channel, struct userNode *user, char *text)\r
+{\r
+       struct chanInfo *cInfo;\r
+       struct userInfo *uInfo;\r
+       struct spamNode *sNode;\r
+       struct floodNode *fNode;\r
+       unsigned int violation = 0;\r
+       char reason[MAXLEN];\r
+\r
+       /* make sure: spamserv is not disabled; srvx is running; spamserv is in the chan; chan is regged, user does exist */\r
+       if(!spamserv || quit_services || !GetUserMode(channel, spamserv) || !(cInfo = get_chanInfo(channel->name)) || !(uInfo = get_userInfo(user->nick)))\r
+               return;\r
+\r
+       if(!CHECK_CHANOPS(cInfo))\r
+       {\r
+               struct modeNode *mn = GetUserMode(channel, user);\r
+               if(mn && mn->modes & MODE_CHANOP)\r
+                       return;\r
+        //if(check_user_level(channel, user, lvlGiveOps, 1, 0)) \r
+        //    return;\r
+       }\r
+       \r
+       if(!CHECK_VOICED(cInfo))\r
+       {\r
+               struct modeNode *mn = GetUserMode(channel, user);\r
+               if(mn && (mn->modes & MODE_VOICE) && !(mn->modes & MODE_CHANOP))\r
+                       return;\r
+       }\r
+    \r
+    if(cInfo->exceptlevel == 0)\r
+        return;\r
+    if(cInfo->exceptlevel < 501) {\r
+      struct userData *uData;\r
+       if((uData = GetChannelUser(channel->channel_info, user->handle_info)) && (uData->access >= cInfo->exceptlevel)) {\r
+        return;\r
+       }\r
+    }\r
+\r
+       to_lower(text);\r
+\r
+       if(CHECK_SPAM(cInfo))\r
+       {\r
+               if(!(sNode = uInfo->spam))\r
+               {\r
+                       spamserv_create_spamNode(channel, uInfo, text);\r
+               }\r
+               else\r
+               {\r
+                       for(; sNode; sNode = sNode->next)\r
+                               if(sNode->channel == channel)\r
+                                       break;\r
+\r
+                       if(!sNode)\r
+                       {\r
+                               spamserv_create_spamNode(channel, uInfo, text);\r
+                       }\r
+                       else\r
+                       {\r
+                               unsigned long crc = crc32(text);\r
+\r
+                               if(crc == sNode->crc32)\r
+                               {\r
+                                       unsigned int spamlimit = 2;\r
+                                       sNode->count++;\r
+\r
+                                       switch(cInfo->info[ci_SpamLimit])\r
+                                       {\r
+                                               case 'a': spamlimit = 2; break;\r
+                                               case 'b': spamlimit = 3; break;\r
+                                               case 'c': spamlimit = 4; break;\r
+                                               case 'd': spamlimit = 5; break;\r
+                                               case 'e': spamlimit = 6; break;\r
+                                       }\r
+\r
+                                       if(sNode->count == spamlimit)\r
+                                       {\r
+                                               uInfo->warnlevel += SPAM_WARNLEVEL;\r
+\r
+                                               if(uInfo->warnlevel < MAX_WARNLEVEL)\r
+                                                       spamserv_notice(user, spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_SPAM, spamserv_conf.network_rules);\r
+                                       }\r
+                                       else if(sNode->count > spamlimit)\r
+                                       {\r
+                                               switch(cInfo->info[ci_WarnReaction])\r
+                                               {\r
+                                                       case 'k': uInfo->flags |= USER_KICK; break;\r
+                                                       case 'b': uInfo->flags |= USER_KICKBAN; break;\r
+                                                       case 's': uInfo->flags |= USER_SHORT_TBAN; break;\r
+                                                       case 'l': uInfo->flags |= USER_LONG_TBAN; break;\r
+                                                       case 'd': uInfo->flags |= CHECK_KILLED(uInfo) ? USER_GLINE : USER_KILL; break;\r
+                                               }\r
+\r
+                                               spamserv_delete_spamNode(uInfo, sNode);\r
+                                               uInfo->warnlevel += SPAM_WARNLEVEL;\r
+                                               violation = 1;\r
+                                       }\r
+                               }\r
+                               else\r
+                               {\r
+                                       sNode->crc32 = crc;                                     \r
+                                       sNode->count = 1;\r
+                               }\r
+                       }\r
+               }\r
+       }\r
+\r
+       if(CHECK_FLOOD(cInfo))\r
+       {\r
+               if(!(fNode = uInfo->flood))\r
+               {\r
+                       spamserv_create_floodNode(channel, user, &uInfo->flood);\r
+               }\r
+               else\r
+               {\r
+                       for(; fNode; fNode = fNode->next)\r
+                               if(fNode->channel == channel)\r
+                                       break;\r
+                               \r
+                       if(!fNode)\r
+                       {\r
+                               spamserv_create_floodNode(channel, user, &uInfo->flood);\r
+                       }\r
+                       else\r
+                       {\r
+                               if(((now - fNode->time) < FLOOD_EXPIRE))\r
+                               {\r
+                                       fNode->count++;\r
+                                       \r
+                                       if(fNode->count == FLOOD_MAX_LINES - 1)\r
+                                       {\r
+                                               uInfo->warnlevel += FLOOD_WARNLEVEL;\r
+\r
+                                               if(uInfo->warnlevel < MAX_WARNLEVEL)\r
+                                                       spamserv_notice(user, spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_FLOOD, spamserv_conf.network_rules);\r
+                                       }\r
+                                       else if(fNode->count > FLOOD_MAX_LINES)\r
+                                       {\r
+                                               switch(cInfo->info[ci_WarnReaction])\r
+                                               {\r
+                                                       case 'k': uInfo->flags |= USER_KICK; break;\r
+                                                       case 'b': uInfo->flags |= USER_KICKBAN; break;\r
+                                                       case 's': uInfo->flags |= USER_SHORT_TBAN; break;\r
+                                                       case 'l': uInfo->flags |= USER_LONG_TBAN; break;\r
+                                                       case 'd': uInfo->flags |= CHECK_KILLED(uInfo) ? USER_GLINE : USER_KILL; break;\r
+                                               }\r
+\r
+                                               spamserv_delete_floodNode(&uInfo->flood, fNode);\r
+                                               uInfo->warnlevel += FLOOD_WARNLEVEL;\r
+                                               violation = 2;                                          \r
+                                       }\r
+                               }\r
+\r
+                               fNode->time = now;\r
+                       }\r
+               }\r
+       }\r
+\r
+       dict_iterator_t it;\r
+\r
+       for (it = dict_first(cInfo->badwords); it; it = iter_next(it)) {\r
+               struct badword *badword = iter_data(it);\r
+               if(match_ircglob(text, badword->badword_mask)) {\r
+                       spamserv_detected_badword(user, channel, badword);\r
+               }\r
+       }\r
+\r
+       if(CHECK_ADV(cInfo) && check_advertising(cInfo, text))\r
+       {\r
+               if(CHECK_ADV_WARNED(uInfo))\r
+               {\r
+                       switch(cInfo->info[ci_AdvReaction])\r
+                       {\r
+                               case 'k': uInfo->flags |= USER_KICK; break;\r
+                               case 'b': uInfo->flags |= USER_KICKBAN; break;\r
+                               case 's': uInfo->flags |= USER_SHORT_TBAN; break;\r
+                               case 'l': uInfo->flags |= USER_LONG_TBAN; break;\r
+                               case 'd': uInfo->flags |= CHECK_KILLED(uInfo) ? USER_GLINE : USER_KILL; break;\r
+                       }\r
+\r
+                       uInfo->warnlevel += ADV_WARNLEVEL;\r
+                       violation = 3;\r
+               }\r
+               else\r
+               {               \r
+                       uInfo->flags |= USER_ADV_WARNED;\r
+                       uInfo->lastadv = now;\r
+                       uInfo->warnlevel += ADV_WARNLEVEL;\r
+\r
+                       if(uInfo->warnlevel < MAX_WARNLEVEL)\r
+                               spamserv_notice(user, spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_ADV, spamserv_conf.network_rules);\r
+               }               \r
+       }\r
+\r
+       if(!CHECK_WARNED(uInfo) && !CHECK_KILL(uInfo) && !CHECK_GLINE(uInfo) && uInfo->warnlevel == MAX_WARNLEVEL)\r
+       {\r
+               uInfo->flags |= USER_WARNED;\r
+               snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES_2 : SSMSG_WARNING_2, spamserv_conf.network_rules);\r
+               irc_notice(spamserv, user->numeric, reason);\r
+               irc_privmsg(spamserv, user->numeric, reason);\r
+       }\r
+       else if(uInfo->warnlevel > MAX_WARNLEVEL)\r
+       {\r
+               if(CHECK_KILLED(uInfo))\r
+                       uInfo->flags |= USER_GLINE;\r
+               else\r
+                       uInfo->flags |= USER_KILL;\r
+\r
+               violation = 5;\r
+       }\r
+\r
+       if(!violation)\r
+               return;\r
+\r
+       switch(violation)\r
+       {\r
+               case 1: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_SPAM, spamserv_conf.network_rules); break;\r
+               case 2: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_FLOOD, spamserv_conf.network_rules); break;\r
+               case 3: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_ADV, spamserv_conf.network_rules); break;\r
+               default: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES_2 : SSMSG_WARNING_2, spamserv_conf.network_rules); break;\r
+       }\r
+\r
+       if(CHECK_GLINE(uInfo))\r
+       {\r
+               int size = strlen(user->hostname) + 3;\r
+               char *mask = alloca(size);\r
+               snprintf(mask, size, "*@%s", user->hostname);\r
+               gline_add(spamserv->nick, mask, spamserv_conf.gline_duration, reason, now, now, 0, 1);\r
+               spamserv_debug(SSMSG_DEBUG_GLINE, user->nick, user->hostname, channel->name);\r
+       }\r
+       else if(CHECK_KILL(uInfo))\r
+       {\r
+               DelUser(user, spamserv, 1, reason);\r
+               spamserv_debug(SSMSG_DEBUG_KILL, user->nick, channel->name);\r
+       }\r
+       else if(CHECK_LONG_TBAN(uInfo))\r
+       {\r
+               spamserv_punish(channel, user, spamserv_conf.long_ban_duration, reason, 1);\r
+       }\r
+       else if(CHECK_SHORT_TBAN(uInfo))\r
+       {\r
+               spamserv_punish(channel, user, spamserv_conf.short_ban_duration, reason, 1);\r
+       }\r
+       else if(CHECK_KICKBAN(uInfo))\r
+       {\r
+               spamserv_punish(channel, user, 0, reason, 1);\r
+       }\r
+       else if(CHECK_KICK(uInfo))\r
+       {\r
+               spamserv_punish(channel, user, 0, reason, 0);\r
+       }\r
+}\r
+\r
+static int\r
+spamserv_saxdb_read_shitlist(const char *name, void *data, void *extra)\r
+{\r
+       struct record_data *rd = data;\r
+       struct chanInfo *chan = extra;\r
+       char *badword;\r
+       char *triggered, *action;\r
+\r
+       if (rd->type == RECDB_OBJECT) {\r
+               dict_t obj = GET_RECORD_OBJECT(rd);\r
+               /* new style structure */\r
+               badword = database_get_data(obj, KEY_BADWORD_MASK, RECDB_QSTRING);\r
+               triggered = database_get_data(obj, KEY_BADWORD_TRIGGERED, RECDB_QSTRING);\r
+               action = database_get_data(obj, KEY_BADWORD_ACTION, RECDB_QSTRING);\r
+\r
+               add_badword(chan, badword, strtoul(triggered, NULL, 0), strtoul(action, NULL, 0), name);\r
+       }\r
+       return 0;\r
+}\r
+\r
+static int\r
+spamserv_saxdb_read(struct dict *database)\r
+{\r
+       dict_iterator_t it;\r
+       struct dict *badwords;\r
+       struct record_data *hir;\r
+       struct chanNode *channel;\r
+       struct chanInfo *cInfo;\r
+       struct string_list *strlist;\r
+       unsigned int flags,exceptlevel,badwordid;\r
+       char *str, *info;\r
+       unsigned long expiry;    \r
+\r
+       for(it = dict_first(database); it; it = iter_next(it))\r
+       {\r
+               hir = iter_data(it);\r
+\r
+               if(hir->type != RECDB_OBJECT)\r
+               {\r
+                       log_module(SS_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, iter_key(it));\r
+                       continue;\r
+               }\r
+\r
+               channel = GetChannel(iter_key(it));\r
+               strlist = database_get_data(hir->d.object, KEY_EXCEPTIONS, RECDB_STRING_LIST);\r
+\r
+               str = database_get_data(hir->d.object, KEY_FLAGS, RECDB_QSTRING);\r
+               flags = str ? atoi(str) : 0;\r
+\r
+               info = database_get_data(hir->d.object, KEY_INFO, RECDB_QSTRING);\r
+        str = database_get_data(hir->d.object, KEY_EXCEPTLEVEL, RECDB_QSTRING);\r
+        exceptlevel = str ? atoi(str) : 400;\r
+        \r
+               str = database_get_data(hir->d.object, KEY_EXPIRY, RECDB_QSTRING);\r
+               expiry = str ? strtoul(str, NULL, 0) : 0;\r
+\r
+               if(channel && info)\r
+               {\r
+                       if((cInfo = spamserv_register_channel(channel, strlist, flags, info)))\r
+                       {\r
+                               /* if the channel is suspended and expiry = 0 it means: channel will\r
+                                  never expire ! it does NOT mean, the channel is not suspended */\r
+                               if(CHECK_SUSPENDED(cInfo) && expiry && (expiry < now))\r
+                               {\r
+                                       cInfo->flags &= ~CHAN_SUSPENDED;\r
+                                       spamserv_join_channel(cInfo->channel);\r
+                               }\r
+                               else if(!CHECK_SUSPENDED(cInfo))\r
+                                       spamserv_join_channel(cInfo->channel);\r
+                               else\r
+                                       cInfo->suspend_expiry = expiry;                 \r
+                cInfo->exceptlevel=exceptlevel;\r
+                cInfo->badwords = dict_new();\r
+                str = database_get_data(hir->d.object, KEY_LASTBADWORDID, RECDB_QSTRING);\r
+                badwordid = str ? atoi(str) : 0;\r
+                cInfo->last_badword_id = badwordid;\r
+                if ((badwords = database_get_data(hir->d.object, KEY_BADWORDS, RECDB_OBJECT)))\r
+                       dict_foreach(badwords, spamserv_saxdb_read_shitlist, cInfo);\r
+                       }\r
+               }\r
+               else\r
+                       log_module(SS_LOG, LOG_ERROR, "Couldn't register channel %s. Channel or info invalid.", iter_key(it));  \r
+       }\r
+\r
+       return 0;\r
+}\r
+\r
+static int\r
+spamserv_saxdb_write(struct saxdb_context *ctx)\r
+{\r
+       dict_iterator_t it;\r
+\r
+       for(it = dict_first(registered_channels_dict); it; it = iter_next(it))\r
+       {\r
+               struct chanInfo *cInfo = iter_data(it);\r
+\r
+               saxdb_start_record(ctx, cInfo->channel->name, 1);\r
+\r
+               if(cInfo->exceptions->used)\r
+                       saxdb_write_string_list(ctx, KEY_EXCEPTIONS, cInfo->exceptions);\r
+\r
+               if(cInfo->flags)\r
+                       saxdb_write_int(ctx, KEY_FLAGS, cInfo->flags);  \r
+\r
+               saxdb_write_string(ctx, KEY_INFO, cInfo->info);         \r
+        saxdb_write_int(ctx, KEY_EXCEPTLEVEL, cInfo->exceptlevel);             \r
+\r
+               if(cInfo->suspend_expiry)\r
+                       saxdb_write_int(ctx, KEY_EXPIRY, cInfo->suspend_expiry);\r
+\r
+               saxdb_write_int(ctx, KEY_LASTBADWORDID, cInfo->last_badword_id);\r
+               saxdb_start_record(ctx, KEY_BADWORDS, 1);\r
+               dict_iterator_t itbad;\r
+               for (itbad = dict_first(cInfo->badwords); itbad; itbad = iter_next(itbad)) {\r
+                       struct badword *badword = iter_data(itbad);\r
+                       saxdb_start_record(ctx, badword->id, 1);\r
+                       saxdb_write_string(ctx, KEY_BADWORDID, badword->id);\r
+                       saxdb_write_string(ctx, KEY_BADWORD_MASK, badword->badword_mask);\r
+                       saxdb_write_int(ctx, KEY_BADWORD_ACTION, badword->action);\r
+                       saxdb_write_int(ctx, KEY_BADWORD_TRIGGERED, badword->triggered);\r
+                       saxdb_end_record(ctx);\r
+               }\r
+               saxdb_end_record(ctx);\r
+               saxdb_end_record(ctx);          \r
+       }\r
+       return 0;\r
+}\r
+\r
+static void\r
+spamserv_conf_read(void)\r
+{\r
+       dict_t conf_node;\r
+       const char *str; \r
+\r
+       if(!(conf_node = conf_get_data(SPAMSERV_CONF_NAME, RECDB_OBJECT)))\r
+       {\r
+               log_module(SS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", SPAMSERV_CONF_NAME);\r
+               return;\r
+       }\r
+\r
+       str = database_get_data(conf_node, KEY_DEBUG_CHANNEL, RECDB_QSTRING);\r
+\r
+       if(str)\r
+       {\r
+               spamserv_conf.debug_channel = AddChannel(str, now, "+tinms", NULL);\r
+\r
+               if(spamserv_conf.debug_channel)\r
+                       spamserv_join_channel(spamserv_conf.debug_channel);\r
+       }\r
+       else\r
+       {\r
+               spamserv_conf.debug_channel = NULL;\r
+       }\r
+\r
+       spamserv_conf.global_exceptions = database_get_data(conf_node, KEY_GLOBAL_EXCEPTIONS, RECDB_STRING_LIST);\r
+\r
+       str = database_get_data(conf_node, KEY_NETWORK_RULES, RECDB_QSTRING);\r
+       spamserv_conf.network_rules = str ? str : NULL;\r
+\r
+       str = database_get_data(conf_node, KEY_TRIGGER, RECDB_QSTRING);\r
+       spamserv_conf.trigger = str ? str[0] : 0;\r
+\r
+       str = database_get_data(conf_node, KEY_SHORT_BAN_DURATION, RECDB_QSTRING);\r
+       spamserv_conf.short_ban_duration = str ? ParseInterval(str) : ParseInterval("15m");\r
+\r
+       str = database_get_data(conf_node, KEY_LONG_BAN_DURATION, RECDB_QSTRING);\r
+       spamserv_conf.long_ban_duration = str ? ParseInterval(str) : ParseInterval("1h");\r
+\r
+       str = database_get_data(conf_node, KEY_GLINE_DURATION, RECDB_QSTRING);\r
+       spamserv_conf.gline_duration = str ? ParseInterval(str) : ParseInterval("1h");\r
+\r
+       str = database_get_data(conf_node, KEY_EXCEPTION_MAX, RECDB_QSTRING);\r
+       spamserv_conf.exception_max = str ? strtoul(str, NULL, 0) : 10;\r
+\r
+       str = database_get_data(conf_node, KEY_EXCEPTION_MIN_LEN, RECDB_QSTRING);\r
+       spamserv_conf.exception_min_len = str ? strtoul(str, NULL, 0) : 4;\r
+\r
+       str = database_get_data(conf_node, KEY_EXCEPTION_MAX_LEN, RECDB_QSTRING);\r
+       spamserv_conf.exception_max_len = str ? strtoul(str, NULL, 0) : 15;\r
+\r
+       str = database_get_data(conf_node, KEY_ADV_CHAN_MUST_EXIST, RECDB_QSTRING);\r
+       spamserv_conf.adv_chan_must_exist = str ? enabled_string(str) : 1;\r
+\r
+       str = database_get_data(conf_node, KEY_STRIP_MIRC_CODES, RECDB_QSTRING);\r
+       spamserv_conf.strip_mirc_codes = str ? enabled_string(str) : 0;\r
+\r
+       str = database_get_data(conf_node, KEY_ALLOW_MOVE_MERGE, RECDB_QSTRING);\r
+       spamserv_conf.allow_move_merge = str ? enabled_string(str) : 0;\r
+}\r
+\r
+static void\r
+spamserv_db_cleanup(void)\r
+{\r
+       dict_iterator_t it;\r
+\r
+       while((it = dict_first(registered_channels_dict)))\r
+       {\r
+               spamserv_unregister_channel(iter_data(it));\r
+       }\r
+\r
+       while((it = dict_first(killed_users_dict)))\r
+       {\r
+               free(iter_data(it));\r
+       }\r
+       \r
+       dict_delete(registered_channels_dict);\r
+       dict_delete(connected_users_dict);\r
+       dict_delete(killed_users_dict);\r
+}\r
+\r
+void\r
+init_spamserv(const char *nick)\r
+{\r
+       if(!nick)\r
+               return;\r
+\r
+       const char *modes = conf_get_data("services/spamserv/modes", RECDB_QSTRING);    \r
+       spamserv = AddLocalUser(nick, nick, NULL, "Anti Spam Services", modes);\r
+       spamserv_service = service_register(spamserv);\r
+       service_register(spamserv)->trigger = spamserv_conf.trigger;\r
+\r
+       conf_register_reload(spamserv_conf_read);\r
+\r
+       SS_LOG = log_register_type("SpamServ", "file:spamserv.log");    \r
+\r
+       registered_channels_dict = dict_new();\r
+       connected_users_dict = dict_new();\r
+       killed_users_dict = dict_new();\r
+\r
+       reg_new_user_func(spamserv_new_user_func);\r
+       reg_del_user_func(spamserv_del_user_func);\r
+       reg_nick_change_func(spamserv_nick_change_func);\r
+       reg_join_func(spamserv_user_join);\r
+       reg_part_func(spamserv_user_part);\r
+\r
+       timeq_add(now + FLOOD_TIMEQ_FREQ, timeq_flood, NULL);\r
+       timeq_add(now + JOINFLOOD_TIMEQ_FREQ, timeq_joinflood, NULL);\r
+       timeq_add(now + ADV_TIMEQ_FREQ, timeq_adv, NULL);\r
+       timeq_add(now + WARNLEVEL_TIMEQ_FREQ, timeq_warnlevel, NULL);\r
+       timeq_add(now + KILL_TIMEQ_FREQ, timeq_kill, NULL);\r
+\r
+       spamserv_module = module_register("SpamServ", SS_LOG, "spamserv.help", NULL);\r
+       modcmd_register(spamserv_module, "REGISTER", cmd_register, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, "flags", "+acceptchan,+helping", NULL);\r
+       modcmd_register(spamserv_module, "UNREGISTER", cmd_unregister, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, "flags", "+loghostmask", NULL);\r
+       modcmd_register(spamserv_module, "ADDEXCEPTION", cmd_addexception, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);\r
+       modcmd_register(spamserv_module, "DELEXCEPTION", cmd_delexception, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);\r
+       modcmd_register(spamserv_module, "STATUS", cmd_status, 1, 0, NULL);\r
+    modcmd_register(spamserv_module, "ADDBAD", cmd_addbad, 2, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);\r
+    modcmd_register(spamserv_module, "DELBAD", cmd_delbad, 2, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);\r
+    modcmd_register(spamserv_module, "SETBAD", cmd_setbad, 2, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);\r
+    modcmd_register(spamserv_module, "LISTBAD", cmd_listbad, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);\r
+       modcmd_register(spamserv_module, "SET", cmd_set, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);\r
+       modcmd_register(spamserv_module, "SET SPAMLIMIT", opt_spamlimit, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);\r
+       modcmd_register(spamserv_module, "SET ADVREACTION", opt_advreaction, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);\r
+       modcmd_register(spamserv_module, "SET WARNREACTION", opt_warnreaction, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);\r
+       modcmd_register(spamserv_module, "SET ADVSCAN", opt_advscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);\r
+       modcmd_register(spamserv_module, "SET SPAMSCAN", opt_spamscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);\r
+       modcmd_register(spamserv_module, "SET FLOODSCAN", opt_floodscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);\r
+       modcmd_register(spamserv_module, "SET JOINFLOODSCAN", opt_joinflood, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);\r
+       modcmd_register(spamserv_module, "SET SCANCHANOPS", opt_scanops, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);\r
+       modcmd_register(spamserv_module, "SET SCANVOICED", opt_scanvoiced, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);\r
+    modcmd_register(spamserv_module, "SET EXCEPTLEVEL", opt_exceptlevel, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);\r
+\r
+       saxdb_register("SpamServ", spamserv_saxdb_read, spamserv_saxdb_write);\r
+       reg_exit_func(spamserv_db_cleanup);\r
+       message_register_table(msgtab);\r
+       crc32_init();\r
+}\r
+=======
 /* spamserv.c - anti spam service
  * Copyright 2004 feigling
  *
@@ -2065,3 +4521,4 @@ init_spamserv(const char *nick)
        message_register_table(msgtab);
        crc32_init();
 }
+>>>>>>> master
index b6b6ac61c6ccda29b71924695938c940f8d6c6c7..d74c85dbd383b844f7b15f558c0c0c4aa03593ba 100644 (file)
@@ -58,14 +58,21 @@ enum channelinfo
 #define CHECK_VOICED(x)                ((x)->flags & CHAN_SCAN_VOICED)
 #define CHECK_SUSPENDED(x)     ((x)->flags & CHAN_SUSPENDED)
 
+#define BADACTION_KICK   0
+#define BADACTION_BAN    1
+#define BADACTION_KILL   2
+#define BADACTION_GLINE  3
+
 struct chanInfo
 {
     struct chanNode        *channel;
     struct string_list     *exceptions;
+    dict_t                        badwords;
     unsigned int           flags : 30;
     unsigned int           exceptlevel;
     char                   info[CHAN_INFO_SIZE];
     unsigned long          suspend_expiry;
+    unsigned int                  last_badword_id;
 };
 
 /***********************************************/
@@ -151,6 +158,13 @@ struct userInfo
        time_t                  lastadv;
 };
 
+struct badword {
+    char *id;
+    char *badword_mask;
+    unsigned int triggered : 29;
+    unsigned int action : 3;
+};
+
 /***********************************************/
 /*                 Other Stuff                 */
 /***********************************************/
@@ -168,5 +182,6 @@ void spamserv_channel_message(struct chanNode *channel, struct userNode *user, c
 void spamserv_cs_suspend(struct chanNode *channel, time_t expiry, int suspend, char *reason);
 int  spamserv_cs_move_merge(struct userNode *user, struct chanNode *channel, struct chanNode *target, int move);
 void spamserv_cs_unregister(struct userNode *user, struct chanNode *channel, enum cs_unreg type, char *reason);
+static struct badword *add_badword(struct chanInfo *cInfo, const char *badword_mask, unsigned int triggered, unsigned int action, const char *id);
 
-#endif
\ No newline at end of file
+#endif
index 23ee5ceb3ec9c8ecdb0329f599882c379f286395..63676f4d3a791dc871d5fe50f08d80fd4879c43c 100644 (file)
@@ -6,19 +6,23 @@
         "  SET           Changes various channel settings.",
         "  STATUS        Shows general information about $X.",
         "  VERSION       Prints the srvx and $X version information.",
+        "  ADDBAD        Adds a new badword with the given mask to the badwordlist of the specified channel.",
+        "  DELBAD        Deletes the given badword from the badwordlist of the specified channel.",
+        "  SETBAD        This command will set various badword options.",
+        "  LISTBAD       Causes $X to send you the badwordlist of the specified channel.",
         "$bStaff Commands:$b",
         "  REGISTER      Registers a new channel.",
         "  UNREGISTER    Removes $X from a registered channel.");
 "ADDEXCEPTION" ("/msg $X ADDEXCEPTION [word]",
         "Without an argument, it will show all existing exceptions.",
-         "With an argument, it will add the given word to the exception list.",
-         "$X checks, if one of the words in the sentence of a user is in the exception list; if so, $X will not punish the user, doesn't matter, if it's a bad advertisement.",
-         "This means, you have to make sure, all exceptions are adequate.",
-         "$bFirst example$b: You added the word \"gamesurge.net\" to the exception list and someone posts \"www.gamesurge.net/aup\", he won't get punished.",
-         "$bSecond example$b: You added the word \"support\" to the list and someone tells another person to join #support, he won't get punished.",
-         "$bThird example$b: You added \"GameSurge\" to the list and someone posts \"JOIN #channelxyz on GameSurge\", he will NOT get punished, because the word \"GameSurge\" is in the sentence.",
-         "If he would say \"JOIN #channelxyz\", $X would punish him.",
-         "$uSee Also:$u delexception");
+      "With an argument, it will add the given word to the exception list.",
+      "$X checks, if one of the words in the sentence of a user is in the exception list; if so, $X will not punish the user, doesn't matter, if it's a bad advertisement.",
+      "This means, you have to make sure, all exceptions are adequate.",
+      "$bFirst example$b: You added the word \"gamesurge.net\" to the exception list and someone posts \"www.gamesurge.net/aup\", he won't get punished.",
+      "$bSecond example$b: You added the word \"support\" to the list and someone tells another person to join #support, he won't get punished.",
+      "$bThird example$b: You added \"GameSurge\" to the list and someone posts \"JOIN #channelxyz on GameSurge\", he will NOT get punished, because the word \"GameSurge\" is in the sentence.",
+      "If he would say \"JOIN #channelxyz\", $X would punish him.",
+      "$uSee Also:$u delexception");
 "DELEXCEPTION" ("/msg $X DELEXCEPTION",
         "Without an argument, it will show all existing exceptions.",
         "With an argument, it will delete the given word from the exception list.",
         "If you are not network staff, you must add $bCONFIRM$b to the end of your line to confirm unregistration.",
         "$bSee Also:$b register");
 "VERSION" ("/msg $X VERSION",
-        "$bVERSION$b causes $X to send you the srvx version and some additional version information about $X.");
\ No newline at end of file
+        "$bVERSION$b causes $X to send you the srvx version and some additional version information about $X.");
+"LISTBAD" ("/msg $X <#channel> LISTBAD",
+        "$bLIST$b causes $X to send you the badwordlist of the specified channel");
+"ADDBAD" ("/msg $X <#channel> ADDBAD <MASK>",
+        "$bADDBAD$b adds a new badword with the given mask to the badwordlist of the specified channel");
+"DELBAD" ("/msg $X <#channel> DELBAD <badwordid>",
+        "$bDELBAD$b deletes the given badword from the badwordlist of the specified channel");
+"SETBAD" ("/msg $X SETBAD <#channel> <badwordid> [<parameter> [setting]]",
+        "This command will set various badword options. With no parameters, it will show the current values of all settings of the given badword.",
+        "Only channel owners and coowners may change settings.",
+        "MASK:      Mask of the given badword.",
+        "ACTION:    What happens when someone used the given badword.",
+        "$uSee Also:$u setbad action");
+"SETBAD ACTION" ("/msg $X SETBAD <#channel> <badwordid> ACTION <KICK|BAN|KILL|GLINE>",
+        "What happens when someone used the given badword.");
\ No newline at end of file