diff -NuwrU 4 srvx-1.4.0-rc3/spamserv.help srvx-1.4.0-rc3-ss/spamserv.help
--- srvx-1.4.0-rc3/spamserv.help	2010-03-07 18:19:32.000000000 +0100
+++ srvx-1.4.0-rc3-ss/spamserv.help	2010-03-07 18:19:18.000000000 +0100
@@ -0,0 +1,91 @@
+"<INDEX>" ("$b$X Help$b",
+        "The $b$X$b service checks the channel for spam, flood, joinflood and disallowed advertisements.",
+        "$bUser Commands:$b",
+        "  ADDEXCEPTION  Adds a word to the exception list.",
+        "  DELEXCEPTION  Deletes a word from the exception list.",
+        "  SET           Changes various channel settings.",
+        "  STATUS        Shows general information about $X.",
+        "  VERSION       Prints the srvx and $X version information.",
+        "$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");
+"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.",
+        "$uSee Also:$u addexception");
+"SET" ("/msg $X SET <#channel> [<parameter> [setting]]",
+        "This command will set various channel options. With no arguments, it will show the current values of all channel options.",
+        "Only channel owners and coowners may change settings.",
+        "SPAMLIMIT:      Number of equal lines, a user may send.",
+        "ADVREACTION:    What happens when someone advertises after warning.",
+        "WARNREACTION:   What happens when someone continues spamming/flooding after warning.",
+        "ADVSCAN:        Enables/Disables scanning for advertisements.",
+        "SPAMSCAN:       Enables/Disables scanning for spam.",
+        "FLOODSCAN:      Enables/Disables scanning for flood.",
+        "JOINFLOODSCAN:  Enables/Disables scanning for joinflood.",
+        "SCANCHANOPS:    Indicates whether $X has to scan messages from channel ops.",
+        "SCANVOICED:     Indicates whether $X has to scan messages from voiced users.",
+        "$uSee Also:$u set spamlimit, set advreaction, set warnreaction, set advscan, set spamscan, set floodscan, set joinfloodscan, set scanchanops, set scanvoiced");
+"SET SPAMLIMIT" ("/msg $X SET <#channel> SPAMLIMIT <value>",
+        "You can specify the number of equal messages, a user may send.  Valid settings are:",
+        "$b0$b  Users may send the same message $b2$b times.",
+        "$b1$b  Users may send the same message $b3$b times.",
+        "$b2$b  Users may send the same message $b4$b times.",
+        "$b3$b  Users may send the same message $b5$b times.",
+        "$b4$b  Users may send the same message $b6$b times.",
+        "$uSee Also:$u set spamscan");
+"SET ADVREACTION" ("/msg $X SET <#channel> ADVREACTION <value>",
+        "This setting controls what happens to those who send disallowed advertisements to the channel after a warning:",
+        "$b0$b  Kick on disallowed advertising.",
+        "$b1$b  Kickban on disallowed advertising.",
+        "$b2$b  Short timed ban (default: 15 minutes) on disallowed advertising.",
+        "$b3$b  Long timed ban (default: 1 hour) on disallowed advertising.",
+        "$b4$b  Kill on disallowed advertising. Only settable by irc operators.",
+        "$uSee Also:$u set advscan");
+"SET WARNREACTION" ("/msg $X SET <#channel> WARNREACTION <value>",
+        "This setting controls what happens to those who spam or flood the channel after a warning:",
+        "$b0$b  Kick after warning.",
+        "$b1$b  Kickban after warning.",
+        "$b2$b  Short timed ban (default: 15 minutes) after warning.",
+        "$b3$b  Long timed ban (default: 1 hour) after warning.",
+        "$b4$b  Kill after warning. Only settable by irc operators.",
+        "$uSee Also:$u set spamscan, set floodscan");
+"SET ADVSCAN" ("/msg $X SET <#channel> ADVSCAN <1/0>",
+        "If this setting is enabled, $X checks all messages for advertisements.",
+        "Advertisements are: www.*, http:*, ftp.*, ftp:* and #*; e.g. #srvx, http://www.srvx.net etc ..");
+"SET SPAMSCAN" ("/msg $X SET <#channel> SPAMSCAN <1/0>",
+        "If this setting is enabled, $X checks all incoming channel messages for spam.",
+        "Posting the same message multiple times is considered as spam, which means, if someone posts the same message more than the number of times, which is allowed (/msg $X set SPAMLIMIT), $X will punish him.");
+"SET FLOODSCAN" ("/msg $X SET <#channel> FLOODSCAN <1/0>",
+        "If this setting is enabled, $X checks, if a person tries to flood the channel.",
+        "Posting messages in a small amount of time is considered as flood, so if someone tries to flood the channel, $X will punish him.");
+"SET JOINFLOODSCAN" ("/msg $X SET <#channel> JOINFLOODSCAN <1/0>",
+        "If this setting is enabled, $X checks, if a person joins the channel more than one time.",
+        "Normally users join a channel and stay in the channel or part and do not rejoin after a few seconds.",
+        "If they want to cause trouble, they join/part the channel very often. $X will punish every user, who does that.");
+"SET SCANCHANOPS" ("/msg $X SET <#channel> SCANCHANOPS <1/0>",
+        "If this setting is disabled, $X doesn't check messages from oped users for spam, flood and advertisements.");
+"SET SCANVOICED" ("/msg $X SET <#channel> SCANVOICED <1/0>",
+        "If this setting is disabled, $X doesn't check messages from voiced users for spam, flood and advertisements.");
+"REGISTER" ("/msg $X REGISTER <#channel>",
+        "Registers a channel with $X.",
+        "The Channel must be registered with $C and may not be suspended.",
+        "$uSee Also:$u unregister");
+"STATUS" ("/msg $X STATUS [MEMORY|CHANNELS]",
+        "$bSTATUS$b shows you general information about $X. An irc operator can get information about the memory usage and a list of all registered channels.");
+"UNREGISTER" ("/msg $X UNREGISTER <#channel> [CONFIRM]",
+        "Removes $X from the given channel.",
+        "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.");
diff -NuwrU 4 srvx-1.4.0-rc3/src/chanserv.c srvx-1.4.0-rc3-ss/src/chanserv.c
--- srvx-1.4.0-rc3/src/chanserv.c	2008-07-12 05:49:09.000000000 +0200
+++ srvx-1.4.0-rc3-ss/src/chanserv.c	2010-03-07 18:08:23.000000000 +0100
@@ -23,8 +23,9 @@
 #include "global.h"
 #include "modcmd.h"
 #include "opserv.h" /* for opserv_bad_channel() */
 #include "saxdb.h"
+#include "spamserv.h"
 #include "timeq.h"
 
 #define CHANSERV_CONF_NAME  "services/chanserv"
 
@@ -653,9 +654,8 @@
 struct chanData *channelList;
 static struct module *chanserv_module;
 static unsigned int userCount;
 
-#define GetChannelUser(channel, handle) _GetChannelUser(channel, handle, 1, 0)
 #define GetChannelAccess(channel, handle) _GetChannelUser(channel, handle, 0, 0)
 #define GetTrueChannelAccess(channel, handle) _GetChannelUser(channel, handle, 0, 1)
 
 unsigned short
@@ -1173,14 +1173,17 @@
 
     free(user->info);
     free(user);
     if(do_gc && !channel->users && !IsProtected(channel))
+    {
+        spamserv_cs_unregister(NULL, channel->channel, lost_all_users, NULL);
         unregister_channel(channel, "lost all users.");
 }
+}
 
 static void expire_ban(void *data);
 
-static struct banData*
+struct banData*
 add_channel_ban(struct chanData *channel, const char *mask, char *owner, unsigned long set, unsigned long triggered, unsigned long expires, char *reason)
 {
     struct banData *bd;
     unsigned int ii, l1, l2;
@@ -1386,8 +1389,9 @@
             continue;
 
         /* Unregister the channel */
         log_module(CS_LOG, LOG_INFO, "(%s) Channel registration expired.", channel->channel->name);
+        spamserv_cs_unregister(NULL, channel->channel, expire, NULL);
         unregister_channel(channel, "registration expired.");
     }
 
     if(chanserv_conf.channel_expire_frequency)
@@ -2169,21 +2173,50 @@
 
     sprintf(reason, "unregistered by %s.", user->handle_info->handle);
     name = strdup(channel->name);
     unregister_channel(cData, reason);
+    spamserv_cs_unregister(user, channel, manually, "unregistered");
     reply("CSMSG_UNREG_SUCCESS", name);
     free(name);
     return 1;
 }
 
+static void
+ss_cs_join_channel(struct chanNode *channel, int spamserv_join)
+{
+    extern struct userNode *spamserv;
+    struct mod_chanmode *change;
+
+    if(spamserv && spamserv_join && get_chanInfo(channel->name))
+    {
+        change = mod_chanmode_alloc(2);
+        change->argc = 2;
+        change->args[0].mode = MODE_CHANOP;
+        change->args[0].u.member = AddChannelUser(chanserv, channel);
+        change->args[1].mode = MODE_CHANOP;
+        change->args[1].u.member = AddChannelUser(spamserv, channel);
+    }
+    else
+    {
+        change = mod_chanmode_alloc(1);
+        change->argc = 1;
+        change->args[0].mode = MODE_CHANOP;
+        change->args[0].u.member = AddChannelUser(chanserv, channel);
+    }
+
+   mod_chanmode_announce(chanserv, channel, change);
+ 	mod_chanmode_free(change);
+}
+
 static CHANSERV_FUNC(cmd_move)
 {
     struct mod_chanmode change;
     struct chanNode *target;
     struct modeNode *mn;
     struct userData *uData;
     char reason[MAXLEN];
     struct do_not_register *dnr;
+    int chanserv_join = 0, spamserv_join;
 
     REQUIRE_PARAMS(2);
 
     if(IsProtected(channel->channel_info))
@@ -2223,9 +2256,9 @@
     if(!(target = GetChannel(argv[1])))
     {
         target = AddChannel(argv[1], now, NULL, NULL);
         if(!IsSuspended(channel->channel_info))
-            AddChannelUser(chanserv, target);
+            chanserv_join = 1;
     }
     else if(target->channel_info)
     {
         reply("CSMSG_ALREADY_REGGED", target->name);
@@ -2237,14 +2270,9 @@
         reply("CSMSG_MUST_BE_OPPED", target->name);
         return 0;
     }
     else if(!IsSuspended(channel->channel_info))
-    {
-        change.argc = 1;
-        change.args[0].mode = MODE_CHANOP;
-        change.args[0].u.member = AddChannelUser(chanserv, target);
-        mod_chanmode_announce(chanserv, target, &change);
-    }
+        chanserv_join = 1;
 
     if(off_channel > 0)
     {
         /* Clear MODE_REGISTERED from old channel, add it to new. */
@@ -2262,9 +2290,12 @@
     target->channel_info = channel->channel_info;
     target->channel_info->channel = target;
     channel->channel_info = NULL;
 
-    reply("CSMSG_MOVE_SUCCESS", target->name);
+    spamserv_join = spamserv_cs_move_merge(user, channel, target, 1);
+ 	
+ 	if(chanserv_join)
+ 		ss_cs_join_channel(target, spamserv_join);
 
     sprintf(reason, "%s moved to %s by %s.", channel->name, target->name, user->handle_info->handle);
     if(!IsSuspended(target->channel_info))
     {
@@ -2274,8 +2305,9 @@
     }
     UnlockChannel(channel);
     LockChannel(target);
     global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+    reply("CSMSG_MOVE_SUCCESS", target->name);
     return 1;
 }
 
 static void
@@ -2507,8 +2539,9 @@
     }
 
     /* Merge the channel structures and associated data. */
     merge_channel(channel->channel_info, target->channel_info);
+    spamserv_cs_move_merge(user, channel, target, 0);
     sprintf(reason, "merged into %s by %s.", target->name, user->handle_info->handle);
     unregister_channel(channel->channel_info, reason);
     reply("CSMSG_MERGE_SUCCESS", target->name);
     return 1;
@@ -4939,14 +4972,10 @@
     suspended->cData->channel = channel;
     suspended->cData->flags &= ~CHANNEL_SUSPENDED;
     if(!IsOffChannel(suspended->cData))
     {
-        struct mod_chanmode change;
-        mod_chanmode_init(&change);
-        change.argc = 1;
-        change.args[0].mode = MODE_CHANOP;
-        change.args[0].u.member = AddChannelUser(chanserv, channel);
-        mod_chanmode_announce(chanserv, channel, &change);
+        spamserv_cs_suspend(channel, 0, 0, NULL);
+		ss_cs_join_channel(channel, 1);
     }
 }
 
 static CHANSERV_FUNC(cmd_csuspend)
@@ -5019,8 +5048,9 @@
         }
 
         /* Mark the channel as suspended, then part. */
         channel->channel_info->flags |= CHANNEL_SUSPENDED;
+        spamserv_cs_suspend(channel, expiry, 1, suspended->reason);
         DelChannelUser(chanserv, channel, suspended->reason, 0);
         reply("CSMSG_SUSPENDED", channel->name);
         sprintf(reason, "%s suspended by %s.", channel->name, suspended->suspender);
         global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
diff -NuwrU 4 srvx-1.4.0-rc3/src/chanserv.h srvx-1.4.0-rc3-ss/src/chanserv.h
--- srvx-1.4.0-rc3/src/chanserv.h	2008-07-12 05:49:09.000000000 +0200
+++ srvx-1.4.0-rc3-ss/src/chanserv.h	2010-03-07 18:08:24.000000000 +0100
@@ -170,8 +170,11 @@
     unsigned long expires;
     char   reason[1];
 };
 
+#define GetChannelUser(channel, handle) _GetChannelUser(channel, handle, 1, 0)
+struct userData *_GetChannelUser(struct chanData *channel, struct handle_info *handle, int override, int allow_suspended);
+struct banData *add_channel_ban(struct chanData *channel, const char *mask, char *owner, unsigned long set, unsigned long triggered, unsigned long expires, char *reason);
 void init_chanserv(const char *nick);
 void del_channel_user(struct userData *user, int do_gc);
 struct channelList *chanserv_support_channels(void);
 unsigned short user_level_from_name(const char *name, unsigned short clamp_level);
diff -NuwrU 4 srvx-1.4.0-rc3/src/helpfile.c srvx-1.4.0-rc3-ss/src/helpfile.c
--- srvx-1.4.0-rc3/src/helpfile.c	2008-07-12 05:49:09.000000000 +0200
+++ srvx-1.4.0-rc3-ss/src/helpfile.c	2010-03-07 18:08:25.000000000 +0100
@@ -22,8 +22,9 @@
 #include "helpfile.h"
 #include "log.h"
 #include "modcmd.h"
 #include "nickserv.h"
+#include "spamserv.h"
 
 #if defined(HAVE_DIRENT_H)
 #include <dirent.h>
 #endif
@@ -40,9 +41,9 @@
 
 #define DEFAULT_LINE_SIZE       MAX_LINE_SIZE
 #define DEFAULT_TABLE_SIZE      80
 
-extern struct userNode *global, *chanserv, *opserv, *nickserv;
+extern struct userNode *global, *chanserv, *opserv, *nickserv, *spamserv;
 struct userNode *message_dest;
 struct userNode *message_source;
 struct language *lang_C;
 struct dict *languages;
@@ -528,8 +529,11 @@
             break;
         case 'N':
             value = nickserv ? nickserv->nick : "NickServ";
             break;
+        case 'X':
+            value = spamserv ? spamserv->nick : "SpamServ";
+            break;
         case 's':
             value = self->name;
             break;
         case 'H':
diff -NuwrU 4 srvx-1.4.0-rc3/src/main.c srvx-1.4.0-rc3-ss/src/main.c
--- srvx-1.4.0-rc3/src/main.c	2008-07-12 05:49:09.000000000 +0200
+++ srvx-1.4.0-rc3-ss/src/main.c	2010-03-07 18:08:26.000000000 +0100
@@ -32,8 +32,9 @@
 #include "chanserv.h"
 #include "global.h"
 #include "modules.h"
 #include "opserv.h"
+#include "spamserv.h"
 
 #ifdef HAVE_GETOPT_H
 #include <getopt.h>
 #else
diff -NuwrU 4 srvx-1.4.0-rc3/src/main-common.c srvx-1.4.0-rc3-ss/src/main-common.c
--- srvx-1.4.0-rc3/src/main-common.c	2008-03-09 01:52:18.000000000 +0100
+++ srvx-1.4.0-rc3-ss/src/main-common.c	2010-03-07 18:08:26.000000000 +0100
@@ -441,8 +441,13 @@
     if (info && (info[0] == '.'))
         info = NULL;
     init_chanserv(info);
 
+    info = conf_get_data("services/spamserv/nick", RECDB_QSTRING);
+    if (info && (info[0] == '.'))
+        info = NULL;
+    init_spamserv(info);
+
     god_policer_params = policer_params_new();
     if ((dict = conf_get_data("policers/commands-god", RECDB_OBJECT))) {
         dict_foreach(dict, set_policer_param, god_policer_params);
     } else {
diff -NuwrU 4 srvx-1.4.0-rc3/src/Makefile.am srvx-1.4.0-rc3-ss/src/Makefile.am
--- srvx-1.4.0-rc3/src/Makefile.am	2008-07-12 05:49:09.000000000 +0200
+++ srvx-1.4.0-rc3-ss/src/Makefile.am	2010-03-07 18:08:27.000000000 +0100
@@ -10,8 +10,9 @@
 	nickserv.help \
 	opserv.help \
 	saxdb.help \
 	mail.help \
+	spamserv.help \
 	mod-helpserv.help \
 	mod-memoserv.help \
 	mod-sockcheck.help
 EXTRA_DIST = $(noinst_DATA)
@@ -82,8 +83,9 @@
 	proto.h \
 	recdb.c recdb.h \
 	sar.c sar.h \
 	saxdb.c saxdb.h \
+	spamserv.c spamserv.h \
 	timeq.c timeq.h \
 	tools.c
 
 checkdb_SOURCES = checkdb.c common.h compat.c compat.h dict-splay.c dict.h recdb.c recdb.h saxdb.c saxdb.h tools.c conf.h log.h modcmd.h saxdb.h timeq.h
diff -NuwrU 4 srvx-1.4.0-rc3/src/Makefile.in srvx-1.4.0-rc3-ss/src/Makefile.in
--- srvx-1.4.0-rc3/src/Makefile.in	2008-07-12 05:50:55.000000000 +0200
+++ srvx-1.4.0-rc3-ss/src/Makefile.in	2010-03-07 18:08:28.000000000 +0100
@@ -66,9 +66,10 @@
 	helpfile.$(OBJEXT) ioset.$(OBJEXT) log.$(OBJEXT) \
 	main.$(OBJEXT) md5.$(OBJEXT) modcmd.$(OBJEXT) \
 	modules.$(OBJEXT) nickserv.$(OBJEXT) opserv.$(OBJEXT) \
 	policer.$(OBJEXT) recdb.$(OBJEXT) sar.$(OBJEXT) \
-	saxdb.$(OBJEXT) timeq.$(OBJEXT) tools.$(OBJEXT)
+	saxdb.$(OBJEXT) timeq.$(OBJEXT) tools.$(OBJEXT) \
+	spamserv.$(OBJEXT) 
 srvx_OBJECTS = $(am_srvx_OBJECTS)
 DEFAULT_INCLUDES = -I.@am__isrc@
 depcomp = $(SHELL) $(top_srcdir)/depcomp
 am__depfiles_maybe = depfiles
@@ -216,8 +217,9 @@
 	nickserv.help \
 	opserv.help \
 	saxdb.help \
 	mail.help \
+	spamserv.help \
 	mod-helpserv.help \
 	mod-memoserv.help \
 	mod-sockcheck.help
 
@@ -272,8 +274,9 @@
 	proto.h \
 	recdb.c recdb.h \
 	sar.c sar.h \
 	saxdb.c saxdb.h \
+	spamserv.c spamserv.h \
 	timeq.c timeq.h \
 	tools.c
 
 checkdb_SOURCES = checkdb.c common.h compat.c compat.h dict-splay.c dict.h recdb.c recdb.h saxdb.c saxdb.h tools.c conf.h log.h modcmd.h saxdb.h timeq.h
@@ -399,8 +402,9 @@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proto-p10.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/recdb.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sar.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/saxdb.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/spamserv.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/slab-read.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/timeq.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tools.Po@am__quote@
 
diff -NuwrU 4 srvx-1.4.0-rc3/src/proto-common.c srvx-1.4.0-rc3-ss/src/proto-common.c
--- srvx-1.4.0-rc3/src/proto-common.c	2008-07-12 05:49:09.000000000 +0200
+++ srvx-1.4.0-rc3-ss/src/proto-common.c	2010-03-07 18:08:18.000000000 +0100
@@ -22,8 +22,9 @@
 #include "gline.h"
 #include "ioset.h"
 #include "log.h"
 #include "nickserv.h"
+#include "spamserv.h"
 #include "timeq.h"
 #ifdef HAVE_SYS_SOCKET_H
 #include <sys/socket.h>
 #endif
@@ -444,8 +445,10 @@
     cf = &chanmsg_funcs[(unsigned char)pd->text[0]];
     if (cf->func && !pd->is_notice
         && GetUserMode(cn, cf->service) && !IsDeaf(cf->service))
         cf->func(pd->user, cn, pd->text+1, cf->service, pd->is_notice);
+    else
+        spamserv_channel_message(cn, pd->user, pd->text);
 
     /* This catches *all* text sent to the channel that the services server sees */
     for (x = 0; x < ALLCHANMSG_FUNCS_MAX; x++) {
        cf = (struct chanmsg_func *)&allchanmsg_funcs[x];
diff -NuwrU 4 srvx-1.4.0-rc3/src/spamserv.c srvx-1.4.0-rc3-ss/src/spamserv.c
--- srvx-1.4.0-rc3/src/spamserv.c	2010-03-07 18:18:23.000000000 +0100
+++ srvx-1.4.0-rc3-ss/src/spamserv.c	2010-03-07 18:08:19.000000000 +0100
@@ -0,0 +1,1985 @@
+/* spamserv.c - anti spam service
+ * Copyright 2004 feigling
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.  Important limitations are
+ * listed in the COPYING file that accompanies this software.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, email srvx-maintainers@srvx.net.
+ *
+ * $Id: spamserv.c,v 1.08 2004/06/27 22:21:00 feigling Exp $
+ */
+
+#include "conf.h"
+#include "spamserv.h"
+#include "chanserv.h"
+#include "global.h"
+#include "modcmd.h"
+#include "saxdb.h"
+#include "timeq.h"
+#include "gline.h"
+
+#define SPAMSERV_CONF_NAME           "services/spamserv"
+
+#define KEY_EXCEPTIONS               "exceptions"
+#define KEY_FLAGS                    "flags"
+#define KEY_INFO                     "info"
+#define KEY_EXPIRY                   "expiry"
+
+#define KEY_DEBUG_CHANNEL            "debug_channel"
+#define KEY_GLOBAL_EXCEPTIONS        "global_exceptions"
+#define KEY_NETWORK_RULES            "network_rules"
+#define KEY_TRIGGER                  "trigger"
+#define KEY_SHORT_BAN_DURATION       "short_ban_duration"
+#define KEY_LONG_BAN_DURATION        "long_ban_duration"
+#define KEY_GLINE_DURATION           "gline_duration"
+#define KEY_EXCEPTION_MAX            "exception_max"
+#define KEY_EXCEPTION_MIN_LEN        "exception_min_len"
+#define KEY_EXCEPTION_MAX_LEN        "exception_max_len"
+#define KEY_ADV_CHAN_MUST_EXIST      "adv_chan_must_exist"
+#define KEY_STRIP_MIRC_CODES         "strip_mirc_codes"
+#define KEY_ALLOW_MOVE_MERGE         "allow_move_merge"
+
+#define SPAMSERV_FUNC(NAME)	MODCMD_FUNC(NAME)
+#define SPAMSERV_SYNTAX()	svccmd_send_help(user, spamserv, cmd)
+#define SPAMSERV_MIN_PARMS(N) do { \
+(void)argv; \
+  if(argc < N) { \
+    ss_reply(MSG_MISSING_PARAMS, argv[0]); \
+    SPAMSERV_SYNTAX(); \
+    return 0; } } while(0)
+
+struct userNode			*spamserv;
+static struct module	*spamserv_module;
+static struct service	*spamserv_service;
+static struct log_type	*SS_LOG;
+static unsigned long	crc_table[256];
+
+dict_t registered_channels_dict;
+dict_t connected_users_dict;
+dict_t killed_users_dict;
+
+#define spamserv_notice(target, format...) send_message(target , spamserv , ## format)
+#define spamserv_debug(format...) do { if(spamserv_conf.debug_channel) send_channel_notice(spamserv_conf.debug_channel , spamserv , ## format); } while(0)
+#define ss_reply(format...)	send_message(user , spamserv , ## format)
+
+#define SET_SUBCMDS_SIZE 9
+
+const char *set_subcommands[SET_SUBCMDS_SIZE] = {"SPAMLIMIT", "ADVREACTION", "WARNREACTION", "ADVSCAN", "SPAMSCAN", "FLOODSCAN", "JOINFLOODSCAN", "SCANCHANOPS", "SCANVOICED"};
+
+static void spamserv_clear_spamNodes(struct chanNode *channel);
+static void spamserv_punish(struct chanNode *channel, struct userNode *user, time_t expires, char *reason, int ban);
+static unsigned long crc32(const char *text);
+
+#define BINARY_OPTION(arguments...)	return binary_option(arguments, user, channel, argc, argv);
+#define MULTIPLE_OPTION(arguments...)	return multiple_option(arguments, values, ArrayLength(values), user, channel, argc, argv);
+
+static const struct message_entry msgtab[] = {
+    { "SSMSG_CHANNEL_OPTIONS",         "Channel Options:" },
+    { "SSMSG_STRING_VALUE",            "$b%s$b%s" },
+    { "SSMSG_NUMERIC_VALUE",           "$b%s$b%d - %s" },
+    { "SSMSG_INVALID_NUM_SET",         "$b'%d'$b is an invalid %s setting." },
+    { "SSMSG_INVALID_OPTION",          "$b%s$b is not a valid %s option." },
+    { "SSMSG_INVALID_BINARY",          "$b%s$b is an invalid binary value." },
+
+    { "SSMSG_NOT_REGISTERED",          "$b%s$b has not been registered with $b$X$b." },
+    { "SSMSG_NOT_REGISTERED_CS",       "$b%s$b has not been registered with $b$C$b." },
+    { "SSMSG_ALREADY_REGISTERED",      "$b%s$b is already registered." },
+    { "SSMSG_DEBUG_CHAN",              "You may not register the debug channel." },
+    { "SSMSG_SUSPENDED_CS",            "$b$C$b access to $b%s$b has been temporarily suspended, thus you can't %s it." },
+    { "SSMSG_SUSPENDED",               "$b$X$b access to $b%s$b has been temporarily suspended." },
+    { "SSMSG_NO_REGISTER",             "Due to an error it was not possible to register $b%s$b." },
+    { "SSMSG_REG_SUCCESS",             "Channel $b%s$b registered." },
+    { "SSMSG_UNREG_SUCCESS",           "$b%s$b has been unregistered." },
+    { "SSMSG_NO_ACCESS",               "You lack sufficient access to use this command." },
+    { "SSMSG_MUST_BE_OPER",            "You must be an irc operator to set this option." },
+    { "SSMSG_CONFIRM_UNREG",           "To confirm this unregistration, you must append 'CONFIRM' to the end of your command. For example, 'unregister CONFIRM'." },
+
+    { "SSMSG_NO_EXCEPTIONS",           "No words found in the exception list." },
+    { "SSMSG_NO_SUCH_EXCEPTION",       "Word $b%s$b not found in the exception list." },
+    { "SSMSG_EXCEPTION_LIST",          "The following words are in the exception list:" },
+    { "SSMSG_EXCEPTION_ADDED",         "Word $b%s$b added to the exception list." },
+    { "SSMSG_EXCEPTION_DELETED",       "Word $b%s$b deleted from the exception list." },
+    { "SSMSG_EXCEPTION_IN_LIST",       "The word $b%s$b is already in the exception list." },
+    { "SSMSG_EXCEPTION_MAX",           "The exception list has reached the maximum exceptions (max %lu). Delete a word to add another one." },
+    { "SSMSG_EXCEPTION_TOO_SHORT",     "The word must be at least %lu characters long." },
+    { "SSMSG_EXCEPTION_TOO_LONG",      "The word may not be longer than %lu characters." },
+
+    { "SSMSG_STATUS",                  "$bStatus:$b" },
+    { "SSMSG_STATUS_USERS",            "Total Users Online:  %u" },
+    { "SSMSG_STATUS_CHANNELS",         "Registered Channels: %u" },
+    { "SSMSG_STATUS_MEMORY",           "$bMemory Information:$b" },
+    { "SSMSG_STATUS_CHANNEL_LIST",     "$bRegistered Channels:$b" },
+    { "SSMSG_STATUS_NO_CHANNEL",       "No channels registered." },
+	{ NULL, NULL }
+};
+
+#define SSMSG_DEBUG_KICK              "Kicked user $b%s$b from $b%s$b, reason: %s"
+#define SSMSG_DEBUG_BAN               "Banned user $b%s$b from $b%s$b, reason: %s"
+#define SSMSG_DEBUG_KILL              "Killed user $b%s$b, last violation in $b%s$b"
+#define SSMSG_DEBUG_GLINE             "Glined user $b%s$b, host $b%s$b, last violation in $b%s$b"
+#define SSMSG_DEBUG_RECONNECT         "Killed user $b%s$b reconnected to the network"
+#define SSMSG_SPAM                    "Spamming"
+#define SSMSG_FLOOD                   "Flooding the channel/network"
+#define SSMSG_ADV                     "Advertising"
+#define SSMSG_JOINFLOOD               "Join flooding the channel"
+#define SSMSG_WARNING                 "%s is against the network rules"
+#define SSMSG_WARNING_2               "You are violating the network rules"
+#define SSMSG_WARNING_RULES           "%s is against the network rules. Read the network rules at %s"
+#define SSMSG_WARNING_RULES_2         "You are violating the network rules. Read the network rules at %s"
+
+static struct
+{
+	struct chanNode *debug_channel;
+	struct string_list *global_exceptions;
+	const char *network_rules;
+	unsigned char trigger;
+	unsigned long short_ban_duration;
+	unsigned long long_ban_duration;
+	unsigned long gline_duration;
+	unsigned long exception_max;
+	unsigned long exception_min_len;
+	unsigned long exception_max_len;
+	unsigned int adv_chan_must_exist : 1;
+	unsigned int strip_mirc_codes : 1;
+	unsigned int allow_move_merge : 1;
+} spamserv_conf;
+
+/***********************************************/
+/*                   Channel                   */
+/***********************************************/
+
+struct chanInfo*
+get_chanInfo(const char *channelname)
+{
+	return dict_find(registered_channels_dict, channelname, 0);
+}
+
+static void
+spamserv_join_channel(struct chanNode *channel)
+{
+	struct mod_chanmode change;
+	mod_chanmode_init(&change);
+	change.argc = 1;
+	change.args[0].mode = MODE_CHANOP;
+	change.args[0].u.member = AddChannelUser(spamserv, channel);
+	mod_chanmode_announce(spamserv, channel, &change);
+}
+
+static void
+spamserv_part_channel(struct chanNode *channel, char *reason)
+{
+	/* we only have to clear the spamNodes because every other node expires on it's own */
+	spamserv_clear_spamNodes(channel);
+	DelChannelUser(spamserv, channel, reason, 0);
+}
+
+static struct chanInfo*
+spamserv_register_channel(struct chanNode *channel, struct string_list *exceptions, unsigned int flags, char *info)
+{
+	struct chanInfo *cInfo = malloc(sizeof(struct chanInfo));
+	
+	if(!cInfo)
+	{
+		log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for cInfo; channel: %s", channel->name);
+		return NULL;
+	}
+
+	cInfo->channel = channel;
+	cInfo->exceptions = exceptions ? string_list_copy(exceptions) : alloc_string_list(1);
+	cInfo->flags = flags;
+	safestrncpy(cInfo->info, info, sizeof(cInfo->info));
+	cInfo->suspend_expiry = 0;
+	dict_insert(registered_channels_dict, cInfo->channel->name, cInfo);
+
+	return cInfo;
+}
+
+static void
+spamserv_unregister_channel(struct chanInfo *cInfo)
+{
+	if(!cInfo)
+		return;
+
+	dict_remove(registered_channels_dict, cInfo->channel->name);
+	free_string_list(cInfo->exceptions);
+	free(cInfo);
+}
+
+void
+spamserv_cs_suspend(struct chanNode *channel, time_t expiry, int suspend, char *reason)
+{
+	struct chanInfo *cInfo = get_chanInfo(channel->name);
+
+	if(cInfo)
+	{
+		if(suspend)
+		{
+			cInfo->flags |= CHAN_SUSPENDED;
+			cInfo->suspend_expiry = expiry;
+			spamserv_part_channel(channel, reason);
+		}
+		else
+		{
+			if(CHECK_SUSPENDED(cInfo))
+			{
+				cInfo->flags &= ~CHAN_SUSPENDED;
+				cInfo->suspend_expiry = 0;
+			}
+		}
+	}
+}
+
+int
+spamserv_cs_move_merge(struct userNode *user, struct chanNode *channel, struct chanNode *target, int move)
+{
+	struct chanInfo *cInfo = get_chanInfo(channel->name);
+
+	if(cInfo)
+	{
+		char reason[MAXLEN];
+
+		if(!spamserv_conf.allow_move_merge || get_chanInfo(target->name))
+		{
+			if(move)
+				snprintf(reason, sizeof(reason), "unregistered due to a channel move to %s", target->name);
+			else
+				snprintf(reason, sizeof(reason), "unregistered due to a channel merge into %s", target->name);
+
+			spamserv_cs_unregister(user, channel, manually, reason);
+			return 0;
+		}
+
+		cInfo->channel = target;
+
+		dict_remove(registered_channels_dict, channel->name);
+		dict_insert(registered_channels_dict, target->name, cInfo);
+
+		if(move)
+		{
+			snprintf(reason, sizeof(reason), "Channel moved to %s by %s.", target->name, user->handle_info->handle);
+		}
+		else
+		{
+			spamserv_join_channel(target);
+			snprintf(reason, sizeof(reason), "%s merged into %s by %s.", channel->name, target->name, user->handle_info->handle);	
+		}
+
+		if(!CHECK_SUSPENDED(cInfo))
+			spamserv_part_channel(channel, reason);
+
+		if(move)
+			snprintf(reason, sizeof(reason), "$X (channel %s) moved to %s by %s.", channel->name, target->name, user->handle_info->handle);
+		else
+			snprintf(reason, sizeof(reason), "$X (channel %s) merged into %s by %s.", channel->name, target->name, user->handle_info->handle);
+
+		global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+		return 1;
+	}
+
+	return 0;
+}
+
+void
+spamserv_cs_unregister(struct userNode *user, struct chanNode *channel, enum cs_unreg type, char *reason)
+{
+	struct chanInfo *cInfo = get_chanInfo(channel->name);
+
+	if(cInfo)
+	{
+		char global[MAXLEN], partmsg[MAXLEN];
+
+		switch (type)
+		{
+		case manually:
+			snprintf(global, sizeof(global), "$X (channel %s) %s by %s.", channel->name, reason, user->handle_info->handle);
+			snprintf(partmsg, sizeof(partmsg), "%s %s by %s.", channel->name, reason, user->handle_info->handle);			
+			break;
+		case expire:
+			snprintf(global, sizeof(global), "$X (channel %s) registration expired.", channel->name);
+			snprintf(partmsg, sizeof(partmsg), "%s registration expired.", channel->name);			
+			break;
+		case lost_all_users:
+			snprintf(global, sizeof(global), "$X (channel %s) lost all users.", channel->name);
+			snprintf(partmsg, sizeof(partmsg), "%s lost all users.", channel->name);			
+			break;
+		}
+
+		if(!CHECK_SUSPENDED(cInfo))
+			spamserv_part_channel(channel, partmsg);
+		
+		spamserv_unregister_channel(cInfo);
+		global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, global);
+	}
+}
+
+/***********************************************/
+/*                    User                     */
+/***********************************************/
+
+static struct userInfo*
+get_userInfo(const char *nickname)
+{
+	return dict_find(connected_users_dict, nickname, 0);
+}
+
+static void
+spamserv_create_spamNode(struct chanNode *channel, struct userInfo *uInfo, char *text)
+{
+	struct spamNode *sNode = malloc(sizeof(struct spamNode));
+
+	if(!sNode)
+	{
+		log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for sNode; channel: %s; user: %s", channel->name, uInfo->user->nick);
+		return;
+	}
+
+	sNode->channel = channel;	
+	sNode->crc32 = crc32(text);
+	sNode->count = 1;
+	sNode->next = NULL;
+
+	if(uInfo->spam)
+	{
+		struct spamNode *temp = uInfo->spam;
+		
+		while(temp->next)
+			temp = temp->next;
+
+		sNode->prev = temp;
+		temp->next = sNode;
+	}
+	else
+	{
+		sNode->prev = NULL;
+		uInfo->spam = sNode;
+	}
+}
+
+static void
+spamserv_delete_spamNode(struct userInfo *uInfo, struct spamNode *sNode)
+{
+	if(!sNode)
+		return;
+
+	if(sNode == uInfo->spam)
+		uInfo->spam = sNode->next;
+	
+	if(sNode->next)
+		 sNode->next->prev = sNode->prev;
+	if(sNode->prev)
+		 sNode->prev->next = sNode->next;
+
+	free(sNode);
+}
+
+static void
+spamserv_clear_spamNodes(struct chanNode *channel)
+{
+	struct userInfo *uInfo;
+	struct spamNode *sNode;
+	unsigned int i;
+
+	for(i = 0; i < channel->members.used; i++)
+	{
+		if((uInfo = get_userInfo(channel->members.list[i]->user->nick)))
+		{
+			if((sNode = uInfo->spam))
+			{
+				for(; sNode; sNode = sNode->next)
+					if(sNode->channel == channel)
+						break;
+					
+				if(sNode)
+					spamserv_delete_spamNode(uInfo, sNode);
+			}
+		}
+	}
+}
+
+static void
+spamserv_create_floodNode(struct chanNode *channel, struct userNode *user, struct floodNode **uI_fNode)
+{
+	struct floodNode *fNode = malloc(sizeof(struct floodNode));
+
+	if(!fNode)
+	{
+		log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for fNode; channel: %s; user: %s", channel->name, user->nick);
+		return;
+	}
+
+	fNode->channel = channel;
+	fNode->owner = user;
+	fNode->count = 1;
+	fNode->time = now;	
+	fNode->next = NULL;
+
+	if(*uI_fNode)
+	{
+		struct floodNode *temp = *uI_fNode;
+		
+		while(temp->next)
+			temp = temp->next;
+		
+		fNode->prev = temp;
+		temp->next = fNode;
+	}
+	else
+	{
+		fNode->prev = NULL;
+		*uI_fNode = fNode;
+	}
+}
+
+static void
+spamserv_delete_floodNode(struct floodNode **uI_fNode, struct floodNode *fNode)
+{
+	if(!fNode)
+		return;
+
+	if(fNode == *uI_fNode)
+		*uI_fNode = fNode->next;
+	
+	if(fNode->next)
+		 fNode->next->prev = fNode->prev;
+	if(fNode->prev)
+		 fNode->prev->next = fNode->next;
+
+	free(fNode);
+}
+
+static void
+spamserv_create_user(struct userNode *user)
+{
+	struct userInfo *uInfo = malloc(sizeof(struct userInfo));
+	struct killNode *kNode = dict_find(killed_users_dict, irc_ntoa(&user->ip), 0);
+
+	if(!uInfo)
+	{
+		log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for uInfo; nick: %s", user->nick);
+		return;
+	}
+
+	if(kNode)
+		spamserv_debug(SSMSG_DEBUG_RECONNECT, user->nick);
+
+	uInfo->user = user;
+	uInfo->spam = NULL;
+	uInfo->flood = NULL;
+	uInfo->joinflood = NULL;
+	uInfo->flags = kNode ? USER_KILLED : 0;
+	uInfo->warnlevel = kNode ? kNode->warnlevel : 0;
+	uInfo->lastadv = 0;
+
+	dict_insert(connected_users_dict, user->nick, uInfo);
+
+	if(kNode)
+	{
+		dict_remove(killed_users_dict, irc_ntoa(&user->ip));
+		free(kNode);
+	}
+}
+
+static void
+spamserv_delete_user(struct userInfo *uInfo)
+{
+	if(!uInfo)
+		return;
+
+	if(uInfo->spam)
+		while(uInfo->spam)
+			spamserv_delete_spamNode(uInfo, uInfo->spam);	
+
+	if(uInfo->flood)
+		while(uInfo->flood)
+			spamserv_delete_floodNode(&uInfo->flood, uInfo->flood);
+
+	if(uInfo->joinflood)
+		while(uInfo->joinflood)
+			spamserv_delete_floodNode(&uInfo->joinflood, uInfo->joinflood);
+
+	dict_remove(connected_users_dict, uInfo->user->nick);
+	free(uInfo);
+}
+
+static int
+spamserv_new_user_func(struct userNode *user)
+{
+	if(!IsLocal(user))
+		spamserv_create_user(user);
+  
+	return 0;
+}
+
+static void
+spamserv_del_user_func(struct userNode *user, struct userNode *killer, UNUSED_ARG(const char *why))
+{
+	struct userInfo *uInfo = get_userInfo(user->nick);
+	struct killNode *kNode;
+
+	if(killer == spamserv)
+	{
+		kNode = malloc(sizeof(struct killNode));
+
+		if(!kNode)
+		{
+			log_module(SS_LOG, LOG_ERROR, "Couldn't allocate memory for killNode - nickname %s", user->nick);
+			spamserv_delete_user(uInfo);			
+			return;
+		}
+
+		if(uInfo->warnlevel > KILL_WARNLEVEL)
+			kNode->warnlevel = uInfo->warnlevel - KILL_WARNLEVEL;
+		else
+			kNode->warnlevel = 0;
+
+		kNode->time = now;
+
+		dict_insert(killed_users_dict, irc_ntoa(&user->ip), kNode);
+	}
+
+	spamserv_delete_user(uInfo);	
+}
+
+static void
+spamserv_nick_change_func(struct userNode *user, const char *old_nick)
+{
+	struct userInfo *uInfo = get_userInfo(old_nick);
+
+	dict_remove(connected_users_dict, old_nick);
+	dict_insert(connected_users_dict, user->nick, uInfo);
+}
+
+static int
+spamserv_user_join(struct modeNode *mNode)
+{
+	struct chanNode	*channel = mNode->channel;
+	struct userNode	*user = mNode->user;    
+	struct chanInfo	*cInfo;
+	struct userInfo	*uInfo;
+	struct floodNode *jfNode;
+
+	if(user->uplink->burst || !(cInfo = get_chanInfo(channel->name)) || !CHECK_JOINFLOOD(cInfo) || !(uInfo = get_userInfo(user->nick)))
+		return 0;
+
+	if(!(jfNode = uInfo->joinflood))
+	{
+		spamserv_create_floodNode(channel, user, &uInfo->joinflood);
+	}
+	else
+	{
+		for(; jfNode; jfNode = jfNode->next)
+			if(jfNode->channel == channel)
+				break;
+
+		if(!jfNode)
+		{
+			spamserv_create_floodNode(channel, user, &uInfo->joinflood);
+		}
+		else
+		{
+			jfNode->count++;
+			jfNode->time = now;		
+
+			if(jfNode->count > JOINFLOOD_MAX)
+			{
+				char reason[MAXLEN];
+
+				spamserv_delete_floodNode(&uInfo->joinflood, jfNode);
+				snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_JOINFLOOD, spamserv_conf.network_rules);
+				spamserv_punish(channel, user, JOINFLOOD_B_DURATION, reason, 1);
+			}
+		}
+	}
+
+	return 0;
+}
+
+static void
+spamserv_user_part(struct modeNode *mn, UNUSED_ARG(const char *reason))
+{
+	struct userNode *user = mn->user;
+	struct chanNode *channel = mn->channel;
+	struct userInfo *uInfo;
+	struct spamNode *sNode;
+	struct floodNode *fNode;
+
+	if(user->dead || !get_chanInfo(channel->name) || !(uInfo = get_userInfo(user->nick)))
+		return;
+
+	if((sNode = uInfo->spam))
+	{
+		for(; sNode; sNode = sNode->next)
+			if(sNode->channel == channel)
+				break;
+
+		if(sNode)
+			spamserv_delete_spamNode(uInfo, sNode);
+	}
+
+	if((fNode = uInfo->flood))
+	{
+		for(; fNode; fNode = fNode->next)
+			if(fNode->channel == channel)
+				break;
+
+		if(fNode)
+			spamserv_delete_floodNode(&uInfo->flood, fNode);
+	}
+}
+
+/***********************************************/
+/*                 Other Stuff                 */
+/***********************************************/
+
+static void
+crc32_init(void)
+{
+	unsigned long crc;
+	int i, j;
+
+	for(i = 0; i < 256; i++)
+	{
+		crc = i;
+
+		for(j = 8; j > 0; j--)
+		{
+			if(crc & 1)
+			{
+				crc = (crc >> 1) ^ 0xEDB88320L;
+			}
+			else
+			{
+				crc >>= 1;
+			}
+		}
+
+		crc_table[i] = crc;
+	}
+}
+
+static unsigned long
+crc32(const char *text)
+{
+	register unsigned long crc = 0xFFFFFFFF;
+	unsigned int c, i = 0;
+	
+	while((c = (unsigned int)text[i++]) != 0)
+		crc = ((crc >> 8) & 0x00FFFFFF) ^ crc_table[(crc^c) & 0xFF];
+ 
+	return (crc^0xFFFFFFFF);
+}
+
+static void
+timeq_flood(UNUSED_ARG(void *data))
+{
+	dict_iterator_t		it;
+	struct userInfo		*uInfo;
+	struct floodNode	*fNode;
+
+	for(it = dict_first(connected_users_dict); it; it = iter_next(it))
+	{
+		uInfo = iter_data(it);
+
+		if(!(fNode = uInfo->flood))
+			continue;
+
+		for(; fNode; fNode = fNode->next)
+		{
+			if(now - fNode->time > FLOOD_EXPIRE)
+			{
+				if(!(--fNode->count))
+					spamserv_delete_floodNode(&uInfo->flood, fNode);
+			}
+		}
+	}
+	
+	timeq_add(now + FLOOD_TIMEQ_FREQ, timeq_flood, NULL);
+}
+
+static void
+timeq_joinflood(UNUSED_ARG(void *data))
+{
+	dict_iterator_t it;
+	struct userInfo *uInfo;
+	struct floodNode *fNode;
+
+	for(it = dict_first(connected_users_dict); it; it = iter_next(it))
+	{
+		uInfo = iter_data(it);
+
+		if(!(fNode = uInfo->joinflood))
+			continue;
+
+		for(; fNode; fNode = fNode->next)
+		{
+			if(now - fNode->time > JOINFLOOD_EXPIRE)
+			{
+				if(!(--fNode->count))
+					spamserv_delete_floodNode(&uInfo->joinflood, fNode);				
+			}
+		}
+	}
+
+	timeq_add(now + JOINFLOOD_TIMEQ_FREQ, timeq_joinflood, NULL);
+}
+
+static void
+timeq_adv(UNUSED_ARG(void *data))
+{
+	dict_iterator_t it;
+	struct userInfo *uInfo;
+
+	for(it = dict_first(connected_users_dict); it; it = iter_next(it))
+	{
+		uInfo = iter_data(it);
+
+		if(uInfo->lastadv && uInfo->lastadv - now > ADV_EXPIRE)
+		{
+			uInfo->lastadv = 0;
+			uInfo->flags &= ~USER_ADV_WARNED;
+		}
+	}
+
+	timeq_add(now + ADV_TIMEQ_FREQ, timeq_adv, NULL);
+}
+
+static void
+timeq_warnlevel(UNUSED_ARG(void *data))
+{
+	dict_iterator_t it;
+	struct userInfo *uInfo;
+
+	for(it = dict_first(connected_users_dict); it; it = iter_next(it))
+	{
+		uInfo = iter_data(it);
+
+		if(uInfo->warnlevel > 0)
+			uInfo->warnlevel--;
+	}
+
+	timeq_add(now + WARNLEVEL_TIMEQ_FREQ, timeq_warnlevel, NULL);
+}
+
+static void
+timeq_kill(UNUSED_ARG(void *data))
+{
+	dict_iterator_t it;
+	struct killNode *kNode;
+
+	for(it = dict_first(killed_users_dict); it; it = iter_next(it))
+	{
+		kNode = iter_data(it);
+
+		if(kNode->time - now > KILL_EXPIRE)
+			free(kNode);
+	}
+
+	timeq_add(now + KILL_TIMEQ_FREQ, timeq_kill, NULL);
+}
+
+static int
+binary_option(char *name, unsigned long mask, struct userNode *user, struct chanNode *channel, int argc, char *argv[])
+{
+	struct chanInfo *cInfo = get_chanInfo(channel->name);
+	int value;
+
+	if(argc > 1)
+	{
+		if(enabled_string(argv[1]))
+		{
+			cInfo->flags |= mask;
+			value = 1;
+		}
+		else if(disabled_string(argv[1]))
+		{
+		    cInfo->flags &= ~mask;
+		    value = 0;
+		}
+		else
+		{
+		   spamserv_notice(user, "SSMSG_INVALID_BINARY", argv[1]);
+		   return 0;
+		}
+	}
+	else
+	{
+		value = (cInfo->flags & mask) ? 1 : 0;
+	}
+
+	spamserv_notice(user, "SSMSG_STRING_VALUE", name, value ? "Enabled." : "Disabled.");
+	return 1;
+}
+
+struct valueData
+{
+	char *description;
+	char value;
+	int  oper_only : 1;
+};
+
+static int
+multiple_option(char *name, char *description, enum channelinfo info, struct valueData *values, int count, struct userNode *user, struct chanNode *channel, int argc, char *argv[])
+{
+	struct chanInfo *cInfo = get_chanInfo(channel->name);
+	int index;
+
+	if(argc > 1)
+	{
+		index = atoi(argv[1]);
+		
+		if(index < 0 || index >= count)
+		{
+			spamserv_notice(user, "SSMSG_INVALID_NUM_SET", index, description);
+
+            for(index = 0; index < count; index++)
+                spamserv_notice(user, "SSMSG_NUMERIC_VALUE", name, index, values[index].description);
+
+			return 0;
+		}
+
+		if(values[index].oper_only && !IsOper(user))
+		{
+			spamserv_notice(user, "SSMSG_MUST_BE_OPER");
+			return 0;
+		}
+		
+		cInfo->info[info] = values[index].value;
+	}
+	else
+	{
+		for(index = 0; index < count && cInfo->info[info] != values[index].value; index++);
+	}
+
+	spamserv_notice(user, "SSMSG_NUMERIC_VALUE", name, index, values[index].description);
+	return 1;
+}
+
+static int
+show_exceptions(struct userNode *user, struct chanInfo *cInfo)
+{
+	struct helpfile_table table;
+	unsigned int i;
+
+	if(!cInfo->exceptions->used)
+	{
+		spamserv_notice(user, "SSMSG_NO_EXCEPTIONS");
+		return 0;
+	}
+
+	spamserv_notice(user, "SSMSG_EXCEPTION_LIST");
+
+	table.length = 0;
+	table.width = 1;
+	table.flags = TABLE_REPEAT_ROWS | TABLE_NO_FREE | TABLE_NO_HEADERS;
+	table.contents = alloca(cInfo->exceptions->used * sizeof(*table.contents));
+
+	for(i = 0; i < cInfo->exceptions->used; i++)
+	{
+		table.contents[table.length] = alloca(table.width * sizeof(**table.contents));
+		table.contents[table.length][0] = cInfo->exceptions->list[i];
+		table.length++;
+	}
+	
+	table_send(spamserv, user->nick, 0, NULL, table);
+
+	return 1;
+}
+
+static void
+show_memory_usage(struct userNode *user)
+{
+	dict_iterator_t it;
+	struct helpfile_table table;
+	struct chanInfo *cInfo;
+	struct userInfo *uInfo;
+	struct spamNode *sNode;
+	struct floodNode *fNode;
+	double channel_size = 0, user_size, size;
+	unsigned int spamcount = 0, floodcount = 0, i, j;
+	char buffer[64];
+
+	for(it = dict_first(registered_channels_dict); it; it = iter_next(it))
+	{
+		cInfo = iter_data(it);
+
+		if(!cInfo->exceptions->used)
+			continue;
+
+		for(i = 0; i < cInfo->exceptions->used; i++)
+			channel_size += strlen(cInfo->exceptions->list[i]) * sizeof(char);		
+	}
+
+	for(it = dict_first(connected_users_dict); it; it = iter_next(it))
+	{
+		uInfo = iter_data(it);
+
+		for(sNode = uInfo->spam; sNode; sNode = sNode->next, spamcount++);
+		for(fNode = uInfo->flood; fNode; fNode = fNode->next, floodcount++);
+		for(fNode = uInfo->joinflood; fNode; fNode = fNode->next, floodcount++);
+	}
+
+	channel_size += dict_size(registered_channels_dict) * sizeof(struct chanInfo);
+	
+	user_size = dict_size(connected_users_dict) * sizeof(struct userInfo) +
+				dict_size(killed_users_dict) * sizeof(struct killNode) +
+				spamcount * sizeof(struct spamNode)	+
+				floodcount *  sizeof(struct floodNode);
+
+	size = channel_size + user_size;
+	
+	ss_reply("SSMSG_STATUS_MEMORY");
+	
+	table.length = 3;
+	table.width = 4;
+	table.flags = TABLE_NO_FREE | TABLE_NO_HEADERS | TABLE_PAD_LEFT;
+	table.contents = calloc(table.length, sizeof(char**));
+
+	// chanInfo
+	table.contents[0] = calloc(table.width, sizeof(char*));
+	snprintf(buffer, sizeof(buffer), "Channel Memory Usage:");
+	table.contents[0][0] = strdup(buffer);
+	snprintf(buffer, sizeof(buffer), " %g Byte; ", channel_size);
+	table.contents[0][1] = strdup(buffer);
+	snprintf(buffer, sizeof(buffer), "%g KiloByte; ", channel_size / 1024);
+	table.contents[0][2] = strdup(buffer);
+	snprintf(buffer, sizeof(buffer), "%g MegaByte", channel_size / 1024 / 1024);
+	table.contents[0][3] = strdup(buffer);
+
+	// userInfo
+	table.contents[1] = calloc(table.width, sizeof(char*));
+	snprintf(buffer, sizeof(buffer), "User Memory Usage   :");
+	table.contents[1][0] = strdup(buffer);
+	snprintf(buffer, sizeof(buffer), " %g Byte; ", user_size);
+	table.contents[1][1] = strdup(buffer);
+	snprintf(buffer, sizeof(buffer), "%g KiloByte; ", user_size / 1024);
+	table.contents[1][2] = strdup(buffer);
+	snprintf(buffer, sizeof(buffer), "%g MegaByte", user_size / 1024 / 1024);
+	table.contents[1][3] = strdup(buffer);
+
+	// total memory usage
+	table.contents[2] = calloc(table.width, sizeof(char*));
+	snprintf(buffer, sizeof(buffer), "Total Memory Usage  :");
+	table.contents[2][0] = strdup(buffer);
+	snprintf(buffer, sizeof(buffer), " %g Byte; ", size);
+	table.contents[2][1] = strdup(buffer);
+	snprintf(buffer, sizeof(buffer), "%g KiloByte; ", size / 1024);
+	table.contents[2][2] = strdup(buffer);
+	snprintf(buffer, sizeof(buffer), "%g MegaByte", size / 1024 / 1024);
+	table.contents[2][3] = strdup(buffer);
+
+	table_send(spamserv, user->nick, 0, NULL, table);
+	
+	for(i = 0; i < table.length; i++)
+	{
+		for(j = 0; j < table.width; j++)
+			free((char*)table.contents[i][j]);
+
+        free(table.contents[i]);
+	}
+
+	free(table.contents);
+}
+
+static void
+show_registered_channels(struct userNode *user)
+{
+	struct helpfile_table table;
+	dict_iterator_t it;
+
+	spamserv_notice(user, "SSMSG_STATUS_CHANNEL_LIST");
+
+	if(!dict_size(registered_channels_dict))
+	{
+		spamserv_notice(user, "SSMSG_STATUS_NO_CHANNEL");
+		return;
+	}
+
+	table.length = 0;
+	table.width = 1;
+	table.flags = TABLE_REPEAT_ROWS | TABLE_NO_FREE | TABLE_NO_HEADERS;
+	table.contents = alloca(dict_size(registered_channels_dict) * sizeof(*table.contents));
+
+	for(it = dict_first(registered_channels_dict); it; it = iter_next(it))
+	{
+		struct chanInfo *cInfo = iter_data(it);
+
+		table.contents[table.length] = alloca(table.width * sizeof(**table.contents));
+		table.contents[table.length][0] = cInfo->channel->name;
+		table.length++;
+	}
+	
+	table_send(spamserv, user->nick, 0, NULL, table);
+}
+
+/***********************************************/
+/*                SpamServ_Func                */
+/***********************************************/
+
+static 
+SPAMSERV_FUNC(cmd_register)
+{
+	struct chanInfo *cInfo;
+	char reason[MAXLEN];
+
+	if(!channel || !channel->channel_info)
+	{
+		ss_reply("SSMSG_NOT_REGISTERED_CS", channel->name);
+		return 0;
+	}
+
+	if(get_chanInfo(channel->name))
+	{
+		ss_reply("SSMSG_ALREADY_REGISTERED", channel->name);
+		return 0;
+	}
+
+	if(IsSuspended(channel->channel_info))
+	{
+		ss_reply("SSMSG_SUSPENDED_CS", channel->name, "register");
+		return 0;
+	}
+
+	if(channel == spamserv_conf.debug_channel)
+	{
+		ss_reply("SSMSG_DEBUG_CHAN");
+		return 0;
+	}
+
+	if(!(cInfo = spamserv_register_channel(channel, spamserv_conf.global_exceptions, CHAN_FLAGS_DEFAULT , CHAN_INFO_DEFAULT)))
+	{
+		ss_reply("SSMSG_NO_REGISTER", channel->name);
+		return 0;
+	}
+
+	spamserv_join_channel(cInfo->channel);
+	
+	snprintf(reason, sizeof(reason), "%s (channel %s) registered by %s.", spamserv->nick, channel->name, user->handle_info->handle);
+	global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+	ss_reply("SSMSG_REG_SUCCESS", channel->name);
+
+	return 1;
+}
+
+static 
+SPAMSERV_FUNC(cmd_unregister)
+{
+	struct chanInfo *cInfo;
+	struct chanData *cData;
+	struct userData *uData;
+	char reason[MAXLEN];
+
+	if(!channel || !(cData = channel->channel_info) || !(cInfo = get_chanInfo(channel->name)))
+	{
+		ss_reply("SSMSG_NOT_REGISTERED", channel->name);
+		return 0;
+	}
+
+	if(!(uData = GetChannelUser(cData, user->handle_info)) || (uData->access < UL_OWNER))
+	{
+        ss_reply("SSMSG_NO_ACCESS");
+        return 0;
+	}
+
+	if(!IsHelping(user))
+	{
+        if(IsSuspended(cData))
+        {
+            ss_reply("SSMSG_SUSPENDED_CS", channel->name, "unregister");
+            return 0;
+        }
+
+		if(argc < 2 || strcasecmp(argv[1], "CONFIRM"))
+		{
+			ss_reply("SSMSG_CONFIRM_UNREG");
+			return 0;
+		}
+        }
+
+	if(!CHECK_SUSPENDED(cInfo))
+	{
+		snprintf(reason, sizeof(reason), "%s unregistered by %s.", spamserv->nick, user->handle_info->handle);		
+		spamserv_part_channel(channel, reason);
+	}
+	
+	spamserv_unregister_channel(cInfo);	
+
+	snprintf(reason, sizeof(reason), "%s (channel %s) unregistered by %s.", spamserv->nick, channel->name, user->handle_info->handle);
+	global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+	ss_reply("SSMSG_UNREG_SUCCESS", channel->name);
+
+	return 1;
+}
+
+static 
+SPAMSERV_FUNC(cmd_status)
+{
+	ss_reply("SSMSG_STATUS");
+	ss_reply("SSMSG_STATUS_USERS", dict_size(connected_users_dict));
+	ss_reply("SSMSG_STATUS_CHANNELS", dict_size(registered_channels_dict));
+
+	if(IsOper(user) && argc > 1)
+	{
+		if(!irccasecmp(argv[1], "memory"))
+			show_memory_usage(user);
+		else if(!irccasecmp(argv[1], "channels"))
+			show_registered_channels(user);		
+	}
+	
+	return 1;
+}
+
+static 
+SPAMSERV_FUNC(cmd_addexception)
+{
+	struct chanInfo *cInfo = get_chanInfo(channel->name);
+	struct userData *uData;
+	unsigned int i;
+
+	if(!cInfo || !channel->channel_info)
+	{
+		ss_reply("SSMSG_NOT_REGISTERED", channel->name);
+		return 0;
+	}
+
+	if(CHECK_SUSPENDED(cInfo))
+	{
+		ss_reply("SSMSG_SUSPENDED", channel->name);
+		return 0;
+	}
+
+	if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)) || (uData->access < 400))
+	{
+		ss_reply("SSMSG_NO_ACCESS");
+		return 0;
+	}
+
+	if(argc < 2)
+		return show_exceptions(user, cInfo);
+
+	if(cInfo->exceptions->used == spamserv_conf.exception_max && !IsOper(user))
+	{
+		ss_reply("SSMSG_EXCEPTION_MAX", spamserv_conf.exception_max);
+		return 0;
+	}
+
+	if(strlen(argv[1]) < spamserv_conf.exception_min_len)
+	{
+		ss_reply("SSMSG_EXCEPTION_TOO_SHORT", spamserv_conf.exception_min_len);
+		return 0;
+	}
+	else if(strlen(argv[1]) > spamserv_conf.exception_max_len)
+	{
+		ss_reply("SSMSG_EXCEPTION_TOO_LONG", spamserv_conf.exception_max_len);
+		return 0;
+	}
+
+	for(i = 0; i < cInfo->exceptions->used; i++)
+	{
+		if(!irccasecmp(argv[1], cInfo->exceptions->list[i]))
+		{
+			ss_reply("SSMSG_EXCEPTION_IN_LIST", argv[1]);
+			return 0;
+		}
+	}
+
+	string_list_append(cInfo->exceptions, strdup(argv[1]));
+	ss_reply("SSMSG_EXCEPTION_ADDED", argv[1]);
+
+	return 1;
+}
+
+static 
+SPAMSERV_FUNC(cmd_delexception)
+{
+	struct chanInfo *cInfo = get_chanInfo(channel->name);
+	struct userData *uData;
+	unsigned int i;
+	int found = -1;
+
+	if(!cInfo || !channel->channel_info)
+	{
+		ss_reply("SSMSG_NOT_REGISTERED", channel->name);
+		return 0;
+	}
+
+	if(CHECK_SUSPENDED(cInfo))
+	{
+		ss_reply("SSMSG_SUSPENDED", channel->name);
+		return 0;
+	}
+
+	if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)) || (uData->access < 400))
+	{
+		ss_reply("SSMSG_NO_ACCESS");
+		return 0;
+	}
+
+	if(argc < 2)
+		return show_exceptions(user, cInfo);
+
+	for(i = 0; i < cInfo->exceptions->used; i++)
+	{
+		if(!irccasecmp(argv[1], cInfo->exceptions->list[i]))
+		{
+			found = i;
+			break;
+		}
+	}
+	
+	if(found == -1)
+	{
+		ss_reply("SSMSG_NO_SUCH_EXCEPTION", argv[1]);
+		return 0;
+	}
+
+	string_list_delete(cInfo->exceptions, i);
+	ss_reply("SSMSG_EXCEPTION_DELETED", argv[1]);
+
+	return 1;
+}
+
+static 
+SPAMSERV_FUNC(cmd_set)
+{
+	struct chanInfo *cInfo = get_chanInfo(channel->name);
+	struct userData *uData;
+	struct svccmd	*subcmd;	
+	char cmd_name[MAXLEN];
+	unsigned int i;
+
+	if(!cInfo)
+	{
+		ss_reply("SSMSG_NOT_REGISTERED", channel->name);
+		return 0;
+	}
+
+	if(CHECK_SUSPENDED(cInfo))
+	{
+		ss_reply("SSMSG_SUSPENDED", channel->name);
+		return 0;
+	}
+
+	if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)) || (uData->access < 400))
+	{
+		ss_reply("SSMSG_NO_ACCESS");
+		return 0;
+	}
+	
+	if(argc < 2)
+	{
+		ss_reply("SSMSG_CHANNEL_OPTIONS");
+
+		for(i = 0; i < SET_SUBCMDS_SIZE; i++)
+		{
+			sprintf(cmd_name, "%s %s", cmd->name, set_subcommands[i]);
+
+			if((subcmd = dict_find(cmd->parent->commands, cmd_name, NULL)))
+				subcmd->command->func(user, channel, 1, argv + 1, subcmd);
+		}
+
+		return 1;
+	}
+
+	sprintf(cmd_name, "%s %s", cmd->name, argv[1]);
+	subcmd = dict_find(cmd->parent->commands, cmd_name, NULL);
+
+	if(!subcmd)
+	{
+		reply("SSMSG_INVALID_OPTION", argv[1], argv[0]);
+		return 0;
+	}
+
+	return subcmd->command->func(user, channel, argc - 1, argv + 1, subcmd);
+}
+
+static 
+SPAMSERV_FUNC(opt_spamlimit)
+{
+	struct valueData values[] =
+	{
+		{"Users may send the same message $b2$b times.", 'a', 0},
+		{"Users may send the same message $b3$b times.", 'b', 0},
+		{"Users may send the same message $b4$b times.", 'c', 0},
+		{"Users may send the same message $b5$b times.", 'd', 0},
+		{"Users may send the same message $b6$b times.", 'e', 0}
+	};
+
+	MULTIPLE_OPTION("SpamLimit     ", "SpamLimit", ci_SpamLimit);
+}
+
+static 
+SPAMSERV_FUNC(opt_advreaction)
+{
+	struct valueData values[] =
+	{
+		{"Kick on disallowed advertising.", 'k', 0},
+		{"Kickban on disallowed advertising.", 'b', 0},
+		{"Short timed ban on disallowed advertising.", 's', 0},
+		{"Long timed ban on disallowed advertising.", 'l', 0},
+		{"Kill on disallowed advertising.", 'd', 1}
+	};
+
+	MULTIPLE_OPTION("AdvReaction   ", "AdvReaction", ci_AdvReaction);
+}
+
+static 
+SPAMSERV_FUNC(opt_warnreaction)
+{
+	struct valueData values[] =
+	{
+		{"Kick after warning.", 'k', 0},
+		{"Kickban after warning.", 'b', 0},
+		{"Short timed ban after warning.", 's', 0},
+		{"Long timed ban after warning.", 'l', 0},
+		{"Kill after warning.", 'd', 1}
+	};
+
+	MULTIPLE_OPTION("WarnReaction  ", "WarnReaction", ci_WarnReaction);
+}
+
+static 
+SPAMSERV_FUNC(opt_advscan)
+{
+	BINARY_OPTION("AdvScan       ", CHAN_ADV_SCAN);
+}
+
+static 
+SPAMSERV_FUNC(opt_spamscan)
+{
+	BINARY_OPTION("SpamScan      ", CHAN_SPAMSCAN);
+}
+
+static 
+SPAMSERV_FUNC(opt_floodscan)
+{
+	BINARY_OPTION("FloodScan     ", CHAN_FLOODSCAN);
+}
+
+static 
+SPAMSERV_FUNC(opt_joinflood)
+{
+	BINARY_OPTION("JoinFloodScan ", CHAN_JOINFLOOD);
+}
+
+static 
+SPAMSERV_FUNC(opt_scanops)
+{
+	BINARY_OPTION("ScanChanOps   ", CHAN_SCAN_CHANOPS);
+}
+
+static 
+SPAMSERV_FUNC(opt_scanvoiced)
+{
+	BINARY_OPTION("ScanVoiced    ", CHAN_SCAN_VOICED);
+}
+
+static void 
+to_lower(char *message)
+{
+	unsigned int i, diff = 'a' - 'A';
+
+	for(i = 0; i < strlen(message); i++)
+	{
+		if((message[i] >= 'A') && (message[i] <= 'Z'))
+			message[i] = message[i] + diff;
+	}
+}
+
+static char *
+strip_mirc_codes(char *text)
+{
+	// taken from xchat and modified
+	int nc = 0, i = 0, col = 0, len = strlen(text);
+	static char new_str[MAXLEN];
+
+	while(len > 0)
+	{
+		if((col && isdigit(*text) && nc < 2) ||
+			(col && *text == ',' && isdigit(*(text + 1)) && nc < 3))
+		{
+			nc++;
+
+			if(*text == ',')
+				nc = 0;
+		}
+		else
+		{
+			col = 0;
+
+			switch(*text)
+			{
+			case '\003':
+				col = 1;
+				nc = 0;
+				break;
+			case '\002':
+			case '\022':
+			case '\026':			
+			case '\031':
+			case '\037':
+				break;
+			default:
+				new_str[i] = *text;
+				i++;
+			}
+		}
+
+		text++;
+		len--;
+	}
+
+	new_str[i] = '\0';
+
+	return new_str;
+}
+
+static int
+is_in_exception_list(struct chanInfo *cInfo, char *message)
+{
+	unsigned int i;
+
+	for(i = 0; i < cInfo->exceptions->used; i++)
+		if(strstr(message, cInfo->exceptions->list[i]))
+			return 1;
+
+	return 0;
+}
+
+static int
+check_advertising(struct chanInfo *cInfo, char *message)
+{
+	unsigned int i = 0;
+
+	if(spamserv_conf.strip_mirc_codes)
+		message = strip_mirc_codes(message);
+
+	if(is_in_exception_list(cInfo, message))
+		return 0;
+
+	while(message[i] != 0)
+	{
+		if(message[i] == '#')
+		{
+			char channelname[CHANNELLEN];
+			unsigned int j = 0;
+
+			if(!spamserv_conf.adv_chan_must_exist)
+				return 1;
+
+			/* only return 1, if the channel does exist */	
+
+			while((message[i] != 0) && (message[i] != ' '))
+			{
+				channelname[j] = message[i];
+				i++;
+				j++;				
+			}
+
+			channelname[j] = '\0';
+
+			if(GetChannel(channelname))
+				return 1;
+		}
+		else if((message[i] == 'w') && (message[i+1] == 'w') && (message[i+2] == 'w') && (message[i+3] == '.'))
+			return 1;
+		else if((message[i] == 'h') && (message[i+1] == 't') && (message[i+2] == 't') && (message[i+3] == 'p') && (message[i+4] == ':'))
+			return 1;
+		else if((message[i] == 'f') && (message[i+1] == 't') && (message[i+2] == 'p') && ((message[i+3] == '.') || (message[i+3] == ':')))
+			return 1;
+
+		i++;
+	}
+
+	return 0;
+}
+
+static void
+spamserv_punish(struct chanNode *channel, struct userNode *user, time_t expires, char *reason, int ban)
+{
+	if(ban)
+	{
+		struct mod_chanmode change;
+		char *hostmask = generate_hostmask(user, GENMASK_STRICT_HOST | GENMASK_ANY_IDENT);
+
+		sanitize_ircmask(hostmask);
+
+		if(expires)
+			add_channel_ban(channel->channel_info, hostmask, spamserv->nick, now, now, now + expires, reason);
+
+		mod_chanmode_init(&change);
+		change.argc = 1;
+		change.args[0].mode = MODE_BAN;
+      change.args[0].u.hostmask = hostmask;
+		mod_chanmode_announce(spamserv, channel, &change);        
+
+		free(hostmask);
+
+		spamserv_debug(SSMSG_DEBUG_BAN, user->nick, channel->name, reason);
+	}
+	else
+		spamserv_debug(SSMSG_DEBUG_KICK, user->nick, channel->name, reason);
+
+	KickChannelUser(user, channel, spamserv, reason);	
+}
+
+void
+spamserv_channel_message(struct chanNode *channel, struct userNode *user, char *text)
+{
+	struct chanInfo *cInfo;
+	struct userInfo	*uInfo;
+	struct spamNode *sNode;
+	struct floodNode *fNode;
+	unsigned int violation = 0;
+	char reason[MAXLEN];
+
+	/* make sure: spamserv is not disabled; srvx is running; spamserv is in the chan; chan is regged, user does exist */
+	if(!spamserv || quit_services || !GetUserMode(channel, spamserv) || !(cInfo = get_chanInfo(channel->name)) || !(uInfo = get_userInfo(user->nick)))
+		return;
+
+	if(!CHECK_CHANOPS(cInfo))
+	{
+		struct modeNode *mn = GetUserMode(channel, user);
+		if(mn->modes & MODE_CHANOP)
+			return;
+	}
+	
+	if(!CHECK_VOICED(cInfo))
+	{
+		struct modeNode *mn = GetUserMode(channel, user);
+		if((mn->modes & MODE_VOICE) && !(mn->modes & MODE_CHANOP))
+			return;
+	}
+
+	to_lower(text);
+
+	if(CHECK_SPAM(cInfo))
+	{
+		if(!(sNode = uInfo->spam))
+		{
+			spamserv_create_spamNode(channel, uInfo, text);
+		}
+		else
+		{
+			for(; sNode; sNode = sNode->next)
+				if(sNode->channel == channel)
+					break;
+
+			if(!sNode)
+			{
+				spamserv_create_spamNode(channel, uInfo, text);
+			}
+			else
+			{
+				unsigned long crc = crc32(text);
+
+				if(crc == sNode->crc32)
+				{
+					unsigned int spamlimit = 2;
+					sNode->count++;
+
+					switch(cInfo->info[ci_SpamLimit])
+					{
+						case 'a': spamlimit = 2; break;
+						case 'b': spamlimit = 3; break;
+						case 'c': spamlimit = 4; break;
+						case 'd': spamlimit = 5; break;
+						case 'e': spamlimit = 6; break;
+					}
+
+					if(sNode->count == spamlimit)
+					{
+						uInfo->warnlevel += SPAM_WARNLEVEL;
+
+						if(uInfo->warnlevel < MAX_WARNLEVEL)
+							spamserv_notice(user, spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_SPAM, spamserv_conf.network_rules);
+					}
+					else if(sNode->count > spamlimit)
+					{
+						switch(cInfo->info[ci_WarnReaction])
+						{
+							case 'k': uInfo->flags |= USER_KICK; break;
+							case 'b': uInfo->flags |= USER_KICKBAN; break;
+							case 's': uInfo->flags |= USER_SHORT_TBAN; break;
+							case 'l': uInfo->flags |= USER_LONG_TBAN; break;
+							case 'd': uInfo->flags |= CHECK_KILLED(uInfo) ? USER_GLINE : USER_KILL; break;
+						}
+
+						spamserv_delete_spamNode(uInfo, sNode);
+						uInfo->warnlevel += SPAM_WARNLEVEL;
+						violation = 1;
+					}
+				}
+				else
+				{
+					sNode->crc32 = crc;					
+					sNode->count = 1;
+				}
+			}
+		}
+	}
+
+	if(CHECK_FLOOD(cInfo))
+	{
+		if(!(fNode = uInfo->flood))
+		{
+			spamserv_create_floodNode(channel, user, &uInfo->flood);
+		}
+		else
+		{
+			for(; fNode; fNode = fNode->next)
+				if(fNode->channel == channel)
+					break;
+				
+			if(!fNode)
+			{
+				spamserv_create_floodNode(channel, user, &uInfo->flood);
+			}
+			else
+			{
+				if(((now - fNode->time) < FLOOD_EXPIRE))
+				{
+					fNode->count++;
+					
+					if(fNode->count == FLOOD_MAX_LINES - 1)
+					{
+						uInfo->warnlevel += FLOOD_WARNLEVEL;
+
+						if(uInfo->warnlevel < MAX_WARNLEVEL)
+							spamserv_notice(user, spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_FLOOD, spamserv_conf.network_rules);
+					}
+					else if(fNode->count > FLOOD_MAX_LINES)
+					{
+						switch(cInfo->info[ci_WarnReaction])
+						{
+							case 'k': uInfo->flags |= USER_KICK; break;
+							case 'b': uInfo->flags |= USER_KICKBAN; break;
+							case 's': uInfo->flags |= USER_SHORT_TBAN; break;
+							case 'l': uInfo->flags |= USER_LONG_TBAN; break;
+							case 'd': uInfo->flags |= CHECK_KILLED(uInfo) ? USER_GLINE : USER_KILL; break;
+						}
+
+						spamserv_delete_floodNode(&uInfo->flood, fNode);
+						uInfo->warnlevel += FLOOD_WARNLEVEL;
+						violation = 2;						
+					}
+				}
+
+				fNode->time = now;
+			}
+		}
+	}
+
+	if(CHECK_ADV(cInfo) && check_advertising(cInfo, text))
+	{
+		if(CHECK_ADV_WARNED(uInfo))
+		{
+			switch(cInfo->info[ci_AdvReaction])
+			{
+				case 'k': uInfo->flags |= USER_KICK; break;
+				case 'b': uInfo->flags |= USER_KICKBAN; break;
+				case 's': uInfo->flags |= USER_SHORT_TBAN; break;
+				case 'l': uInfo->flags |= USER_LONG_TBAN; break;
+				case 'd': uInfo->flags |= CHECK_KILLED(uInfo) ? USER_GLINE : USER_KILL; break;
+			}
+
+			uInfo->warnlevel += ADV_WARNLEVEL;
+			violation = 3;
+		}
+		else
+		{		
+			uInfo->flags |= USER_ADV_WARNED;
+			uInfo->lastadv = now;
+			uInfo->warnlevel += ADV_WARNLEVEL;
+
+			if(uInfo->warnlevel < MAX_WARNLEVEL)
+				spamserv_notice(user, spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_ADV, spamserv_conf.network_rules);
+		}		
+	}
+
+	if(!CHECK_WARNED(uInfo) && !CHECK_KILL(uInfo) && !CHECK_GLINE(uInfo) && uInfo->warnlevel == MAX_WARNLEVEL)
+	{
+		uInfo->flags |= USER_WARNED;
+		snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES_2 : SSMSG_WARNING_2, spamserv_conf.network_rules);
+		irc_notice(spamserv, user->numeric, reason);
+		irc_privmsg(spamserv, user->numeric, reason);
+	}
+	else if(uInfo->warnlevel > MAX_WARNLEVEL)
+	{
+		if(CHECK_KILLED(uInfo))
+			uInfo->flags |= USER_GLINE;
+		else
+			uInfo->flags |= USER_KILL;
+
+		violation = 5;
+	}
+
+	if(!violation)
+		return;
+
+	switch(violation)
+	{
+		case 1:	snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_SPAM, spamserv_conf.network_rules); break;
+		case 2:	snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_FLOOD, spamserv_conf.network_rules); break;
+		case 3:	snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES : SSMSG_WARNING, SSMSG_ADV, spamserv_conf.network_rules); break;
+		default: snprintf(reason, sizeof(reason), spamserv_conf.network_rules ? SSMSG_WARNING_RULES_2 : SSMSG_WARNING_2, spamserv_conf.network_rules); break;
+	}
+
+	if(CHECK_GLINE(uInfo))
+	{
+		int size = strlen(user->hostname) + 3;
+		char *mask = alloca(size);
+		snprintf(mask, size, "*@%s", user->hostname);
+		gline_add(spamserv->nick, mask, spamserv_conf.gline_duration, reason, now, now, 1);
+		spamserv_debug(SSMSG_DEBUG_GLINE, user->nick, user->hostname, channel->name);
+	}
+	else if(CHECK_KILL(uInfo))
+	{
+		DelUser(user, spamserv, 1, reason);
+		spamserv_debug(SSMSG_DEBUG_KILL, user->nick, channel->name);
+	}
+	else if(CHECK_LONG_TBAN(uInfo))
+	{
+		spamserv_punish(channel, user, spamserv_conf.long_ban_duration, reason, 1);
+	}
+	else if(CHECK_SHORT_TBAN(uInfo))
+	{
+		spamserv_punish(channel, user, spamserv_conf.short_ban_duration, reason, 1);
+	}
+	else if(CHECK_KICKBAN(uInfo))
+	{
+		spamserv_punish(channel, user, 0, reason, 1);
+	}
+	else if(CHECK_KICK(uInfo))
+	{
+		spamserv_punish(channel, user, 0, reason, 0);
+	}
+}
+
+static int
+spamserv_saxdb_read(struct dict *database)
+{
+	dict_iterator_t it;
+	struct record_data *hir;
+	struct chanNode	*channel;
+	struct chanInfo	*cInfo;
+	struct string_list *strlist;
+	unsigned int flags;
+	char *str, *info;	
+	time_t expiry;    
+
+	for(it = dict_first(database); it; it = iter_next(it))
+	{
+		hir = iter_data(it);
+
+		if(hir->type != RECDB_OBJECT)
+		{
+			log_module(SS_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, iter_key(it));
+			continue;
+		}
+
+		channel = GetChannel(iter_key(it));
+		strlist = database_get_data(hir->d.object, KEY_EXCEPTIONS, RECDB_STRING_LIST);
+
+		str = database_get_data(hir->d.object, KEY_FLAGS, RECDB_QSTRING);
+		flags = str ? atoi(str) : 0;
+
+		info = database_get_data(hir->d.object, KEY_INFO, RECDB_QSTRING);
+
+		str = database_get_data(hir->d.object, KEY_EXPIRY, RECDB_QSTRING);
+		expiry = str ? strtoul(str, NULL, 0) : 0;
+
+		if(channel && info)
+		{
+			if((cInfo = spamserv_register_channel(channel, strlist, flags, info)))
+			{
+				/* if the channel is suspended and expiry = 0 it means: channel will
+				   never expire ! it does NOT mean, the channel is not suspended */
+				if(CHECK_SUSPENDED(cInfo) && expiry && (expiry < now))
+				{
+					cInfo->flags &= ~CHAN_SUSPENDED;
+					spamserv_join_channel(cInfo->channel);
+				}
+				else if(!CHECK_SUSPENDED(cInfo))
+					spamserv_join_channel(cInfo->channel);
+				else
+					cInfo->suspend_expiry = expiry;				
+			}
+		}
+		else
+			log_module(SS_LOG, LOG_ERROR, "Couldn't register channel %s. Channel or info invalid.", iter_key(it));	
+	}
+
+	return 0;
+}
+
+static int
+spamserv_saxdb_write(struct saxdb_context *ctx)
+{
+	dict_iterator_t it;
+
+	for(it = dict_first(registered_channels_dict); it; it = iter_next(it))
+	{
+		struct chanInfo *cInfo = iter_data(it);
+
+		saxdb_start_record(ctx, cInfo->channel->name, 1);
+
+		if(cInfo->exceptions->used)
+			saxdb_write_string_list(ctx, KEY_EXCEPTIONS, cInfo->exceptions);
+
+		if(cInfo->flags)
+			saxdb_write_int(ctx, KEY_FLAGS, cInfo->flags);	
+
+		saxdb_write_string(ctx, KEY_INFO, cInfo->info);			
+
+		if(cInfo->suspend_expiry)
+			saxdb_write_int(ctx, KEY_EXPIRY, cInfo->suspend_expiry);		
+
+		saxdb_end_record(ctx);		
+	}
+	return 0;
+}
+
+static void
+spamserv_conf_read(void)
+{
+	dict_t conf_node;
+	const char *str; 
+
+	if(!(conf_node = conf_get_data(SPAMSERV_CONF_NAME, RECDB_OBJECT)))
+	{
+		log_module(SS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", SPAMSERV_CONF_NAME);
+		return;
+	}
+
+	str = database_get_data(conf_node, KEY_DEBUG_CHANNEL, RECDB_QSTRING);
+
+	if(str)
+	{
+		spamserv_conf.debug_channel = AddChannel(str, now, "+tinms", NULL);
+
+		if(spamserv_conf.debug_channel)
+			spamserv_join_channel(spamserv_conf.debug_channel);
+	}
+	else
+	{
+		spamserv_conf.debug_channel = NULL;
+	}
+
+	spamserv_conf.global_exceptions = database_get_data(conf_node, KEY_GLOBAL_EXCEPTIONS, RECDB_STRING_LIST);
+
+	str = database_get_data(conf_node, KEY_NETWORK_RULES, RECDB_QSTRING);
+	spamserv_conf.network_rules = str ? str : NULL;
+
+	str = database_get_data(conf_node, KEY_TRIGGER, RECDB_QSTRING);
+	spamserv_conf.trigger = str ? str[0] : 0;
+
+	str = database_get_data(conf_node, KEY_SHORT_BAN_DURATION, RECDB_QSTRING);
+	spamserv_conf.short_ban_duration = str ? ParseInterval(str) : ParseInterval("15m");
+
+	str = database_get_data(conf_node, KEY_LONG_BAN_DURATION, RECDB_QSTRING);
+	spamserv_conf.long_ban_duration = str ? ParseInterval(str) : ParseInterval("1h");
+
+	str = database_get_data(conf_node, KEY_GLINE_DURATION, RECDB_QSTRING);
+	spamserv_conf.gline_duration = str ? ParseInterval(str) : ParseInterval("1h");
+
+	str = database_get_data(conf_node, KEY_EXCEPTION_MAX, RECDB_QSTRING);
+	spamserv_conf.exception_max = str ? strtoul(str, NULL, 0) : 10;
+
+	str = database_get_data(conf_node, KEY_EXCEPTION_MIN_LEN, RECDB_QSTRING);
+	spamserv_conf.exception_min_len = str ? strtoul(str, NULL, 0) : 4;
+
+	str = database_get_data(conf_node, KEY_EXCEPTION_MAX_LEN, RECDB_QSTRING);
+	spamserv_conf.exception_max_len = str ? strtoul(str, NULL, 0) : 15;
+
+	str = database_get_data(conf_node, KEY_ADV_CHAN_MUST_EXIST, RECDB_QSTRING);
+	spamserv_conf.adv_chan_must_exist = str ? enabled_string(str) : 1;
+
+	str = database_get_data(conf_node, KEY_STRIP_MIRC_CODES, RECDB_QSTRING);
+	spamserv_conf.strip_mirc_codes = str ? enabled_string(str) : 0;
+
+	str = database_get_data(conf_node, KEY_ALLOW_MOVE_MERGE, RECDB_QSTRING);
+	spamserv_conf.allow_move_merge = str ? enabled_string(str) : 0;
+}
+
+static void
+spamserv_db_cleanup(void)
+{
+	dict_iterator_t it;
+
+	while((it = dict_first(registered_channels_dict)))
+	{
+		spamserv_unregister_channel(iter_data(it));
+	}
+
+	while((it = dict_first(killed_users_dict)))
+	{
+		free(iter_data(it));
+	}
+	
+	dict_delete(registered_channels_dict);
+	dict_delete(connected_users_dict);
+	dict_delete(killed_users_dict);
+}
+
+void
+init_spamserv(const char *nick)
+{
+	if(!nick)
+		return;
+
+	const char *modes = conf_get_data("services/spamserv/modes", RECDB_QSTRING);	
+	spamserv = AddLocalUser(nick, nick, NULL, "Anti Spam Services", modes);
+	spamserv_service = service_register(spamserv);
+	service_register(spamserv)->trigger = spamserv_conf.trigger;
+
+	conf_register_reload(spamserv_conf_read);
+
+	SS_LOG = log_register_type("SpamServ", "file:spamserv.log");	
+
+	registered_channels_dict = dict_new();
+	connected_users_dict = dict_new();
+	killed_users_dict = dict_new();
+
+	reg_new_user_func(spamserv_new_user_func);
+	reg_del_user_func(spamserv_del_user_func);
+	reg_nick_change_func(spamserv_nick_change_func);
+	reg_join_func(spamserv_user_join);
+	reg_part_func(spamserv_user_part);
+
+	timeq_add(now + FLOOD_TIMEQ_FREQ, timeq_flood, NULL);
+	timeq_add(now + JOINFLOOD_TIMEQ_FREQ, timeq_joinflood, NULL);
+	timeq_add(now + ADV_TIMEQ_FREQ, timeq_adv, NULL);
+	timeq_add(now + WARNLEVEL_TIMEQ_FREQ, timeq_warnlevel, NULL);
+	timeq_add(now + KILL_TIMEQ_FREQ, timeq_kill, NULL);
+
+	spamserv_module = module_register("SpamServ", SS_LOG, "spamserv.help", NULL);
+	modcmd_register(spamserv_module, "REGISTER", cmd_register, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, "flags", "+acceptchan,+helping", NULL);
+	modcmd_register(spamserv_module, "UNREGISTER", cmd_unregister, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, "flags", "+loghostmask", NULL);
+	modcmd_register(spamserv_module, "ADDEXCEPTION", cmd_addexception, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+	modcmd_register(spamserv_module, "DELEXCEPTION", cmd_delexception, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+	modcmd_register(spamserv_module, "STATUS", cmd_status, 1, 0, NULL);
+	modcmd_register(spamserv_module, "SET", cmd_set, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+	modcmd_register(spamserv_module, "SET SPAMLIMIT", opt_spamlimit, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+	modcmd_register(spamserv_module, "SET ADVREACTION", opt_advreaction, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+	modcmd_register(spamserv_module, "SET WARNREACTION", opt_warnreaction, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+	modcmd_register(spamserv_module, "SET ADVSCAN", opt_advscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+	modcmd_register(spamserv_module, "SET SPAMSCAN", opt_spamscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+	modcmd_register(spamserv_module, "SET FLOODSCAN", opt_floodscan, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+	modcmd_register(spamserv_module, "SET JOINFLOODSCAN", opt_joinflood, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+	modcmd_register(spamserv_module, "SET SCANCHANOPS", opt_scanops, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+	modcmd_register(spamserv_module, "SET SCANVOICED", opt_scanvoiced, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+
+	saxdb_register("SpamServ", spamserv_saxdb_read, spamserv_saxdb_write);
+	reg_exit_func(spamserv_db_cleanup);
+	message_register_table(msgtab);
+	crc32_init();
+}
diff -NuwrU 4 srvx-1.4.0-rc3/src/spamserv.h srvx-1.4.0-rc3-ss/src/spamserv.h
--- srvx-1.4.0-rc3/src/spamserv.h	2010-03-07 18:18:25.000000000 +0100
+++ srvx-1.4.0-rc3-ss/src/spamserv.h	2010-03-07 18:08:20.000000000 +0100
@@ -0,0 +1,171 @@
+/* spamserv.h - anti spam service
+ * Copyright 2004 feigling
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.  Important limitations are
+ * listed in the COPYING file that accompanies this software.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, email srvx-maintainers@srvx.net.
+ *
+ * $Id: spamserv.h,v 1.3 2004/06/27 22:20:00 feigling Exp $
+ */
+
+#ifndef _spamserv_h
+#define _spamserv_h
+
+#ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+/***********************************************/
+/*                   Channel                   */
+/***********************************************/
+
+enum channelinfo
+{
+    ci_SpamLimit = 0,
+    ci_AdvReaction,
+    ci_WarnReaction,
+    ci_Max
+};
+
+#define CHAN_INFO_SIZE		(ci_Max + 1)
+#define CHAN_INFO_DEFAULT	"bls"
+
+#define CHAN_SPAMSCAN		0x00000001
+#define CHAN_FLOODSCAN		0x00000002
+#define CHAN_JOINFLOOD		0x00000004
+#define CHAN_ADV_SCAN		0x00000008
+#define CHAN_SCAN_CHANOPS	0x00000010
+#define CHAN_SCAN_VOICED	0x00000020
+#define CHAN_SUSPENDED		0x00000040
+
+#define CHAN_FLAGS_DEFAULT	(CHAN_SPAMSCAN | CHAN_FLOODSCAN | CHAN_JOINFLOOD)
+
+#define CHECK_SPAM(x)		((x)->flags & CHAN_SPAMSCAN)
+#define CHECK_FLOOD(x)		((x)->flags & CHAN_FLOODSCAN)
+#define CHECK_JOINFLOOD(x)	((x)->flags & CHAN_JOINFLOOD)
+#define CHECK_ADV(x)		((x)->flags & CHAN_ADV_SCAN)
+#define CHECK_CHANOPS(x)	((x)->flags & CHAN_SCAN_CHANOPS)
+#define CHECK_VOICED(x)		((x)->flags & CHAN_SCAN_VOICED)
+#define CHECK_SUSPENDED(x)	((x)->flags & CHAN_SUSPENDED)
+
+struct chanInfo
+{
+    struct chanNode        *channel;
+    struct string_list     *exceptions;
+    unsigned int           flags : 30;
+    char                   info[CHAN_INFO_SIZE];
+    time_t                 suspend_expiry;
+};
+
+/***********************************************/
+/*                    User                     */
+/***********************************************/
+
+#define USER_KICK           0x00000001
+#define USER_KICKBAN        0x00000002
+#define USER_SHORT_TBAN     0x00000004
+#define USER_LONG_TBAN      0x00000008
+#define USER_KILL           0x00000010
+#define USER_GLINE          0x00000020
+#define USER_WARNED         0x00000040
+#define USER_KILLED         0x00000080
+#define USER_ADV_WARNED     0x00000100
+
+#define CHECK_KICK(x)		((x)->flags & USER_KICK)
+#define CHECK_KICKBAN(x)	((x)->flags & USER_KICKBAN)
+#define CHECK_SHORT_TBAN(x)	((x)->flags & USER_SHORT_TBAN)
+#define CHECK_LONG_TBAN(x)	((x)->flags & USER_LONG_TBAN)
+#define CHECK_KILL(x)		((x)->flags & USER_KILL)
+#define CHECK_GLINE(x)		((x)->flags & USER_GLINE)
+#define CHECK_WARNED(x)		((x)->flags & USER_WARNED)
+#define CHECK_KILLED(x)		((x)->flags & USER_KILLED)
+#define CHECK_ADV_WARNED(x)	((x)->flags & USER_ADV_WARNED)
+
+#define SPAM_WARNLEVEL          1
+
+#define FLOOD_TIMEQ_FREQ        5
+#define FLOOD_EXPIRE            5
+#define FLOOD_WARNLEVEL         1
+#define FLOOD_MAX_LINES         8
+
+#define JOINFLOOD_TIMEQ_FREQ    225
+#define JOINFLOOD_EXPIRE        450
+#define JOINFLOOD_MAX           3
+#define JOINFLOOD_B_DURATION    900
+
+#define ADV_TIMEQ_FREQ          300
+#define ADV_EXPIRE              900
+#define ADV_WARNLEVEL           2
+
+#define WARNLEVEL_TIMEQ_FREQ    1800
+#define MAX_WARNLEVEL           6
+
+#define KILL_TIMEQ_FREQ         450
+#define KILL_EXPIRE             1800
+#define KILL_WARNLEVEL          3
+
+struct spamNode
+{
+	struct chanNode		*channel;
+	unsigned long		crc32;
+	unsigned int		count;
+	struct spamNode		*prev;
+	struct spamNode		*next;
+};
+
+struct floodNode
+{
+	struct chanNode		*channel;
+	struct userNode		*owner;
+	unsigned int		count;
+	time_t        		time;
+	struct floodNode	*prev;
+	struct floodNode	*next;
+};
+
+struct killNode
+{
+	unsigned int		warnlevel;
+	time_t        		time;
+};
+
+struct userInfo
+{
+    struct userNode		*user;
+	struct spamNode		*spam;
+	struct floodNode	*flood;
+	struct floodNode	*joinflood;
+	unsigned int		flags : 30;
+	unsigned int		warnlevel;
+	time_t        		lastadv;
+};
+
+/***********************************************/
+/*                 Other Stuff                 */
+/***********************************************/
+
+enum cs_unreg
+{
+    manually,
+    expire,
+    lost_all_users
+};
+
+void init_spamserv(const char *nick);
+struct chanInfo *get_chanInfo(const char *channelname);
+void spamserv_channel_message(struct chanNode *channel, struct userNode *user, char *text);
+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);
+
+#endif
diff -NuwrU 4 srvx-1.4.0-rc3/src/spamserv.help srvx-1.4.0-rc3-ss/src/spamserv.help
--- srvx-1.4.0-rc3/src/spamserv.help	2010-03-07 18:18:43.000000000 +0100
+++ srvx-1.4.0-rc3-ss/src/spamserv.help	2010-03-07 18:08:42.000000000 +0100
@@ -0,0 +1,91 @@
+"<INDEX>" ("$b$X Help$b",
+        "The $b$X$b service checks the channel for spam, flood, joinflood and disallowed advertisements.",
+        "$bUser Commands:$b",
+        "  ADDEXCEPTION  Adds a word to the exception list.",
+        "  DELEXCEPTION  Deletes a word from the exception list.",
+        "  SET           Changes various channel settings.",
+        "  STATUS        Shows general information about $X.",
+        "  VERSION       Prints the srvx and $X version information.",
+        "$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");
+"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.",
+        "$uSee Also:$u addexception");
+"SET" ("/msg $X SET <#channel> [<parameter> [setting]]",
+        "This command will set various channel options. With no arguments, it will show the current values of all channel options.",
+        "Only channel owners and coowners may change settings.",
+        "SPAMLIMIT:      Number of equal lines, a user may send.",
+        "ADVREACTION:    What happens when someone advertises after warning.",
+        "WARNREACTION:   What happens when someone continues spamming/flooding after warning.",
+        "ADVSCAN:        Enables/Disables scanning for advertisements.",
+        "SPAMSCAN:       Enables/Disables scanning for spam.",
+        "FLOODSCAN:      Enables/Disables scanning for flood.",
+        "JOINFLOODSCAN:  Enables/Disables scanning for joinflood.",
+        "SCANCHANOPS:    Indicates whether $X has to scan messages from channel ops.",
+        "SCANVOICED:     Indicates whether $X has to scan messages from voiced users.",
+        "$uSee Also:$u set spamlimit, set advreaction, set warnreaction, set advscan, set spamscan, set floodscan, set joinfloodscan, set scanchanops, set scanvoiced");
+"SET SPAMLIMIT" ("/msg $X SET <#channel> SPAMLIMIT <value>",
+        "You can specify the number of equal messages, a user may send.  Valid settings are:",
+        "$b0$b  Users may send the same message $b2$b times.",
+        "$b1$b  Users may send the same message $b3$b times.",
+        "$b2$b  Users may send the same message $b4$b times.",
+        "$b3$b  Users may send the same message $b5$b times.",
+        "$b4$b  Users may send the same message $b6$b times.",
+        "$uSee Also:$u set spamscan");
+"SET ADVREACTION" ("/msg $X SET <#channel> ADVREACTION <value>",
+        "This setting controls what happens to those who send disallowed advertisements to the channel after a warning:",
+        "$b0$b  Kick on disallowed advertising.",
+        "$b1$b  Kickban on disallowed advertising.",
+        "$b2$b  Short timed ban (default: 15 minutes) on disallowed advertising.",
+        "$b3$b  Long timed ban (default: 1 hour) on disallowed advertising.",
+        "$b4$b  Kill on disallowed advertising. Only settable by irc operators.",
+        "$uSee Also:$u set advscan");
+"SET WARNREACTION" ("/msg $X SET <#channel> WARNREACTION <value>",
+        "This setting controls what happens to those who spam or flood the channel after a warning:",
+        "$b0$b  Kick after warning.",
+        "$b1$b  Kickban after warning.",
+        "$b2$b  Short timed ban (default: 15 minutes) after warning.",
+        "$b3$b  Long timed ban (default: 1 hour) after warning.",
+        "$b4$b  Kill after warning. Only settable by irc operators.",
+        "$uSee Also:$u set spamscan, set floodscan");
+"SET ADVSCAN" ("/msg $X SET <#channel> ADVSCAN <1/0>",
+        "If this setting is enabled, $X checks all messages for advertisements.",
+        "Advertisements are: www.*, http:*, ftp.*, ftp:* and #*; e.g. #srvx, http://www.srvx.net etc ..");
+"SET SPAMSCAN" ("/msg $X SET <#channel> SPAMSCAN <1/0>",
+        "If this setting is enabled, $X checks all incoming channel messages for spam.",
+        "Posting the same message multiple times is considered as spam, which means, if someone posts the same message more than the number of times, which is allowed (/msg $X set SPAMLIMIT), $X will punish him.");
+"SET FLOODSCAN" ("/msg $X SET <#channel> FLOODSCAN <1/0>",
+        "If this setting is enabled, $X checks, if a person tries to flood the channel.",
+        "Posting messages in a small amount of time is considered as flood, so if someone tries to flood the channel, $X will punish him.");
+"SET JOINFLOODSCAN" ("/msg $X SET <#channel> JOINFLOODSCAN <1/0>",
+        "If this setting is enabled, $X checks, if a person joins the channel more than one time.",
+        "Normally users join a channel and stay in the channel or part and do not rejoin after a few seconds.",
+        "If they want to cause trouble, they join/part the channel very often. $X will punish every user, who does that.");
+"SET SCANCHANOPS" ("/msg $X SET <#channel> SCANCHANOPS <1/0>",
+        "If this setting is disabled, $X doesn't check messages from oped users for spam, flood and advertisements.");
+"SET SCANVOICED" ("/msg $X SET <#channel> SCANVOICED <1/0>",
+        "If this setting is disabled, $X doesn't check messages from voiced users for spam, flood and advertisements.");
+"REGISTER" ("/msg $X REGISTER <#channel>",
+        "Registers a channel with $X.",
+        "The Channel must be registered with $C and may not be suspended.",
+        "$uSee Also:$u unregister");
+"STATUS" ("/msg $X STATUS [MEMORY|CHANNELS]",
+        "$bSTATUS$b shows you general information about $X. An irc operator can get information about the memory usage and a list of all registered channels.");
+"UNREGISTER" ("/msg $X UNREGISTER <#channel> [CONFIRM]",
+        "Removes $X from the given channel.",
+        "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.");
\ Kein Zeilenumbruch am Dateiende.
diff -NuwrU 4 srvx-1.4.0-rc3/srvx.conf.example srvx-1.4.0-rc3-ss/srvx.conf.example
--- srvx-1.4.0-rc3/srvx.conf.example	2008-07-12 05:49:09.000000000 +0200
+++ srvx-1.4.0-rc3-ss/srvx.conf.example	2010-03-07 18:21:44.000000000 +0100
@@ -262,8 +262,44 @@
 	// "modes" "+iok";
         // should users get community announcements by default or not?
         "announcements_default" "on";
     };
+    
+     "spamserv" {
+        // You may disable a service by removing its "nick" config item.
+        // That means: SERVICES WILL ONLY WORK IF YOU DEFINE THEIR NICK.
+        // (This is changed relative srvx-1.0.x, which would use default
+        // unless you specified ".disabled".)
+        "nick" "SpamServ";
+        "debug_channel" "#spamserv";
+        // url of the network rules. if you don't have network rules, remove this key.
+        "network_rules" "http://www.gamesurge.net/aup";
+        // trigger for spamserv; remove this key to disable the trigger
+        "trigger" "-";
+        // ban duration of a short timedban.
+        "short_ban_duration" "15m";
+        // ban duration of a long timedban.
+        "long_ban_duration" "1h";
+        // duration of a gline. SpamServ will issue it after several violations and a kill.
+        "gline_duration" "1h";
+        // users may add "exception_max" exceptions to the list. IRCOps can override "exception_max".
+        "exception_max" "10";
+        // minimum & maximum length of an exception.
+        "exception_min_len" "4";
+        "exception_max_len" "12";
+        // if someone advertises a channel, which doesn't exist (channel is empty, no users),
+        // SpamServ doesn't punish the user.
+        // enable this setting, if SpamServ has to ignore advertisements of channels, which do not exist.
+        // disable this setting, if SpamServ has to punish the users whenever they advertise.
+        "adv_chan_must_exist" "0";
+        // remove all mirc codes from messages before checking for advertisements.
+        // if this setting is disabled and someone spams a url which
+        // contains a bold char, SpamServ doesn't punish him.
+        "strip_mirc_codes" "1";
+        // enable this, if SpamServ has to "follow" ChanServ, when a channel moves or merges.
+        // disable it, if it shouldn't be possible to move or merge SpamServ with /msg chanserv move|merge.
+        "allow_move_merge" "1";
+    };
 };
 
 // The modules section gives configuration information to optional modules.
 "modules" {
@@ -413,8 +449,9 @@
     "modcmd" { "mondo_section" "modcmd"; };
     "NickServ" { "mondo_section" "NickServ"; };
     "OpServ" { "mondo_section" "OpServ"; };
     "sendmail" { "mondo_section" "sendmail"; };
+    "SpamServ" { "mondo_section" "SpamServ"; };
 
     // These are the options if you want a database to be in its own file.
     "mondo" {
         // Where to put it?
