Merge remote branch 'upstream/master'
authorroot <root@localhost>
Thu, 2 Feb 2012 21:44:45 +0000 (22:44 +0100)
committerroot <root@localhost>
Thu, 2 Feb 2012 21:44:45 +0000 (22:44 +0100)
Conflicts:
src/proto-p10.c

27 files changed:
INSTALL
configure.in
src/Makefile.am
src/chanserv.c
src/chanserv.h
src/hash.c
src/hash.h
src/helpfile.c
src/main-common.c
src/main.c
src/mod-helpserv.c
src/mod-watchdog.c [new file with mode: 0644]
src/mod-watchdog.help [new file with mode: 0644]
src/modcmd.c
src/nickserv.c
src/nickserv.h
src/nickserv.help
src/opserv.c
src/opserv.h
src/proto-bahamut.c
src/proto-common.c
src/proto-p10.c
src/proto.h
src/spamserv.c [new file with mode: 0644]
src/spamserv.h [new file with mode: 0644]
src/spamserv.help [new file with mode: 0644]
srvx.conf.example

diff --git a/INSTALL b/INSTALL
index c31fd17db17c3b7a4ff06069ad83f86ba3ce5b51..e74bb49bae4c6759ae4b57ce60d9ccbc1c5a8745 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -49,6 +49,14 @@ You may also have trouble unless your compiler's C preprocessor
 supports ISO C99 varadic macros.  gcc is the compiler we use for
 almost all our testing, and we recommend it for use with srvx.
 
+PreInstall:
+----------
+$ aclocal
+$ autoconf
+$ autoreconf -f -i -Wall,no-obsolete
+$ cp /usr/share/libtool/* ./
+$ automake
+
 Quick Install:
 ----------
 $ ./configure
index 5b350a985dfd344e391d97260f830f51c3cee088..a3a885165a080b602c80af02da02b311a5ace647 100644 (file)
@@ -3,7 +3,7 @@ dnl Process this file with autoconf to create a configure script.
 dnl General initialization.
 AC_PREREQ(2.64)
 AC_INIT([srvx],[1.4.0-rc3],[srvx-bugs@lists.sourceforge.net])
-CODENAME=surge
+CODENAME=wgn
 AC_CONFIG_HEADERS(src/config.h)
 AC_CONFIG_SRCDIR(src/opserv.c)
 dnl AM_CANONICAL_TARGET must be before AM_INIT_AUTOMAKE() or autoconf whines
@@ -24,6 +24,7 @@ AC_PROG_INSTALL
 AC_PROG_LN_S
 AC_PROG_MAKE_SET
 AC_PROG_GCC_TRADITIONAL
+AC_PROG_RANLIB
 
 dnl Look for a git client
 AC_CHECK_PROGS(GIT, [git])
index adcb5518c5d0c8dee722fd3e35d82183e19b6cb6..cdf6ae818dce7a44319a9f3aca288ab297251157 100644 (file)
@@ -11,7 +11,9 @@ noinst_DATA = \
        opserv.help \
        saxdb.help \
        mail.help \
+    spamserv.help \
        mod-helpserv.help \
+    mod-watchdog.help \
        mod-memoserv.help \
        mod-qserver.help \
        mod-snoop.help \
@@ -53,6 +55,7 @@ EXTRA_srvx_SOURCES = \
        proto-p10.c \
        mod-blacklist.c \
        mod-helpserv.c \
+    mod-watchdog.c \
        mod-memoserv.c \
        mod-qserver.c \
        mod-snoop.c \
@@ -86,6 +89,7 @@ srvx_SOURCES = \
        recdb.c recdb.h \
        sar.c sar.h \
        saxdb.c saxdb.h \
+    spamserv.c spamserv.h \
        timeq.c timeq.h \
        tools.c
 
index 2ba265f1ab6cc778baceed3c9fae8cb4c50a534c..75122799b0d28a6e729cea46cbe3d51f9eb3652a 100644 (file)
 #include "conf.h"
 #include "global.h"
 #include "modcmd.h"
-#include "opserv.h" /* for opserv_bad_channel() */
+#include "opserv.h" /* for opserv_bad_channel() and devnull management */
 #include "nickserv.h" /* for oper_outranks() */
 #include "saxdb.h"
+#include "spamserv.h"
 #include "timeq.h"
 
 #define CHANSERV_CONF_NAME  "services/chanserv"
 #define KEY_NODELETE_LEVEL          "nodelete_level"
 #define KEY_MAX_USERINFO_LENGTH     "max_userinfo_length"
 #define KEY_GIVEOWNERSHIP_PERIOD    "giveownership_timeout"
+#define KEY_INVITED_INTERVAL           "invite_timeout"
+#define KEY_NEW_CHANNEL_AUTHED      "new_channel_authed_join"
+#define KEY_NEW_CHANNEL_UNAUTHED    "new_channel_unauthed_join"
+#define KEY_NEW_CHANNEL_MSG         "new_channel_message"
 
 /* ChanServ database */
 #define KEY_CHANNELS                "channels"
 #define KEY_USERS           "users"
 #define KEY_BANS            "bans"
 #define KEY_MAX             "max"
+#define KEY_MAX_TIME        "max_time"
 #define KEY_NOTES           "notes"
 #define KEY_TOPIC_MASK      "topic_mask"
 #define KEY_OWNER_TRANSFER  "owner_transfer"
+#define KEY_EXPIRE          "expire"
 
 /* User data */
 #define KEY_LEVEL   "level"
 #define KEY_INFO    "info"
 #define KEY_SEEN    "seen"
 
+/* Votes */
+#define KEY_VOTE    "vote"
+#define KEY_VOTE_START    "votestart"
+#define KEY_VOTE_OPTIONS    "voptions"
+#define KEY_VOTE_OPTION_NAME    "voptionname"
+#define KEY_VOTE_VOTED    "vvoted"
+#define KEY_VOTE_VOTEDFOR    "vvotefor"
+#define KEY_VOTE_OPTION_ID    "voptionid"
+#define KEY_VOTE_OPTION_VOTED    "voptionvoted"
+
 /* Ban data */
 #define KEY_OWNER       "owner"
 #define KEY_REASON      "reason"
@@ -192,7 +209,7 @@ static const struct message_entry msgtab[] = {
 /* User management */
     { "CSMSG_ADDED_USER", "Added %s to the %s user list with access %d." },
     { "CSMSG_DELETED_USER", "Deleted %s (with access %d) from the %s user list." },
-    { "CSMSG_BAD_RANGE", "Invalid access range; minimum (%d) must be greater than maximum (%d)." },
+    { "CSMSG_BAD_RANGE", "Invalid access range; minimum (%d) must be lower than maximum (%d)." },
     { "CSMSG_DELETED_USERS", "Deleted accounts matching $b%s$b with access from $b%d$b to $b%d$b from the %s user list." },
     { "CSMSG_TRIMMED_USERS", "Trimmed $b%d users$b with access from %d to %d from the %s user list who were inactive for at least %s." },
     { "CSMSG_INCORRECT_ACCESS", "%s has access $b%d$b, not %s." },
@@ -265,6 +282,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_SET_USERINFO",      "$bUserInfo    $b %d" },
     { "CSMSG_SET_GIVE_VOICE",    "$bGiveVoice   $b %d" },
     { "CSMSG_SET_TOPICSNARF",    "$bTopicSnarf  $b %d" },
+    { "CSMSG_SET_VOTE",          "$bVote        $b %d" },
     { "CSMSG_SET_INVITEME",      "$bInviteMe    $b %d" },
     { "CSMSG_SET_ENFOPS",        "$bEnfOps      $b %d" },
     { "CSMSG_SET_GIVE_OPS",      "$bGiveOps     $b %d" },
@@ -278,6 +296,8 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_SET_CTCPREACTION",  "$bCTCPReaction$b %d - %s" },
     { "CSMSG_SET_TOPICREFRESH",  "$bTopicRefresh$b %d - %s" },
     { "CSMSG_SET_UNREVIEWED",    "$bUnreviewed  $b %s" },
+    { "CSMSG_SET_EXPIRE",        "$bExpire      $b %s" },
+    { "CSMSG_SET_EXPIRE_OFF",    "$bExpire      $b off" },
     { "CSMSG_USET_NOAUTOOP",     "$bNoAutoOp    $b %s" },
     { "CSMSG_USET_NOAUTOVOICE",  "$bNoAutoVoice $b %s" },
     { "CSMSG_USET_AUTOINVITE",   "$bAutoInvite  $b %s" },
@@ -325,6 +345,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_ACCESS_SEARCH_HEADER", "%s users from level %d to %d matching %s:" },
     { "CSMSG_INVALID_ACCESS", "$b%s$b is an invalid access level." },
     { "CSMSG_CHANGED_ACCESS", "%s now has access $b%d$b in %s." },
+    { "CSMSG_TOTAL_USERS", "There are $b%d$b users in %s." },
 
 /* Channel note list */
     { "CSMSG_NOTELIST_HEADER", "Notes for $b%s$b:" },
@@ -367,6 +388,9 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_UC_H_TITLE", "network helper" },
     { "CSMSG_LC_H_TITLE", "support helper" },
     { "CSMSG_LAME_SMURF_TARGET", "%s is an IRC operator." },
+    { "CSMSG_MYACCESS_COUNT", "%s has access in $b%d$b channels and is owner of $b%d$b channel(s)." },
+       { "CSMSG_MYACCESS_COUNT_1", "%s has access in $b%d$b channel and is owner of $b%d$b channel(s)." },
+
 
 /* Seen information */
     { "CSMSG_NEVER_SEEN", "%s has never been seen in $b%s$b." },
@@ -384,6 +408,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_CHANNEL_MODES", "$bMode Lock:           $b%s" },
     { "CSMSG_CHANNEL_NOTE", "$b%s:%*s$b%s" },
     { "CSMSG_CHANNEL_MAX", "$bRecord Visitors:     $b%i" },
+    { "CSMSG_CHANNEL_MAX_TIME", "$bRecord Visitors:     $b%i (%s ago.)" },
     { "CSMSG_CHANNEL_OWNER", "$bOwner:               $b%s" },
     { "CSMSG_CHANNEL_BANS", "$bBan Count:           $b%i" },
     { "CSMSG_CHANNEL_USERS", "$bTotal User Count:    $b%i" },
@@ -452,6 +477,32 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_HUGGLES_HIM", "\001ACTION huggles %s\001" },
     { "CSMSG_HUGGLES_YOU", "\001ACTION huggles you\001" },
 
+/* Vote */
+    { "CSMSG_ADDVOTE_DONE", "Vote added. Use $baddoption$b to add at least 2 vote options and then $bstartvote$b to start the voting." },
+    { "CSMSG_ADDVOTE_FULL", "There is already a vote in this channel. Use $bdelvote$b to delete it." },
+    { "CSMSG_DELVOTE_DONE", "Vote deleted." },
+    { "CSMSG_NO_VOTE", "There is no vote in this channel." },
+    { "CSMSG_ADDOPTION_DONE", "Vote option added with id %i (%i - %i)." },
+    { "CSMSG_DELOPTION_DONE", "Vote option deleted." },
+    { "CSMSG_DELOPTION_NONE", "Vote option does not exist." },
+    { "CSMSG_VOTE_NEED_OPTIONS", "There must be at least 2 options in a vote." },
+    { "CSMSG_VOTE_NOT_STARTED", "The vote is not started. Use $bstartvote$b to allow voting." },
+    { "CSMSG_VOTE_QUESTION", "Question:   %s" },
+    { "CSMSG_VOTE_OPTION", "$b%i$b:   %s ($b%i$b votes)" },
+    { "CSMSG_VOTERES_QUESTION", "Question:   %s" },
+    { "CSMSG_VOTERES_OPTION", "\ 2%i\ 2:   %s (\ 2%i\ 2 votes)" },
+    { "CSMSG_STARTVOTE_TOP", "\ 2%s\ 2 has started a voting:" },
+    { "CSMSG_STARTVOTE_QUESTION", "\ 2Question:\ 2 %s" },
+    { "CSMSG_STARTVOTE_OPTION", "\ 2%i:\ 2  %s" },
+    { "CSMSG_STARTVOTE_ACCESS", "All channel users with at least \ 2%i\ 2 access can vote." },
+    { "CSMSG_STARTVOTE_HOWTO", "To vote for an option, use \ 2vote ID\ 2. To see the available options and the current votes, use \ 2vote\ 2 without parameters." },
+    { "CSMSG_STARTVOTE_RUNNING", "The vote is already running." },
+    { "CSMSG_VOTE_VOTED", "You have already voted." },
+    { "CSMSG_VOTE_INVALID", "Vote option does not exist." },
+    { "CSMSG_VOTE_DONE", "You voted for $b%s$b." },
+    { "CSMSG_ENDVOTE_DONE", "The vote has been finished." },
+    { "CSMSG_ENDVOTE_STOPPED", "The vote is not running." },
+
 /* Other things */
     { "CSMSG_EVENT_SEARCH_RESULTS", "The following channel events were found:" },
     { NULL, NULL }
@@ -498,6 +549,8 @@ static struct
     unsigned long   db_backup_frequency;
     unsigned long   channel_expire_frequency;
     unsigned long   dnr_expire_frequency;
+    
+    unsigned long   invited_timeout;
 
     unsigned long   info_delay;
     unsigned long   adjust_delay;
@@ -526,6 +579,10 @@ static struct
     const char          *irc_operator_epithet;
     const char          *network_helper_epithet;
     const char          *support_helper_epithet;
+
+    const char          *new_channel_authed;
+    const char          *new_channel_unauthed;
+    const char          *new_channel_msg;
 } chanserv_conf;
 
 struct listData
@@ -540,6 +597,12 @@ struct listData
     struct helpfile_table table;
 };
 
+struct ChanUser
+{
+       struct userNode *user;
+    struct chanNode *chan;
+};
+
 enum note_access_type
 {
     NOTE_SET_CHANNEL_ACCESS,
@@ -609,7 +672,8 @@ static const struct {
     { "CSMSG_SET_CTCPUSERS", "ctcpusers", 0, 9, 0, 0 },
     { "CSMSG_SET_USERINFO", "userinfo", 1, ~0, CHANNEL_INFO_LINES, 1 },
     { "CSMSG_SET_INVITEME", "inviteme", 1, ~0, CHANNEL_PEON_INVITE, 200 },
-    { "CSMSG_SET_TOPICSNARF", "topicsnarf", 501, ~0, CHANNEL_TOPIC_SNARF, 1 }
+    { "CSMSG_SET_TOPICSNARF", "topicsnarf", 501, ~0, CHANNEL_TOPIC_SNARF, 1 },
+    { "CSMSG_SET_VOTE", "vote", 100, ~0, 0, 0 }
 };
 
 struct charOptionValues {
@@ -656,9 +720,9 @@ 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)
+static void unregister_channel(struct chanData *channel, const char *reason);
 
 unsigned short
 user_level_from_name(const char *name, unsigned short clamp_level)
@@ -850,6 +914,15 @@ chanserv_create_note_type(const char *name)
     return ntype;
 }
 
+static void
+free_vote_options(void *data)
+{
+    struct vote_option *vOpt = data;
+    free(vOpt->name);
+    free(vOpt->option_str);
+    free(vOpt);
+}
+
 static void
 chanserv_deref_note_type(void *data)
 {
@@ -1017,6 +1090,65 @@ static MODCMD_FUNC(cmd_removenote) {
     return 1;
 }
 
+static void
+chanserv_expire_channel(void *data)
+{
+    struct chanData *channel = data;
+    char reason[MAXLEN];
+    sprintf(reason, "channel expired.");
+    channel->expiry = 0;
+    spamserv_cs_unregister(NULL, channel->channel, expire, NULL);
+    unregister_channel(channel, reason);
+}
+
+static MODCMD_FUNC(chan_opt_expire)
+{
+    struct chanData *cData = channel->channel_info;
+    unsigned long value = cData->expiry;
+
+    if(argc > 1)
+    {
+        if((!IsOper(user) || !user->handle_info || (user->handle_info->opserv_level < chanserv_conf.nodelete_level)))
+        {
+            reply("MSG_SETTING_PRIVILEGED", argv[0]);
+            return 0;
+        }
+        unsigned long expiry,duration;
+
+        /* The two directions can have different ACLs. */
+        if(!strcmp(argv[1], "0"))
+            expiry = 0;
+        else if((duration = ParseInterval(argv[1])))
+            expiry = now + duration;
+        else
+        {
+            reply("MSG_INVALID_DURATION", argv[1]);
+            return 0;
+        }
+
+        if (expiry != value)
+        {
+            if(value) {
+                //unset old timer
+                timeq_del(value, chanserv_expire_channel, cData, 0);
+            }
+            value = expiry;
+            cData->expiry = value;
+            if(value > 0) {
+                //New timer!
+                timeq_add(expiry, chanserv_expire_channel, cData);
+            }
+        }
+    }
+
+    if(cData->expiry > now) {
+        char expirestr[INTERVALLEN];
+        reply("CSMSG_SET_EXPIRE", intervalString(expirestr, cData->expiry - now, user->handle_info));
+    } else
+        reply("CSMSG_SET_EXPIRE_OFF");
+    return 1;
+}
+
 static int
 mode_lock_violated(const struct mod_chanmode *orig, const struct mod_chanmode *change)
 {
@@ -1112,6 +1244,8 @@ register_channel(struct chanNode *cNode, char *registrar)
     channel->channel = cNode;
     LockChannel(cNode);
     cNode->channel_info = channel;
+    
+    channel->vote = NULL;
 
     return channel;
 }
@@ -1149,8 +1283,6 @@ add_channel_user(struct chanData *channel, struct handle_info *handle, unsigned
     return ud;
 }
 
-static void unregister_channel(struct chanData *channel, const char *reason);
-
 void
 del_channel_user(struct userData *user, int do_gc)
 {
@@ -1175,13 +1307,15 @@ del_channel_user(struct userData *user, int do_gc)
 
     free(user->info);
     free(user);
-    if(do_gc && !channel->users && !IsProtected(channel))
+    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;
@@ -1349,6 +1483,8 @@ unregister_channel(struct chanData *channel, const char *reason)
         if(cNode)
             cNode->channel_info = NULL;
     }
+    if(channel->expiry)
+        timeq_del(channel->expiry, chanserv_expire_channel, channel, 0);
     channel->channel->channel_info = NULL;
 
     dict_delete(channel->notes);
@@ -1389,6 +1525,7 @@ expire_channels(void *data)
 
         /* 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.");
     }
 
@@ -2098,6 +2235,7 @@ static CHANSERV_FUNC(cmd_register)
 
     /* Initialize the channel's max user record. */
     cData->max = channel->members.used;
+    cData->max_time = 0;
 
     if(handle != user->handle_info)
         reply("CSMSG_PROXY_SUCCESS", handle->handle, channel->name);
@@ -2147,7 +2285,7 @@ static CHANSERV_FUNC(cmd_unregister)
         return 0;
     }
 
-    if(IsProtected(cData))
+    if(IsProtected(cData) && !IsOper(user))
     {
         reply("CSMSG_UNREG_NODELETE", channel->name);
         return 0;
@@ -2172,11 +2310,39 @@ static CHANSERV_FUNC(cmd_unregister)
     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;
@@ -2185,6 +2351,7 @@ static CHANSERV_FUNC(cmd_move)
     struct userData *uData;
     char reason[MAXLEN];
     struct do_not_register *dnr;
+    int chanserv_join = 0, spamserv_join;
 
     REQUIRE_PARAMS(2);
 
@@ -2226,7 +2393,7 @@ static CHANSERV_FUNC(cmd_move)
     {
         target = AddChannel(argv[1], now, NULL, NULL);
         if(!IsSuspended(channel->channel_info))
-            AddChannelUser(chanserv, target);
+            chanserv_join = 1;
     }
     else if(target->channel_info)
     {
@@ -2240,12 +2407,7 @@ static CHANSERV_FUNC(cmd_move)
         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)
     {
@@ -2269,7 +2431,10 @@ static CHANSERV_FUNC(cmd_move)
     for(uData = target->channel_info->users; uData; uData = uData->next)
         scan_user_presence(uData, 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))
@@ -2281,6 +2446,7 @@ static CHANSERV_FUNC(cmd_move)
     UnlockChannel(channel);
     LockChannel(target);
     global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+    reply("CSMSG_MOVE_SUCCESS", target->name);
     return 1;
 }
 
@@ -2461,8 +2627,10 @@ merge_data(struct chanData *source, struct chanData *target)
         target->ownerTransfer = source->ownerTransfer;
     if(source->may_opchan)
         target->may_opchan = 1;
-    if(source->max > target->max)
+    if(source->max > target->max) {
         target->max = source->max;
+        target->max_time = source->max_time;
+    }
 }
 
 static void
@@ -2522,6 +2690,7 @@ static CHANSERV_FUNC(cmd_merge)
 
     /* 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);
@@ -3067,6 +3236,44 @@ static CHANSERV_FUNC(cmd_devoice)
     return modify_users(CSFUNC_ARGS, NULL, MODE_REMOVE|MODE_VOICE, "CSMSG_DEVOICED_USERS");
 }
 
+static CHANSERV_FUNC(cmd_opme)
+{
+    struct mod_chanmode change;
+    const char *errmsg;
+
+    mod_chanmode_init(&change);
+    change.argc = 1;
+    change.args[0].u.member = GetUserMode(channel, user);
+    if(!change.args[0].u.member)
+    {
+        if(argc)
+            reply("MSG_CHANNEL_ABSENT", channel->name);
+        return 0;
+    }
+
+    struct devnull_class *devnull;
+    if(user->handle_info->devnull && (devnull = devnull_get(user->handle_info->devnull)) && (devnull->modes & DEVNULL_MODE_OPME))
+    {
+        change.args[0].mode = MODE_CHANOP;
+        errmsg = "CSMSG_ALREADY_OPPED";
+    }
+    else
+    {
+        if(argc)
+            reply("CSMSG_NO_ACCESS");
+        return 0;
+    }
+    change.args[0].mode &= ~change.args[0].u.member->modes;
+    if(!change.args[0].mode)
+    {
+        if(argc)
+            reply(errmsg, channel->name);
+        return 0;
+    }
+    modcmd_chanmode_announce(&change);
+    return 1;
+}
+
 static int
 bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban, unsigned int *victimCount, struct modeNode **victims)
 {
@@ -3104,6 +3311,20 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
 
     offset = (action & ACTION_ADD_TIMED_BAN) ? 3 : 2;
     REQUIRE_PARAMS(offset);
+    if(argc > offset && IsNetServ(user))
+    {
+        if(*argv[offset] == '$') {
+            struct userNode *hib;
+            const char *accountnameb = argv[offset] + 1;
+            if(!(hib = GetUserH(accountnameb)))
+            {
+                reply("MSG_HANDLE_UNKNOWN", accountnameb);
+                return 0;
+            }
+            user=hib;
+            offset++;
+        }
+    }
     if(argc > offset)
     {
         reason = unsplit_string(argv + offset, argc - offset, NULL);
@@ -3440,7 +3661,7 @@ static CHANSERV_FUNC(cmd_addtimedban)
     return eject_user(CSFUNC_ARGS, ACTION_KICK | ACTION_BAN | ACTION_ADD_BAN | ACTION_ADD_TIMED_BAN);
 }
 
-static struct mod_chanmode *
+struct mod_chanmode *
 find_matching_bans(struct banList *bans, struct userNode *actee, const char *mask)
 {
     struct mod_chanmode *change;
@@ -3637,6 +3858,8 @@ static CHANSERV_FUNC(cmd_myaccess)
     static struct string_buffer sbuf;
     struct handle_info *target_handle;
     struct userData *uData;
+    int ccount = 0;
+       int ocount = 0;
 
     if(argc < 2)
         target_handle = user->handle_info;
@@ -3661,13 +3884,18 @@ static CHANSERV_FUNC(cmd_myaccess)
     for(uData = target_handle->channels; uData; uData = uData->u_next)
     {
         struct chanData *cData = uData->channel;
+        ccount++;
         unsigned int base_len;
 
         if(uData->access > UL_OWNER)
             continue;
+        if(uData->access == UL_OWNER)
+            ocount++;
+
         if(IsProtected(cData)
            && (target_handle != user->handle_info)
-           && !GetTrueChannelAccess(cData, user->handle_info))
+           && !GetTrueChannelAccess(cData, user->handle_info)
+           && !IsNetworkHelper(user))
             continue;
         sbuf.used = 0;
         string_buffer_append_printf(&sbuf, "[%s (%d,", cData->channel->name, uData->access);
@@ -3693,6 +3921,12 @@ static CHANSERV_FUNC(cmd_myaccess)
         send_message_type(4, user, cmd->parent->bot, "%s", sbuf.list);
     }
 
+    if(ccount == 1) {
+        reply("CSMSG_MYACCESS_COUNT_1", target_handle->handle, ccount, ocount);
+    } else {
+        reply("CSMSG_MYACCESS_COUNT", target_handle->handle, ccount, ocount);
+    }
+
     return 1;
 }
 
@@ -3928,6 +4162,7 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
     }
     free(lData.table.contents[0]);
     free(lData.table.contents);
+    reply("CSMSG_TOTAL_USERS",(lData.table.length - 1),channel->name);
     return 1;
 }
 
@@ -4182,7 +4417,7 @@ static CHANSERV_FUNC(cmd_mode)
         base_oplevel = 1;
     else
         base_oplevel = 1 + UL_OWNER - uData->access;
-    change = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED|MCP_NO_APASS, base_oplevel);
+    change = mod_chanmode_parse(channel, user, argv+1, argc-1, MCP_KEY_FREE|MCP_IGN_REGISTERED|MCP_NO_APASS, base_oplevel);
     if(!change)
     {
         reply("MSG_INVALID_MODES", unsplit_string(argv+1, argc-1, NULL));
@@ -4205,9 +4440,27 @@ static CHANSERV_FUNC(cmd_mode)
     return 1;
 }
 
+static void
+chanserv_del_invite_mark(void *data)
+{
+       struct ChanUser *chanuser = data;
+       struct chanNode *channel = chanuser->chan;
+       unsigned int i;
+       if(!channel) return;
+       for(i = 0; i < channel->invited.used; i++)
+    {
+        if(channel->invited.list[i] == chanuser->user) {
+                       userList_remove(&channel->invited, chanuser->user);
+               }
+       }
+       free(chanuser);
+}
+
 static CHANSERV_FUNC(cmd_invite)
 {
     struct userNode *invite;
+    struct ChanUser *chanuser;
+    unsigned int i;
 
     if(argc > 1)
     {
@@ -4225,6 +4478,14 @@ static CHANSERV_FUNC(cmd_invite)
         reply("CSMSG_ALREADY_PRESENT", invite->nick, channel->name);
         return 0;
     }
+    
+    for(i = 0; i < channel->invited.used; i++)
+    {
+        if(channel->invited.list[i] == invite) {
+            reply("CSMSG_ALREADY_INVITED", invite->nick, channel->name);
+            return 0;
+        }
+    }
 
     if(user != invite)
     {
@@ -4240,6 +4501,12 @@ static CHANSERV_FUNC(cmd_invite)
     if(argc > 1)
         reply("CSMSG_INVITED_USER", argv[1], channel->name);
 
+    userList_append(&channel->invited, invite);
+    chanuser = calloc(1, sizeof(*chanuser));
+    chanuser->user=invite;
+    chanuser->chan=channel;
+    timeq_add(now + chanserv_conf.invited_timeout, chanserv_del_invite_mark, chanuser);
+
     return 1;
 }
 
@@ -4260,6 +4527,28 @@ static CHANSERV_FUNC(cmd_inviteme)
     return 1;
 }
 
+static CHANSERV_FUNC(cmd_invitemeall)
+{
+    struct handle_info *target = user->handle_info;
+    struct userData *uData;
+
+    if(!target->channels)
+    {
+        reply("CSMSG_SQUAT_ACCESS", target->handle);
+        return 1;
+    }
+       
+    for(uData = target->channels; uData; uData = uData->u_next)
+    {
+        struct chanData *cData = uData->channel;
+        if(uData->access >= cData->lvlOpts[lvlInviteMe])
+               {
+            irc_invite(cmd->parent->bot, user, cData->channel);
+        }
+    }
+    return 1;
+}
+
 static void
 show_suspension_info(struct svccmd *cmd, struct userNode *user, struct suspended *suspended)
 {
@@ -4348,7 +4637,11 @@ static CHANSERV_FUNC(cmd_info)
         reply("CSMSG_CHANNEL_NOTE", iter_key(it), padding > 0 ? padding : 1, "", note->note);
     }
 
-    reply("CSMSG_CHANNEL_MAX", cData->max);
+    if(cData->max_time) {
+        reply("CSMSG_CHANNEL_MAX_TIME", cData->max, intervalString(buffer, now - cData->max_time, user->handle_info));
+    } else {
+        reply("CSMSG_CHANNEL_MAX", cData->max);
+    }
     for(owner = cData->users; owner; owner = owner->next)
         if(owner->access == UL_OWNER)
             reply("CSMSG_CHANNEL_OWNER", owner->handle->handle);
@@ -4417,6 +4710,8 @@ send_staff_list(struct userNode *to, struct userList *list, int skip_flags)
             continue;
         if(IsBot(user))
             continue;
+               if(IsInvi(user))
+                   continue;
         table.contents[table.length] = alloca(table.width*sizeof(**table.contents));
         if(IsAway(user))
         {
@@ -4552,8 +4847,16 @@ static CHANSERV_FUNC(cmd_resync)
         {
             if(!(mn->modes & MODE_CHANOP))
             {
-                changes->args[used].mode = MODE_CHANOP;
-                changes->args[used++].u.member = mn;
+                if(!uData || IsUserAutoOp(uData)) 
+                {
+                    changes->args[used].mode = MODE_CHANOP;
+                    changes->args[used++].u.member = mn;
+                    if(!(mn->modes & MODE_VOICE))
+                    {
+                        changes->args[used].mode = MODE_VOICE;
+                        changes->args[used++].u.member = mn;
+                    }
+                }
             }
         }
         else if(!cData->lvlOpts[lvlGiveVoice]
@@ -4564,7 +4867,7 @@ static CHANSERV_FUNC(cmd_resync)
                 changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_VOICE);
                 changes->args[used++].u.member = mn;
             }
-            if(!(mn->modes & MODE_VOICE))
+            if(!(mn->modes & MODE_VOICE) && (!uData || IsUserAutoOp(uData)))
             {
                 changes->args[used].mode = MODE_VOICE;
                 changes->args[used++].u.member = mn;
@@ -4930,12 +5233,8 @@ chanserv_expire_suspension(void *data)
     /* If appropriate, re-join ChanServ to the channel. */
     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);
     }
 
     /* Mark everyone currently in the channel as present. */
@@ -5021,6 +5320,7 @@ static CHANSERV_FUNC(cmd_csuspend)
 
         /* 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);
@@ -5370,7 +5670,7 @@ static MODCMD_FUNC(chan_opt_modes)
         {
             memset(&channel->channel_info->modes, 0, sizeof(channel->channel_info->modes));
         }
-        else if(!(new_modes = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED|MCP_NO_APASS, 0)))
+        else if(!(new_modes = mod_chanmode_parse(channel, user, argv+1, argc-1, MCP_KEY_FREE|MCP_IGN_REGISTERED|MCP_NO_APASS|(IsOper(user) && IsHelping(user) ? MCP_OPERMODE : 0), 0)))
         {
             reply("CSMSG_INVALID_MODE_LOCK", unsplit_string(argv+1, argc-1, NULL));
             return 0;
@@ -5698,6 +5998,11 @@ static MODCMD_FUNC(chan_opt_topicsnarf)
     return channel_level_option(lvlTopicSnarf, CSFUNC_ARGS);
 }
 
+static MODCMD_FUNC(chan_opt_vote)
+{
+    return channel_level_option(lvlVote, CSFUNC_ARGS);
+}
+
 static MODCMD_FUNC(chan_opt_inviteme)
 {
     return channel_level_option(lvlInviteMe, CSFUNC_ARGS);
@@ -6189,6 +6494,363 @@ static MODCMD_FUNC(cmd_deleteme)
     return 1;
 }
 
+static CHANSERV_FUNC(cmd_addvote)
+{
+    struct chanData *cData = channel->channel_info;
+    struct userData *uData, *target;
+    struct handle_info *hi;
+    if (!cData) return 0;
+    REQUIRE_PARAMS(2);
+    hi = user->handle_info;
+    if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+    {
+        reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+        return 0;
+    }
+    if(target->access < 300) {
+        reply("CSMSG_NO_ACCESS");
+        return 0;
+    }
+    if (cData->vote) {
+        reply("CSMSG_ADDVOTE_FULL");
+        return 0;
+    }
+    char *msg;
+    msg = unsplit_string(argv + 1, argc - 1, NULL);
+    cData->vote = strdup(msg);
+    cData->vote_start=0;
+    dict_delete(cData->vote_options);
+    cData->vote_options = dict_new();
+    dict_set_free_data(cData->vote_options, free_vote_options);
+    for(uData = channel->channel_info->users; uData; uData = uData->next)
+    {
+        uData->voted = 0;
+        uData->votefor = 0;
+    }
+    reply("CSMSG_ADDVOTE_DONE");
+    return 1;
+}
+
+static CHANSERV_FUNC(cmd_delvote)
+{
+    struct chanData *cData = channel->channel_info;
+    struct userData *target;
+    struct handle_info *hi;
+    if (!cData) return 0;
+    hi = user->handle_info;
+    if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+    {
+        reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+        return 0;
+    }
+    if(target->access < 300) {
+        reply("CSMSG_NO_ACCESS");
+        return 0;
+    }
+    if (!cData->vote) {
+        reply("CSMSG_NO_VOTE");
+        return 0;
+    }
+    free(cData->vote);
+    cData->vote = NULL;
+    reply("CSMSG_DELVOTE_DONE");
+    return 1;
+}
+
+static CHANSERV_FUNC(cmd_addoption)
+{
+    struct chanData *cData = channel->channel_info;
+    struct userData *target;
+    struct handle_info *hi;
+    if (!cData) return 0;
+    REQUIRE_PARAMS(2);
+    hi = user->handle_info;
+    if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+    {
+        reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+        return 0;
+    }
+    if(target->access < 300) {
+        reply("CSMSG_NO_ACCESS");
+        return 0;
+    }
+    if (!cData->vote) {
+        reply("CSMSG_NO_VOTE");
+        return 0;
+    }
+    
+    char *msg;
+    
+    msg = unsplit_string(argv + 1, argc - 1, NULL);
+    
+    dict_iterator_t it;
+    unsigned int lastid = 1;
+    for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+        struct vote_option *cvOpt = iter_data(it);
+        if(cvOpt->option_id > lastid)
+            lastid = cvOpt->option_id;
+    }
+    struct vote_option *vOpt;
+    vOpt = calloc(1, sizeof(*vOpt));
+    vOpt->name = strdup(msg);
+    vOpt->option_id = (lastid + 1);
+    char str[50];
+    sprintf(str,"%i",(lastid + 1));
+    vOpt->option_str = strdup(str);
+    vOpt->voted = 0;
+    dict_insert(cData->vote_options,vOpt->option_str,vOpt);
+    
+    reply("CSMSG_ADDOPTION_DONE",dict_size(cData->vote_options),lastid,(lastid + 1));
+    return 1;
+}
+
+static CHANSERV_FUNC(cmd_deloption)
+{
+    struct chanData *cData = channel->channel_info;
+    struct userData *uData, *target;
+    struct handle_info *hi;
+    if (!cData) return 0;
+    REQUIRE_PARAMS(2);
+    hi = user->handle_info;
+    if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+    {
+        reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+        return 0;
+    }
+    if(target->access < 300) {
+        reply("CSMSG_NO_ACCESS");
+        return 0;
+    }
+    if (!cData->vote) {
+        reply("CSMSG_NO_VOTE");
+        return 0;
+    }
+    if(cData->vote_start) {
+        if(dict_size(cData->vote_options) < 3) {
+            reply("CSMSG_VOTE_NEED_OPTIONS");
+            return 0;
+        }
+    }
+    
+    int find_id = atoi(argv[1]);
+    int ii = 0;
+    unsigned int found = 0;
+    dict_iterator_t it;
+    
+    for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+        ii++;
+        if (find_id == ii) {
+            struct vote_option *vOpt = iter_data(it);
+            found = vOpt->option_id;
+            char str[50];
+            sprintf(str,"%i",vOpt->option_id);
+            dict_remove(cData->vote_options, str);
+        }
+    }
+    
+    if(found > 0) {
+        for(uData = channel->channel_info->users; uData; uData = uData->next) {
+            if(uData->votefor == found) {
+                uData->voted = 0;
+                uData->votefor = 0;
+            }
+        }
+        reply("CSMSG_DELOPTION_DONE");
+        return 1;
+    } else {
+        reply("CSMSG_DELOPTION_NONE");
+        return 0;
+    }
+}
+
+static CHANSERV_FUNC(cmd_vote)
+{
+    struct chanData *cData = channel->channel_info;
+    struct userData *target;
+    struct handle_info *hi;
+    unsigned int votedfor = 0;
+    char *votedfor_str = NULL;
+    
+    if (!cData || !cData->vote) {
+        reply("CSMSG_NO_VOTE");
+        return 0;
+    }
+    if(argc > 1 && cData->vote_start) {
+        hi = user->handle_info;
+        if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+        {
+            reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+            return 0;
+        }
+        if(!check_user_level(channel, user, lvlVote, 1, 0)) {
+            reply("CSMSG_NO_ACCESS");
+            return 0;
+        }
+        if(target->voted) {
+            reply("CSMSG_VOTE_VOTED");
+            return 0;
+        }
+        int find_id = atoi(argv[1]);
+        int ii = 0;
+        dict_iterator_t it;
+        for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+            ii++;
+            if (find_id == ii) {
+                struct vote_option *vOpt = iter_data(it);
+                vOpt->voted++;
+                target->voted = 1;
+                target->votefor = vOpt->option_id;
+                votedfor = vOpt->option_id;
+                votedfor_str = vOpt->name;
+            }
+        }
+        if(votedfor == 0) {
+            reply("CSMSG_VOTE_INVALID");
+            return 0;
+        }
+    }
+    if (!cData->vote_start) {
+        reply("CSMSG_VOTE_NOT_STARTED");
+    }
+    reply("CSMSG_VOTE_QUESTION",cData->vote);
+    
+    unsigned int voteid = 0;
+    dict_iterator_t it;
+    
+    for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+        struct vote_option *vOpt = iter_data(it);
+        voteid++;
+        reply("CSMSG_VOTE_OPTION",voteid,vOpt->name,vOpt->voted);
+    }
+    if(argc > 1 && cData->vote_start && votedfor_str) {
+        reply("CSMSG_VOTE_DONE",votedfor_str);
+    }
+    return 1;
+}
+
+static CHANSERV_FUNC(cmd_startvote)
+{
+    struct chanData *cData = channel->channel_info;
+    struct userData *target;
+    struct handle_info *hi;
+    if (!cData) return 0;
+    hi = user->handle_info;
+    if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+    {
+        reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+        return 0;
+    }
+    if(target->access < 300) {
+        reply("CSMSG_NO_ACCESS");
+        return 0;
+    }
+    if (!cData->vote) {
+        reply("CSMSG_NO_VOTE");
+        return 0;
+    }
+    if(cData->vote_start) {
+        reply("CSMSG_STARTVOTE_RUNNING");
+        return 0;
+    }
+    if(dict_size(cData->vote_options) < 2) {
+        reply("CSMSG_VOTE_NEED_OPTIONS");
+        return 0;
+    }
+    cData->vote_start = 1;
+    char response[MAXLEN];
+    sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_TOP"), user->nick);
+    irc_privmsg(cmd->parent->bot, channel->name, response);
+    sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_QUESTION"), cData->vote);
+    irc_privmsg(cmd->parent->bot, channel->name, response);
+    unsigned int voteid = 0;
+    dict_iterator_t it;
+    for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+        struct vote_option *vOpt = iter_data(it);
+        voteid++;
+        sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_OPTION"), voteid, vOpt->name);
+        irc_privmsg(cmd->parent->bot, channel->name, response);
+    }
+    sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_ACCESS"), cData->lvlOpts[lvlVote]); //Todo
+    irc_privmsg(cmd->parent->bot, channel->name, response);
+    sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_HOWTO")); //Todo
+    irc_privmsg(cmd->parent->bot, channel->name, response);
+    return 1;
+}
+
+static CHANSERV_FUNC(cmd_endvote)
+{
+    struct chanData *cData = channel->channel_info;
+    struct userData *target;
+    struct handle_info *hi;
+    if (!cData) return 0;
+    hi = user->handle_info;
+    if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+    {
+        reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+        return 0;
+    }
+    if(target->access < 300) {
+        reply("CSMSG_NO_ACCESS");
+        return 0;
+    }
+    if (!cData->vote) {
+        reply("CSMSG_NO_VOTE");
+        return 0;
+    }
+    if(!cData->vote_start) {
+        reply("CSMSG_ENDVOTE_STOPPED");
+        return 0;
+    }
+    cData->vote_start = 0;
+    reply("CSMSG_ENDVOTE_DONE");
+    return 1;
+}
+
+static CHANSERV_FUNC(cmd_voteresults)
+{
+    struct chanData *cData = channel->channel_info;
+    struct userData *target;
+    struct handle_info *hi;
+    if (!cData) return 0;
+    if (!cData->vote) {
+        reply("CSMSG_NO_VOTE");
+        return 0;
+    }
+    if (argc > 1 && !irccasecmp(argv[1], "*")) {
+        hi = user->handle_info;
+        if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+        {
+            reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+            return 0;
+        }
+        if(target->access < 300) {
+            reply("CSMSG_NO_ACCESS");
+            return 0;
+        }
+        char response[MAXLEN];
+        sprintf(response, user_find_message(user, "CSMSG_VOTERES_QUESTION"), cData->vote);
+        irc_privmsg(cmd->parent->bot, channel->name, response);
+        unsigned int voteid = 0;
+        dict_iterator_t it;
+        for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+            struct vote_option *vOpt = iter_data(it);
+            voteid++;
+            sprintf(response, user_find_message(user, "CSMSG_VOTERES_OPTION"), voteid, vOpt->name, vOpt->voted);
+            irc_privmsg(cmd->parent->bot, channel->name, response);
+        }
+    } else {
+        reply("CSMSG_VOTE_QUESTION",cData->vote);
+        unsigned int voteid = 0;
+       dict_iterator_t it;
+        for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
+            struct vote_option *vOpt = iter_data(it);
+            voteid++;
+            reply("CSMSG_VOTE_OPTION",voteid,vOpt->name,vOpt->voted);
+        }
+    }
+    return 1;
+}
+
 static void
 chanserv_refresh_topics(UNUSED_ARG(void *data))
 {
@@ -6389,6 +7051,16 @@ handle_new_channel(struct chanNode *channel)
         SetChannelTopic(channel, chanserv, channel->channel_info->topic, 1);
 }
 
+void handle_new_channel_created(char *chan, struct userNode *user) {
+    if(user->handle_info && chanserv_conf.new_channel_authed) {
+        send_target_message(5, chan, chanserv, "%s", chanserv_conf.new_channel_authed);
+    } else if(!user->handle_info && chanserv_conf.new_channel_unauthed) {
+        send_target_message(5, chan, chanserv, "%s", chanserv_conf.new_channel_unauthed);
+    }
+    if(chanserv_conf.new_channel_msg)
+        send_target_message(5, chan, chanserv, "%s", chanserv_conf.new_channel_msg);
+}
+
 /* Welcome to my worst nightmare. Warning: Read (or modify)
    the code below at your own risk. */
 static int
@@ -6403,13 +7075,23 @@ handle_join(struct modeNode *mNode)
     struct handle_info *handle;
     unsigned int modes = 0, info = 0;
     char *greeting;
+    unsigned int i = 0;
 
     if(IsLocal(user) || !channel->channel_info || IsSuspended(channel->channel_info))
         return 0;
 
     cData = channel->channel_info;
-    if(channel->members.used > cData->max)
+    if(channel->members.used > cData->max) {
         cData->max = channel->members.used;
+        cData->max_time = now;
+    }
+
+    for(i = 0; i < channel->invited.used; i++)
+    {
+        if(channel->invited.list[i] == user) {
+            userList_remove(&channel->invited, user);
+        }
+    }
 
     /* Check for bans.  If they're joining through a ban, one of two
      * cases applies:
@@ -6975,6 +7657,8 @@ chanserv_conf_read(void)
     chanserv_conf.channel_expire_delay = str ? ParseInterval(str) : 86400*30;
     str = database_get_data(conf_node, KEY_DNR_EXPIRE_FREQ, RECDB_QSTRING);
     chanserv_conf.dnr_expire_frequency = str ? ParseInterval(str) : 3600;
+    str = database_get_data(conf_node, KEY_INVITED_INTERVAL, RECDB_QSTRING);
+    chanserv_conf.invited_timeout = str ? ParseInterval(str) : 600*2;
     str = database_get_data(conf_node, KEY_NODELETE_LEVEL, RECDB_QSTRING);
     chanserv_conf.nodelete_level = str ? atoi(str) : 1;
     str = database_get_data(conf_node, KEY_MAX_CHAN_USERS, RECDB_QSTRING);
@@ -7002,12 +7686,18 @@ chanserv_conf_read(void)
     chanserv_conf.network_helper_epithet = str ? str : "a wannabe tyrant";
     str = database_get_data(conf_node, KEY_SUPPORT_HELPER_EPITHET, RECDB_QSTRING);
     chanserv_conf.support_helper_epithet = str ? str : "a wannabe tyrant";
+    str = database_get_data(conf_node, KEY_NEW_CHANNEL_AUTHED, RECDB_QSTRING);
+    chanserv_conf.new_channel_authed = (str && *str) ? str : NULL;
+    str = database_get_data(conf_node, KEY_NEW_CHANNEL_UNAUTHED, RECDB_QSTRING);
+    chanserv_conf.new_channel_unauthed = (str && *str) ? str : NULL;
+    str = database_get_data(conf_node, KEY_NEW_CHANNEL_MSG, RECDB_QSTRING);
+    chanserv_conf.new_channel_msg = (str && *str) ? str : NULL;
     str = database_get_data(conf_node, "default_modes", RECDB_QSTRING);
     if(!str)
         str = "+nt";
     safestrncpy(mode_line, str, sizeof(mode_line));
     ii = split_line(mode_line, 0, ArrayLength(modes), modes);
-    if((change = mod_chanmode_parse(NULL, modes, ii, MCP_KEY_FREE|MCP_NO_APASS, 0))
+    if((change = mod_chanmode_parse(NULL, NULL, modes, ii, MCP_KEY_FREE|MCP_NO_APASS, 0))
        && (change->argc < 2))
     {
         chanserv_conf.default_modes = *change;
@@ -7028,7 +7718,7 @@ chanserv_conf_read(void)
             /* multiple choice options */
             "CtcpReaction", "Protect", "Toys", "TopicRefresh",
             /* binary options */
-            "DynLimit", "NoDelete",
+            "DynLimit", "NoDelete", "expire", "Vote",
             /* delimiter */
             NULL
         };
@@ -7123,12 +7813,33 @@ chanserv_note_type_read(const char *key, struct record_data *rd)
     ntype->max_length = str ? strtoul(str, NULL, 0) : 400;
 }
 
+static void
+vote_option_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
+{
+    struct vote_option *vOpt;
+    char *str;
+    
+    if(rd->type != RECDB_OBJECT || !dict_size(rd->d.object))
+    {
+        log_module(CS_LOG, LOG_ERROR, "Invalid vote option in %s.", chan->channel->name);
+        return;
+    }
+    
+    vOpt = calloc(1, sizeof(*vOpt));
+    vOpt->name = strdup(database_get_data(rd->d.object, KEY_VOTE_OPTION_NAME, RECDB_QSTRING));
+    str = database_get_data(rd->d.object, KEY_VOTE_OPTION_VOTED, RECDB_QSTRING);
+    vOpt->voted = str ? atoi(str) : 0;
+    vOpt->option_id = str ? atoi(key) : 0;
+    vOpt->option_str = strdup(key);
+    dict_insert(chan->vote_options,vOpt->option_str,vOpt);
+}
+
 static void
 user_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
 {
     struct handle_info *handle;
     struct userData *uData;
-    char *seen, *inf, *flags;
+    char *seen, *inf, *flags, *voted, *votefor;
     unsigned long last_seen;
     unsigned short access_level;
 
@@ -7149,6 +7860,8 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
     seen = database_get_data(rd->d.object, KEY_SEEN, RECDB_QSTRING);
     last_seen = seen ? strtoul(seen, NULL, 0) : now;
     flags = database_get_data(rd->d.object, KEY_FLAGS, RECDB_QSTRING);
+    voted = database_get_data(rd->d.object, KEY_VOTE_VOTED, RECDB_QSTRING);
+    votefor = database_get_data(rd->d.object, KEY_VOTE_VOTEDFOR, RECDB_QSTRING);
     handle = get_handle_info(key);
     if(!handle)
     {
@@ -7158,6 +7871,13 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
 
     uData = add_channel_user(chan, handle, access_level, last_seen, inf);
     uData->flags = flags ? strtoul(flags, NULL, 0) : 0;
+    if(chan->vote) {
+        uData->voted = voted ? strtoul(voted, NULL, 0) : 0;
+        uData->votefor = votefor ? strtoul(votefor, NULL, 0) : 0;
+    } else {
+        uData->voted = 0;
+        uData->votefor = 0;
+    }
 }
 
 static void
@@ -7309,6 +8029,20 @@ chanserv_channel_read(const char *key, struct record_data *hir)
             cData->chOpts[chOpt] = ((count <= charOptions[chOpt].old_idx) ? str : CHANNEL_DEFAULT_OPTIONS)[charOptions[chOpt].old_idx];
     }
 
+    if((str = database_get_data(hir->d.object, KEY_EXPIRE, RECDB_QSTRING)))
+    {
+        cData->expiry = atoi(str);
+        if(cData->expiry > 0) {
+            if(cData->expiry > now) {
+                timeq_add(cData->expiry, chanserv_expire_channel, cData);
+            } else {
+                timeq_add(1, chanserv_expire_channel, cData);
+            }
+        }
+    } else {
+        cData->expiry = 0;
+    }
+
     if((obj = database_get_data(hir->d.object, KEY_SUSPENDED, RECDB_OBJECT)))
     {
         suspended = chanserv_read_suspended(obj);
@@ -7361,6 +8095,8 @@ chanserv_channel_read(const char *key, struct record_data *hir)
     cData->ownerTransfer = str ? strtoul(str, NULL, 0) : 0;
     str = database_get_data(channel, KEY_MAX, RECDB_QSTRING);
     cData->max = str ? atoi(str) : 0;
+    str = database_get_data(channel, KEY_MAX_TIME, RECDB_QSTRING);
+    cData->max_time = str ? atoi(str) : 0;
     str = database_get_data(channel, KEY_GREETING, RECDB_QSTRING);
     cData->greeting = str ? strdup(str) : NULL;
     str = database_get_data(channel, KEY_USER_GREETING, RECDB_QSTRING);
@@ -7370,10 +8106,24 @@ chanserv_channel_read(const char *key, struct record_data *hir)
     str = database_get_data(channel, KEY_TOPIC, RECDB_QSTRING);
     cData->topic = str ? strdup(str) : NULL;
 
+    str = database_get_data(channel, KEY_VOTE, RECDB_QSTRING);
+    if(str) {
+        cData->vote = str ? strdup(str) : NULL;
+        dict_delete(cData->vote_options);
+        cData->vote_options = dict_new();
+        dict_set_free_data(cData->vote_options, free_vote_options);
+        str = database_get_data(channel, KEY_VOTE_START, RECDB_QSTRING);
+        cData->vote_start = str ? atoi(str) : 0;
+        obj = database_get_data(channel, KEY_VOTE_OPTIONS, RECDB_OBJECT);
+        for(it = dict_first(obj); it; it = iter_next(it)) {
+            vote_option_read_helper(iter_key(it), iter_data(it), cData);
+        }
+    }
+
     if(!IsSuspended(cData)
        && (str = database_get_data(channel, KEY_MODES, RECDB_QSTRING))
        && (argc = split_line(str, 0, ArrayLength(argv), argv))
-       && (modes = mod_chanmode_parse(cNode, argv, argc, MCP_KEY_FREE|MCP_NO_APASS, 0))) {
+       && (modes = mod_chanmode_parse(cNode, NULL, argv, argc, MCP_KEY_FREE|MCP_NO_APASS, 0))) {
         cData->modes = *modes;
         if(off_channel > 0)
           cData->modes.modes_set |= MODE_REGISTERED;
@@ -7496,6 +8246,10 @@ chanserv_write_users(struct saxdb_context *ctx, struct userData *uData)
         saxdb_write_int(ctx, KEY_SEEN, uData->seen);
         if(uData->flags)
             saxdb_write_int(ctx, KEY_FLAGS, uData->flags);
+        if(uData->channel->vote && uData->voted)
+            saxdb_write_int(ctx, KEY_VOTE_VOTED, uData->voted);
+        if(uData->channel->vote && uData->votefor)
+            saxdb_write_int(ctx, KEY_VOTE_VOTEDFOR, uData->votefor);
         if(uData->info)
             saxdb_write_string(ctx, KEY_INFO, uData->info);
         saxdb_end_record(ctx);
@@ -7551,11 +8305,13 @@ chanserv_write_channel(struct saxdb_context *ctx, struct chanData *channel)
     int high_present;
     enum levelOption lvlOpt;
     enum charOption chOpt;
+    dict_iterator_t it;
 
     saxdb_start_record(ctx, channel->channel->name, 1);
 
     saxdb_write_int(ctx, KEY_REGISTERED, channel->registered);
     saxdb_write_int(ctx, KEY_MAX, channel->max);
+    saxdb_write_int(ctx, KEY_MAX_TIME, channel->max_time);
     if(channel->topic)
         saxdb_write_string(ctx, KEY_TOPIC, channel->topic);
     if(channel->registrar)
@@ -7568,6 +8324,29 @@ chanserv_write_channel(struct saxdb_context *ctx, struct chanData *channel)
         saxdb_write_string(ctx, KEY_TOPIC_MASK, channel->topic_mask);
     if(channel->suspended)
         chanserv_write_suspended(ctx, "suspended", channel->suspended);
+    if(channel->expiry)
+        saxdb_write_int(ctx, KEY_EXPIRE, channel->expiry);
+
+    if(channel->vote) {
+        saxdb_write_string(ctx, KEY_VOTE, channel->vote);
+        if(channel->vote_start)
+            saxdb_write_int(ctx, KEY_VOTE_START, channel->vote_start);
+        if (dict_size(channel->vote_options)) {
+            saxdb_start_record(ctx, KEY_VOTE_OPTIONS, 1);
+            for (it = dict_first(channel->vote_options); it; it = iter_next(it)) {
+                struct vote_option *vOpt = iter_data(it);
+                char str[50];
+                sprintf(str,"%i",vOpt->option_id);
+                saxdb_start_record(ctx, str, 0);
+                if(vOpt->voted)
+                    saxdb_write_int(ctx, KEY_VOTE_OPTION_VOTED, vOpt->voted);
+                if(vOpt->name)
+                    saxdb_write_string(ctx, KEY_VOTE_OPTION_NAME, vOpt->name);
+                saxdb_end_record(ctx);
+            }
+            saxdb_end_record(ctx);
+        }
+    }
 
     saxdb_start_record(ctx, KEY_OPTIONS, 0);
     saxdb_write_int(ctx, KEY_FLAGS, channel->flags);
@@ -7812,6 +8591,7 @@ init_chanserv(const char *nick)
     DEFINE_COMMAND(topic, 1, MODCMD_REQUIRE_REGCHAN, "template", "op", "flags", "+never_csuspend", NULL);
     DEFINE_COMMAND(mode, 1, MODCMD_REQUIRE_REGCHAN, "template", "op", NULL);
     DEFINE_COMMAND(inviteme, 1, MODCMD_REQUIRE_CHANNEL, "access", "1", NULL);
+    DEFINE_COMMAND(invitemeall, 1, MODCMD_REQUIRE_AUTHED, NULL);
     DEFINE_COMMAND(invite, 1, MODCMD_REQUIRE_CHANNEL, "access", "master", NULL);
     DEFINE_COMMAND(set, 1, MODCMD_REQUIRE_CHANUSER, "access", "op", NULL);
     DEFINE_COMMAND(wipeinfo, 2, MODCMD_REQUIRE_CHANUSER, "access", "master", NULL);
@@ -7858,7 +8638,18 @@ init_chanserv(const char *nick)
     DEFINE_COMMAND(8ball, 2, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
     DEFINE_COMMAND(d, 2, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
     DEFINE_COMMAND(huggle, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
-
+    
+    DEFINE_COMMAND(addvote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+    DEFINE_COMMAND(delvote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+    DEFINE_COMMAND(addoption, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+    DEFINE_COMMAND(deloption, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+    DEFINE_COMMAND(vote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+    DEFINE_COMMAND(startvote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+    DEFINE_COMMAND(endvote, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+    DEFINE_COMMAND(voteresults, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_REGCHAN, NULL);
+
+    DEFINE_COMMAND(opme, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
+    
     /* Channel options */
     DEFINE_CHANNEL_OPTION(defaulttopic);
     DEFINE_CHANNEL_OPTION(topicmask);
@@ -7875,6 +8666,7 @@ init_chanserv(const char *nick)
     DEFINE_CHANNEL_OPTION(userinfo);
     DEFINE_CHANNEL_OPTION(dynlimit);
     DEFINE_CHANNEL_OPTION(topicsnarf);
+    DEFINE_CHANNEL_OPTION(vote);
     DEFINE_CHANNEL_OPTION(nodelete);
     DEFINE_CHANNEL_OPTION(toys);
     DEFINE_CHANNEL_OPTION(setters);
@@ -7883,6 +8675,7 @@ init_chanserv(const char *nick)
     DEFINE_CHANNEL_OPTION(ctcpreaction);
     DEFINE_CHANNEL_OPTION(inviteme);
     DEFINE_CHANNEL_OPTION(unreviewed);
+    modcmd_register(chanserv_module, "set expire", chan_opt_expire, 1, 0, "flags", "+helping", NULL);
     modcmd_register(chanserv_module, "set unreviewed on", NULL, 0, 0, "flags", "+helping", NULL);
     modcmd_register(chanserv_module, "set unreviewed off", NULL, 0, 0, "flags", "+oper", NULL);
     if(off_channel > 1)
index a84a86c0aab8fcca545cf9a92a46900f88078f04..22fb7173c86ee88d90c5c77a4c7b19d3ccb62e9e 100644 (file)
@@ -45,6 +45,7 @@ enum levelOption {
     lvlUserInfo,
     lvlInviteMe,
     lvlTopicSnarf,
+    lvlVote,
     NUM_LEVEL_OPTIONS
 };
 
@@ -83,6 +84,7 @@ struct chanData
     unsigned long       visited;
     unsigned long       limitAdjusted;
     unsigned long       ownerTransfer;
+    unsigned long       expiry;
 
     char    *topic;
     char    *greeting;
@@ -90,9 +92,14 @@ struct chanData
     char    *registrar;
     char    *topic_mask;
 
+    char          *vote;
+    unsigned int  vote_start;
+    dict_t        vote_options;
+
     unsigned int        flags : 30;
     unsigned int        may_opchan : 1;
     unsigned int        max;
+    unsigned int        max_time;
     unsigned int        last_refresh;
     unsigned short      banCount;
     unsigned short      userCount;
@@ -127,6 +134,9 @@ struct userData
     unsigned int        present : 1;
     unsigned int        flags : USER_FLAGS_SIZE;
 
+    unsigned short      voted;
+    unsigned int        votefor;
+
     /* linked list of userDatas for a chanData */
     struct userData     *prev;
     struct userData     *next;
@@ -162,6 +172,14 @@ struct suspended
     struct suspended    *previous;
 };
 
+struct vote_option
+{
+    char                *name;
+    unsigned int        option_id;
+    char                *option_str;
+    unsigned int        voted;
+};
+
 struct do_not_register
 {
     char   chan_name[CHANNELLEN+1];
@@ -171,11 +189,16 @@ struct do_not_register
     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);
 struct do_not_register *chanserv_is_dnr(const char *chan_name, struct handle_info *handle);
 int check_user_level(struct chanNode *channel, struct userNode *user, enum levelOption opt, int allow_override, int exempt_owner);
+struct mod_chanmode *find_matching_bans(struct banList *bans, struct userNode *actee, const char *mask);
+void handle_new_channel_created(char *chan, struct userNode *user);
 
 #endif
index c214cffcfee5a346bfe59466779115705b5f8886..a55ad9ba92568bc06f6e3b031953a41bce38f273 100644 (file)
@@ -382,6 +382,7 @@ AddChannel(const char *name, unsigned long time_, const char *modes, char *banli
         strcpy(cNode->name, name);
         banList_init(&cNode->banlist);
         modeList_init(&cNode->members);
+        userList_init(&cNode->invited);
         mod_chanmode(NULL, cNode, argv, nn, MCP_FROM_SERVER);
         dict_insert(channels, cNode->name, cNode);
         cNode->timestamp = time_;
@@ -469,6 +470,7 @@ DelChannel(struct chanNode *channel)
 
     modeList_clean(&channel->members);
     banList_clean(&channel->banlist);
+    userList_clean(&channel->invited);
     free(channel);
 }
 
@@ -663,9 +665,9 @@ ChannelUserKicked(struct userNode* kicker, struct userNode* victim, struct chanN
     unsigned int n;
     struct modeNode *mn;
 
-    if (!victim || !channel || IsService(victim) || !GetUserMode(channel, victim))
+    if (!victim || !channel || !GetUserMode(channel, victim))
         return;
-
+    
     /* Update the kicker's idle time (kicker may be null if it was a server) */
     if (kicker && (mn = GetUserMode(channel, kicker)))
         mn->idle_since = now;
index 8d3cb211914f21180b0c8a93a9672245bc260e5e..dd22ad3f1dd460a6f46ee1a3d85df448cf885777 100644 (file)
 #define MODE_REGISTERED     0x8000 /* Bahamut +r */
 #define MODE_APASS          0x10000 /* +A adminpass */
 #define MODE_UPASS          0x20000 /* +U userpass */
+#define MODE_NONOTICES      0x40000 /* +N */
+#define MODE_NOAMSGS        0x80000 /* +M */
+#define MODE_ALTCHAN        0x100000 /* +F */
+#define MODE_ACCESS         0x200000 /* +a */
+#define MODE_NOFLOOD        0x400000 /* +f */
+#define MODE_AUDITORIUM     0x800000 /* +u */
+#define MODE_SSLCHAN        0x1000000 /* +S */
 #define MODE_REMOVE         0x80000000
 
-#define FLAGS_OPER          0x0001 /* global operator +o */
-#define FLAGS_INVISIBLE     0x0004 /* invisible +i */
-#define FLAGS_WALLOP        0x0008 /* receives wallops +w */
-#define FLAGS_DEAF          0x0020 /* deaf +d */
-#define FLAGS_SERVICE       0x0040 /* cannot be kicked, killed or deoped +k */
-#define FLAGS_GLOBAL        0x0080 /* receives global messages +g */
-#define FLAGS_NOCHAN        0x0100 /* hide channels in whois +n */
+#define FLAGS_OPER          0x0001 /* +o global operator */
+#define FLAGS_INVISIBLE     0x0004 /* +i invisible */
+#define FLAGS_WALLOP        0x0008 /* +w receives wallops */
+#define FLAGS_DEAF          0x0020 /* +d deaf */
+#define FLAGS_SERVICE       0x0040 /* +k cannot be kicked, killed or deoped */
+#define FLAGS_GLOBAL        0x0080 /* +g receives global messages */
+#define FLAGS_NOCHAN        0x0100 /* +n hide channels in whois */
 #define FLAGS_PERSISTENT    0x0200 /* for reserved nicks, this isn't just one-shot */
 #define FLAGS_GAGGED        0x0400 /* for gagged users */
 #define FLAGS_AWAY          0x0800 /* for away users */
 #define FLAGS_STAMPED       0x1000 /* for users who have been stamped */
-#define FLAGS_HIDDEN_HOST   0x2000 /* user's host is masked by their account */
-#define FLAGS_REGNICK       0x4000 /* user owns their current nick */
+#define FLAGS_HIDDEN_HOST   0x2000 /* +x user's host is masked by their account */
+#define FLAGS_REGNICK       0x4000 /* +r user owns their current nick */
 #define FLAGS_REGISTERING   0x8000 /* user has issued account register command, is waiting for email cookie */
 #define FLAGS_DUMMY         0x10000 /* user is not announced to other servers */
-#define FLAGS_NOIDLE        0x20000 /* hide idle time in whois +I */
+#define FLAGS_NOIDLE        0x20000 /* +I hide idle time in whois */
+#define FLAGS_NETSERV       0x40000 /* +S */
+#define FLAGS_SECURITYSERV  0x80000 /* +D */
+#define FLAGS_XTRAOP        0x100000 /* +X */
+#define FLAGS_HIDDENOPER    0x200000 /* +H */
+#define FLAGS_SERVERNOTICE  0x400000 /* +s */
+#define FLAGS_SEENOIDLE     0x800000 /* +t */
 
 #define IsOper(x)               ((x)->modes & FLAGS_OPER)
 #define IsService(x)            ((x)->modes & FLAGS_SERVICE)
 #define IsRegistering(x)        ((x)->modes & FLAGS_REGISTERING)
 #define IsDummy(x)              ((x)->modes & FLAGS_DUMMY)
 #define IsNoIdle(x)             ((x)->modes & FLAGS_NOIDLE)
+#define IsSecurityServ(x)       ((x)->modes & FLAGS_SECURITYSERV)
+#define IsNetServ(x)            ((x)->modes & FLAGS_NETSERV)
+#define IsXtraOp(x)             ((x)->modes & FLAGS_XTRAOP)
+#define IsServerNotice(x)       ((x)->modes & FLAGS_SERVERNOTICE)
+#define IsHiddenOper(x)         ((x)->modes & FLAGS_HIDDENOPER)
+#define IsSeeNoIdle(x)          ((x)->modes & FLAGS_SEENOIDLE)
 #define IsFakeHost(x)           ((x)->fakehost[0] != '\0')
 #define IsFakeIdent(x)          ((x)->fakeident[0] != '\0')
 #define IsLocal(x)              ((x)->uplink == self)
 
+#define NOFLOODLEN      15
 #define NICKLEN         30
 #define USERLEN         10
 #define HOSTLEN         63
 #define REALLEN         50
-#define TOPICLEN        250
+#define TOPICLEN        500
 #define CHANNELLEN      200
 #define MAXOPLEVEL      999
 
@@ -131,8 +151,11 @@ struct userNode {
 struct chanNode {
     chan_mode_t modes;
     unsigned int limit;
+    unsigned int access;
     unsigned int locks;
     char key[KEYLEN + 1];
+    char altchan[CHANNELLEN + 1];
+    char noflood[NOFLOODLEN + 1];
     char upass[KEYLEN + 1];
     char apass[KEYLEN + 1];
     unsigned long timestamp; /* creation time */
@@ -144,6 +167,7 @@ struct chanNode {
     struct modeList members;
     struct banList banlist;
     struct policer join_policer;
+    struct userList invited;
     unsigned int join_flooded : 1;
     unsigned int bad_channel : 1;
 
index de67b2cc7736ca1db94c920572c1c1828d0850e7..aaf0d80ab961eed5048225f3d241f536ff4a106b 100644 (file)
@@ -23,6 +23,7 @@
 #include "log.h"
 #include "modcmd.h"
 #include "nickserv.h"
+#include "spamserv.h"
 
 #if defined(HAVE_DIRENT_H)
 #include <dirent.h>
@@ -41,7 +42,7 @@ static const struct message_entry msgtab[] = {
 #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;
@@ -529,12 +530,24 @@ vsend_message(const char *dest, struct userNode *src, struct handle_info *handle
         case 'N':
             value = nickserv ? nickserv->nick : "NickServ";
             break;
+        case 'X':
+            value = spamserv ? spamserv->nick : "SpamServ";
+            break;
         case 's':
             value = self->name;
             break;
-        case 'H':
+        case 'A':
             value = handle ? handle->handle : "Account";
             break;
+        case 'U':
+            value = message_dest ? message_dest->nick : "Nick";
+            break;
+        case 'I':
+            value = message_dest ? (IsFakeIdent(message_dest) ? message_dest->fakeident : message_dest->ident) : "Ident";
+            break;
+        case 'H':
+            value = message_dest ? (IsFakeHost(message_dest) ? message_dest->fakehost : message_dest->hostname) : "Hostname";
+            break;
 #define SEND_LINE(TRUNCED) do { \
     line[pos] = 0; \
     if (pos > 0) { \
index 229088b7b3155add24428b768b37ac59295f14df..85b9dd34a2dd3395658562eaee9fad9280b4af68 100644 (file)
@@ -53,6 +53,7 @@ static const struct message_entry msgtab[] = {
     { "MSG_STUPID_ACCESS_CHANGE", "Please ask someone $belse$b to demote you." },
     { "MSG_NO_SEARCH_ACCESS", "You do not have enough access to search based on $b%s$b." },
     { "MSG_INVALID_CRITERIA", "$b%s$b is an invalid search criteria." },
+    { "MSG_INVALID_FIELD", "$b%s$b is an invalid search field." },
     { "MSG_MATCH_COUNT", "Found $b%u$b matches." },
     { "MSG_NO_MATCHES", "Nothing matched the criteria of your search." },
     { "MSG_TOPIC_UNKNOWN", "No help on that topic." },
@@ -441,6 +442,11 @@ conf_globals(void)
     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))) {
index 6db248274f77b7177c4fcc8231ea6777fe63d7f9..4b8c71464550958b7b1895a5dd88a6e6d8d99908 100644 (file)
@@ -33,6 +33,7 @@
 #include "global.h"
 #include "modules.h"
 #include "opserv.h"
+#include "spamserv.h"
 
 #ifdef HAVE_GETOPT_H
 #include <getopt.h>
index c9eec1b025d4dfec3464f0d0522c0dde1241280e..7f645a9d73d68b4c646f07cf11f063fc17d45336 100644 (file)
@@ -53,6 +53,7 @@ const char *helpserv_module_deps[] = { NULL };
 #define KEY_NICK "nick"
 #define KEY_DB_BADCHANS "badchans"
 #define KEY_HELP_CHANNEL "help_channel"
+#define KEY_PUBLIC_CHANNEL "public_channel"
 #define KEY_PAGE_DEST "page_dest"
 #define KEY_CMDWORD "cmdword"
 #define KEY_PERSIST_LENGTH "persist_length"
@@ -85,6 +86,7 @@ const char *helpserv_module_deps[] = { NULL };
 #define KEY_PRIVMSG_ONLY "privmsg_only"
 #define KEY_REQ_ON_JOIN "req_on_join"
 #define KEY_AUTO_VOICE "auto_voice"
+#define KEY_AUTO_JOIN "auto_join"
 #define KEY_AUTO_DEVOICE "auto_devoice"
 #define KEY_LAST_ACTIVE "last_active"
 
@@ -170,7 +172,9 @@ static const struct message_entry msgtab[] = {
     { "HSMSG_SET_PRIVMSGONLY",    "$bPrivmsgOnly     $b %s" },
     { "HSMSG_SET_REQONJOIN",      "$bReqOnJoin       $b %s" },
     { "HSMSG_SET_AUTOVOICE",      "$bAutoVoice       $b %s" },
+    { "HSMSG_SET_AUTOJOIN",       "$bAutoJoin        $b %s" },
     { "HSMSG_SET_AUTODEVOICE",    "$bAutoDevoice     $b %s" },
+                               { "HSMSG_SET_PUBLICCHAN",     "$bPublicChan      $b %s" },
     { "HSMSG_PAGE_NOTICE", "notice" },
     { "HSMSG_PAGE_PRIVMSG", "privmsg" },
     { "HSMSG_PAGE_ONOTICE", "onotice" },
@@ -500,6 +504,7 @@ struct helpserv_bot {
     struct userNode *helpserv;
 
     struct chanNode *helpchan;
+    struct chanNode *publicchan;
 
     struct chanNode *page_targets[PGSRC_COUNT];
     enum page_type page_types[PGSRC_COUNT];
@@ -521,6 +526,7 @@ struct helpserv_bot {
     unsigned int privmsg_only : 1;
     unsigned int req_on_join : 1;
     unsigned int auto_voice : 1;
+    unsigned int auto_join : 1;
     unsigned int auto_devoice : 1;
 
     unsigned int helpchan_empty : 1;
@@ -1060,8 +1066,11 @@ static void helpserv_usermsg(struct userNode *user, struct helpserv_bot *hs, con
             helpserv_msguser(user, "HSMSG_USERCMD_NO_REQUEST");
             return;
         }
-        if ((hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_PART) && !GetUserMode(hs->helpchan, user)) {
-            helpserv_msguser(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", hs->helpchan->name);
+        if ((hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_PART) && !GetUserMode(hs->helpchan, user) && (!hs->publicchan || (hs->publicchan && !GetUserMode(hs->publicchan, user)))) {
+             if(hs->publicchan)
+                helpserv_msguser(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", hs->publicchan->name);
+             else
+                helpserv_msguser(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", hs->helpchan->name);
             return;
         }
 
@@ -1902,7 +1911,11 @@ static int helpserv_assign(int from_opserv, struct helpserv_bot *hs, struct user
     req->helper = GetHSUser(hs, user->handle_info);
     assert(req->helper);
     req->assigned = now;
-
+    
+    if (req->user && hs->auto_join) {
+        irc_svsjoin(hs->helpserv,req->user,hs->helpchan);
+    }
+    
     if (old_helper) {
         helpserv_notice(user, "HSMSG_REQ_REASSIGNED", req->id, old_helper->handle->handle);
         req->helper->reassigned_to[0]++;
@@ -1941,7 +1954,7 @@ static int helpserv_assign(int from_opserv, struct helpserv_bot *hs, struct user
         if ((change.args[0].u.member = GetUserMode(hs->helpchan, req->user)))
             mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
     }
-
+    
     return 1;
 }
 
@@ -2695,7 +2708,6 @@ static struct helpserv_bot *register_helpserv(const char *nick, const char *help
         dict_insert(helpserv_bots_bychan_dict, hs->helpchan->name, botlist);
     }
     helpserv_botlist_append(botlist, hs);
-
     return hs;
 }
 
@@ -2703,7 +2715,7 @@ static HELPSERV_FUNC(cmd_register) {
     char *nick, *helpchan, reason[MAXLEN];
     struct handle_info *handle;
 
-    REQUIRE_PARMS(4);
+    REQUIRE_PARMS(3);
     nick = argv[1];
     if (!is_valid_nick(nick)) {
         helpserv_notice(user, "HSMSG_ILLEGAL_NICK", nick);
@@ -2764,7 +2776,7 @@ static void helpserv_free_bot(void *data) {
 }
 
 static void helpserv_unregister(struct helpserv_bot *bot, const char *quit_fmt, const char *global_fmt, const char *actor) {
-    char reason[MAXLEN], channame[CHANNELLEN], botname[NICKLEN];
+    char reason[MAXLEN], channame[CHANNELLEN], *botname;
     struct helpserv_botlist *botlist;
     size_t len;
 
@@ -2772,8 +2784,7 @@ static void helpserv_unregister(struct helpserv_bot *bot, const char *quit_fmt,
     helpserv_botlist_remove(botlist, bot);
     if (!botlist->used)
         dict_remove(helpserv_bots_bychan_dict, bot->helpchan->name);
-    len = strlen(bot->helpserv->nick) + 1;
-    safestrncpy(botname, bot->helpserv->nick, len);
+    botname=bot->helpserv->nick;
     len = strlen(bot->helpchan->name) + 1;
     safestrncpy(channame, bot->helpchan->name, len);
     snprintf(reason, sizeof(reason), quit_fmt, actor);
@@ -3291,23 +3302,72 @@ static HELPSERV_OPTION(opt_auto_voice) {
     OPTION_BINARY(hs->auto_voice, "HSMSG_SET_AUTOVOICE");
 }
 
+static HELPSERV_OPTION(opt_auto_join) {
+    OPTION_BINARY(hs->auto_join, "HSMSG_SET_AUTOJOIN");
+}
+
 static HELPSERV_OPTION(opt_auto_devoice) {
     OPTION_BINARY(hs->auto_devoice, "HSMSG_SET_AUTODEVOICE");
 }
 
+static HELPSERV_OPTION(opt_publicchan) {
+ char *publicchan;
+ int changed=0;
+ if (argc > 0) {
+  publicchan = argv[0];
+               if(strcmp(publicchan, "*")) {
+         if (!IsChannelName(publicchan)) {
+    helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", publicchan);
+    HELPSERV_SYNTAX();
+    return 0;
+   }
+   if (opserv_bad_channel(publicchan)) {
+    helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", publicchan);
+    return 0;
+   }
+               }
+               if (!hs->publicchan || (hs->publicchan && irccasecmp(hs->publicchan->name, publicchan))) {
+   if(hs->publicchan) {
+                 //there is another public chan o.O
+                        //part
+                        DelChannelUser(hs->helpserv, hs->publicchan, "unregistered.", 0);
+                               hs->publicchan = NULL;
+                }
+                       changed = 1;
+                       if(strcmp(publicchan, "*")) {
+                        if (!(hs->publicchan = GetChannel(publicchan))) {
+     hs->publicchan = AddChannel(publicchan, now, NULL, NULL);
+     AddChannelUser(hs->helpserv, hs->publicchan)->modes |= MODE_CHANOP;
+    } else {
+     struct mod_chanmode change;
+     mod_chanmode_init(&change);
+     change.argc = 1;
+     change.args[0].mode = MODE_CHANOP;
+     change.args[0].u.member = AddChannelUser(hs->helpserv, hs->publicchan);
+     mod_chanmode_announce(hs->helpserv, hs->publicchan, &change);
+                        }
+                       }
+  }
+ } else {
+        changed = 0;
+       }
+       helpserv_notice(user, "HSMSG_SET_PUBLICCHAN", (hs->publicchan) ? hs->publicchan->name : user_find_message(user,"MSG_NONE")); \
+ return changed;
+}
+
 static HELPSERV_FUNC(cmd_set) {
     helpserv_option_func_t *opt;
 
     if (argc < 2) {
         unsigned int i;
         helpserv_option_func_t *display[] = {
-            opt_pagetarget_command, opt_pagetarget_alert, opt_pagetarget_status,
+            opt_publicchan, opt_pagetarget_command, opt_pagetarget_alert, opt_pagetarget_status,
             opt_pagetype, opt_alert_page_type, opt_status_page_type,
             opt_greeting, opt_req_opened, opt_req_assigned, opt_req_closed,
             opt_idle_delay, opt_whine_delay, opt_whine_interval,
             opt_empty_interval, opt_stale_delay, opt_request_persistence,
             opt_helper_persistence, opt_notification, opt_id_wrap,
-            opt_req_maxlen, opt_privmsg_only, opt_req_on_join, opt_auto_voice,
+            opt_req_maxlen, opt_privmsg_only, opt_req_on_join, opt_auto_voice, opt_auto_join,
             opt_auto_devoice
         };
 
@@ -3594,6 +3654,7 @@ helpserv_bot_write(const char *key, void *data, void *extra) {
 
     /* Other settings and state */
     saxdb_write_string(ctx, KEY_HELP_CHANNEL, hs->helpchan->name);
+    if(hs->publicchan) saxdb_write_string(ctx, KEY_PUBLIC_CHANNEL, hs->publicchan->name);
     slist = alloc_string_list(PGSRC_COUNT);
     for (pagesrc=0; pagesrc<PGSRC_COUNT; pagesrc++) {
         struct chanNode *target = hs->page_targets[pagesrc];
@@ -3629,6 +3690,7 @@ helpserv_bot_write(const char *key, void *data, void *extra) {
     saxdb_write_int(ctx, KEY_PRIVMSG_ONLY, hs->privmsg_only);
     saxdb_write_int(ctx, KEY_REQ_ON_JOIN, hs->req_on_join);
     saxdb_write_int(ctx, KEY_AUTO_VOICE, hs->auto_voice);
+    saxdb_write_int(ctx, KEY_AUTO_JOIN, hs->auto_join);
     saxdb_write_int(ctx, KEY_AUTO_DEVOICE, hs->auto_devoice);
     saxdb_write_int(ctx, KEY_LAST_ACTIVE, hs->last_active);
 
@@ -3649,7 +3711,7 @@ helpserv_saxdb_write(struct saxdb_context *ctx) {
 static int helpserv_bot_read(const char *key, void *data, UNUSED_ARG(void *extra)) {
     struct record_data *br = data, *raw_record;
     struct helpserv_bot *hs;
-    char *registrar, *helpchannel_name, *str;
+    char *registrar, *helpchannel_name, *publicchannel_name, *str;
     dict_t users, requests;
     enum page_source pagesrc;
     enum message_type msgtype;
@@ -3670,6 +3732,25 @@ static int helpserv_bot_read(const char *key, void *data, UNUSED_ARG(void *extra
 
     hs = register_helpserv(key, helpchannel_name, registrar);
 
+                               publicchannel_name = database_get_data(GET_RECORD_OBJECT(br), KEY_PUBLIC_CHANNEL, RECDB_QSTRING);
+    if (publicchannel_name) {
+                                if(!IsChannelName(publicchannel_name)) {
+        log_module(HS_LOG, LOG_ERROR, "%s has an invalid channel name.", key);
+        return 0;
+                                       } else {
+                                        if (!(hs->publicchan = GetChannel(publicchannel_name))) {
+       hs->publicchan = AddChannel(publicchannel_name, now, NULL, NULL);
+       AddChannelUser(hs->helpserv, hs->publicchan)->modes |= MODE_CHANOP;
+      } else {
+       struct mod_chanmode change;
+       mod_chanmode_init(&change);
+       change.argc = 1;
+       change.args[0].mode = MODE_CHANOP;
+       change.args[0].u.member = AddChannelUser(hs->helpserv, hs->publicchan);
+       mod_chanmode_announce(hs->helpserv, hs->publicchan, &change);
+                                               }
+                                       }
+    }
     raw_record = dict_find(GET_RECORD_OBJECT(br), KEY_PAGE_DEST, NULL);
     switch (raw_record ? raw_record->type : RECDB_INVALID) {
     case RECDB_QSTRING:
@@ -3736,6 +3817,8 @@ static int helpserv_bot_read(const char *key, void *data, UNUSED_ARG(void *extra
     hs->req_on_join = str ? enabled_string(str) : 0;
     str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_VOICE, RECDB_QSTRING);
     hs->auto_voice = str ? enabled_string(str) : 0;
+    str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_JOIN, RECDB_QSTRING);
+    hs->auto_join = str ? enabled_string(str) : 0;
     str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_DEVOICE, RECDB_QSTRING);
     hs->auto_devoice = str ? enabled_string(str) : 0;
     str = database_get_data(GET_RECORD_OBJECT(br), KEY_LAST_ACTIVE, RECDB_QSTRING);
@@ -3837,6 +3920,8 @@ static void handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason)) {
                 struct helpserv_request *req = iter_data(it);
 
                 if (mn->user != req->user)
+                    continue;
+                                                                                                                               if (GetUserMode(hs->helpchan, mn->user)) //publicchan
                     continue;
                 if (req->text->used) {
                     helpserv_message(hs, mn->user, MSGTYPE_REQ_DROPPED);
@@ -4027,6 +4112,7 @@ static void associate_requests_bybot(struct helpserv_bot *hs, struct userNode *u
         if (!nicknewest || (nicknewest->opened < req->opened))
             nicknewest = req;
 
+        
         if (hs->auto_voice && req->helper)
         {
             struct mod_chanmode change;
@@ -4041,7 +4127,7 @@ static void associate_requests_bybot(struct helpserv_bot *hs, struct userNode *u
     if ((force_greet && nicknewest) || (newest && (nicknewest == newest))) {
         /* Let the user know. Either the user is forced to be greeted, or the
          * above has changed which request will get their next message. */
-        helpserv_msguser(user, "HSMSG_GREET_EXISTING_REQ", hs->helpchan->name, nicknewest->id);
+        //helpserv_msguser(user, "HSMSG_GREET_EXISTING_REQ", hs->helpchan->name, nicknewest->id);
     }
 }
 
@@ -4658,7 +4744,9 @@ int helpserv_init() {
     helpserv_define_option("PRIVMSGONLY", opt_privmsg_only);
     helpserv_define_option("REQONJOIN", opt_req_on_join);
     helpserv_define_option("AUTOVOICE", opt_auto_voice);
+    helpserv_define_option("AUTOJOIN", opt_auto_join);
     helpserv_define_option("AUTODEVOICE", opt_auto_devoice);
+                               helpserv_define_option("PUBLICCHAN", opt_publicchan);
 
     helpserv_usercmd_dict = dict_new();
     dict_insert(helpserv_usercmd_dict, "WAIT", usercmd_wait);
diff --git a/src/mod-watchdog.c b/src/mod-watchdog.c
new file mode 100644 (file)
index 0000000..d9da25d
--- /dev/null
@@ -0,0 +1,676 @@
+/* mod-watchdog.c - Watchdog module for srvx
+ * Copyright 2003-2004 Martijn Smit and srvx Development Team
+ *
+ * This file is part of srvx.
+ *
+ * srvx 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.
+ *
+ * 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 srvx; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
+ */
+
+/* Adds new section to srvx.conf:
+ * "modules" {
+ *     "watchdog" {
+ *         "nick" "Watchdog";
+ *         "modes" "+iok";
+           "ban_duration" "2h"; //only if the channel is registered with chanserv
+           "gline_duration" "1h";
+           "punishment_reason" "Your message contained a forbidden word.";
+ *     };
+ *  };
+ *
+ */
+
+#include "chanserv.h"
+#include "opserv.h"
+#include "conf.h"
+#include "modcmd.h"
+#include "saxdb.h"
+#include "timeq.h"
+#include "gline.h"
+
+#define KEY_BADWORDS "badwords"
+#define KEY_BADWORD_MASK "mask"
+#define KEY_BADWORD_TRIGGERED "count"
+#define KEY_BADWORD_ACTION "action"
+#define KEY_CHANNELS "channel"
+#define KEY_BADWORDID "badwordid"
+
+static const struct message_entry msgtab[] = {
+    { "WDMSG_REGISTER_SUCCESS", "$b%s$b is now registered with %s." },
+    { "WDMSG_NOT_REGISTERED", "$b%s$b is not registered with %s." },
+    { "WDMSG_ALREADY_ADDED", "$b%s$b is already added. (ID: %s)" },
+    { "WDMSG_BADWORD_ADDED", "added '$b%s$b' to the badword list with ID %s." },
+    { "WDMSG_BADWORD_NOT_FOUND", "badword with ID %s does not exist." },
+    { "WDMSG_BADWORD_REMOVED", "badword ID $b%s$b has been removed (mask: '%s')" },
+    { "WDMSG_BADWORD_SET_DONE", "Done." },
+    { "WDMSG_BADWORD_SET_INVALID", "Invalid Option for setting %s" },
+    { "OSMSG_BADWORD_SETTING_INVALID", "unknown setting $b%s$b." },
+    { "WDMSG_BADWORD_SET", "Settings for BadWord entry $b%s$b" },
+    { "WDMSG_BADWORD_SET_MASK",   "$bMASK$b:   %s" },
+    { "WDMSG_BADWORD_SET_ACTION", "$bACTION$b: %s" },
+    { NULL, NULL }
+};
+
+struct badword {
+    char *id;
+    char *badword_mask;
+    unsigned int triggered : 29;
+    unsigned int action : 3;
+};
+
+struct watchdog_channel {
+    struct chanNode *channel;
+    //struct shitList *shitlist;
+};
+
+/* badword.action fields */
+#define BADACTION_KICK   0
+#define BADACTION_BAN    1
+#define BADACTION_KILL   2
+#define BADACTION_GLINE  3
+
+static struct {
+    const char *nick;
+    const char *modes;
+    const char *punishment_reason;
+    unsigned long ban_duration;
+    unsigned long gline_duration;
+} watchdog_conf;
+
+const char *watchdog_module_deps[] = { NULL };
+struct userNode *watchdog;
+static struct module *watchdog_module;
+static struct service *watchdog_service;
+static dict_t shitlist;
+static dict_t chanlist;
+static struct log_type *MS_LOG;
+static unsigned int last_badword_id = 0;
+
+static struct watchdog_channel *add_channel(const char *name);
+static struct badword *add_badword(const char *badword_mask, unsigned int triggered, unsigned int action, const char *id);
+#define watchdog_notice(target, format...) send_message(target , watchdog , ## format)
+
+static MODCMD_FUNC(cmd_addbad)
+{
+    dict_iterator_t it;
+    char *mask = unsplit_string(argv + 1, argc - 1, NULL);
+    for (it = dict_first(shitlist); it; it = iter_next(it)) {
+        struct badword *badword = iter_data(it);
+        if(match_ircglob(mask,badword->badword_mask)) {
+            reply("WDMSG_ALREADY_ADDED", mask, badword->id);
+            return 1;
+        }
+    }
+
+    struct badword *new_badword = add_badword(mask, 0, BADACTION_KICK, NULL);
+    for (it = dict_first(shitlist); it; it = iter_next(it)) {
+        struct badword *badword = iter_data(it);
+        if(match_ircglob(badword->badword_mask, new_badword->badword_mask) && badword != new_badword) {
+            dict_remove(shitlist, badword->id);
+        }
+    }
+
+    reply("WDMSG_BADWORD_ADDED", new_badword->badword_mask, new_badword->id);
+    return 1;
+}
+
+static MODCMD_FUNC(cmd_delbad)
+{
+    unsigned int n;
+
+    for (n=1; n<argc; n++) {
+        struct badword *badword = dict_find(shitlist, argv[n], NULL);
+        if (!badword) {
+            reply("WDMSG_BADWORD_NOT_FOUND", argv[n]);
+            continue;
+        }
+        reply("WDMSG_BADWORD_REMOVED", argv[n], badword->badword_mask);
+        dict_remove(shitlist, argv[n]);
+    }
+    return 1;
+}
+
+static MODCMD_FUNC(cmd_setbad)
+{
+    struct badword *badword;
+    if ((badword = dict_find(shitlist, argv[1], NULL))) {
+        if (argc > 3) {
+            unsigned int ii;
+            char *setting = argv[2];
+            char *value = argv[3];
+            for( ii = 0; setting[ ii ]; ii++)
+                setting[ ii ] = toupper( setting[ ii ] );
+            for( ii = 0; value[ ii ]; ii++)
+                value[ ii ] = toupper( value[ ii ] );
+            if(!strcmp("MASK",setting)) {
+                  free(badword->badword_mask);
+                  badword->badword_mask = strdup(argv[3]);
+                  badword->triggered = 0;
+                  reply("WDMSG_BADWORD_SET_DONE");
+            }
+            else if(!strcmp("ACTION",setting)) {
+                 if (!strcmp("1",value) || !strcmp("KICK",value)) {
+                    badword->action = BADACTION_KICK;
+                    reply("WDMSG_BADWORD_SET_DONE");
+                 } else if (!strcmp("2",value) || !strcmp("BAN",value)) {
+                    badword->action = BADACTION_BAN;
+                    reply("WDMSG_BADWORD_SET_DONE");
+                 } else if (!strcmp("3",value) || !strcmp("KILL",value)) {
+                    badword->action = BADACTION_KILL;
+                    reply("WDMSG_BADWORD_SET_DONE");
+                 } else if (!strcmp("4",value) || !strcmp("GLINE",value)) {
+                    badword->action = BADACTION_GLINE;
+                    reply("WDMSG_BADWORD_SET_DONE");
+                 } else {
+                    reply("WDMSG_BADWORD_SET_INVALID", setting);
+                 }
+            } else {
+                 reply("WDMSG_BADWORD_SETTING_INVALID", setting);
+            }
+            
+        } else {
+            reply("WDMSG_BADWORD_SET", badword->id);
+            reply("WDMSG_BADWORD_SET_MASK", badword->badword_mask);
+            switch(badword->action) {
+                case BADACTION_KICK:
+                  reply("WDMSG_BADWORD_SET_ACTION", "KICK");
+                  break;
+                case BADACTION_BAN:
+                  reply("WDMSG_BADWORD_SET_ACTION", "BAN");
+                  break;
+                case BADACTION_KILL:
+                  reply("WDMSG_BADWORD_SET_ACTION", "KILL");
+                  break;
+                case BADACTION_GLINE:
+                  reply("WDMSG_BADWORD_SET_ACTION", "GLINE");
+                  break;
+                default:
+                  reply("WDMSG_BADWORD_SET_ACTION", "*undef*");
+            }
+        }
+    } else {
+        reply("WDMSG_BADWORD_NOT_FOUND", argv[1]);
+        return 0;
+    }
+    return 1;
+}
+
+int
+badwords_sort(const void *pa, const void *pb)
+{
+        struct badword *a = *(struct badword**)pa;
+        struct badword *b = *(struct badword**)pb;
+
+        return strtoul(a->id, NULL, 0) - strtoul(b->id, NULL, 0);
+}
+
+static MODCMD_FUNC(cmd_listbad)
+{
+    struct helpfile_table tbl;
+    unsigned int count = 0, ii = 0;
+    struct badword **badwords;
+    
+    dict_iterator_t it;
+    for (it = dict_first(shitlist); it; it = iter_next(it)) {
+        count++;
+    }
+    tbl.length = count+1;
+    tbl.width = 4;
+    tbl.flags = 0;
+    tbl.flags = TABLE_NO_FREE;
+    tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
+    tbl.contents[0] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
+    tbl.contents[0][0] = "#";
+    tbl.contents[0][1] = "Badword";
+    tbl.contents[0][2] = "Action";
+    tbl.contents[0][3] = "(Triggered)";
+    if(!count)
+    {
+        table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
+        reply("MSG_NONE");
+        free(tbl.contents[0]);
+        free(tbl.contents);
+        return 0;
+    }
+    badwords = alloca(count * sizeof(badwords[0]));
+    for (it = dict_first(shitlist); it; it = iter_next(it)) {
+        struct badword *bw = iter_data(it);
+        badwords[ii++] = bw;
+    }
+    qsort(badwords, count, sizeof(badwords[0]), badwords_sort);
+    for (ii = 1; ii <= count; ii++) {
+        struct badword *bw = badwords[ii-1];
+        tbl.contents[ii] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
+        tbl.contents[ii][0] = strdup(bw->id);
+        tbl.contents[ii][1] = strdup(bw->badword_mask);
+        switch(bw->action) {
+            case BADACTION_KICK:
+              tbl.contents[ii][2] = "KICK";
+              break;
+            case BADACTION_BAN:
+              tbl.contents[ii][2] = "BAN";
+              break;
+            case BADACTION_KILL:
+              tbl.contents[ii][2] = "KILL";
+              break;
+            case BADACTION_GLINE:
+              tbl.contents[ii][2] = "GLINE";
+              break;
+            default:
+              tbl.contents[ii][2] = "*undef*";
+        }
+        tbl.contents[ii][3] = strtab(bw->triggered);
+    }
+    table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
+    for(ii = 1; ii < tbl.length; ++ii)
+    {
+        free(tbl.contents[ii]);
+    }
+    free(tbl.contents[0]);
+    free(tbl.contents);
+    return 1;
+}
+
+static MODCMD_FUNC(cmd_register)
+{
+    dict_iterator_t it;
+    struct modeNode *mn;
+
+    if(channel)
+    {
+
+        if(channel->bad_channel)
+        {
+            reply("CSMSG_ILLEGAL_CHANNEL", channel->name);
+            return 0;
+        }
+
+        if(!IsHelping(user)
+           && (!(mn = GetUserMode(channel, user)) || !(mn->modes & MODE_CHANOP)))
+        {
+            reply("CSMSG_MUST_BE_OPPED", channel->name);
+            return 0;
+        }
+
+    }
+    else
+    {
+
+        if((argc < 2) || !IsChannelName(argv[1]))
+        {
+            reply("MSG_NOT_CHANNEL_NAME");
+            return 0;
+        }
+
+        if(opserv_bad_channel(argv[1]))
+        {
+            reply("CSMSG_ILLEGAL_CHANNEL", argv[1]);
+            return 0;
+        }
+
+        channel = AddChannel(argv[1], now, NULL, NULL);
+    }
+
+    for (it = dict_first(chanlist); it; it = iter_next(it)) {
+        struct watchdog_channel *chan = iter_data(it);
+        if(chan->channel == channel) {
+            reply("CSMSG_ALREADY_REGGED", channel->name);
+            return 0;
+        }
+    }
+
+    add_channel(channel->name);
+    reply("WDMSG_REGISTER_SUCCESS", channel->name, watchdog->nick);
+    return 1;
+}
+
+static MODCMD_FUNC(cmd_unregister)
+{
+    struct watchdog_channel *chan = NULL;
+    dict_iterator_t it;
+    
+    for (it = dict_first(chanlist); it; it = iter_next(it)) {
+        chan = iter_data(it);
+        if(chan->channel == channel)
+            break;
+    }
+    
+    if(chan && chan->channel == channel) {
+        //found, unregister it!
+        char reason[MAXLEN];
+        sprintf(reason, "Unregistered by %s.", user->handle_info->handle);
+        DelChannelUser(watchdog, channel, reason, 0);
+        dict_remove(chanlist, channel->name);
+        reply("CSMSG_UNREG_SUCCESS", channel->name);
+        return 1;
+    } else {
+        reply("WDMSG_NOT_REGISTERED", channel->name, watchdog->nick);
+        return 0;
+    }
+
+}
+
+static void
+watchdog_detected_badword(struct userNode *user, struct chanNode *chan, struct badword *badword) 
+{
+    char *hostmask;
+    char *reason = watchdog_conf.punishment_reason;
+    char mask[IRC_NTOP_MAX_SIZE+3] = { '*', '@', '\0' };
+    switch(badword->action) {
+        case BADACTION_BAN:
+            hostmask = generate_hostmask(user, GENMASK_STRICT_HOST | GENMASK_ANY_IDENT);
+            sanitize_ircmask(hostmask);
+            if(chan->channel_info) {
+                //registered channel
+                add_channel_ban(chan->channel_info, hostmask, watchdog->nick, now, now, now + watchdog_conf.ban_duration, reason);
+            }
+            struct mod_chanmode change;
+            mod_chanmode_init(&change);
+            change.argc = 1;
+            change.args[0].mode = MODE_BAN;
+            change.args[0].u.hostmask = hostmask;
+            mod_chanmode_announce(watchdog, chan, &change);
+            free(hostmask);
+            
+        case BADACTION_KICK:
+            if(GetUserMode(chan, user))
+                KickChannelUser(user, chan, watchdog, reason); 
+            break;
+        case BADACTION_KILL:
+            DelUser(user, watchdog, 1, reason);
+            break;
+        case BADACTION_GLINE:
+            irc_ntop(mask + 2, sizeof(mask) - 2, &user->ip);
+            gline_add(watchdog->nick, mask, watchdog_conf.gline_duration, reason, now, now, 0, 1);
+            break;
+        default:
+            //error?
+            break;
+        }
+}
+
+static void
+watchdog_channel_message(struct userNode *user, struct chanNode *chan, const char *text, UNUSED_ARG(struct userNode *bot), UNUSED_ARG(unsigned int is_notice))
+{
+    dict_iterator_t it;
+
+    if(!watchdog || !dict_find(chanlist, chan->name, NULL))
+        return;
+
+    for (it = dict_first(shitlist); it; it = iter_next(it)) {
+        struct badword *badword = iter_data(it);
+        if(match_ircglob(text, badword->badword_mask)) {
+            watchdog_detected_badword(user, chan, badword);
+        }
+    }
+}
+
+static struct badword*
+add_badword(const char *badword_mask, unsigned int triggered, unsigned int action, const char *id)
+{
+    struct badword *badword;
+
+    badword = calloc(1, sizeof(*badword));
+    if (!badword)
+        return NULL;
+
+    if(!id) {
+        last_badword_id++;
+        badword->id = strtab(last_badword_id);
+    } else
+        badword->id = strdup(id);
+    badword->badword_mask = strdup(badword_mask);
+    badword->triggered = triggered;
+    badword->action = action;
+    dict_insert(shitlist, badword->id, badword);
+    return badword;
+}
+
+static void
+free_shitlist_entry(void *data)
+{
+    struct badword *badword = data;
+    free(badword->id);
+    free(badword->badword_mask);
+    free(badword);
+}
+
+static struct watchdog_channel*
+add_channel(const char *name)
+{
+    struct watchdog_channel *wc;
+    struct mod_chanmode *change;
+
+    if(!watchdog) //module disabled
+        return NULL;
+
+    wc = calloc(1, sizeof(*wc));
+    if (!wc)
+        return NULL;
+
+    wc->channel = AddChannel(name, now, NULL, NULL);
+    change = mod_chanmode_alloc(1);
+    change->argc = 1;
+    change->args[0].mode = MODE_CHANOP;
+    change->args[0].u.member = AddChannelUser(watchdog, wc->channel);
+    mod_chanmode_announce(watchdog, wc->channel, change);
+       mod_chanmode_free(change);
+    dict_insert(chanlist, wc->channel->name, wc);
+    return wc;
+}
+
+static void
+free_chanlist_entry(void *data)
+{
+    struct watchdog_channel *wc = data;
+    
+    free(wc);
+}
+
+static void
+watchdog_conf_read(void)
+{
+    dict_t conf_node;
+    const char *str;
+
+    str = "modules/watchdog";
+    if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
+        log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
+        return;
+    }
+
+    str = database_get_data(conf_node, "nick", RECDB_QSTRING);
+    if(watchdog_conf.nick && strcmp(watchdog_conf.nick, str)) {
+        //nick changed
+    }
+    watchdog_conf.nick = str;
+    
+    str = database_get_data(conf_node, "modes", RECDB_QSTRING);
+    watchdog_conf.modes = (str ? str : NULL);
+    
+    str = database_get_data(conf_node, "ban_duration", RECDB_QSTRING);
+       watchdog_conf.ban_duration = str ? ParseInterval(str) : ParseInterval("2h");
+    
+    str = database_get_data(conf_node, "gline_duration", RECDB_QSTRING);
+       watchdog_conf.gline_duration = str ? ParseInterval(str) : ParseInterval("1h");
+    
+    str = database_get_data(conf_node, "punishment_reason", RECDB_QSTRING);
+       watchdog_conf.punishment_reason = (str ? str : "Your message contained a forbidden word.");
+    
+}
+
+static int
+watchdog_saxdb_read_shitlist(const char *name, void *data, UNUSED_ARG(void *extra))
+{
+    struct record_data *rd = data;
+    char *badword;
+    char *triggered, *action;
+
+     if (rd->type == RECDB_OBJECT) {
+        dict_t obj = GET_RECORD_OBJECT(rd);
+        /* new style structure */
+        badword = database_get_data(obj, KEY_BADWORD_MASK, RECDB_QSTRING);
+        triggered = database_get_data(obj, KEY_BADWORD_TRIGGERED, RECDB_QSTRING);
+        action = database_get_data(obj, KEY_BADWORD_ACTION, RECDB_QSTRING);
+
+        add_badword(badword, strtoul(triggered, NULL, 0), strtoul(action, NULL, 0), name);
+    }
+    return 0;
+}
+
+static int
+watchdog_saxdb_read_chanlist(const char *name, void *data, UNUSED_ARG(void *extra))
+{
+    struct record_data *rd = data;
+
+     if (rd->type == RECDB_OBJECT) {
+        //dict_t obj = GET_RECORD_OBJECT(rd);
+        /* nothing in here, yet */
+
+        add_channel(name);
+    }
+    return 0;
+}
+
+static int
+watchdog_saxdb_read(struct dict *db)
+{
+    struct dict *object;
+    char *str;
+    str = database_get_data(db, KEY_BADWORDID, RECDB_QSTRING);
+    last_badword_id = str ? strtoul(str, NULL, 0) : 0;
+
+    if ((object = database_get_data(db, KEY_BADWORDS, RECDB_OBJECT)))
+        dict_foreach(object, watchdog_saxdb_read_shitlist, NULL);
+
+    if ((object = database_get_data(db, KEY_CHANNELS, RECDB_OBJECT)))
+        dict_foreach(object, watchdog_saxdb_read_chanlist, NULL);
+
+    return 1;
+}
+
+static int
+watchdog_saxdb_write(struct saxdb_context *ctx)
+{
+    dict_iterator_t it;
+
+    saxdb_write_int(ctx, KEY_BADWORDID, last_badword_id);
+
+    if (dict_size(shitlist)) {
+        saxdb_start_record(ctx, KEY_BADWORDS, 1);
+        for (it = dict_first(shitlist); it; it = iter_next(it)) {
+            struct badword *badword = iter_data(it);
+            if(badword && badword->badword_mask) {
+                saxdb_start_record(ctx, iter_key(it), 0);
+                
+                saxdb_write_string(ctx, KEY_BADWORD_MASK, badword->badword_mask);
+                saxdb_write_int(ctx, KEY_BADWORD_TRIGGERED, badword->triggered);
+                saxdb_write_int(ctx, KEY_BADWORD_ACTION, badword->action);
+                
+                saxdb_end_record(ctx);
+            }
+        }
+        saxdb_end_record(ctx);
+    }
+
+    if (dict_size(chanlist)) {
+        saxdb_start_record(ctx, KEY_CHANNELS, 1);
+        for (it = dict_first(chanlist); it; it = iter_next(it)) {
+            struct watchdog_channel *wc = iter_data(it);
+            if(wc && wc->channel && wc->channel->name) {
+                saxdb_start_record(ctx, wc->channel->name, 0);
+                //anything else?
+                saxdb_end_record(ctx);
+            }
+        }
+        saxdb_end_record(ctx);
+    }
+    
+    return 0;
+}
+
+static void
+watchdog_cleanup(void)
+{
+    dict_delete(shitlist);
+    dict_delete(chanlist);
+}
+
+int
+watchdog_init(void)
+{
+    MS_LOG = log_register_type("Watchdog", "file:watchdog.log");
+    
+    /* set up shitlist dict */
+    dict_delete(shitlist);
+    shitlist = dict_new();
+    dict_set_free_data(shitlist, free_shitlist_entry);
+    /* set up chanlist dict */
+    dict_delete(chanlist);
+    chanlist = dict_new();
+    dict_set_free_data(chanlist, free_chanlist_entry);
+
+    const char *nick, *modes;
+    if((nick = conf_get_data("modules/watchdog/nick", RECDB_QSTRING))) {
+        modes = conf_get_data("modules/watchdog/modes", RECDB_QSTRING);
+        watchdog = AddLocalUser(nick, nick, NULL, "Watchdog Service", modes);
+        watchdog_service = service_register(watchdog);
+        watchdog_service->trigger = ',';
+        reg_allchanmsg_func(watchdog, watchdog_channel_message);
+    }
+
+    conf_register_reload(watchdog_conf_read);
+    reg_exit_func(watchdog_cleanup);
+    saxdb_register("Watchdog", watchdog_saxdb_read, watchdog_saxdb_write);
+
+    watchdog_module = module_register("Watchdog", MS_LOG, "mod-watchdog.help", NULL);
+    modcmd_register(watchdog_module, "addbad", cmd_addbad, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
+    modcmd_register(watchdog_module, "delbad", cmd_delbad, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
+    modcmd_register(watchdog_module, "setbad", cmd_setbad, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
+    modcmd_register(watchdog_module, "listbad", cmd_listbad, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
+    modcmd_register(watchdog_module, "register", cmd_register, 1, MODCMD_REQUIRE_AUTHED, "flags", "+acceptchan,+helping", NULL);
+    modcmd_register(watchdog_module, "unregister", cmd_unregister, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_CHANNEL, "flags", "+helping", NULL);
+    message_register_table(msgtab);
+
+    return 1;
+}
+
+int
+watchdog_finalize(void) {
+    dict_t conf_node;
+    const char *str;
+
+    str = "modules/watchdog";
+    if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
+        log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
+        return 0;
+    }
+
+    str = database_get_data(conf_node, "nick", RECDB_QSTRING);
+    if (str) watchdog_conf.nick = str;
+    
+    str = database_get_data(conf_node, "modes", RECDB_QSTRING);
+    if (str) watchdog_conf.modes = str;
+    
+    str = database_get_data(conf_node, "ban_duration", RECDB_QSTRING);
+       if (str) watchdog_conf.ban_duration = ParseInterval(str);
+    
+    str = database_get_data(conf_node, "gline_duration", RECDB_QSTRING);
+       if (str) watchdog_conf.gline_duration = ParseInterval(str);
+    
+    str = database_get_data(conf_node, "punishment_reason", RECDB_QSTRING);
+       if (str) watchdog_conf.punishment_reason = str;
+    
+    return 1;
+}
diff --git a/src/mod-watchdog.help b/src/mod-watchdog.help
new file mode 100644 (file)
index 0000000..0519ecb
--- /dev/null
@@ -0,0 +1 @@
\ No newline at end of file
index 9e7d15f72325673d737b473c4c624c1179a0e377..3d57da679fb5e7da1748595e9970805dd565c1c6 100644 (file)
@@ -1587,7 +1587,7 @@ static MODCMD_FUNC(cmd_stats_services) {
             service = iter_data(it);
             tbl.contents[ii] = calloc(tbl.width, sizeof(tbl.contents[ii][0]));
             tbl.contents[ii][0] = service->bot->nick;
-            tbl.contents[ii][1] = strtab(dict_size(service->commands));
+            tbl.contents[ii][1] = (service->commands && dict_size(service->commands) ? strtab(dict_size(service->commands)) : strtab(0));
             tbl.contents[ii][2] = service->privileged ? "yes" : "no";
             extra[ii*2] = service->trigger;
             tbl.contents[ii][3] = extra+ii*2;
@@ -1919,7 +1919,7 @@ static MODCMD_FUNC(cmd_version) {
     if (argc > 1)
         send_message_type(4, user, cmd->parent->bot, "%s", git_version);
     else
-        send_message_type(12, user, cmd->parent->bot, "The srvx Development Team includes Paul Chang, Adrian Dewhurst, Miles Peterson, Michael Poole and others.\nThe srvx Development Team can be reached at http://sf.net/projects/srvx/ or in #srvx on irc.gamesurge.net.");
+        send_message_type(12, user, cmd->parent->bot, "The srvx Development Team includes Paul Chang, Adrian Dewhurst, Miles Peterson, Michael Poole and others.\nThe srvx Development Team can be reached at http://sf.net/projects/srvx/ or in #srvx on irc.gamesurge.net.\nThis version has been modified by pk910 - visit #srvx @ irc.webgamesnet.net.");
     return 1;
 }
 
@@ -2418,6 +2418,8 @@ create_default_binds(void) {
             struct svccmd *svccmd;
             svccmd = service_make_alias(service, "stats", "*modcmd.joiner", NULL);
             svccmd->min_opserv_level = 101;
+            svccmd = service_make_alias(service, "devnull", "*modcmd.joiner", NULL);
+            svccmd->min_opserv_level = 200;
             svccmd = service_make_alias(service, "service", "*modcmd.joiner", NULL);
             svccmd->min_opserv_level = 900;
         }
index f8941033ec820bfa388e0035707808e01ecdfc57..581d9b47d57ca0c881d6e6d3dca5af0eda1edcce 100644 (file)
@@ -75,6 +75,7 @@
 #define KEY_OUNREGISTER_INACTIVE "ounregister_inactive"
 #define KEY_OUNREGISTER_FLAGS "ounregister_flags"
 #define KEY_HANDLE_TS_MODE "account_timestamp_mode"
+#define KEY_MAX_AUTHLOG_LEN "max_authlog_len"
 
 #define KEY_ID "id"
 #define KEY_PASSWD "passwd"
@@ -85,6 +86,8 @@
 #define KEY_REGISTER_ON "register"
 #define KEY_LAST_SEEN "lastseen"
 #define KEY_INFO "info"
+#define KEY_DEVNULL "devnull"
+#define KEY_WEBSITE "website"
 #define KEY_USERLIST_STYLE "user_style"
 #define KEY_SCREEN_WIDTH "screen_width"
 #define KEY_LAST_AUTHED_HOST "last_authed_host"
 #define KEY_NOTE_SETTER "setter"
 #define KEY_NOTE_NOTE "note"
 #define KEY_KARMA "karma"
+#define KEY_AUTHLOG "authlog"
+#define KEY_AUTHLOG_LOGIN_TIME "login_time"
+#define KEY_AUTHLOG_LOGOUT_TIME "logout_time"
+#define KEY_AUTHLOG_HOSTMASK "hostmask"
+#define KEY_AUTHLOG_QUIT_REASON "quit_reason"
 
 #define NICKSERV_VALID_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"
 
@@ -212,6 +220,9 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_HANDLEINFO_COOKIE_ALLOWAUTH", "  Cookie: There is currently an allowauth cookie issued for this account" },
     { "NSMSG_HANDLEINFO_COOKIE_UNKNOWN", "  Cookie: There is currently an unknown cookie issued for this account" },
     { "NSMSG_HANDLEINFO_INFOLINE", "  Infoline: %s" },
+    { "NSMSG_HANDLEINFO_DEVNULL", "  DevNull Class: %s" },
+    { "NSMSG_HANDLEINFO_WEBSITE", "  Website: %s" },
+    { "NSMSG_HANDLEINFO_ACCESS", "  Access: %i" },
     { "NSMSG_HANDLEINFO_FLAGS", "  Flags: %s" },
     { "NSMSG_HANDLEINFO_EPITHET", "  Epithet: %s" },
     { "NSMSG_HANDLEINFO_FAKEIDENT", "  Fake ident: %s" },
@@ -305,6 +316,9 @@ static const struct message_entry msgtab[] = {
     { "NSMSG_SETTING_LIST", "$b$N account settings:$b" },
     { "NSMSG_INVALID_OPTION", "$b%s$b is an invalid account setting." },
     { "NSMSG_SET_INFO", "$bINFO:         $b%s" },
+    { "NSMSG_SET_DEVNULL", "$bDEVNULL:      $b%s" },
+    { "NSMSG_SET_AUTOHIDE", "$bAUTOHIDE:     $b%s" },
+    { "NSMSG_SET_WEBSITE", "$bWEBSITE:      $b%s" },
     { "NSMSG_SET_WIDTH", "$bWIDTH:        $b%d" },
     { "NSMSG_SET_TABLEWIDTH", "$bTABLEWIDTH:   $b%d" },
     { "NSMSG_SET_COLOR", "$bCOLOR:        $b%s" },
@@ -399,9 +413,18 @@ static struct {
     unsigned char hard_maxlogins;
     unsigned long ounregister_inactive;
     unsigned long ounregister_flags;
+    unsigned int max_authlog_len;
 } nickserv_conf;
 
+struct pendingLOCUser {
+    struct handle_info *handle_info;
+    unsigned long time;
+    struct authlogEntry *authlog;
+    struct pendingLOCUser *next;
+};
+
 const char *titlehost_suffix = NULL;
+static struct pendingLOCUser *pendingLOCUsers = NULL;
 
 /* We have 2^32 unique account IDs to use. */
 unsigned long int highest_id = 0;
@@ -463,6 +486,8 @@ register_handle(const char *handle, const char *passwd, unsigned long id)
     dict_insert(nickserv_handle_dict, hi->handle, hi);
 
     hi->id = id;
+    hi->website = NULL;
+    hi->devnull = NULL;
     dict_insert(nickserv_id_dict, strdup(id_base64), hi);
 
     return hi;
@@ -549,6 +574,8 @@ free_handle_info(void *vhi)
     free(hi->infoline);
     free(hi->epithet);
     free(hi->fakehost);
+    free(hi->devnull);
+    free(hi->website);
     free(hi->fakeident);
     if (hi->cookie) {
         timeq_del(hi->cookie->expires, nickserv_free_cookie, hi->cookie, 0);
@@ -565,6 +592,26 @@ free_handle_info(void *vhi)
         if (!hil->used)
             dict_remove(nickserv_email_dict, hi->email_addr);
     }
+    struct authlogEntry *authlog, *next;
+    for(authlog = hi->authlog; authlog; authlog = next) {
+        next = authlog->next;
+        struct pendingLOCUser *pending, *prev_pending = NULL;
+        for(pending = pendingLOCUsers; pending; pending = pending->next) {
+            if(pending->authlog == authlog) {
+                if(prev_pending)
+                    prev_pending->next = pending->next;
+                else
+                    pendingLOCUsers = pending->next;
+                free(pending);
+                break;
+            }
+            prev_pending = pending;
+        }
+        free((char *) authlog->hostmask);
+        if(authlog->quit_reason)
+            free((char *) authlog->quit_reason);
+        free(authlog);
+    }
     free(hi);
 }
 
@@ -697,9 +744,51 @@ is_registerable_nick(const char *nick)
 }
 
 static int
-is_valid_email_addr(const char *email)
+is_valid_email_addr(const char *org_email)
 {
-    return strchr(email, '@') != NULL;
+    char email[strlen(org_email)+1];
+    strcpy(email, org_email);
+    //validate email address
+    //1st check: there need to be one @
+    char *p1 = strchr(email, '@');
+    if(!p1 || strchr(p1+1, '@')) return 0;
+    *p1 = '\0';
+    //2nd check: username (bevore @) must be at least 1 char long and out of part_chars
+    char *part_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._%+-";
+    int i;
+    if(p1 - email == 0) return 0;
+    for(i = 0; i < (p1 - email); i++) {
+        if(!strchr(part_chars, email[i])) return 0;
+    }
+    //3rd check: there need to be at least 1 dot in the domain part and all characters out of part_chars
+    part_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-";
+    char *p2 = NULL;
+    p1++;
+    i = 0;
+    while(*p1) {
+        if(*p1 == '.') {
+            if(!i) return 0;
+            i = 0;
+            p2 = p1;
+        } else if(!strchr(part_chars, *p1))
+            return 0;
+        else
+            i++;
+        p1++;
+    }
+    if(!p2) return 0;
+    //4th check: TLD must be <= 5 chars, no special chars
+    i = 0;
+    p2++;
+    while(*p2) {
+        if(!isalpha(*p2))
+            return 0;
+        else
+            i++;
+        p2++;
+    }
+    if(i > 5) return 0;
+    return 1;
 }
 
 static const char *
@@ -785,7 +874,7 @@ valid_user_for(struct userNode *user, struct handle_info *hi)
     unsigned int ii;
 
     /* If no hostmasks on the account, allow it. */
-    if (!hi->masks->used)
+    if (!hi->masks->used || IsDummy(user))
         return 1;
     /* If any hostmask matches, allow it. */
     for (ii=0; ii<hi->masks->used; ii++)
@@ -891,6 +980,10 @@ generate_fakehost(struct handle_info *handle)
         /* A leading dot indicates the stored value is actually a title. */
         snprintf(buffer, sizeof(buffer), "%s.%s.%s", handle->handle, handle->fakehost+1, titlehost_suffix);
         return buffer;
+    } else if (handle->fakehost[0] == '$') {
+        /* A leading $ indicates the stored value begins with the user handle. */
+        snprintf(buffer, sizeof(buffer), "%s%s", handle->handle, handle->fakehost+1);
+        return buffer;
     }
     return handle->fakehost;
 }
@@ -1395,6 +1488,12 @@ static NICKSERV_FUNC(cmd_handleinfo)
     }
 
     reply("NSMSG_HANDLEINFO_INFOLINE", (hi->infoline ? hi->infoline : nsmsg_none));
+    if ((oper_has_access(user, cmd->parent->bot, 200, 1)) || IsNetworkHelper(user))
+        reply("NSMSG_HANDLEINFO_DEVNULL", (hi->devnull ? hi->devnull : nsmsg_none));
+    if (user->handle_info && HANDLE_FLAGGED(user->handle_info, BOT))
+        reply("NSMSG_HANDLEINFO_WEBSITE", (hi->website ? hi->website : nsmsg_none));
+    if(hi->opserv_level > 0 && user->handle_info && HANDLE_FLAGGED(user->handle_info, BOT))
+        reply("NSMSG_HANDLEINFO_ACCESS", hi->opserv_level);
     if (HANDLE_FLAGGED(hi, FROZEN))
         reply("NSMSG_HANDLEINFO_VACATION");
 
@@ -1659,6 +1758,7 @@ static NICKSERV_FUNC(cmd_rename_handle)
     reply("NSMSG_HANDLE_CHANGED", old_handle, hi->handle);
     global_message(MESSAGE_RECIPIENT_STAFF, msgbuf);
     free(old_handle);
+    apply_fakehost(hi, NULL);
     return 1;
 }
 
@@ -1680,6 +1780,50 @@ reg_failpw_func(failpw_func_t func)
     failpw_func_list[failpw_func_used++] = func;
 }
 
+static struct authlogEntry *authlog_add(struct handle_info *hi, struct userNode *user, const char *mask) {
+    if(!hi || (!user && !mask)) return NULL;
+    if(!mask)
+        mask = generate_hostmask(user, GENMASK_USENICK|GENMASK_STRICT_IDENT|GENMASK_NO_HIDING|GENMASK_STRICT_HOST);
+    struct authlogEntry *authlog, *next, *prev = NULL;
+    authlog = malloc(sizeof(*authlog));
+    authlog->login_time = now;
+    authlog->logout_time = 0;
+    authlog->hostmask = mask;
+    authlog->quit_reason = NULL;
+    authlog->user = user;
+    authlog->next = hi->authlog;
+    hi->authlog = authlog;
+    unsigned int i = 0;
+    for(authlog = hi->authlog; authlog; authlog = next) {
+        i++;
+        next = authlog->next;
+        if(i > nickserv_conf.max_authlog_len) {
+            struct pendingLOCUser *pending, *prev_pending = NULL;
+            for(pending = pendingLOCUsers; pending; pending = pending->next) {
+                if(pending->authlog == authlog) {
+                    if(prev_pending)
+                        prev_pending->next = pending->next;
+                    else
+                        pendingLOCUsers = pending->next;
+                    free(pending);
+                    break;
+                }
+                prev_pending = pending;
+            }
+            free((char *) authlog->hostmask);
+            if(authlog->quit_reason)
+                free((char *) authlog->quit_reason);
+            if(prev)
+                prev->next = authlog->next;
+            else
+                hi->authlog = authlog->next;
+            free(authlog);
+        } else
+            prev = authlog;
+    }
+    return hi->authlog;
+}
+
 static NICKSERV_FUNC(cmd_auth)
 {
     int pw_arg, used, maxlogins;
@@ -1779,6 +1923,10 @@ static NICKSERV_FUNC(cmd_auth)
             return 1;
         }
     }
+    if (HANDLE_FLAGGED(hi, AUTOHIDE)) {
+        //ok  we have a fakehost set... but we need to set mode +x
+        irc_svsmode(nickserv,user,"+x");
+    }
 
     set_user_handle_info(user, hi, 1);
     if (nickserv_conf.email_required && !hi->email_addr)
@@ -1793,11 +1941,57 @@ static NICKSERV_FUNC(cmd_auth)
         if (irc_in_addr_is_valid(user->ip) && irc_pton(&ip, NULL, user->hostname))
             string_list_append(hi->masks, generate_hostmask(user, GENMASK_OMITNICK|GENMASK_BYIP|GENMASK_NO_HIDING|GENMASK_ANY_IDENT));
     }
+    authlog_add(hi, user, NULL);
     argv[pw_arg] = "****";
     reply("NSMSG_AUTH_SUCCESS");
     return 1;
 }
 
+struct handle_info *checklogin(const char *user, const char *pass, const char *numeric, const char *hostmask, const char *ipmask)
+{
+    struct handle_info *hi;
+    unsigned int match = 0, ii = 0;
+    hi = dict_find(nickserv_handle_dict, user, NULL);
+    if(!hi)
+        return NULL;
+    /* If no hostmasks on the account, allow it. */
+    if (hi->masks->used) {
+        /* If any hostmask matches, allow it. */
+        for (ii=0; ii<hi->masks->used; ii++)
+          if (match_ircglob(hostmask, hi->masks->list[ii]) || match_ircglob(ipmask, hi->masks->list[ii])) {
+            match = 1;
+            break;
+          }
+        if(!match) 
+          return NULL;
+    }
+    if(!checkpass(pass, hi->passwd))
+        return NULL;
+    if (HANDLE_FLAGGED(hi, SUSPENDED))
+        return NULL;
+    char *ptr = malloc(strlen(hostmask)+1);
+    strcpy(ptr, hostmask);
+    struct authlogEntry *authlog = authlog_add(hi, NULL, ptr);
+    struct pendingLOCUser *pending;
+    if(authlog && (pending = malloc(sizeof(*pending)))) {
+        pending->handle_info = hi;
+        pending->time = now;
+        pending->authlog = authlog;
+        pending->next = pendingLOCUsers;
+        pendingLOCUsers = pending;
+    }
+    return hi;
+}
+
+char *getfakehost(const char *user)
+{
+    struct handle_info *hi;
+    hi = dict_find(nickserv_handle_dict, user, NULL);
+    if(!hi)
+        return 0;
+    return generate_fakehost(hi);
+}
+
 static allowauth_func_t *allowauth_func_list;
 static unsigned int allowauth_func_size = 0, allowauth_func_used = 0;
 
@@ -1816,6 +2010,89 @@ reg_allowauth_func(allowauth_func_t func)
     allowauth_func_list[allowauth_func_used++] = func;
 }
 
+static int cmd_authlog_func(struct userNode *user, struct svccmd *cmd, struct handle_info *hi);
+
+static MODCMD_FUNC(cmd_authlog)
+{
+    return cmd_authlog_func(user, cmd, user->handle_info);
+}
+
+static MODCMD_FUNC(cmd_oauthlog) {
+    struct handle_info *hi;
+    
+    NICKSERV_MIN_PARMS(1);
+    
+    if (!(hi = get_victim_oper(user, argv[1])))
+        return 0;
+    
+    return cmd_authlog_func(user, cmd, hi);
+}
+
+static int cmd_authlog_func(struct userNode *user, struct svccmd *cmd, struct handle_info *hi) {
+    struct helpfile_table tbl;
+    struct authlogEntry *authlog;
+    int i = 0;
+    
+    for(authlog = hi->authlog; authlog; authlog = authlog->next) {
+        i++;
+    }
+    
+    tbl.length = i+1;
+    tbl.width = 4;
+    tbl.flags = 0;
+    tbl.flags = TABLE_NO_FREE;
+    tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
+    tbl.contents[0] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
+    tbl.contents[0][0] = "Hostmask";
+    tbl.contents[0][1] = "Login";
+    tbl.contents[0][2] = "Logout";
+    tbl.contents[0][3] = "Quit Reason";
+    
+    if(!tbl.length) {
+        table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
+        reply("MSG_NONE");
+        free(tbl.contents[0]);
+        free(tbl.contents);
+        return 0;
+    }
+    
+    char *str, *ptr;
+    char intervalBuf[INTERVALLEN];
+    i = 0;
+    for(authlog = hi->authlog; authlog; authlog = authlog->next) {
+        tbl.contents[++i] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
+        tbl.contents[i][0] = authlog->hostmask;
+        str = intervalString(intervalBuf, now - authlog->login_time, hi);
+        ptr = malloc(strlen(str)+1);
+        strcpy(ptr, str);
+        tbl.contents[i][1] = ptr;
+        if(authlog->logout_time)
+            str = intervalString(intervalBuf, now - authlog->logout_time, hi);
+        else if(!authlog->user)
+            str = "Unknown";
+        else {
+            sprintf(intervalBuf, "Never (%s)", authlog->user->nick);
+            str = intervalBuf;
+        }
+        ptr = malloc(strlen(str)+1);
+        strcpy(ptr, str);
+        tbl.contents[i][2] = ptr;
+        tbl.contents[i][3] = (authlog->quit_reason ? authlog->quit_reason : "-");
+    }
+    
+    table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
+    for(i = 1; i < tbl.length; ++i)
+    {
+        free((char *) tbl.contents[i][1]);
+        free((char *) tbl.contents[i][2]);
+        free(tbl.contents[i]);
+    }
+    free(tbl.contents[0]);
+    free(tbl.contents);
+    
+    return 0;
+}
+
 static NICKSERV_FUNC(cmd_allowauth)
 {
     struct userNode *target;
@@ -2271,7 +2548,7 @@ set_list(struct userNode *user, struct handle_info *hi, int override)
     unsigned int i;
     char *set_display[] = {
         "INFO", "WIDTH", "TABLEWIDTH", "COLOR", "PRIVMSG", "STYLE",
-        "EMAIL", "MAXLOGINS", "LANGUAGE"
+        "EMAIL", "MAXLOGINS", "LANGUAGE", "DEVNULL"
     };
 
     send_message(user, nickserv, "NSMSG_SETTING_LIST");
@@ -2346,6 +2623,82 @@ static OPTION_FUNC(opt_info)
     return 1;
 }
 
+static OPTION_FUNC(opt_devnull)
+{
+    const char *devnull;
+    
+    if (argc > 1) {
+        if (!override) {
+            send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
+            return 0;
+        }
+        if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
+            free(hi->devnull);
+            hi->devnull = NULL;
+        } else {
+            devnull = unsplit_string(argv+1, argc-1, NULL);
+            if(devnull_check(devnull) == 1) { 
+                if(hi->devnull)
+                    free(hi->devnull);
+                hi->devnull = strdup(devnull);
+            }
+        }
+    }
+
+    devnull = hi->devnull ? hi->devnull : user_find_message(user, "MSG_NONE");
+    send_message(user, nickserv, "NSMSG_SET_DEVNULL", devnull);
+    return 1;
+}
+
+void nickserv_devnull_delete(char *name) {
+    dict_iterator_t it;
+    struct handle_info *hi;
+
+    for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
+        hi = iter_data(it);
+        if (hi->devnull && !irccasecmp(name, hi->devnull)) {
+            free(hi->devnull);
+            hi->devnull = NULL;
+        }
+    }
+}
+
+void nickserv_devnull_rename(char *oldname, char *newname) {
+    dict_iterator_t it;
+    struct handle_info *hi;
+
+    for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
+        hi = iter_data(it);
+        if (hi->devnull && !irccasecmp(oldname, hi->devnull)) {
+            hi->devnull = strdup(newname);
+        }
+    }
+}
+
+static OPTION_FUNC(opt_website)
+{
+    const char *website;
+    
+    if (argc > 1) {
+        if (!HANDLE_FLAGGED(user->handle_info, BOT)) {
+            send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
+            return 0;
+        }
+        if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
+            free(hi->website);
+            hi->website = NULL;
+        } else {
+            website = unsplit_string(argv+1, argc-1, NULL);
+            hi->website = strdup(website);
+        }
+    }
+    if (HANDLE_FLAGGED(user->handle_info, BOT)) {
+        website = hi->website ? hi->website : user_find_message(user, "MSG_NONE");
+        send_message(user, nickserv, "NSMSG_SET_WEBSITE", website);
+    }
+    return 1;
+}
+
 static OPTION_FUNC(opt_width)
 {
     if (argc > 1)
@@ -2408,6 +2761,23 @@ static OPTION_FUNC(opt_privmsg)
     return 1;
 }
 
+static OPTION_FUNC(opt_autohide)
+{
+    if (argc > 1) {
+        if (enabled_string(argv[1]))
+            HANDLE_SET_FLAG(hi, AUTOHIDE);
+        else if (disabled_string(argv[1]))
+            HANDLE_CLEAR_FLAG(hi, AUTOHIDE);
+        else {
+            send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
+            return 0;
+        }
+    }
+
+    send_message(user, nickserv, "NSMSG_SET_AUTOHIDE", user_find_message(user, HANDLE_FLAGGED(hi, AUTOHIDE) ? "MSG_ON" : "MSG_OFF"));
+    return 1;
+}
+
 static OPTION_FUNC(opt_style)
 {
     char *style;
@@ -2443,6 +2813,8 @@ static OPTION_FUNC(opt_password)
         cryptpass(argv[1], hi->passwd);
 
     send_message(user, nickserv, "NSMSG_SET_PASSWORD", "***");
+    argv[1] = "****";
+    
     return 1;
 }
 
@@ -3072,6 +3444,10 @@ nickserv_saxdb_write(struct saxdb_context *ctx) {
         saxdb_write_int(ctx, KEY_ID, hi->id);
         if (hi->infoline)
             saxdb_write_string(ctx, KEY_INFO, hi->infoline);
+        if (hi->devnull)
+            saxdb_write_string(ctx, KEY_DEVNULL, hi->devnull);
+        if (hi->website)
+            saxdb_write_string(ctx, KEY_WEBSITE, hi->website);
         if (hi->last_quit_host[0])
             saxdb_write_string(ctx, KEY_LAST_QUIT_HOST, hi->last_quit_host);
         saxdb_write_int(ctx, KEY_LAST_SEEN, hi->lastseen);
@@ -3104,6 +3480,21 @@ nickserv_saxdb_write(struct saxdb_context *ctx) {
         flags[0] = hi->userlist_style;
         flags[1] = 0;
         saxdb_write_string(ctx, KEY_USERLIST_STYLE, flags);
+        if(hi->authlog) {
+            saxdb_start_record(ctx, KEY_AUTHLOG, 0);
+            struct authlogEntry *authlog;
+            int i = 0;
+            for(authlog = hi->authlog; authlog; authlog = authlog->next) {
+                saxdb_start_record(ctx, strtab(++i), 0);
+                saxdb_write_int(ctx, KEY_AUTHLOG_LOGIN_TIME, authlog->login_time);
+                saxdb_write_int(ctx, KEY_AUTHLOG_LOGOUT_TIME, authlog->logout_time);
+                saxdb_write_string(ctx, KEY_AUTHLOG_HOSTMASK, authlog->hostmask);
+                if(authlog->quit_reason)
+                    saxdb_write_string(ctx, KEY_AUTHLOG_QUIT_REASON, authlog->quit_reason);
+                saxdb_end_record(ctx);
+            }
+            saxdb_end_record(ctx); //END KEY_AUTHLOG
+        }
         saxdb_end_record(ctx);
     }
     return 0;
@@ -3249,7 +3640,22 @@ static NICKSERV_FUNC(cmd_merge)
     return 1;
 }
 
+#define NICKSERV_DISCRIM_FIELDS_AUTH     0x01
+#define NICKSERV_DISCRIM_FIELDS_EMAIL    0x02
+#define NICKSERV_DISCRIM_FIELDS_SEEN     0x04
+#define NICKSERV_DISCRIM_FIELDS_ACCESS   0x08
+#define NICKSERV_DISCRIM_FIELDS_FAKEHOST 0x10
+#define NICKSERV_DISCRIM_FIELDS_WEBSITE  0x20
+#define NICKSERV_DISCRIM_FIELDS_DEVNULL  0x40
+
+#define NICKSERV_DISCRIM_FIELD_COUNT     7
+
 struct nickserv_discrim {
+    unsigned int show_fields;
+    struct helpfile_table *output_table;
+    int output_table_pos;
+    unsigned int output_table_free_fields;
+    
     unsigned long flags_on, flags_off;
     unsigned long min_registered, max_registered;
     unsigned long lastseen;
@@ -3261,11 +3667,13 @@ struct nickserv_discrim {
     const char *hostmask;
     const char *fakehostmask;
     const char *fakeidentmask;
+    const char *website;
+    const char *devnullclass;
     const char *handlemask;
     const char *emailmask;
 };
 
-typedef void (*discrim_search_func)(struct userNode *source, struct handle_info *hi);
+typedef void (*discrim_search_func)(struct userNode *source, struct handle_info *hi, struct nickserv_discrim *discrim);
 
 struct discrim_apply_info {
     struct nickserv_discrim *discrim;
@@ -3300,6 +3708,40 @@ nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[])
             discrim->limit = strtoul(argv[++i], NULL, 0);
         } else if (!irccasecmp(argv[i], "flags")) {
             nickserv_modify_handle_flags(user, nickserv, argv[++i], &discrim->flags_on, &discrim->flags_off);
+        } else if (!irccasecmp(argv[i], "fields")) {
+            char *fields = argv[++i];
+            char *delimiter = strstr(fields, ",");
+            while(1) {
+                if(delimiter)
+                    *delimiter = '\0';
+                if(!irccasecmp(fields, "auth"))
+                    discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_AUTH;
+                else if(!irccasecmp(fields, "email"))
+                    discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_EMAIL;
+                else if(!irccasecmp(fields, "seen"))
+                    discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_SEEN;
+                else if(!irccasecmp(fields, "access"))
+                    discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_ACCESS;
+                else if(!irccasecmp(fields, "fakehost"))
+                    discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_FAKEHOST;
+                else if(!irccasecmp(fields, "website") && IsBot(user))
+                    discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_WEBSITE;
+                else if(!irccasecmp(fields, "devnull"))
+                    discrim->show_fields |= NICKSERV_DISCRIM_FIELDS_DEVNULL;
+                else {
+                    send_message(user, nickserv, "MSG_INVALID_FIELD", fields);
+                    goto fail;
+                }
+                if(delimiter) {
+                    *delimiter = ',';
+                    fields = delimiter+1;
+                    if(*fields) {
+                        delimiter = strstr(fields, ",");
+                        continue;
+                    }
+                }
+                break;
+            }
         } else if (!irccasecmp(argv[i], "registered")) {
             const char *cmp = argv[++i];
             if (cmp[0] == '<') {
@@ -3366,6 +3808,18 @@ nickserv_discrim_create(struct userNode *user, unsigned int argc, char *argv[])
             } else {
                 discrim->fakeidentmask = argv[i];
             }
+        } else if (!irccasecmp(argv[i], "website")) {
+            if (!irccasecmp(argv[++i], "*")) {
+                discrim->website = 0;
+            } else {
+                discrim->website = argv[i];
+            }
+        } else if (!irccasecmp(argv[i], "devnull")) {
+            if (!irccasecmp(argv[++i], "*")) {
+                discrim->devnullclass = 0;
+            } else {
+                discrim->devnullclass = argv[i];
+            }
         } else if (!irccasecmp(argv[i], "handlemask") || !irccasecmp(argv[i], "accountmask")) {
             if (!irccasecmp(argv[++i], "*")) {
                 discrim->handlemask = 0;
@@ -3442,6 +3896,8 @@ nickserv_discrim_match(struct nickserv_discrim *discrim, struct handle_info *hi)
         || (discrim->handlemask && !match_ircglob(hi->handle, discrim->handlemask))
         || (discrim->fakehostmask && (!hi->fakehost || !match_ircglob(hi->fakehost, discrim->fakehostmask)))
         || (discrim->fakeidentmask && (!hi->fakeident || !match_ircglob(hi->fakeident, discrim->fakeidentmask)))
+        || (discrim->website && (!hi->website || !match_ircglob(hi->website, discrim->website)))
+        || (discrim->devnullclass && (!hi->devnull || !match_ircglob(hi->devnull, discrim->devnullclass)))
         || (discrim->emailmask && (!hi->email_addr || !match_ircglob(hi->email_addr, discrim->emailmask)))
         || (discrim->min_level > hi->opserv_level)
         || (discrim->max_level < hi->opserv_level)
@@ -3487,7 +3943,7 @@ nickserv_discrim_search(struct nickserv_discrim *discrim, discrim_search_func ds
          it = next) {
         next = iter_next(it);
         if (nickserv_discrim_match(discrim, iter_data(it))) {
-            dsf(source, iter_data(it));
+            dsf(source, iter_data(it), discrim);
             matched++;
         }
     }
@@ -3495,18 +3951,51 @@ nickserv_discrim_search(struct nickserv_discrim *discrim, discrim_search_func ds
 }
 
 static void
-search_print_func(struct userNode *source, struct handle_info *match)
+search_print_func(struct userNode *source, struct handle_info *match, struct nickserv_discrim *discrim)
 {
-    send_message(source, nickserv, "NSMSG_SEARCH_MATCH", match->handle);
+    if(discrim->show_fields) {
+        //custom fields
+        if(discrim->output_table) {
+            discrim->output_table->contents[++discrim->output_table_pos] = malloc(discrim->output_table->width * sizeof(discrim->output_table->contents[0][0]));
+            int i = 0;
+            if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_AUTH)
+                discrim->output_table->contents[discrim->output_table_pos][i++] = match->handle;
+            if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_EMAIL)
+                discrim->output_table->contents[discrim->output_table_pos][i++] = (match->email_addr ? match->email_addr : "*");
+            if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_SEEN) {
+                char *seen;
+                char seenBuf[INTERVALLEN];
+                if(match->users) {
+                    seen = "Here";
+                } else if(match->lastseen == 0) {
+                    seen = "Never";
+                } else {
+                    seen = intervalString(seenBuf, now - match->lastseen, source->handle_info);
+                }
+                discrim->output_table_free_fields |= 1 << i;
+                discrim->output_table->contents[discrim->output_table_pos][i++] = strdup(seen);
+            }
+            if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_ACCESS)
+                discrim->output_table->contents[discrim->output_table_pos][i++] = strtab(match->opserv_level);
+            if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_FAKEHOST)
+                discrim->output_table->contents[discrim->output_table_pos][i++] = (match->fakehost ? match->fakehost : "*");
+            if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_WEBSITE)
+                discrim->output_table->contents[discrim->output_table_pos][i++] = (match->website ? match->website : "*");
+            if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_DEVNULL)
+                discrim->output_table->contents[discrim->output_table_pos][i++] = (match->devnull ? match->devnull : "*");
+            
+        }
+    } else
+        send_message(source, nickserv, "NSMSG_SEARCH_MATCH", match->handle);
 }
 
 static void
-search_count_func(UNUSED_ARG(struct userNode *source), UNUSED_ARG(struct handle_info *match))
+search_count_func(UNUSED_ARG(struct userNode *source), UNUSED_ARG(struct handle_info *match), UNUSED_ARG(struct nickserv_discrim *discrim))
 {
 }
 
 static void
-search_unregister_func (struct userNode *source, struct handle_info *match)
+search_unregister_func (struct userNode *source, struct handle_info *match, UNUSED_ARG(struct nickserv_discrim *discrim))
 {
     if (oper_has_access(source, nickserv, match->opserv_level, 0))
         nickserv_unregister_handle(match, source);
@@ -3594,12 +4083,58 @@ static NICKSERV_FUNC(cmd_search)
         discrim->limit = INT_MAX;
 
     matches = nickserv_discrim_search(discrim, action, user);
-
+    
+    if(discrim->show_fields) {
+        int width = 0;
+        int ii;
+        for(ii = 0; ii < NICKSERV_DISCRIM_FIELD_COUNT; ii++) {
+            if(discrim->show_fields & (1 << ii)) width++;
+        }
+        discrim->output_table = malloc(sizeof(discrim->output_table[0]));
+        discrim->output_table->length = matches+1;
+        discrim->output_table->width = width;
+        discrim->output_table->flags = TABLE_NO_FREE;
+        discrim->output_table->contents = malloc(discrim->output_table->length * sizeof(discrim->output_table->contents[0]));
+        discrim->output_table->contents[0] = malloc(discrim->output_table->width * sizeof(discrim->output_table->contents[0][0]));
+        
+        ii = 0;
+        if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_AUTH)
+            discrim->output_table->contents[0][ii++] = "Auth";
+        if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_EMAIL)
+            discrim->output_table->contents[0][ii++] = "EMail";
+        if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_SEEN)
+            discrim->output_table->contents[0][ii++] = "Seen";
+        if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_ACCESS)
+            discrim->output_table->contents[0][ii++] = "Access";
+        if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_FAKEHOST)
+            discrim->output_table->contents[0][ii++] = "Fakehost";
+        if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_WEBSITE)
+            discrim->output_table->contents[0][ii++] = "Website";
+        if(discrim->show_fields & NICKSERV_DISCRIM_FIELDS_DEVNULL)
+            discrim->output_table->contents[0][ii++] = "DevNull";
+        
+        nickserv_discrim_search(discrim, action, user);
+        
+        table_send(nickserv, user->nick, 0, NULL, *discrim->output_table);
+        
+        for(ii = 1; ii < discrim->output_table->length; ++ii) {
+            int ij;
+            for(ij = 0; ij < NICKSERV_DISCRIM_FIELD_COUNT; ij++) {
+                if(discrim->output_table_free_fields & (1 << ij))
+                    free((char*)discrim->output_table->contents[ii][ij]);
+            }
+            free(discrim->output_table->contents[ii]);
+        }
+        free(discrim->output_table->contents[0]);
+        free(discrim->output_table->contents);
+        free(discrim->output_table);
+    }
     if (matches)
         reply("MSG_MATCH_COUNT", matches);
     else
         reply("MSG_NO_MATCHES");
 
+    
     free(discrim);
     return 0;
 }
@@ -3638,6 +4173,45 @@ static MODCMD_FUNC(cmd_checkemail)
     return 1;
 }
 
+static int
+nickserv_db_read_authlog(UNUSED_ARG(const char *key), void *data, void *extra)
+{
+    struct record_data *rd = data;
+    struct handle_info *hi = extra;
+    const char *str;
+    struct authlogEntry *authlog;
+    authlog = malloc(sizeof(*authlog));
+    
+    str = database_get_data(rd->d.object, KEY_AUTHLOG_LOGIN_TIME, RECDB_QSTRING);
+    authlog->login_time = str ? strtoul(str, NULL, 0) : 0;
+    
+    str = database_get_data(rd->d.object, KEY_AUTHLOG_LOGOUT_TIME, RECDB_QSTRING);
+    authlog->logout_time = str ? strtoul(str, NULL, 0) : 0;
+    
+    str = database_get_data(rd->d.object, KEY_AUTHLOG_HOSTMASK, RECDB_QSTRING);
+    authlog->hostmask = str ? strdup(str) : NULL;
+    
+    str = database_get_data(rd->d.object, KEY_AUTHLOG_QUIT_REASON, RECDB_QSTRING);
+    authlog->quit_reason = str ? strdup(str) : NULL;
+    
+    authlog->user = NULL;
+    
+    authlog->next = NULL;
+    
+    //append it to the end of the list...
+    struct authlogEntry *authlog_entry;
+    if(!hi->authlog) {
+        hi->authlog = authlog;
+    } else {
+        for(authlog_entry = hi->authlog; authlog_entry; authlog_entry = authlog_entry->next) {
+            if(!authlog_entry->next) {
+                authlog_entry->next = authlog;
+                break;
+            }
+        }
+    }
+    return 0;
+}
 
 static void
 nickserv_db_read_handle(const char *handle, dict_t obj)
@@ -3688,6 +4262,12 @@ nickserv_db_read_handle(const char *handle, dict_t obj)
     str = database_get_data(obj, KEY_INFO, RECDB_QSTRING);
     if (str)
         hi->infoline = strdup(str);
+    str = database_get_data(obj, KEY_WEBSITE, RECDB_QSTRING);
+    if (str)
+        hi->website = strdup(str);
+    str = database_get_data(obj, KEY_DEVNULL, RECDB_QSTRING);
+    if (str)
+        hi->devnull = strdup(str);
     str = database_get_data(obj, KEY_REGISTER_ON, RECDB_QSTRING);
     hi->registered = str ? strtoul(str, NULL, 0) : now;
     str = database_get_data(obj, KEY_LAST_SEEN, RECDB_QSTRING);
@@ -3813,6 +4393,8 @@ nickserv_db_read_handle(const char *handle, dict_t obj)
             last_note = note;
         }
     }
+    if ((subdb = database_get_data(obj, KEY_AUTHLOG, RECDB_OBJECT)))
+        dict_foreach(subdb, nickserv_db_read_authlog, hi);
 }
 
 static int
@@ -3991,6 +4573,8 @@ nickserv_conf_read(void)
     nickserv_conf.default_maxlogins = str ? strtoul(str, NULL, 0) : 2;
     str = database_get_data(conf_node, "hard_maxlogins", RECDB_QSTRING);
     nickserv_conf.hard_maxlogins = str ? strtoul(str, NULL, 0) : 10;
+    str = database_get_data(conf_node, KEY_MAX_AUTHLOG_LEN, RECDB_QSTRING);
+    nickserv_conf.max_authlog_len = str ? strtoul(str, NULL, 0) : 30;
     str = database_get_data(conf_node, KEY_OUNREGISTER_INACTIVE, RECDB_QSTRING);
     nickserv_conf.ounregister_inactive = str ? ParseInterval(str) : 86400*28;
     str = database_get_data(conf_node, KEY_OUNREGISTER_FLAGS, RECDB_QSTRING);
@@ -4117,6 +4701,30 @@ nickserv_reclaim_p(void *data) {
 
 static void
 check_user_nick(struct userNode *user) {
+    //check if this user is a pending LOC user
+    if(pendingLOCUsers) {
+        struct pendingLOCUser *pending, *next, *prev = NULL;
+        for(pending = pendingLOCUsers; pending; pending = next) {
+            next = pending->next;
+            if(user->handle_info == pending->handle_info) {
+                pending->authlog->user = user;
+                free((char*) pending->authlog->hostmask);
+                pending->authlog->hostmask = generate_hostmask(user, GENMASK_USENICK|GENMASK_STRICT_IDENT|GENMASK_NO_HIDING|GENMASK_STRICT_HOST);
+                if(prev)
+                    prev->next = next;
+                else
+                    pendingLOCUsers = next;
+                free(pending);
+            }
+            if(now - pending->time > 10) {
+                if(prev)
+                    prev->next = next;
+                else
+                    pendingLOCUsers = next;
+                free(pending);
+            }
+        }
+    }
     struct nick_info *ni;
     user->modes &= ~FLAGS_REGNICK;
     if (!(ni = get_nick_info(user->nick)))
@@ -4177,8 +4785,20 @@ handle_nick_change(struct userNode *user, const char *old_nick)
 }
 
 void
-nickserv_remove_user(struct userNode *user, UNUSED_ARG(struct userNode *killer), UNUSED_ARG(const char *why))
+nickserv_remove_user(struct userNode *user, UNUSED_ARG(struct userNode *killer), const char *why)
 {
+    if(user->handle_info) {
+        //check if theres an open authlog entry
+        struct authlogEntry *authlog;
+        for(authlog = user->handle_info->authlog; authlog; authlog = authlog->next) {
+            if(authlog->user == user) {
+                authlog->user = NULL;
+                authlog->logout_time = now;
+                authlog->quit_reason = strdup(why);
+                break;
+            }
+        }
+    }
     dict_remove(nickserv_allow_auth_dict, user->nick);
     timeq_del(0, nickserv_reclaim_p, user, TIMEQ_IGNORE_WHEN);
     set_user_handle_info(user, NULL, 0);
@@ -4233,6 +4853,12 @@ nickserv_db_cleanup(void)
         regfree(&nickserv_conf.valid_handle_regex);
     if (nickserv_conf.valid_nick_regex_set)
         regfree(&nickserv_conf.valid_nick_regex);
+    struct pendingLOCUser *pending, *next;
+    for(pending = pendingLOCUsers; pending; pending = next) {
+        next = pending->next;
+        free(pending);
+    }
+    pendingLOCUsers = NULL;
 }
 
 void
@@ -4305,6 +4931,8 @@ init_nickserv(const char *nick)
     nickserv_define_func("MERGEDB", cmd_mergedb, 999, 1, 0);
     nickserv_define_func("CHECKPASS", cmd_checkpass, 601, 1, 0);
     nickserv_define_func("CHECKEMAIL", cmd_checkemail, 0, 1, 0);
+    nickserv_define_func("AUTHLOG", cmd_authlog, -1, 1, 0);
+    nickserv_define_func("OAUTHLOG", cmd_oauthlog, 0, 1, 0);
     /* other options */
     dict_insert(nickserv_opt_dict, "INFO", opt_info);
     dict_insert(nickserv_opt_dict, "WIDTH", opt_width);
@@ -4312,9 +4940,12 @@ init_nickserv(const char *nick)
     dict_insert(nickserv_opt_dict, "COLOR", opt_color);
     dict_insert(nickserv_opt_dict, "PRIVMSG", opt_privmsg);
     dict_insert(nickserv_opt_dict, "STYLE", opt_style);
+    dict_insert(nickserv_opt_dict, "AUTOHIDE", opt_autohide);
     dict_insert(nickserv_opt_dict, "PASS", opt_password);
     dict_insert(nickserv_opt_dict, "PASSWORD", opt_password);
     dict_insert(nickserv_opt_dict, "FLAGS", opt_flags);
+    dict_insert(nickserv_opt_dict, "WEBSITE", opt_website);
+    dict_insert(nickserv_opt_dict, "DEVNULL", opt_devnull);
     dict_insert(nickserv_opt_dict, "ACCESS", opt_level);
     dict_insert(nickserv_opt_dict, "LEVEL", opt_level);
     dict_insert(nickserv_opt_dict, "EPITHET", opt_epithet);
index 6d6bcd5eedc012daca005b5e4d13347509c20d0f..ee0db3b32d5c46d45c36243469a1cf00a2cc3881 100644 (file)
@@ -38,8 +38,11 @@ struct svccmd;
 #define HI_FLAG_NODELETE       0x00000080
 #define HI_FLAG_NETWORK_HELPER 0x00000100
 #define HI_FLAG_BOT            0x00000200
+#define HI_FLAG_AUTOHIDE       0x00000400
+#define HI_FLAG_INVI           0x00000800
+
 /* Flag characters for the above.  First char is LSB, etc. */
-#define HANDLE_FLAGS "SphgscfnHb"
+#define HANDLE_FLAGS "SphgscfnHbxI"
 
 /* HI_STYLE_* go into handle_info.userlist_style */
 #define HI_STYLE_DEF    'd'
@@ -59,6 +62,7 @@ struct svccmd;
 #define IsHelping(user) (user->handle_info && HANDLE_FLAGGED(user->handle_info, HELPING))
 #define IsStaff(user) (IsOper(user) || IsSupportHelper(user) || IsNetworkHelper(user))
 #define IsBot(user) (user->handle_info && HANDLE_FLAGGED(user->handle_info, BOT))
+#define IsInvi(user) (user->handle_info && HANDLE_FLAGGED(user->handle_info, INVI))
 
 enum cookie_type {
     ACTIVATION,
@@ -84,6 +88,15 @@ struct handle_note {
     char note[1];
 };
 
+struct authlogEntry {
+    unsigned long login_time;
+    unsigned long logout_time;
+    const char *hostmask;
+    const char *quit_reason;
+    struct userNode *user;
+    struct authlogEntry *next;
+};
+
 struct handle_info {
     struct nick_info *nicks;
     struct string_list *masks;
@@ -92,6 +105,9 @@ struct handle_info {
     struct handle_cookie *cookie;
     struct handle_note *notes;
     struct language *language;
+    struct authlogEntry *authlog;
+    char *website;
+    char *devnull;
     char *email_addr;
     char *epithet;
     char *infoline;
@@ -136,6 +152,10 @@ struct modeNode *find_handle_in_channel(struct chanNode *channel, struct handle_
 int nickserv_modify_handle_flags(struct userNode *user, struct userNode *bot, const char *str, unsigned long *add, unsigned long *remove);
 int oper_has_access(struct userNode *user, struct userNode *bot, unsigned int min_level, unsigned int quiet);
 void nickserv_show_oper_accounts(struct userNode *user, struct svccmd *cmd);
+struct handle_info *checklogin(const char *user, const char *pass, const char *numeric, const char *hostmask, const char *ipmask);
+char *getfakehost(const char *user);
+void nickserv_devnull_delete(char *name);
+void nickserv_devnull_rename(char *oldname, char *newname);
 
 /* auth_funcs are called when a user gets a new handle_info.  They are
  * called *after* user->handle_info has been updated.  */
index 81db93d336df0375ea73e9427962df3a16b95564..15a7b9643a1a09c642e55e76dbfbec958cbff71d 100644 (file)
         "$bc$b  Use mIRC color codes in responses",
         "$bf$b  Account frozen/on vacation (will not be unregistered for inactivity; cleared when account is authenticated against)",
         "$bn$b  No-delete (will never be unregistered for inactivity)",
+               "$bb$b  Bot",
+               "$bI$b  Invisible (user will not be shown in staff list)",
         "$uSee Also:$u accountinfo, set");
 "OADDMASK" ("/msg $N OADDMASK <nick|*account> <user@host>",
         "Adds a hostmask to the specified account.",
index 38ad356ee2d84bbc14fbe97b3156460d054ed2da..b4aa0d9f6542641ab97176454fd074271922547c 100644 (file)
@@ -22,6 +22,7 @@
 #include "gline.h"
 #include "global.h"
 #include "nickserv.h"
+#include "chanserv.h"
 #include "modcmd.h"
 #include "opserv.h"
 #include "timeq.h"
 #define KEY_EXPIRES "expires"
 #define KEY_STAFF_AUTH_CHANNEL "staff_auth_channel"
 #define KEY_STAFF_AUTH_CHANNEL_MODES "staff_auth_channel_modes"
+#define KEY_STAFF_AUTH_FORCE_OPS "staff_auth_force_opers"
 #define KEY_CLONE_GLINE_DURATION "clone_gline_duration"
 #define KEY_BLOCK_GLINE_DURATION "block_gline_duration"
 #define KEY_ISSUER "issuer"
 #define KEY_ISSUED "issued"
+#define KEY_DEVNULL_CLASSES "classes"
+#define KEY_DEVNULL_NAME "class"
+#define KEY_DEVNULL_MODE "modes"
+#define KEY_DEVNULL_MAXCHAN "chanlimit"
+#define KEY_DEVNULL_MAXSENDQ "sendq"
 
 #define IDENT_FORMAT            "%s [%s@%s/%s]"
 #define IDENT_DATA(user)        user->nick, user->ident, user->hostname, irc_ntoa(&user->ip)
@@ -257,6 +264,43 @@ static const struct message_entry msgtab[] = {
     { "OSMSG_CSEARCH_CHANNEL_INFO", "%s [%d users] %s %s" },
     { "OSMSG_TRACE_MAX_CHANNELS", "You may not use the 'channel' criterion more than %d times." },
     { "OSMSG_FORCEKICK_LOCAL", "You cannot kick $b%s$b forcefully." },
+    { "OSMSG_SVSNONICK", "$b%s$b is not a valid nick." },
+    { "OSMSG_SVSNICKUSED", "$b%s$b is an already used nickname." },
+    { "OSMSG_SVSNICK", "You have renamed $b%s$b to $b%s$b." },
+    { "OSMSG_SVSJOIN", "$b%s$b joined $b%s$b." },
+    { "OSMSG_SVSMODE", "You have set mode $b%s$b for $b%s$b." },
+    { "OSMSG_SIMUL", "You have simuled $b%s$b: %s" },
+    { "OSMSG_DEVNULL_USER" , "[%s] %s  %s" },
+    { "OSMSG_DEVNULL_MATCH" , "%d Users found." },
+    { "OSMSG_DEVNULL_CLASS" , "%s is not a valid DevNull class." },
+    { "OSMSG_DEVNULL_ADDED", "Added %s to DevNull list (class: %s)" },
+    { "OSMSG_DEVNULL_DELETED", "Deleted %s from DevNull list (class: %s)" },
+    { "OSMSG_DEVNULL_NOTADDED", "User %s is not listed on DevNull list." },
+    { "OSMSG_DEVNULL_ACTION", "Unrecognized trace action $b%s$b" },
+    { "OSMSG_DEVNULL_FOUND", "DevNull Class %s is already existing." },
+    { "OSMSG_DEVNULL_NOTFOUND", "can't find DevNull class %s." },
+    { "OSMSG_DEVNULL_ADDED", "DevNull Class %s added." },
+    { "OSMSG_DEVNULL_REMOVED", "DevNull Class %s removed." },
+    { "OSMSG_DEVNULL_SET", "Settings for DevNull Class %s" },
+    { "OSMSG_DEVNULL_SET_A", "ChanLimit:        %s" },
+    { "OSMSG_DEVNULL_SET_A_i", "ChanLimit:        %i" },
+    { "OSMSG_DEVNULL_SET_B", "UnlimitTarget:    %s" },
+    { "OSMSG_DEVNULL_SET_C", "Flood:            %s" },
+    { "OSMSG_DEVNULL_SET_E", "ChanHide:         %s" },
+    { "OSMSG_DEVNULL_SET_F", "IdleHide:         %s" },
+    { "OSMSG_DEVNULL_SET_G", "ChServMode:       %s" },
+    { "OSMSG_DEVNULL_SET_H", "XtraOpMode:       %s" },
+    { "OSMSG_DEVNULL_SET_I", "NetServMode:      %s" },
+    { "OSMSG_DEVNULL_SET_J", "SeeIdle:          %s" },
+    { "OSMSG_DEVNULL_SET_K", "ForceIdleHide:    %s" },
+    { "OSMSG_DEVNULL_SET_L", "OverrideCC:       %s" },
+    { "OSMSG_DEVNULL_SET_M", "OverrideNoAmsg:   %s" },
+    { "OSMSG_DEVNULL_SET_N", "MaxSendQ:         %s" },
+    { "OSMSG_DEVNULL_SET_N_i", "MaxSendQ:         %i" },
+    { "OSMSG_DEVNULL_SET_OPME", "OpMe:             %s" },
+    { "OSMSG_DEVNULL_SET_DONE", "Done." },
+    { "OSMSG_DEVNULL_RENAMED", "Devnull class %s renamed to %s" },
+    { "OSMSG_DEVNULL_SET_INVALID", "Invalid Option for setting %s" },
     { NULL, NULL }
 };
 
@@ -271,6 +315,7 @@ static dict_t opserv_reserved_nick_dict; /* data is struct userNode* */
 static struct string_list *opserv_bad_words;
 static dict_t opserv_exempt_channels; /* data is not used */
 static dict_t opserv_trusted_hosts; /* data is struct trusted_host* */
+static dict_t opserv_devnull_classes; /* data is struct devnull_class* */
 static dict_t opserv_hostinfo_dict; /* data is struct opserv_hostinfo* */
 static dict_t opserv_user_alerts; /* data is struct opserv_user_alert* */
 static dict_t opserv_nick_based_alerts; /* data is struct opserv_user_alert* */
@@ -279,12 +324,15 @@ static dict_t opserv_account_alerts; /* data is struct opserv_user_alert* */
 static struct module *opserv_module;
 static struct log_type *OS_LOG;
 static unsigned int new_user_flood;
+const char *devnull_modes = DEVNULL_MODES;
 static char *level_strings[1001];
+static char devnull_inverse_modes[256];
 
 static struct {
     struct chanNode *debug_channel;
     struct chanNode *alert_channel;
     struct chanNode *staff_auth_channel;
+    int staff_auth_force;
     struct policer_params *join_policer_params;
     struct policer new_user_policer;
     unsigned long untrusted_max;
@@ -1192,7 +1240,7 @@ static MODCMD_FUNC(cmd_part)
 
 static MODCMD_FUNC(cmd_mode)
 {
-    if (!modcmd_chanmode(argv+1, argc-1, MCP_ALLOW_OVB|MCP_KEY_FREE|MC_ANNOUNCE)) {
+    if (!modcmd_chanmode(argv+1, argc-1, MCP_ALLOW_OVB|MCP_KEY_FREE|MC_ANNOUNCE|MCP_OPERMODE)) {
         reply("MSG_INVALID_MODES", unsplit_string(argv+1, argc-1, NULL));
         return 0;
     }
@@ -1291,7 +1339,7 @@ static MODCMD_FUNC(cmd_whois)
     reply("OSMSG_WHOIS_ACCOUNT", (target->handle_info ? target->handle_info->handle : "Not authenticated"));
     intervalString(buffer, now - target->timestamp, user->handle_info);
     reply("OSMSG_WHOIS_NICK_AGE", buffer);
-    if (target->channels.used <= MAX_CHANNELS_WHOIS)
+    if (target->channels.used <= MAX_CHANNELS_WHOIS || HANDLE_FLAGGED(user->handle_info, BOT))
         opserv_ison(user, target, "OSMSG_WHOIS_CHANNELS");
     else
         reply("OSMSG_WHOIS_HIDECHANS");
@@ -2763,6 +2811,57 @@ trusted_host_read(const char *host, void *data, UNUSED_ARG(void *extra))
     return 0;
 }
 
+static void
+opserv_add_devnull_class(const char *name, unsigned long modes, unsigned long maxchan, unsigned long maxsendq)
+{
+    struct devnull_class *th;
+    th = calloc(1, sizeof(*th));
+    if (!th)
+        return;
+    th->name = strdup(name);
+    th->modes = modes;
+    th->maxchan = maxchan;
+    th->maxsendq = maxsendq;
+    dict_insert(opserv_devnull_classes, th->name, th);
+}
+
+static void
+free_devnull_class(void *data)
+{
+    struct devnull_class *th = data;
+    free(th->name);
+    free(th);
+}
+
+static int
+devnull_class_read(const char *name, void *data, UNUSED_ARG(void *extra))
+{
+    struct record_data *rd = data;
+    const char *nameb = name, *str;
+    unsigned long modes, maxchan, maxsendq;
+    unsigned int ii;
+
+    if (rd->type == RECDB_OBJECT) {
+        dict_t obj = GET_RECORD_OBJECT(rd);
+        /* new style structure */
+        nameb = database_get_data(obj, KEY_DEVNULL_NAME, RECDB_QSTRING);
+        str = database_get_data(obj, KEY_DEVNULL_MODE, RECDB_QSTRING);
+        modes = 0;
+        if (str) {
+            for (ii=0; str[ii]; ii++)
+                modes |= 1 << (devnull_inverse_modes[(unsigned char)str[ii]] - 1);
+        }
+        str = database_get_data(obj, KEY_DEVNULL_MAXCHAN, RECDB_QSTRING);
+        maxchan = str ? ParseInterval(str) : 0;
+        str = database_get_data(obj, KEY_DEVNULL_MAXSENDQ, RECDB_QSTRING);
+        maxsendq = str ? ParseInterval(str) : 0;
+    } else
+        return 0;
+
+    opserv_add_devnull_class(nameb, modes, maxchan, maxsendq);
+    return 0;
+}
+
 static int
 opserv_saxdb_read(struct dict *conf_db)
 {
@@ -2807,6 +2906,8 @@ opserv_saxdb_read(struct dict *conf_db)
     }
     if ((object = database_get_data(conf_db, KEY_TRUSTED_HOSTS, RECDB_OBJECT)))
         dict_foreach(object, trusted_host_read, opserv_trusted_hosts);
+    if ((object = database_get_data(conf_db, KEY_DEVNULL_CLASSES, RECDB_OBJECT)))
+        dict_foreach(object, devnull_class_read, opserv_devnull_classes);
     if ((object = database_get_data(conf_db, KEY_GAGS, RECDB_OBJECT)))
         dict_foreach(object, add_gag_helper, NULL);
     if ((object = database_get_data(conf_db, KEY_ALERTS, RECDB_OBJECT)))
@@ -2864,6 +2965,28 @@ opserv_saxdb_write(struct saxdb_context *ctx)
         }
         saxdb_end_record(ctx);
     }
+    /* devnull_classes */
+    if (dict_size(opserv_devnull_classes)) {
+        saxdb_start_record(ctx, KEY_DEVNULL_CLASSES, 1);
+        for (it = dict_first(opserv_devnull_classes); it; it = iter_next(it)) {
+            struct devnull_class *th = iter_data(it);
+            saxdb_start_record(ctx, iter_key(it), 0);
+            if (th->name) saxdb_write_string(ctx, KEY_DEVNULL_NAME, th->name);
+            if (th->modes) {
+                int ii, flen;
+                char flags[50];
+                for (ii=flen=0; devnull_modes[ii]; ++ii)
+                    if (th->modes & (1 << ii))
+                        flags[flen++] = devnull_modes[ii];
+                flags[flen] = 0;
+                saxdb_write_string(ctx, KEY_DEVNULL_MODE, flags);
+            }
+            if (th->maxchan) saxdb_write_int(ctx, KEY_DEVNULL_MAXCHAN, th->maxchan);
+            if (th->maxsendq) saxdb_write_int(ctx, KEY_DEVNULL_MAXSENDQ, th->maxsendq);
+            saxdb_end_record(ctx);
+        }
+        saxdb_end_record(ctx);
+    }
     /* gags */
     if (gagList) {
         struct gag_entry *gag;
@@ -3971,6 +4094,8 @@ opserv_staff_alert(struct userNode *user, UNUSED_ARG(struct handle_info *old_han
         || user->uplink->burst
         || !user->handle_info)
         return;
+    else if (IsBot(user))
+        return;
     else if (user->handle_info->opserv_level)
         type = "OPER";
     else if (IsNetworkHelper(user))
@@ -3981,9 +4106,9 @@ opserv_staff_alert(struct userNode *user, UNUSED_ARG(struct handle_info *old_han
         return;
 
     if (irc_in_addr_is_valid(user->ip))
-        send_channel_notice(opserv_conf.staff_auth_channel, opserv, IDENT_FORMAT" authed to %s account %s", IDENT_DATA(user), type, user->handle_info->handle);
+        send_channel_message(opserv_conf.staff_auth_channel, opserv, IDENT_FORMAT" authed to %s account %s", IDENT_DATA(user), type, user->handle_info->handle);
     else
-        send_channel_notice(opserv_conf.staff_auth_channel, opserv, "%s [%s@%s] authed to %s account %s", user->nick, user->ident, user->hostname, type, user->handle_info->handle);
+        send_channel_message(opserv_conf.staff_auth_channel, opserv, "%s [%s@%s] authed to %s account %s", user->nick, user->ident, user->hostname, type, user->handle_info->handle);
 }
 
 static void
@@ -4135,6 +4260,581 @@ static MODCMD_FUNC(cmd_delalert)
     return 1;
 }
 
+static MODCMD_FUNC(cmd_listdevnull)
+{
+    struct helpfile_table tbl;
+    unsigned int count = 0, ii = 0;
+    char *on,*off,*half;
+    on = "X";
+    off = "-";
+    half = "1/2";
+    
+    dict_iterator_t it;
+    for (it = dict_first(opserv_devnull_classes); it; it = iter_next(it)) {
+        count++;
+    }
+    tbl.length = count+1;
+    tbl.width = 15;
+    tbl.flags = 0;
+    tbl.flags = TABLE_NO_FREE;
+    tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
+    tbl.contents[0] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
+    tbl.contents[0][0] = "Name";
+    tbl.contents[0][1] = "MC";
+    tbl.contents[0][2] = "UT";
+    tbl.contents[0][3] = "FL";
+    tbl.contents[0][4] = "CH";
+    tbl.contents[0][5] = "IH";
+    tbl.contents[0][6] = "SI";
+    tbl.contents[0][7] = "IH²";
+    tbl.contents[0][8] = "oC";
+    tbl.contents[0][9] = "oM";
+    tbl.contents[0][10] = "+k";
+    tbl.contents[0][11] = "+S";
+    tbl.contents[0][12] = "+X";
+    tbl.contents[0][13] = "MaxQ";
+    tbl.contents[0][14] = "OpMe";
+    if(!count)
+    {
+        table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
+        reply("MSG_NONE");
+        free(tbl.contents[0]);
+        free(tbl.contents);
+        return 0;
+    }
+    for (it = dict_first(opserv_devnull_classes); it; it = iter_next(it)) {
+        struct devnull_class *th = iter_data(it);
+        tbl.contents[++ii] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
+        tbl.contents[ii][0] = th->name;
+        if(DEVNULL_FLAGGED(th, MODE_A)) {
+            tbl.contents[ii][1] = strtab(th->maxchan);
+        } else {
+            tbl.contents[ii][1] = off;
+        }
+        if(DEVNULL_FLAGGED(th, MODE_B)) {
+            tbl.contents[ii][2] = on;
+        } else {
+            tbl.contents[ii][2] = off;
+        }
+        if(DEVNULL_FLAGGED(th, MODE_D)) {
+            tbl.contents[ii][3] = on;
+        } else if(DEVNULL_FLAGGED(th, MODE_C)) {
+            tbl.contents[ii][3] = half;
+        } else {
+            tbl.contents[ii][3] = off;
+        }
+        if(DEVNULL_FLAGGED(th, MODE_E)) {
+            tbl.contents[ii][4] = on;
+        } else {
+            tbl.contents[ii][4] = off;
+        }
+        if(DEVNULL_FLAGGED(th, MODE_F)) {
+            tbl.contents[ii][5] = on;
+        } else {
+            tbl.contents[ii][5] = off;
+        }
+        if(DEVNULL_FLAGGED(th, MODE_J)) {
+            tbl.contents[ii][6] = on;
+        } else {
+            tbl.contents[ii][6] = off;
+        }
+        if(DEVNULL_FLAGGED(th, MODE_K)) {
+            tbl.contents[ii][7] = on;
+        } else {
+            tbl.contents[ii][7] = off;
+        }
+        if(DEVNULL_FLAGGED(th, MODE_L)) {
+            tbl.contents[ii][8] = on;
+        } else {
+            tbl.contents[ii][8] = off;
+        }
+        if(DEVNULL_FLAGGED(th, MODE_M)) {
+            tbl.contents[ii][9] = on;
+        } else {
+            tbl.contents[ii][9] = off;
+        }
+        if(DEVNULL_FLAGGED(th, MODE_G)) {
+            tbl.contents[ii][10] = on;
+        } else {
+            tbl.contents[ii][10] = off;
+        }
+        if(DEVNULL_FLAGGED(th, MODE_I)) {
+            tbl.contents[ii][11] = on;
+        } else {
+            tbl.contents[ii][11] = off;
+        }
+        if(DEVNULL_FLAGGED(th, MODE_H)) {
+            tbl.contents[ii][12] = on;
+        } else {
+            tbl.contents[ii][12] = off;
+        }
+        if(DEVNULL_FLAGGED(th, MODE_N)) {
+            tbl.contents[ii][13] = on;
+        } else {
+            tbl.contents[ii][13] = off;
+        }
+        if(DEVNULL_FLAGGED(th, MODE_OPME)) {
+            tbl.contents[ii][14] = on;
+        } else {
+            tbl.contents[ii][14] = off;
+        }
+    }
+    table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
+    for(ii = 1; ii < tbl.length; ++ii)
+    {
+        free(tbl.contents[ii]);
+    }
+    free(tbl.contents[0]);
+    free(tbl.contents);
+    send_message_type(4, user, opserv,"-");
+    /*
+    tbl.contents[0][1] = "MC";
+    tbl.contents[0][2] = "UT";
+    tbl.contents[0][3] = "FL";
+    tbl.contents[0][4] = "CH";
+    tbl.contents[0][5] = "IH";
+    tbl.contents[0][6] = "SI";
+    tbl.contents[0][7] = "IH²";
+    tbl.contents[0][8] = "oC";
+    tbl.contents[0][9] = "oM";
+    tbl.contents[0][10] = "+k";
+    tbl.contents[0][11] = "+S";
+    tbl.contents[0][12] = "+X";
+    tbl.contents[0][13] = "MaxQ";
+    */
+    send_message_type(4, user, opserv,"MC = MaxChan");
+       send_message_type(4, user, opserv,"UT = UnlimitTargets");
+       send_message_type(4, user, opserv,"FL = Flood (1 = 1/2 flood, X = unlimited)");
+       send_message_type(4, user, opserv,"CH = Channel Hide (mode +n)");
+       send_message_type(4, user, opserv,"IH = Idle Hide (mode +I)");
+       send_message_type(4, user, opserv,"SI = See Idle");
+       send_message_type(4, user, opserv,"IH² = Idle Hide (override SI)");
+       send_message_type(4, user, opserv,"oC = Color override (mode +c)");
+    send_message_type(4, user, opserv,"oM = AMsg override");
+       send_message_type(4, user, opserv,"+k = ChanServ Mode (mode +k)");
+    send_message_type(4, user, opserv,"+S = NetServ Mode (mode +S)");
+    send_message_type(4, user, opserv,"+X = XtraOp Mode (mode +X)");
+    send_message_type(4, user, opserv,"OpMe = ChanServ opme command");
+    return 1;
+}
+
+static MODCMD_FUNC(cmd_adddevnull)
+{
+
+    if (dict_find(opserv_devnull_classes, argv[1], NULL)) {
+        reply("OSMSG_DEVNULL_FOUND", argv[1]);
+        return 0;
+    }
+    
+    opserv_add_devnull_class(argv[1], 0, 0, 0);
+    reply("OSMSG_DEVNULL_ADDED",argv[1]);
+    return 1;
+}
+
+
+static MODCMD_FUNC(cmd_deldevnull)
+{
+    unsigned int n;
+
+    for (n=1; n<argc; n++) {
+        struct devnull_class *th = dict_find(opserv_devnull_classes, argv[n], NULL);
+        if (!th)
+            continue;
+        nickserv_devnull_delete(th->name);
+        dict_remove(opserv_devnull_classes, argv[n]);
+        reply("OSMSG_DEVNULL_REMOVED",argv[n]);
+    }
+    return 1;
+}
+
+static MODCMD_FUNC(cmd_renamedevnull)
+{
+    struct devnull_class *th;
+    if (dict_find(opserv_devnull_classes, argv[2], NULL)) {
+        reply("OSMSG_DEVNULL_FOUND", argv[2]);
+        return 0;
+    }
+    if ((th = dict_find(opserv_devnull_classes, argv[1], NULL))) {
+        opserv_add_devnull_class(argv[2], th->modes, th->maxchan, th->maxsendq);
+        nickserv_devnull_rename(th->name,argv[2]);
+        dict_remove(opserv_devnull_classes, argv[1]);
+        reply("OSMSG_DEVNULL_RENAMED",argv[1],argv[2]);
+    } else {
+        reply("OSMSG_DEVNULL_NOTFOUND", argv[1]);
+        return 0;
+    }
+    return 1;
+}
+
+static MODCMD_FUNC(cmd_setdevnull)
+{
+    struct devnull_class *th;
+    unsigned int offset=0;
+    if ((th = dict_find(opserv_devnull_classes, argv[offset+1], NULL))) {
+        if (argc > 3) {
+            unsigned int ii;
+            for( ii = 0; argv[offset+2][ ii ]; ii++)
+                argv[offset+2][ ii ] = toupper( argv[offset+2][ ii ] );
+            for( ii = 0; argv[offset+3][ ii ]; ii++)
+                argv[offset+3][ ii ] = toupper( argv[offset+3][ ii ] );
+            if(!strcmp("CHANLIMIT",argv[offset+2])) {
+                 if (!strcmp("OFF",argv[offset+3])) {
+                  DEVNULL_CLEAR_FLAG(th, MODE_A);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else {
+                  DEVNULL_SET_FLAG(th, MODE_A);
+                  th->maxchan = strtoul(argv[offset+3], NULL, 0);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 }
+            }
+            else if(!strcmp("UNLIMITTARGET",argv[offset+2]) || !strcmp("UNLIMITEDTARGET",argv[offset+2])) {
+                 if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+                  DEVNULL_CLEAR_FLAG(th, MODE_B);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+                  DEVNULL_SET_FLAG(th, MODE_B);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else {
+                  reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+                 }
+            }
+            else if(!strcmp("FLOOD",argv[offset+2])) {
+                 if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+                  DEVNULL_CLEAR_FLAG(th, MODE_C);
+                  DEVNULL_CLEAR_FLAG(th, MODE_D);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+                  DEVNULL_SET_FLAG(th, MODE_D);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else if (!strcmp("HALF",argv[offset+3]) || !strcmp("2",argv[offset+3]) || !strcmp("1/2",argv[offset+3])) {
+                  DEVNULL_SET_FLAG(th, MODE_C);
+                  DEVNULL_CLEAR_FLAG(th, MODE_D);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else {
+                  reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+                 }
+            }
+            else if(!strcmp("CHANHIDE",argv[offset+2])) {
+                 if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+                  DEVNULL_CLEAR_FLAG(th, MODE_E);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+                  DEVNULL_SET_FLAG(th, MODE_E);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else {
+                  reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+                 }
+            }
+            else if(!strcmp("IDLEHIDE",argv[offset+2])) {
+                 if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+                  DEVNULL_CLEAR_FLAG(th, MODE_F);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+                  DEVNULL_SET_FLAG(th, MODE_F);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else {
+                  reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+                 }
+            }
+            else if(!strcmp("CHSERVMODE",argv[offset+2])) {
+                 if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+                  DEVNULL_CLEAR_FLAG(th, MODE_G);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+                  DEVNULL_SET_FLAG(th, MODE_G);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else {
+                  reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+                 }
+            }
+            else if(!strcmp("XTRAOPMODE",argv[offset+2])) {
+                 if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+                  DEVNULL_CLEAR_FLAG(th, MODE_H);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+                  DEVNULL_SET_FLAG(th, MODE_H);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else {
+                  reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+                 }
+            }
+            else if(!strcmp("NETSERVMODE",argv[offset+2])) {
+                 if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+                  DEVNULL_CLEAR_FLAG(th, MODE_I);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+                  DEVNULL_SET_FLAG(th, MODE_I);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else {
+                  reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+                 }
+            }
+            else if(!strcmp("SEEIDLE",argv[offset+2])) {
+                 if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+                  DEVNULL_CLEAR_FLAG(th, MODE_J);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+                  DEVNULL_SET_FLAG(th, MODE_J);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else {
+                  reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+                 }
+            }
+            else if(!strcmp("FORCEIDLEHIDE",argv[offset+2])) {
+                 if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+                  DEVNULL_CLEAR_FLAG(th, MODE_K);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+                  DEVNULL_SET_FLAG(th, MODE_K);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else {
+                  reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+                 }
+            }
+            else if(!strcmp("OVERRIDECC",argv[offset+2])) {
+                 if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+                  DEVNULL_CLEAR_FLAG(th, MODE_L);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+                  DEVNULL_SET_FLAG(th, MODE_L);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else {
+                  reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+                 }
+            }
+            else if(!strcmp("OVERRIDENOAMSG",argv[offset+2])) {
+                 if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+                  DEVNULL_CLEAR_FLAG(th, MODE_M);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+                  DEVNULL_SET_FLAG(th, MODE_M);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else {
+                  reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+                 }
+            }
+            else if(!strcmp("MAXSENDQ",argv[offset+2])) {
+                 if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+                  DEVNULL_CLEAR_FLAG(th, MODE_N);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else {
+                  DEVNULL_SET_FLAG(th, MODE_N);
+                  th->maxsendq = strtoul(argv[offset+3], NULL, 0);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 }
+            } 
+            else if(!strcmp("OPME",argv[offset+2])) {
+                 if (!strcmp("OFF",argv[offset+3]) || !strcmp("0",argv[offset+3])) {
+                  DEVNULL_CLEAR_FLAG(th, MODE_OPME);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else if (!strcmp("ON",argv[offset+3]) || !strcmp("1",argv[offset+3])) {
+                  DEVNULL_SET_FLAG(th, MODE_OPME);
+                  reply("OSMSG_DEVNULL_SET_DONE");
+                 } else {
+                  reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+3]);
+                 }
+            } else {
+                 reply("OSMSG_DEVNULL_SET_INVALID", argv[offset+2]);
+            }
+            
+        } else {
+            reply("OSMSG_DEVNULL_SET", th->name);
+            if(DEVNULL_FLAGGED(th, MODE_A)) {
+                reply("OSMSG_DEVNULL_SET_A_i", th->maxchan);
+            } else {
+                reply("OSMSG_DEVNULL_SET_A", "off");
+            }
+            if(DEVNULL_FLAGGED(th, MODE_B)) {
+                reply("OSMSG_DEVNULL_SET_B", "on");
+            } else {
+                reply("OSMSG_DEVNULL_SET_B", "off");
+            }
+            if(DEVNULL_FLAGGED(th, MODE_D)) {
+                reply("OSMSG_DEVNULL_SET_C", "on");
+            } else if(DEVNULL_FLAGGED(th, MODE_C)) {
+                reply("OSMSG_DEVNULL_SET_C", "half");
+            } else {
+                reply("OSMSG_DEVNULL_SET_C", "off");
+            }
+            if(DEVNULL_FLAGGED(th, MODE_E)) {
+                reply("OSMSG_DEVNULL_SET_E", "on");
+            } else {
+                reply("OSMSG_DEVNULL_SET_E", "off");
+            }
+            if(DEVNULL_FLAGGED(th, MODE_F)) {
+                reply("OSMSG_DEVNULL_SET_F", "on");
+            } else {
+                reply("OSMSG_DEVNULL_SET_F", "off");
+            }
+            if(DEVNULL_FLAGGED(th, MODE_G)) {
+                reply("OSMSG_DEVNULL_SET_G", "on");
+            } else {
+                reply("OSMSG_DEVNULL_SET_G", "off");
+            }
+            if(DEVNULL_FLAGGED(th, MODE_H)) {
+                reply("OSMSG_DEVNULL_SET_H", "on");
+            } else {
+                reply("OSMSG_DEVNULL_SET_H", "off");
+            }
+            if(DEVNULL_FLAGGED(th, MODE_I)) {
+                reply("OSMSG_DEVNULL_SET_I", "on");
+            } else {
+                reply("OSMSG_DEVNULL_SET_I", "off");
+            }
+            if(DEVNULL_FLAGGED(th, MODE_J)) {
+                reply("OSMSG_DEVNULL_SET_J", "on");
+            } else {
+                reply("OSMSG_DEVNULL_SET_J", "off");
+            }
+            if(DEVNULL_FLAGGED(th, MODE_K)) {
+                reply("OSMSG_DEVNULL_SET_K", "on");
+            } else {
+                reply("OSMSG_DEVNULL_SET_K", "off");
+            }
+            if(DEVNULL_FLAGGED(th, MODE_L)) {
+                reply("OSMSG_DEVNULL_SET_L", "on");
+            } else {
+                reply("OSMSG_DEVNULL_SET_L", "off");
+            }
+            if(DEVNULL_FLAGGED(th, MODE_M)) {
+                reply("OSMSG_DEVNULL_SET_M", "on");
+            } else {
+                reply("OSMSG_DEVNULL_SET_M", "off");
+            }
+            if(DEVNULL_FLAGGED(th, MODE_N)) {
+                reply("OSMSG_DEVNULL_SET_N_i", th->maxsendq);
+            } else {
+                reply("OSMSG_DEVNULL_SET_N", "off");
+            }
+            if(DEVNULL_FLAGGED(th, MODE_OPME)) {
+                reply("OSMSG_DEVNULL_SET_OPME", "on");
+            } else {
+                reply("OSMSG_DEVNULL_SET_OPME", "off");
+            }
+        }
+    } else {
+        reply("OSMSG_DEVNULL_NOTFOUND", argv[offset+1]);
+        return 0;
+    }
+    return 1;
+}
+
+int devnull_check(const char *name) { 
+    if (dict_find(opserv_devnull_classes, name, NULL)) {
+        return 1;
+    }
+    return 0;
+}
+
+struct devnull_class* 
+    devnull_get(const char *name) {
+    return dict_find(opserv_devnull_classes, name, NULL);
+}
+
+void operpart(struct chanNode *chan, struct userNode *user)
+{
+    if(opserv_conf.alert_channel && opserv_conf.staff_auth_force > 0 &&
+      !(irccasecmp(chan->name,opserv_conf.alert_channel->name))) {
+        struct mod_chanmode *change;
+        change = find_matching_bans(&chan->banlist, user, NULL); //don't join them if they're banned (exceptions from forced join)
+        if(change)
+            return;
+        irc_svsjoin(opserv,user,chan);
+    }
+}
+
+void operadd(struct userNode *user)
+{
+    if(opserv_conf.alert_channel && opserv_conf.staff_auth_force > 0)
+        irc_svsjoin(opserv,user,opserv_conf.alert_channel);
+}
+
+void operdel(struct userNode *user)
+{
+    if(opserv_conf.alert_channel && opserv_conf.staff_auth_force == 2)
+        irc_kick(opserv, user, opserv_conf.alert_channel, "mode -o");
+}
+
+static MODCMD_FUNC(cmd_svsjoin)
+{
+    struct userNode *target;
+    if(!(target=GetUserH(argv[1]))) {
+        reply("OSMSG_SVSNONICK", argv[1]);
+        return 0;
+    }
+    if(!IsChannelName(argv[2]))
+    {
+        reply("MSG_NOT_CHANNEL_NAME");
+        return 0;
+    }
+    irc_svsjoinchan(opserv,target,argv[2]);
+    reply("OSMSG_SVSJOIN",target->nick,argv[2]);
+    return 1;
+}
+
+static MODCMD_FUNC(cmd_svsnick)
+{
+    struct userNode *target;
+    if(!(target=GetUserH(argv[1]))) {
+        reply("OSMSG_SVSNONICK", argv[1]);
+        return 0;
+    }
+    if(GetUserH(argv[2]))
+    {
+        reply("OSMSG_SVSNICKUSED",argv[2]);
+        return 0;
+    }
+    irc_svsnick(opserv,target,argv[2]);
+    reply("OSMSG_SVSNICK",target->nick,argv[2]);
+    return 1;
+}
+
+static MODCMD_FUNC(cmd_svsmode)
+{
+    struct userNode *target;
+    char *modestr;
+    if(!(target=GetUserH(argv[1]))) {
+        reply("OSMSG_SVSNONICK", argv[1]);
+        return 0;
+    }
+    modestr = unsplit_string(argv + 2, argc - 2, NULL);
+    irc_svsmode(opserv,target,modestr);
+    reply("OSMSG_SVSMODE",modestr,target->nick);
+    return 1;
+}
+
+static MODCMD_FUNC(cmd_simul)
+{
+    struct userNode *target;
+    char *line;
+    if(!(target=GetUserH(argv[1]))) {
+        reply("OSMSG_SVSNONICK", argv[1]);
+        return 0;
+    }
+    line = unsplit_string(argv + 2, argc - 2, NULL);
+    irc_simul(target,line);
+    reply("OSMSG_SIMUL",target->nick,line);
+    return 1;
+}
+
+static MODCMD_FUNC(cmd_relay)
+{
+    struct userNode *target;
+    char *line;
+    if(!(target=GetUserH(argv[1]))) {
+        reply("OSMSG_SVSNONICK", argv[1]);
+        return 0;
+    }
+    line = unsplit_string(argv + 2, argc - 2, NULL);
+    char sendline[512];
+    if(channel)
+        sprintf(sendline, "relay %s %s :%s",user->nick,channel->name,line);
+    else
+        sprintf(sendline, "relay %s query :%s",user->nick,line);
+    irc_privmsg(opserv,target->numeric,sendline);
+    return 1;
+}
+
 static void
 opserv_conf_read(void)
 {
@@ -4177,8 +4877,11 @@ opserv_conf_read(void)
             str2 = "+timns";
         opserv_conf.staff_auth_channel = AddChannel(str, now, str2, NULL);
         AddChannelUser(opserv, opserv_conf.staff_auth_channel)->modes |= MODE_CHANOP;
+        str2 = database_get_data(conf_node, KEY_STAFF_AUTH_FORCE_OPS, RECDB_QSTRING);
+        opserv_conf.staff_auth_force = str2 ? atoi(str2) : 0;
     } else {
         opserv_conf.staff_auth_channel = NULL;
+        opserv_conf.staff_auth_force = 0;
     }
     str = database_get_data(conf_node, KEY_UNTRUSTED_MAX, RECDB_QSTRING);
     opserv_conf.untrusted_max = str ? strtoul(str, NULL, 0) : 5;
@@ -4224,6 +4927,10 @@ opserv_db_init(void) {
     dict_delete(opserv_trusted_hosts);
     opserv_trusted_hosts = dict_new();
     dict_set_free_data(opserv_trusted_hosts, free_trusted_host);
+    /* set up opserv_devnull_classes dict */
+    dict_delete(opserv_devnull_classes);
+    opserv_devnull_classes = dict_new();
+    dict_set_free_data(opserv_devnull_classes, free_devnull_class);
     /* set up opserv_chan_warn dict */
     dict_delete(opserv_chan_warn);
     opserv_chan_warn = dict_new();
@@ -4259,6 +4966,7 @@ opserv_db_cleanup(void)
     free_string_list(opserv_bad_words);
     dict_delete(opserv_exempt_channels);
     dict_delete(opserv_trusted_hosts);
+    dict_delete(opserv_devnull_classes);
     unreg_del_user_func(opserv_user_cleanup);
     dict_delete(opserv_hostinfo_dict);
     dict_delete(opserv_nick_based_alerts);
@@ -4283,6 +4991,13 @@ init_opserv(const char *nick)
     }
     conf_register_reload(opserv_conf_read);
 
+    unsigned int i;
+    /* set up handle_inverse_flags */
+    memset(devnull_inverse_modes, 0, sizeof(devnull_inverse_modes));
+    for (i=0; devnull_modes[i]; i++) {
+        devnull_inverse_modes[(unsigned char)devnull_modes[i]] = i + 1;
+    }
+
     memset(level_strings, 0, sizeof(level_strings));
     opserv_module = module_register("OpServ", OS_LOG, "opserv.help", opserv_help_expand);
     opserv_define_func("ACCESS", cmd_access, 0, 0, 0);
@@ -4361,6 +5076,16 @@ init_opserv(const char *nick)
 #if defined(WITH_MALLOC_SRVX) || defined(WITH_MALLOC_SLAB)
     opserv_define_func("STATS MEMORY", cmd_stats_memory, 0, 0, 0);
 #endif
+    opserv_define_func("DEVNULL ADD", cmd_adddevnull, 200, 0, 2);
+    opserv_define_func("DEVNULL DEL", cmd_deldevnull, 200, 0, 2);
+    opserv_define_func("DEVNULL RENAME", cmd_renamedevnull, 200, 0, 3);
+    opserv_define_func("DEVNULL SET", cmd_setdevnull, 200, 0, 2);
+    opserv_define_func("DEVNULL LIST", cmd_listdevnull, 200, 0, 0);
+    opserv_define_func("SVSJOIN", cmd_svsjoin, 800, 0, 3);
+    opserv_define_func("SVSMODE", cmd_svsmode, 800, 0, 3);
+    opserv_define_func("SVSNICK", cmd_svsnick, 800, 0, 3);
+    opserv_define_func("RELAY", cmd_relay, 800, 0, 0);
+    opserv_define_func("SIMUL", cmd_simul, 999, 0, 2);
     opserv_define_func("TRACE", cmd_trace, 100, 0, 3);
     opserv_define_func("TRACE PRINT", NULL, 0, 0, 0);
     opserv_define_func("TRACE COUNT", NULL, 0, 0, 0);
index 7daff43443d7b742d0ecf630a36b7df32ef61ca0..92b35ecf0d4c4a644153cf2768f7172689e1efe6 100644 (file)
 #ifndef _opserv_h
 #define _opserv_h
 
+/* DEVNULL_MODE_* go into devnull_class.modes */
+#define DEVNULL_MODE_A 0x00000001
+#define DEVNULL_MODE_B 0x00000002
+#define DEVNULL_MODE_C 0x00000004
+#define DEVNULL_MODE_D 0x00000008
+#define DEVNULL_MODE_E 0x00000010
+#define DEVNULL_MODE_F 0x00000020
+#define DEVNULL_MODE_G 0x00000040
+#define DEVNULL_MODE_H 0x00000080
+#define DEVNULL_MODE_I 0x00000100
+#define DEVNULL_MODE_J 0x00000200
+#define DEVNULL_MODE_K 0x00000400
+#define DEVNULL_MODE_L 0x00000800
+#define DEVNULL_MODE_M 0x00001000
+#define DEVNULL_MODE_N 0x00002000
+#define DEVNULL_MODE_OPME 0x00004000
+
+#define DEVNULL_MODES "abcdefghijklmno"
+
+#define DEVNULL_FLAGGED(hi, tok) ((hi)->modes & DEVNULL_##tok)
+#define DEVNULL_SET_FLAG(hi, tok) ((hi)->modes |= DEVNULL_##tok)
+#define DEVNULL_CLEAR_FLAG(hi, tok) ((hi)->modes &= ~DEVNULL_##tok)
+
+struct devnull_class {
+    char *name;
+    unsigned long modes;
+    unsigned long maxchan;
+    unsigned long maxsendq;
+};
+
 void init_opserv(const char *nick);
 unsigned int gag_create(const char *mask, const char *owner, const char *reason, unsigned long expires);
 int opserv_bad_channel(const char *name);
+void devnull_delete(const char *auth);
+void devnull_rename(const char *oldauth, const char *newauth);
+int devnull_check(const char *name);
+struct devnull_class* devnull_get(const char *name);
+struct userNode* GetOpServ(void);
+void operpart(struct chanNode *chan, struct userNode *user);
+void operadd(struct userNode *user);
+void operdel(struct userNode *user);
 
 #endif
index af7e5546ea133a75e8915bba9f05532fff64639d..a0c1d98ef102d5f80004c0a05aa3cb8dc21a2f79 100644 (file)
@@ -1231,7 +1231,7 @@ void mod_usermode(struct userNode *user, const char *mode_change) {
 }
 
 struct mod_chanmode *
-mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags, short base_oplevel)
+mod_chanmode_parse(struct chanNode *channel, struct userNode *user, char **modes, unsigned int argc, unsigned int flags, short base_oplevel)
 {
     struct mod_chanmode *change;
     unsigned int ii, in_arg, ch_arg, add;
index 6039d759158228af5520aa5ea91ccd8a576723e7..bc37f32a13aa945940a109cba3d627f620120c43 100644 (file)
@@ -23,6 +23,9 @@
 #include "ioset.h"
 #include "log.h"
 #include "nickserv.h"
+#include "opserv.h"
+#include "chanserv.h"
+#include "spamserv.h"
 #include "timeq.h"
 #ifdef HAVE_SYS_SOCKET_H
 #include <sys/socket.h>
@@ -447,6 +450,8 @@ privmsg_chan_helper(struct chanNode *cn, void *data)
         && (offchannel_allowed[(unsigned char)pd->text[0]]
             || (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++) {
@@ -478,6 +483,8 @@ part_helper(struct chanNode *cn, void *data)
 {
     struct part_desc *desc = data;
     DelChannelUser(desc->user, cn, desc->text, false);
+    if (IsOper(desc->user))
+        operpart(cn, desc->user);
 }
 
 static CMD_FUNC(cmd_part)
@@ -624,6 +631,9 @@ mod_chanmode_dup(struct mod_chanmode *orig, unsigned int extra)
         res->modes_set = orig->modes_set;
         res->modes_clear = orig->modes_clear;
         res->new_limit = orig->new_limit;
+        res->new_access = orig->new_access;
+        memcpy(res->new_altchan, orig->new_altchan, sizeof(res->new_altchan));
+        memcpy(res->new_noflood, orig->new_noflood, sizeof(res->new_noflood));
         memcpy(res->new_key, orig->new_key, sizeof(res->new_key));
         memcpy(res->new_upass, orig->new_upass, sizeof(res->new_upass));
         memcpy(res->new_apass, orig->new_apass, sizeof(res->new_apass));
@@ -643,8 +653,14 @@ mod_chanmode_apply(struct userNode *who, struct chanNode *channel, struct mod_ch
     channel->modes = (channel->modes & ~change->modes_clear) | change->modes_set;
     if (change->modes_set & MODE_LIMIT)
         channel->limit = change->new_limit;
+    if (change->modes_set & MODE_ACCESS)
+        channel->access = change->new_access;
     if (change->modes_set & MODE_KEY)
         strcpy(channel->key, change->new_key);
+    if (change->modes_set & MODE_ALTCHAN)
+        strcpy(channel->altchan, change->new_altchan);
+    if (change->modes_set & MODE_NOFLOOD)
+        strcpy(channel->noflood, change->new_noflood);
     if (change->modes_set & MODE_UPASS)
        strcpy(channel->upass, change->new_upass);
     if (change->modes_set & MODE_APASS)
@@ -721,7 +737,7 @@ mod_chanmode(struct userNode *who, struct chanNode *channel, char **modes, unsig
         base_oplevel = member->oplevel;
     else
         base_oplevel = MAXOPLEVEL;
-    if (!(change = mod_chanmode_parse(channel, modes, argc, flags, base_oplevel)))
+    if (!(change = mod_chanmode_parse(channel, who, modes, argc, flags, base_oplevel)))
         return 0;
     if (flags & MC_ANNOUNCE)
         mod_chanmode_announce(who, channel, change);
@@ -741,6 +757,9 @@ irc_make_chanmode(struct chanNode *chan, char *out)
     mod_chanmode_init(&change);
     change.modes_set = chan->modes;
     change.new_limit = chan->limit;
+    change.new_access = chan->access;
+    safestrncpy(change.new_altchan, chan->altchan, sizeof(change.new_altchan));
+    safestrncpy(change.new_noflood, chan->noflood, sizeof(change.new_noflood));
     safestrncpy(change.new_key, chan->key, sizeof(change.new_key));
     safestrncpy(change.new_upass, chan->upass, sizeof(change.new_upass));
     safestrncpy(change.new_apass, chan->apass, sizeof(change.new_apass));
@@ -774,7 +793,12 @@ generate_hostmask(struct userNode *user, int options)
     }
     hostname = user->hostname;
     if (IsFakeHost(user) && IsHiddenHost(user) && !(options & GENMASK_NO_HIDING)) {
-        hostname = user->fakehost;
+        if(user->fakehost && user->fakehost[0] == '$') {
+            hostname = alloca(strlen(user->handle_info->handle) + strlen(user->fakehost));
+            sprintf(hostname, "%s%s", user->handle_info->handle, user->fakehost+1);
+        } else {
+            hostname = user->fakehost;
+        }
     } else if (IsHiddenHost(user) && user->handle_info && hidden_host_suffix && !(options & GENMASK_NO_HIDING)) {
         hostname = alloca(strlen(user->handle_info->handle) + strlen(hidden_host_suffix) + 2);
         sprintf(hostname, "%s.%s", user->handle_info->handle, hidden_host_suffix);
index 71f6d3766acd5f2ef97de6a6f155b5d1ec31038a..d08fb1804fd82c48aaf5a3f9ffcd624d1a48d05a 100644 (file)
@@ -40,6 +40,7 @@
 #define CMD_EOB_ACK             "EOB_ACK"
 #define CMD_ERROR               "ERROR"
 #define CMD_FAKEHOST            "FAKE"
+#define CMD_FAKEHOST2           "FAKE2"
 #define CMD_GET                 "GET"
 #define CMD_GLINE               "GLINE"
 #define CMD_HASH                "HASH"
@@ -72,6 +73,7 @@
 #define CMD_PROTO               "PROTO"
 #define CMD_QUIT                "QUIT"
 #define CMD_REHASH              "REHASH"
+#define CMD_RELAY               "RELAY"
 #define CMD_RESET               "RESET"
 #define CMD_RESTART             "RESTART"
 #define CMD_RPING               "RPING"
@@ -86,6 +88,8 @@
 #define CMD_SQUIT               "SQUIT"
 #define CMD_STATS               "STATS"
 #define CMD_SVSNICK             "SVSNICK"
+#define CMD_SVSMODE             "SVSMODE"
+#define CMD_SVSJOIN             "SVSJOIN"
 #define CMD_TIME                "TIME"
 #define CMD_TOPIC               "TOPIC"
 #define CMD_TRACE               "TRACE"
 #define TOK_EOB_ACK             "EA"
 #define TOK_ERROR               "Y"
 #define TOK_FAKEHOST            "FA"
+#define TOK_FAKEHOST2           "NFH"
 #define TOK_GET                 "GET"
 #define TOK_GLINE               "GL"
 #define TOK_HASH                "HASH"
 #define TOK_PROTO               "PROTO"
 #define TOK_QUIT                "Q"
 #define TOK_REHASH              "REHASH"
+#define TOK_RELAY               "RL"
 #define TOK_RESET               "RESET"
 #define TOK_RESTART             "RESTART"
 #define TOK_RPING               "RI"
 #define TOK_SQUIT               "SQ"
 #define TOK_STATS               "R"
 #define TOK_SVSNICK             "SN"
+#define TOK_SVSMODE             "SM"
+#define TOK_SVSJOIN             "SJ"
 #define TOK_TIME                "TI"
 #define TOK_TOPIC               "T"
 #define TOK_TRACE               "TR"
 #define P10_EOB_ACK             TYPE(EOB_ACK)
 #define P10_ERROR               TYPE(ERROR)
 #define P10_FAKEHOST            TYPE(FAKEHOST)
+#define P10_FAKEHOST2           TYPE(FAKEHOST2)
 #define P10_GET                 TYPE(GET)
 #define P10_GLINE               TYPE(GLINE)
 #define P10_HASH                TYPE(HASH)
 #define P10_PROTO               TYPE(PROTO)
 #define P10_QUIT                TYPE(QUIT)
 #define P10_REHASH              TYPE(REHASH)
+#define P10_RELAY               TYPE(RELAY)
 #define P10_RESET               TYPE(RESET)
 #define P10_RESTART             TYPE(RESTART)
 #define P10_RPING               TYPE(RPING)
 #define P10_SQUIT               TYPE(SQUIT)
 #define P10_STATS               TYPE(STATS)
 #define P10_SVSNICK             TYPE(SVSNICK)
+#define P10_SVSMODE             TYPE(SVSMODE)
+#define P10_SVSJOIN             TYPE(SVSJOIN)
 #define P10_TIME                TYPE(TIME)
 #define P10_TOPIC               TYPE(TOPIC)
 #define P10_TRACE               TYPE(TRACE)
@@ -299,8 +311,8 @@ static const char *his_servername;
 static const char *his_servercomment;
 static struct channelList dead_channels;
 
-/* These correspond to 1 << X:      012345678901234567 */
-const char irc_user_mode_chars[] = "o iw dkgn    x   I";
+/* These correspond to 1 << X:      012345678901234567890123 */
+const char irc_user_mode_chars[] = "o iw dkgn    x   ISDXHst";
 
 static struct userNode *AddUser(struct server* uplink, const char *nick, const char *ident, const char *hostname, const char *modes, const char *numeric, const char *userinfo, unsigned long timestamp, const char *realip);
 
@@ -499,7 +511,25 @@ irc_account(struct userNode *user, const char *stamp, unsigned long timestamp, u
 void
 irc_fakehost(struct userNode *user, const char *host, const char *ident, int force)
 {
-    putsock("%s " P10_FAKEHOST " %s %s %s%s", self->numeric, user->numeric, ident, host, force ? " FORCE" : "");
+    /* SRVX added the possibility for FAKE IDENTS
+     * but this is currently *NOT* supported by our IRCu
+     *
+     * edit 24.11.11: 
+     *  NFH (P10_FAKEHOST2) is now supported by our IRCu (git-65-21592a4)
+     */
+    putsock("%s " P10_FAKEHOST2 " %s %s %s%s", self->numeric, user->numeric, ident, host, force ? " FORCE" : "");
+}
+
+void 
+irc_relay(char *message)
+{
+    putsock("%s " P10_RELAY " %s", self->numeric, message);
+}
+
+void 
+irc_simul(struct userNode *target, char *command)
+{
+    putsock("%s " P10_RELAY " %s SI %s :%s", self->numeric, target->numeric, target->numeric, command);
 }
 
 void
@@ -600,6 +630,24 @@ irc_privmsg(struct userNode *from, const char *to, const char *message)
         putsock("%s " P10_PRIVMSG " %s :%s", from->numeric, to, message);
 }
 
+void
+irc_svsmode(struct userNode *from, struct userNode *user, const char *modes)
+{
+putsock("%s " P10_SVSMODE " %s %s", from->numeric, user->numeric, modes);
+}
+
+void
+irc_svsjoin(struct userNode *from, struct userNode *user, struct chanNode *chan)
+{
+putsock("%s " P10_SVSJOIN " %s %s", from->numeric, user->numeric, chan->name);
+}
+
+void
+irc_svsjoinchan(struct userNode *from, struct userNode *user, const char *chan)
+{
+putsock("%s " P10_SVSJOIN " %s %s", from->numeric, user->numeric, chan);
+}
+
 void
 irc_eob(void)
 {
@@ -660,12 +708,16 @@ irc_introduce(const char *passwd)
 void
 irc_gline(struct server *srv, struct gline *gline)
 {
+    //<prefix> GL <target> [!][+|-|>|<]<mask> [<expiration>] [<lastmod>] [<lifetime>] [:<reason>]
+    //expiration = relative time (seconds)
+    //lastmod = timestamp
+    //livetime = timestamp
     if (gline->lastmod)
-        putsock("%s " P10_GLINE " %s +%s %lu %lu %lu :%s",
-                self->numeric, (srv ? srv->numeric : "*"), gline->target, gline->expires, gline->lastmod, gline->lifetime, gline->reason);
+        putsock("%s " P10_GLINE " %s +%s %lu %lu %lu :%s", self->numeric, (srv ? srv->numeric : "*"), 
+                gline->target, gline->expires-now, gline->lastmod, gline->lifetime, gline->reason);
     else
-        putsock("%s " P10_GLINE " %s +%s %lu :%s",
-                self->numeric, (srv ? srv->numeric : "*"), gline->target, gline->expires, gline->reason);
+        putsock("%s " P10_GLINE " %s +%s %lu :%s", self->numeric, (srv ? srv->numeric : "*"), 
+                gline->target, gline->expires-now, gline->reason);
 }
 
 void
@@ -681,6 +733,7 @@ void
 irc_ungline(const char *mask)
 {
     putsock("%s " P10_GLINE " * -%s %lu", self->numeric, mask, now);
+    //putsock("%s " P10_GLINE " * %s * %lu", self->numeric, mask, now);
 }
 
 /* Return negative if *(struct modeNode**)pa is "less than" pb,
@@ -1168,6 +1221,8 @@ create_helper(char *name, void *data)
         return;
     }
 
+    handle_new_channel_created(name, cd->user);
+
     AddChannelUser(cd->user, AddChannel(name, cd->when, NULL, NULL));
 }
 
@@ -1301,7 +1356,7 @@ static CMD_FUNC(cmd_burst)
             int n_modes;
             for (pos=argv[next], n_modes = 1; *pos; pos++)
                 if ((*pos == 'k') || (*pos == 'l') || (*pos == 'A')
-                    || (*pos == 'U'))
+                    || (*pos == 'U') || (*pos == 'a') || (*pos == 'F'))
                     n_modes++;
             if (next + n_modes > argc)
                 n_modes = argc - next;
@@ -1556,6 +1611,8 @@ static CMD_FUNC(cmd_kick)
 {
     if (argc < 3)
         return 0;
+    if (GetUserN(argv[2]) && IsOper(GetUserN(argv[2])))
+        operpart(GetChannel(argv[1]), GetUserN(argv[2]));
     ChannelUserKicked(GetUserH(origin), GetUserN(argv[2]), GetChannel(argv[1]));
     return 1;
 }
@@ -1678,6 +1735,105 @@ static CMD_FUNC(cmd_time)
     return 1;
 }
 
+static CMD_FUNC(cmd_relay)
+{
+    struct server *sNode;
+    unsigned int len;
+    char buf[3];
+    //<sender> RELAY <destination> <command>
+    len = strlen(argv[1]);
+    buf[2] = 0;
+    switch(len) {
+        case 2:
+            sNode = GetServerN(argv[1]);
+            break;
+        case 5:
+            buf[0] = argv[1][0];
+            buf[1] = argv[1][1];
+            sNode = GetServerN(buf);
+            break;
+        case 6:
+            buf[0] = argv[1][1];
+            buf[1] = argv[1][2];
+            sNode = GetServerN(buf);
+            break;
+        default:
+            /* Invalid destination. Ignore. */
+            return 0;
+    }
+    if(sNode->numeric == self->numeric) {
+        //ok  someone relayed something to us!
+        if(strcmp("LQ", argv[2]) == 0) {
+            //oooh thats exciting - we've got a LOC Query! :D
+            //LQ !ABADE pk910 80.153.5.212 server.zoelle1.de ~watchcat :test
+            //ok  let's check the login datas
+            struct handle_info *hi;
+            char tmp[MAXLEN], tmp2[MAXLEN];
+            sprintf(tmp, "%s@%s",argv[7],argv[6]);
+            sprintf(tmp2, "%s@%s",argv[7],argv[5]);
+            if((hi = checklogin(argv[4],argv[argc-1],&argv[3][1],tmp,tmp2))) {
+             //login ok
+             struct devnull_class *th;
+             char devnull[512];
+             if(hi->devnull && (th = devnull_get(hi->devnull))) {
+                const char *devnull_modes = DEVNULL_MODES;
+                int ii, flen;
+                char flags[50];
+                for (ii=flen=0; devnull_modes[ii]; ++ii)
+                    if (th->modes & (1 << ii))
+                        flags[flen++] = devnull_modes[ii];
+                flags[flen] = 0;
+                sprintf(devnull, "+%s %s %lu %lu",flags,th->name,th->maxchan,th->maxsendq);
+             } else {
+                devnull[0] = 0;
+             }
+             if(!HANDLE_FLAGGED(hi, AUTOHIDE)) {
+                sprintf(tmp,"%s LA %s 0 %s\n",argv[3],hi->handle,devnull);
+             } else if(getfakehost(argv[4])) {
+                sprintf(tmp,"%s LA %s %s %s\n",argv[3],hi->handle,getfakehost(argv[4]),devnull);
+             } else {
+                extern const char *hidden_host_suffix;
+                sprintf(tmp,"%s LA %s %s.%s %s\n",argv[3],hi->handle,hi->handle,hidden_host_suffix,devnull);
+             }
+             irc_relay(tmp);
+            } else {
+             //login rejected
+             sprintf(tmp,"%s LR\n",argv[3]);
+             irc_relay(tmp);
+            }
+        } else if(strcmp("UC", argv[2]) == 0) {
+            char tmp[MAXLEN];
+            sprintf(tmp,"%s UC %s %s",argv[3],argv[3],argv[4]);
+            irc_relay(tmp);
+        } else if(strcmp("JA", argv[2]) == 0) {
+            struct userData *uData;
+            struct chanNode *cn;
+            struct userNode *user;
+            char tmp[MAXLEN];
+            cn = GetChannel(argv[4]);
+            if (!cn) return 0;
+            if (!(user = GetUserN(argv[3]))) return 0;
+            if(!cn->channel_info) {
+                //channel not registered
+                sprintf(tmp,"%s JAA %s %s\n",argv[3],cn->name,argv[6]);
+            } else if((uData = GetChannelUser(cn->channel_info, user->handle_info))) {
+                if(uData->access >= atoi(argv[5])) {
+                    //we can join
+                    sprintf(tmp,"%s JAA %s %s\n",argv[3],cn->name,argv[6]);
+                } else {
+                    //access too low
+                    sprintf(tmp,"%s JAR %s %i %i\n",argv[3],cn->name,uData->access,uData->access);
+                }
+            } else {
+                //0 access
+                sprintf(tmp,"%s JAR %s %s %s\n",argv[3],cn->name,"0","0");
+            }
+            irc_relay(tmp);
+        }
+    }
+    return 1;
+}
+
 static CMD_FUNC(cmd_xquery)
 {
     struct server *source;
@@ -1815,6 +1971,8 @@ init_parse(void)
     dict_insert(irc_func_dict, TOK_STATS, cmd_stats);
     dict_insert(irc_func_dict, CMD_SVSNICK, cmd_svsnick);
     dict_insert(irc_func_dict, TOK_SVSNICK, cmd_svsnick);
+    dict_insert(irc_func_dict, CMD_RELAY, cmd_relay);
+    dict_insert(irc_func_dict, TOK_RELAY, cmd_relay);
     dict_insert(irc_func_dict, CMD_WHOIS, cmd_whois);
     dict_insert(irc_func_dict, TOK_WHOIS, cmd_whois);
     dict_insert(irc_func_dict, CMD_GLINE, cmd_gline);
@@ -2322,8 +2480,10 @@ void mod_usermode(struct userNode *user, const char *mode_change) {
         case 'o':
             do_user_mode(FLAGS_OPER);
             if (!add) {
+                operdel(user);
                 userList_remove(&curr_opers, user);
             } else if (!userList_contains(&curr_opers, user)) {
+                operadd(user);
                 userList_append(&curr_opers, user);
                 call_oper_funcs(user);
             }
@@ -2340,6 +2500,12 @@ void mod_usermode(struct userNode *user, const char *mode_change) {
         case 'g': do_user_mode(FLAGS_GLOBAL); break;
         case 'n': do_user_mode(FLAGS_NOCHAN); break;
         case 'I': do_user_mode(FLAGS_NOIDLE); break;
+        case 'S': do_user_mode(FLAGS_NETSERV); break;
+        case 'D': do_user_mode(FLAGS_SECURITYSERV); break;
+        case 'X': do_user_mode(FLAGS_XTRAOP); break;
+        case 's': do_user_mode(FLAGS_SERVERNOTICE); break;
+        case 'H': do_user_mode(FLAGS_HIDDENOPER); break;
+        case 't': do_user_mode(FLAGS_SEENOIDLE); break;
         case 'x': do_user_mode(FLAGS_HIDDEN_HOST); break;
         case 'r':
             if (*word) {
@@ -2415,7 +2581,7 @@ keyncpy(char output[], char input[], size_t output_size)
 }
 
 struct mod_chanmode *
-mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags, short base_oplevel)
+mod_chanmode_parse(struct chanNode *channel, struct userNode *user, char **modes, unsigned int argc, unsigned int flags, short base_oplevel)
 {
     struct mod_chanmode *change;
     unsigned int ii, in_arg, ch_arg, add;
@@ -2439,6 +2605,10 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
         case 'C': do_chan_mode(MODE_NOCTCPS); break;
         case 'D': do_chan_mode(MODE_DELAYJOINS); break;
         case 'c': do_chan_mode(MODE_NOCOLORS); break;
+        case 'M': do_chan_mode(MODE_NOAMSGS); break;
+        case 'N': do_chan_mode(MODE_NONOTICES); break;
+        case 'u': do_chan_mode(MODE_AUDITORIUM); break;
+        case 'S': do_chan_mode(MODE_SSLCHAN); break;
         case 'i': do_chan_mode(MODE_INVITEONLY); break;
         case 'm': do_chan_mode(MODE_MODERATED); break;
         case 'n': do_chan_mode(MODE_NOPRIVMSGS); break;
@@ -2447,8 +2617,14 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
         case 's': do_chan_mode(MODE_SECRET); break;
         case 't': do_chan_mode(MODE_TOPICLIMIT); break;
         case 'z':
-          if (!(flags & MCP_REGISTERED))
-              do_chan_mode(MODE_REGISTERED);
+          if (!(flags & MCP_REGISTERED) && (!(flags & MCP_IGN_REGISTERED) || add)) {
+            do_chan_mode(MODE_REGISTERED);
+          } else if (flags & MCP_IGN_REGISTERED) {
+            /* ignore the modechange but continue parsing */
+          } else {
+            mod_chanmode_free(change);
+            return NULL;
+          }
           break;
 #undef do_chan_mode
         case 'l':
@@ -2462,6 +2638,17 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
                 change->modes_clear |= MODE_LIMIT;
             }
             break;
+        case 'a':
+            if (add) {
+                if (in_arg >= argc)
+                    goto error;
+                change->modes_set |= MODE_ACCESS;
+                change->new_access = atoi(modes[in_arg++]);
+            } else {
+                change->modes_set &= ~MODE_ACCESS;
+                change->modes_clear |= MODE_ACCESS;
+            }
+            break;
         case 'k':
             if (add) {
                 if ((in_arg >= argc)
@@ -2477,6 +2664,70 @@ mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, un
                 }
             }
             break;
+        case 'f':
+                   if (add) {
+                           if (in_arg >= argc)
+                    goto error;
+                char *mode = modes[in_arg++];
+                if(mode[0] == '!' && !(flags & MCP_OPERMODE)) //noflood flag also for overriders
+                   goto error;//only allow opers
+                else if(mode[0] == '!')
+                   mode++;
+                
+                if(mode[0] == '+' || mode[0] == '@') {
+                    mode++;
+                }
+                char *p;
+                int count = 0, time = 0;
+                for(p = mode; p[0]; p++) {
+                    if(p[0] == ':') {
+                        char tmpchar = p[0];
+                        p[0] = '\0';
+                        count = strtoul(mode,0,10);
+                        p[0] = tmpchar;
+                                               p++;
+                                               time = strtoul(p,0,10);
+                                               break;
+                               }
+                }
+                if(count <= 0 || time <= 0 || count > 100 || time > 600)
+                                   goto error;
+                               change->modes_set |= MODE_NOFLOOD;
+                               safestrncpy(change->new_noflood, modes[in_arg - 1], sizeof(change->new_noflood));
+                       } else {
+                           change->modes_clear |= MODE_NOFLOOD;
+                       }
+            break;
+        case 'F':
+            if (add) {
+                if (in_arg >= argc)
+                    goto error;
+                char *altchan = modes[in_arg++];
+                struct chanNode *target;
+                if(!IsChannelName(altchan) || !(target = GetChannel(altchan)))
+                    goto error;
+                if(!(flags & MCP_OPERMODE)) {
+                    //check if the user has the permissions to use this channel as target
+                    struct modeNode *mn;
+                    struct userData *uData;
+                    struct chanData *cData;
+                    if(user && (mn = GetUserMode(target, user)) && (mn->modes & MODE_CHANOP)) {
+                        //allow - user is opped on target channel
+                    } else if(user && user->handle_info && 
+                              (uData = GetChannelUser(channel->channel_info, user->handle_info)) && 
+                              (cData = uData->channel) && 
+                              uData->access >= cData->lvlOpts[lvlGiveOps]
+                             ) {
+                        //allow - user has access to get op on the channel
+                    } else 
+                        goto error;
+                }
+                change->modes_set |= MODE_ALTCHAN;
+                safestrncpy(change->new_altchan, altchan, sizeof(change->new_altchan));
+            } else {
+                change->modes_clear |= MODE_ALTCHAN;
+            }
+            break;
         case 'U':
             if (flags & MCP_NO_APASS)
                 goto error;
@@ -2657,10 +2908,17 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod
         DO_MODE_CHAR(INVITEONLY, 'i');
         DO_MODE_CHAR(NOPRIVMSGS, 'n');
         DO_MODE_CHAR(LIMIT, 'l');
+        DO_MODE_CHAR(ACCESS, 'a');
+        DO_MODE_CHAR(ALTCHAN, 'F');
+        DO_MODE_CHAR(NOFLOOD, 'f');
         DO_MODE_CHAR(DELAYJOINS, 'D');
         DO_MODE_CHAR(REGONLY, 'r');
         DO_MODE_CHAR(NOCOLORS, 'c');
         DO_MODE_CHAR(NOCTCPS, 'C');
+        DO_MODE_CHAR(NONOTICES, 'N');
+        DO_MODE_CHAR(NOAMSGS, 'M');
+        DO_MODE_CHAR(AUDITORIUM, 'u');
+        DO_MODE_CHAR(SSLCHAN, 'S');
         DO_MODE_CHAR(REGISTERED, 'z');
 #undef DO_MODE_CHAR
         if (change->modes_clear & channel->modes & MODE_KEY)
@@ -2704,6 +2962,10 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod
         DO_MODE_CHAR(REGONLY, 'r');
         DO_MODE_CHAR(NOCOLORS, 'c');
         DO_MODE_CHAR(NOCTCPS, 'C');
+        DO_MODE_CHAR(NONOTICES, 'N');
+        DO_MODE_CHAR(NOAMSGS, 'M');
+        DO_MODE_CHAR(AUDITORIUM, 'u');
+        DO_MODE_CHAR(SSLCHAN, 'S');
         DO_MODE_CHAR(REGISTERED, 'z');
 #undef DO_MODE_CHAR
         if(change->modes_set & MODE_KEY)
@@ -2716,6 +2978,14 @@ mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod
             sprintf(int_buff, "%d", change->new_limit);
             mod_chanmode_append(&chbuf, 'l', int_buff);
         }
+        if(change->modes_set & MODE_ACCESS) {
+            sprintf(int_buff, "%d", change->new_access);
+            mod_chanmode_append(&chbuf, 'a', int_buff);
+        }
+        if (change->modes_set & MODE_ALTCHAN)
+            mod_chanmode_append(&chbuf, 'F', change->new_altchan);
+        if (change->modes_set & MODE_NOFLOOD)
+            mod_chanmode_append(&chbuf, 'f', change->new_noflood);
     }
     for (arg = 0; arg < change->argc; ++arg) {
         if (change->args[arg].mode & MODE_REMOVE)
@@ -2762,6 +3032,9 @@ mod_chanmode_format(struct mod_chanmode *change, char *outbuff)
         DO_MODE_CHAR(INVITEONLY, 'i');
         DO_MODE_CHAR(NOPRIVMSGS, 'n');
         DO_MODE_CHAR(LIMIT, 'l');
+        DO_MODE_CHAR(ACCESS, 'a');
+        DO_MODE_CHAR(ALTCHAN, 'F');
+        DO_MODE_CHAR(NOFLOOD, 'f');
         DO_MODE_CHAR(KEY, 'k');
         DO_MODE_CHAR(UPASS, 'U');
         DO_MODE_CHAR(APASS, 'A');
@@ -2769,6 +3042,10 @@ mod_chanmode_format(struct mod_chanmode *change, char *outbuff)
         DO_MODE_CHAR(REGONLY, 'r');
         DO_MODE_CHAR(NOCOLORS, 'c');
         DO_MODE_CHAR(NOCTCPS, 'C');
+        DO_MODE_CHAR(NONOTICES, 'N');
+        DO_MODE_CHAR(NOAMSGS, 'M');
+        DO_MODE_CHAR(AUDITORIUM, 'u');
+        DO_MODE_CHAR(SSLCHAN, 'S');
         DO_MODE_CHAR(REGISTERED, 'z');
 #undef DO_MODE_CHAR
     }
@@ -2785,9 +3062,16 @@ mod_chanmode_format(struct mod_chanmode *change, char *outbuff)
         DO_MODE_CHAR(REGONLY, 'r');
         DO_MODE_CHAR(NOCOLORS, 'c');
         DO_MODE_CHAR(NOCTCPS, 'C');
+        DO_MODE_CHAR(NONOTICES, 'N');
+               DO_MODE_CHAR(NOAMSGS, 'M');
+        DO_MODE_CHAR(AUDITORIUM, 'u');
+        DO_MODE_CHAR(SSLCHAN, 'S');
         DO_MODE_CHAR(REGISTERED, 'z');
         DO_MODE_CHAR(LIMIT, 'l'), args_used += sprintf(args + args_used, " %d", change->new_limit);
         DO_MODE_CHAR(KEY, 'k'), args_used += sprintf(args + args_used, " %s", change->new_key);
+        DO_MODE_CHAR(ACCESS, 'a'), args_used += sprintf(args + args_used, " %d", change->new_access);
+        DO_MODE_CHAR(ALTCHAN, 'F'), args_used += sprintf(args + args_used, " %s", change->new_altchan);
+        DO_MODE_CHAR(NOFLOOD, 'f'), args_used += sprintf(args + args_used, " %s", change->new_noflood);
         DO_MODE_CHAR(UPASS, 'U'), args_used += sprintf(args + args_used, " %s", change->new_upass);
         DO_MODE_CHAR(APASS, 'A'), args_used += sprintf(args + args_used, " %s", change->new_apass);
 #undef DO_MODE_CHAR
@@ -2812,6 +3096,14 @@ clear_chanmode(struct chanNode *channel, const char *modes)
         case 't': cleared |= MODE_TOPICLIMIT; break;
         case 'i': cleared |= MODE_INVITEONLY; break;
         case 'n': cleared |= MODE_NOPRIVMSGS; break;
+        case 'F':
+            cleared |= MODE_ALTCHAN;
+            channel->altchan[0] = '\0';
+            break;
+        case 'f':
+            cleared |= MODE_NOFLOOD;
+            channel->noflood[0] = '\0';
+            break;
         case 'k':
             cleared |= MODE_KEY;
             channel->key[0] = '\0';
@@ -2828,11 +3120,19 @@ clear_chanmode(struct chanNode *channel, const char *modes)
             cleared |= MODE_LIMIT;
             channel->limit = 0;
             break;
+        case 'a':
+            cleared |= MODE_ACCESS;
+            channel->access = 0;
+            break;
         case 'b': cleared |= MODE_BAN; break;
         case 'D': cleared |= MODE_DELAYJOINS; break;
         case 'r': cleared |= MODE_REGONLY; break;
         case 'c': cleared |= MODE_NOCOLORS; break;
         case 'C': cleared |= MODE_NOCTCPS; break;
+        case 'M': cleared |= MODE_NOAMSGS; break;
+        case 'u': cleared |= MODE_AUDITORIUM; break;
+        case 'S': cleared |= MODE_SSLCHAN; break;
+        case 'N': cleared |= MODE_NONOTICES; break;
         case 'z': cleared |= MODE_REGISTERED; break;
         }
     }
index 6ded4dfb49463e798ea758455327894312a4c209..2bd70b496e4a41bb42fd86ccae92a3e2c3d8bcfa 100644 (file)
@@ -152,6 +152,13 @@ void irc_account(struct userNode *user, const char *stamp, unsigned long timesta
 void irc_regnick(struct userNode *user);
 void irc_fakehost(struct userNode *user, const char *host, const char *ident, int force);
 
+/* svs maintenance */
+void irc_svsmode(struct userNode *from, struct userNode *user, const char *modes);
+void irc_svsjoin(struct userNode *from, struct userNode *user, struct chanNode *chan);
+void irc_svsjoinchan(struct userNode *from, struct userNode *user, const char *chan);
+void irc_relay(char *message);
+void irc_simul(struct userNode *target, char *command);
+
 /* numeric messages */
 void irc_numeric(struct userNode *user, unsigned int num, const char *format, ...);
 /* RFC1459-compliant numeric responses */
@@ -185,6 +192,8 @@ unsigned int irc_user_modes(const struct userNode *user, char modes[], size_t le
 
 /* Channel mode manipulation */
 #define KEYLEN          23
+#define NOFLOODLEN      15
+#define CHANNELLEN      200
 typedef unsigned long chan_mode_t;
 /* Rules for struct mod_chanmode:
  * For a membership mode change, args[n].mode can contain more than
@@ -193,11 +202,13 @@ typedef unsigned long chan_mode_t;
  */
 struct mod_chanmode {
     chan_mode_t modes_set, modes_clear;
-    unsigned int new_limit, argc;
+    unsigned int new_limit, new_access, argc;
 #ifndef NDEBUG
     unsigned int alloc_argc;
 #endif
     char new_key[KEYLEN + 1];
+    char new_altchan[CHANNELLEN + 1];
+    char new_noflood[NOFLOODLEN + 1];
     char new_upass[KEYLEN + 1];
     char new_apass[KEYLEN + 1];
     struct {
@@ -208,15 +219,17 @@ struct mod_chanmode {
         } u;
     } args[1];
 };
-#define MCP_ALLOW_OVB     0x0001 /* allow op, voice, ban manipulation */
-#define MCP_FROM_SERVER   0x0002 /* parse as from a server */
-#define MCP_KEY_FREE      0x0004 /* -k without a key argument */
-#define MCP_REGISTERED    0x0008 /* chan is already registered; do not allow changes to MODE_REGISTERED */
-#define MCP_UPASS_FREE    0x0010 /* -U without a key argument */
-#define MCP_APASS_FREE    0x0020 /* -A without a key argument */
-#define MCP_NO_APASS      0x0040 /* Do not allow +/-A or +/-U */
-#define MC_ANNOUNCE       0x0100 /* send a mod_chanmode() change out */
-#define MC_NOTIFY         0x0200 /* make local callbacks to announce */
+#define MCP_ALLOW_OVB      0x0001 /* allow op, voice, ban manipulation */
+#define MCP_FROM_SERVER    0x0002 /* parse as from a server */
+#define MCP_KEY_FREE       0x0004 /* -k without a key argument */
+#define MCP_REGISTERED     0x0008 /* chan is already registered; do not allow changes to MODE_REGISTERED */
+#define MCP_UPASS_FREE     0x0010 /* -U without a key argument */
+#define MCP_APASS_FREE     0x0020 /* -A without a key argument */
+#define MCP_NO_APASS       0x0040 /* Do not allow +/-A or +/-U */
+#define MCP_IGN_REGISTERED 0x0080 /* chan is already registered; ignore changes to MODE_REGISTERED */
+#define MC_ANNOUNCE        0x0100 /* send a mod_chanmode() change out */
+#define MC_NOTIFY          0x0200 /* make local callbacks to announce */
+#define MCP_OPERMODE       0x0400
 #ifdef NDEBUG
 #define mod_chanmode_init(CHANMODE) do { memset((CHANMODE), 0, sizeof(*CHANMODE)); } while (0)
 #else
@@ -225,7 +238,7 @@ struct mod_chanmode {
 
 struct mod_chanmode *mod_chanmode_alloc(unsigned int argc);
 struct mod_chanmode *mod_chanmode_dup(struct mod_chanmode *orig, unsigned int extra);
-struct mod_chanmode *mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags, short base_oplevel);
+struct mod_chanmode *mod_chanmode_parse(struct chanNode *channel, struct userNode *user, char **modes, unsigned int argc, unsigned int flags, short base_oplevel);
 void mod_chanmode_apply(struct userNode *who, struct chanNode *channel, struct mod_chanmode *change);
 void mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod_chanmode *change);
 char *mod_chanmode_format(struct mod_chanmode *desc, char *buffer);
diff --git a/src/spamserv.c b/src/spamserv.c
new file mode 100644 (file)
index 0000000..35cf836
--- /dev/null
@@ -0,0 +1,2055 @@
+/* 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_EXCEPTLEVEL              "exceptlevel"
+#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 10
+
+const char *set_subcommands[SET_SUBCMDS_SIZE] = {"SPAMLIMIT", "ADVREACTION", "WARNREACTION", "ADVSCAN", "SPAMSCAN", "FLOODSCAN", "JOINFLOODSCAN", "EXCEPTLEVEL","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_EASYNUMERIC_VALUE",       "$b%s$b%d" },
+    { "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;
+    cInfo->exceptlevel = 400;
+       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 void
+spamserv_new_user_func(struct userNode *user)
+{
+       if(!IsLocal(user))
+               spamserv_create_user(user);
+}
+
+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(!CHECK_CHANOPS(cInfo))
+       {
+               //struct modeNode *mn = GetUserMode(channel, user);
+               //if(mn->modes & MODE_CHANOP)
+               //      return;
+        if(check_user_level(channel, user, lvlGiveOps, 1, 0)) 
+            return 0;
+       }
+    
+    if(!CHECK_VOICED(cInfo))
+       {
+        if(check_user_level(channel, user, lvlGiveVoice, 1, 0)) 
+            return 0;
+    }
+    
+    if(cInfo->exceptlevel == 0)
+        return 0;
+    if(cInfo->exceptlevel < 501) {
+      struct userData *uData;
+       if((uData = GetChannelUser(channel->channel_info, user->handle_info)) && (uData->access >= cInfo->exceptlevel)) {
+        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 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(!check_user_level(channel,user,lvlSetters,1,0))
+       {
+               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 
+SPAMSERV_FUNC(opt_exceptlevel)
+{
+ struct chanInfo *cInfo = get_chanInfo(channel->name);
+ struct userData *uData;
+ int index;
+ if(argc > 1)
+       {
+               index = atoi(argv[1]);
+               if(index < 1 || index > 501)
+               {
+                       spamserv_notice(user, "SSMSG_INVALID_NUM_SET", index, "ExceptLevel");
+                       return 0;
+               }
+        if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)) || (uData->access < cInfo->exceptlevel || index > uData->access))
+        {
+            ss_reply("SSMSG_NO_ACCESS");
+            return 0;
+        }
+        cInfo->exceptlevel=index;
+    } else {
+     index=cInfo->exceptlevel;
+    }
+    spamserv_notice(user, "SSMSG_EASYNUMERIC_VALUE", "ExceptLevel   ", index);
+    return 1;
+}
+
+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;
+}
+
+struct banData *add_channel_ban(struct chanData *channel, const char *mask, char *owner, unsigned long set, unsigned long triggered, unsigned long expires, char *reason);
+
+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 && mn->modes & MODE_CHANOP)
+                       return;
+        //if(check_user_level(channel, user, lvlGiveOps, 1, 0)) 
+        //    return;
+       }
+       
+       if(!CHECK_VOICED(cInfo))
+       {
+               struct modeNode *mn = GetUserMode(channel, user);
+               if(mn && (mn->modes & MODE_VOICE) && !(mn->modes & MODE_CHANOP))
+                       return;
+       }
+    
+    if(cInfo->exceptlevel == 0)
+        return;
+    if(cInfo->exceptlevel < 501) {
+      struct userData *uData;
+       if((uData = GetChannelUser(channel->channel_info, user->handle_info)) && (uData->access >= cInfo->exceptlevel)) {
+        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, 0, 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,exceptlevel;
+       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_EXCEPTLEVEL, RECDB_QSTRING);
+        exceptlevel = str ? atoi(str) : 400;
+        
+               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;                 
+                cInfo->exceptlevel=exceptlevel;
+                       }
+               }
+               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);         
+        saxdb_write_int(ctx, KEY_EXCEPTLEVEL, cInfo->exceptlevel);             
+
+               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);
+    modcmd_register(spamserv_module, "SET EXCEPTLEVEL", opt_exceptlevel, 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 --git a/src/spamserv.h b/src/spamserv.h
new file mode 100644 (file)
index 0000000..a97ff6e
--- /dev/null
@@ -0,0 +1,172 @@
+/* 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;
+    unsigned int           exceptlevel;
+    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
\ No newline at end of file
diff --git a/src/spamserv.help b/src/spamserv.help
new file mode 100644 (file)
index 0000000..23ee5ce
--- /dev/null
@@ -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.");
\ No newline at end of file
index 5c1556bda393698ebb38d9a0d8870cd16468ea56..331975133d9a93dcb109339ce1f95cbf8c11264f 100644 (file)
             "lc_h" "800"; // specifically lower case h
             "uc_H" "800"; // .. and upper case H
             "S" "999";
-            "b" "1";
+            "b" "800";
+                       "I" "800";
         };
         // and for who can change epithets for staff
         "set_epithet_level" "800";
         // how long must an account be inactive so it can be ounregistered without force?
         "ounregister_inactive" "1M";
         // which flags on an account require the ounregister to be used with force?
-        "ounregister_flags" "ShgsfnHbu";
+        "ounregister_flags" "ShgsfnHbuI";
         /* "require_qualified" has been removed. It is now
          * integrated into the modcmd command authorization
          * and dispatch mechanism.  "/msg OpServ help modcmd"
         // who to tell about staff auths?
         "staff_auth_channel" "#opserv";
         "staff_auth_channel_modes" "+tinms";
+        // Force Opers to be in staff_auth_channel
+        // 0 = don't force opers to be in the channel
+        // 1 = force opers to be in the channel
+        // 2 = force opers to be in the channel but kick them if they get mode -o set (deoper)
+        "staff_auth_force_opers" "2";
         // how many clones to allow from an untrusted host?
         "untrusted_max" "4";
         // how long of a g-line should be issued if the max hosts is exceeded?
             "size" "200";
             "drain-rate" "3";
         };
+        
     };
 
     "chanserv" {
         // How often to look for dnrs that have expired?
         "dnr_expire_freq" "1h";
         // what !set options should we show when user calls "!set" with no arguments?
-        "set_shows" ("DefaultTopic", "TopicMask", "Greeting", "UserGreeting", "Modes", "PubCmd", "InviteMe", "StrictOp", "AutoOp", "EnfModes", "EnfTopic", "TopicSnarf", "UserInfo", "GiveVoice", "GiveOps", "EnfOps", "Setters", "CtcpUser", "CtcpReaction", "Protect", "Toys", "DynLimit", "NoDelete");
+        "set_shows" ("DefaultTopic", "TopicMask", "Greeting", "UserGreeting", "Modes", "PubCmd", "InviteMe", "EnfModes", "EnfTopic", "TopicSnarf", "UserInfo", "GiveVoice", "GiveOps", "EnfOps", "Setters", "CtcpUsers", "CtcpReaction", "Protect", "Toys", "DynLimit", "NoDelete");
 
         // A list of !8ball responses
         "8ball" ("Not a chance.",
         "max_owned" "5";
         // how long between automatic topic refreshes with TopicRefresh 0
         "refresh_period" "3h";
+        // how long between two invites of an user
+        "invite_timeout" "10s";
         // what should !access say for various staff?
         "irc_operator_epithet" "a megalomaniacal power hungry tyrant";
         "network_helper_epithet" "a wannabe tyrant";
         "nodelete_level" "1";
         // how long before a new channel owner can give ownership away?
         "giveownership_timeout" "1w";
+        // message sent to new channels
+        "new_channel_unauthed_join" ""; //only sent if the user is unauthed
+        "new_channel_authed_join" ""; //only sent if the user is authed
+        "new_channel_message" ""; //always after the message above
+        
     };
 
     "global" {
         // 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.
         // "search" ("example.org", "example.net");
         // "nameservers" ("127.0.0.1");
     };
+    "watchdog" {
+        "nick" "Watchdog";
+        "modes" "+iok";
+        "ban_duration" "2h"; //only if the channel is registered with chanserv
+        "gline_duration" "1h";
+        "punishment_reason" "Your message contained a forbidden word.";
+    };
 };
 
 "policers" {
     "NickServ" { "mondo_section" "NickServ"; };
     "OpServ" { "mondo_section" "OpServ"; };
     "sendmail" { "mondo_section" "sendmail"; };
+    "SpamServ" { "mondo_section" "SpamServ"; };
+    "Watchdog" { "mondo_section" "Watchdog"; };
 
     // These are the options if you want a database to be in its own file.
     "mondo" {