changed spamserv,chanserv and watchdog messages to privmsgs
[srvx.git] / src / chanserv.c
index 8bc2a6100de4be82242795aeb47400c3fe1aacf8..44f6d9d5f1488f88c0b24d06260e0fce960e1eb9 100644 (file)
@@ -1,5 +1,5 @@
 /* chanserv.c - Channel service bot
- * Copyright 2000-2006 srvx Development Team
+ * Copyright 2000-2007 srvx Development Team
  *
  * This file is part of srvx.
  *
 #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 CHANSERV_CONF_NAME  "services/chanserv"
 
 /* ChanServ options */
-#define KEY_SUPPORT_CHANNEL     "support_channel"
-#define KEY_SUPPORT_CHANNEL_MODES "support_channel_modes"
-#define KEY_DB_BACKUP_FREQ     "db_backup_freq"
-#define KEY_INFO_DELAY         "info_delay"
-#define KEY_MAX_GREETLEN               "max_greetlen"
-#define KEY_ADJUST_THRESHOLD           "adjust_threshold"
-#define KEY_ADJUST_DELAY       "adjust_delay"
-#define KEY_CHAN_EXPIRE_FREQ   "chan_expire_freq"
-#define KEY_CHAN_EXPIRE_DELAY  "chan_expire_delay"
-#define KEY_DNR_EXPIRE_FREQ    "dnr_expire_freq"
-#define KEY_MAX_CHAN_USERS             "max_chan_users"
-#define KEY_MAX_CHAN_BANS      "max_chan_bans"
-#define KEY_NICK               "nick"
-#define KEY_OLD_CHANSERV_NAME  "old_chanserv_name"
-#define KEY_8BALL_RESPONSES     "8ball"
-#define KEY_OLD_BAN_NAMES       "old_ban_names"
-#define KEY_REFRESH_PERIOD      "refresh_period"
+#define KEY_SUPPORT_CHANNEL         "support_channel"
+#define KEY_SUPPORT_CHANNEL_MODES   "support_channel_modes"
+#define KEY_DB_BACKUP_FREQ          "db_backup_freq"
+#define KEY_INFO_DELAY              "info_delay"
+#define KEY_MAX_GREETLEN            "max_greetlen"
+#define KEY_ADJUST_THRESHOLD        "adjust_threshold"
+#define KEY_ADJUST_DELAY            "adjust_delay"
+#define KEY_CHAN_EXPIRE_FREQ        "chan_expire_freq"
+#define KEY_CHAN_EXPIRE_DELAY       "chan_expire_delay"
+#define KEY_DNR_EXPIRE_FREQ         "dnr_expire_freq"
+#define KEY_MAX_CHAN_USERS          "max_chan_users"
+#define KEY_MAX_CHAN_BANS           "max_chan_bans"
+#define KEY_MIN_TIME_BANS                      "min_time_bans"
+#define KEY_NICK                    "nick"
+#define KEY_OLD_CHANSERV_NAME       "old_chanserv_name"
+#define KEY_8BALL_RESPONSES         "8ball"
+#define KEY_OLD_BAN_NAMES           "old_ban_names"
+#define KEY_REFRESH_PERIOD          "refresh_period"
 #define KEY_CTCP_SHORT_BAN_DURATION "ctcp_short_ban_duration"
 #define KEY_CTCP_LONG_BAN_DURATION  "ctcp_long_ban_duration"
 #define KEY_MAX_OWNED               "max_owned"
 #define KEY_IRC_OPERATOR_EPITHET    "irc_operator_epithet"
 #define KEY_NETWORK_HELPER_EPITHET  "network_helper_epithet"
 #define KEY_SUPPORT_HELPER_EPITHET  "support_helper_epithet"
-#define KEY_NODELETE_LEVEL      "nodelete_level"
-#define KEY_MAX_USERINFO_LENGTH "max_userinfo_length"
-#define KEY_GIVEOWNERSHIP_PERIOD "giveownership_timeout"
+#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_REVOKE_MODE_A           "revoke_mode_a"
+#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_NOTE_TYPES          "note_types"
+#define KEY_CHANNELS                "channels"
+#define KEY_NOTE_TYPES              "note_types"
 
 /* Note type parameters */
-#define KEY_NOTE_OPSERV_ACCESS  "opserv_access"
-#define KEY_NOTE_CHANNEL_ACCESS "channel_access"
-#define KEY_NOTE_SETTER_ACCESS  "setter_access"
-#define KEY_NOTE_VISIBILITY     "visibility"
-#define KEY_NOTE_VIS_PRIVILEGED "privileged"
-#define KEY_NOTE_VIS_CHANNEL_USERS "channel_users"
-#define KEY_NOTE_VIS_ALL        "all"
-#define KEY_NOTE_MAX_LENGTH     "max_length"
-#define KEY_NOTE_SETTER         "setter"
-#define KEY_NOTE_NOTE           "note"
+#define KEY_NOTE_OPSERV_ACCESS      "opserv_access"
+#define KEY_NOTE_CHANNEL_ACCESS     "channel_access"
+#define KEY_NOTE_SETTER_ACCESS      "setter_access"
+#define KEY_NOTE_VISIBILITY         "visibility"
+#define KEY_NOTE_VIS_PRIVILEGED     "privileged"
+#define KEY_NOTE_VIS_CHANNEL_USERS  "channel_users"
+#define KEY_NOTE_VIS_ALL            "all"
+#define KEY_NOTE_MAX_LENGTH         "max_length"
+#define KEY_NOTE_SETTER             "setter"
+#define KEY_NOTE_NOTE               "note"
 
 /* Do-not-register channels */
-#define KEY_DNR                 "dnr"
-#define KEY_DNR_SET             "set"
-#define KEY_DNR_SETTER          "setter"
-#define KEY_DNR_REASON          "reason"
+#define KEY_DNR             "dnr"
+#define KEY_DNR_SET         "set"
+#define KEY_DNR_SETTER      "setter"
+#define KEY_DNR_REASON      "reason"
 
 /* Channel data */
-#define KEY_REGISTERED         "registered"
-#define KEY_REGISTRAR          "registrar"
-#define KEY_SUSPENDED           "suspended"
-#define KEY_PREVIOUS            "previous"
-#define KEY_SUSPENDER          "suspender"
-#define KEY_ISSUED              "issued"
-#define KEY_REVOKED             "revoked"
-#define KEY_SUSPEND_EXPIRES     "suspend_expires"
-#define KEY_SUSPEND_REASON      "suspend_reason"
-#define KEY_VISITED            "visited"
-#define KEY_TOPIC              "topic"
-#define KEY_GREETING           "greeting"
-#define KEY_USER_GREETING      "user_greeting"
-#define KEY_MODES              "modes"
-#define KEY_FLAGS              "flags"
-#define KEY_OPTIONS             "options"
-#define KEY_USERS              "users"
-#define KEY_BANS               "bans"
-#define KEY_MAX                        "max"
-#define KEY_NOTES               "notes"
-#define KEY_TOPIC_MASK          "topic_mask"
-#define KEY_OWNER_TRANSFER      "owner_transfer"
+#define KEY_REGISTERED      "registered"
+#define KEY_REGISTRAR       "registrar"
+#define KEY_SUSPENDED       "suspended"
+#define KEY_PREVIOUS        "previous"
+#define KEY_SUSPENDER       "suspender"
+#define KEY_ISSUED          "issued"
+#define KEY_REVOKED         "revoked"
+#define KEY_SUSPEND_EXPIRES "suspend_expires"
+#define KEY_SUSPEND_REASON  "suspend_reason"
+#define KEY_GIVEOWNERSHIP   "giveownership"
+#define KEY_STAFF_ISSUER    "staff_issuer"
+#define KEY_OLD_OWNER       "old_owner"
+#define KEY_TARGET          "target"
+#define KEY_TARGET_ACCESS   "target_access"
+#define KEY_VISITED         "visited"
+#define KEY_TOPIC           "topic"
+#define KEY_GREETING        "greeting"
+#define KEY_USER_GREETING   "user_greeting"
+#define KEY_MODES           "modes"
+#define KEY_FLAGS           "flags"
+#define KEY_OPTIONS         "options"
+#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_ADVTOPIC_ENTRIES "adv_topic"
+#define KEY_OWNER_TRANSFER  "owner_transfer"
+#define KEY_EXPIRE          "expire"
 
 /* User data */
-#define KEY_LEVEL              "level"
-#define KEY_INFO               "info"
-#define KEY_SEEN               "seen"
+#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"
-#define KEY_SET                        "set"
-#define KEY_DURATION           "duration"
-#define KEY_EXPIRES             "expires"
-#define KEY_TRIGGERED          "triggered"
+#define KEY_OWNER       "owner"
+#define KEY_REASON      "reason"
+#define KEY_SET         "set"
+#define KEY_DURATION    "duration"
+#define KEY_EXPIRES     "expires"
+#define KEY_TRIGGERED   "triggered"
 
 #define CHANNEL_DEFAULT_FLAGS   (CHANNEL_OFFCHANNEL | CHANNEL_UNREVIEWED)
 #define CHANNEL_PRESERVED_FLAGS (CHANNEL_UNREVIEWED)
@@ -130,6 +156,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_MUST_BE_OPPED", "You must be a channel operator in $b%s$b to register it." },
     { "CSMSG_PROXY_FORBIDDEN", "You may not register a channel for someone else." },
     { "CSMSG_OWN_TOO_MANY", "%s already owns enough channels (at least %d); use FORCE to override." },
+    { "CMSG_ALERT_REGISTERED" "%s registered to %s by %s." },
 
 /* Do-not-register channels */
     { "CSMSG_NOT_DNR", "$b%s$b is not a valid channel name or *account." },
@@ -152,6 +179,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_UNREG_NODELETE", "$b%s$b is protected from unregistration." },
     { "CSMSG_CHAN_SUSPENDED", "$b$C$b access to $b%s$b has been temporarily suspended (%s)." },
     { "CSMSG_CONFIRM_UNREG", "To confirm this unregistration, you must use 'unregister %s'." },
+    { "CMSG_ALERT_UNREGISTERED" "%s %s" },
 
 /* Channel moving */
     { "CSMSG_MOVE_SUCCESS", "Channel registration has been moved to $b%s$b." },
@@ -180,6 +208,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_ALREADY_VOICED", "You are already voiced in $b%s$b." },
     { "CSMSG_ALREADY_DOWN", "You are not opped or voiced in $b%s$b." },
     { "CSMSG_ALREADY_OPCHANNED", "There has been no net.join since the last opchan in $b%s$b." },
+    { "CSMSG_OUT_OF_CHANNEL", "For some reason I don't seem to be in $b%s$b." },
     { "CSMSG_OPCHAN_DONE", "I have (re-)opped myself in $b%s$b." },
 
 /* Removing yourself from a channel. */
@@ -190,7 +219,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." },
@@ -237,6 +266,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_TOPIC_LOCKED", "The %s topic is locked." },
     { "CSMSG_MASK_BUT_NO_TOPIC", "Warning: $b%s$b does not have a default topic, but you just set the topic mask." },
     { "CSMSG_TOPIC_MISMATCH", "Warning: The default topic for $b%s$b does not match the topic mask; changing it anyway." },
+    { "CSMSG_ADVTOPIC_INVALID_ID", "%d is an invalid advtopic id." },
 
     { "CSMSG_MODES_SET", "Channel modes are now $b%s$b." },
     { "CSMSG_DEFAULTED_MODES", "Channel modes for $b%s$b are set to their defaults." },
@@ -254,6 +284,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_INVALID_NUMERIC",   "$b%d$b is not a valid choice.  Choose one:" },
     { "CSMSG_SET_DEFAULT_TOPIC", "$bDefaultTopic$b %s" },
     { "CSMSG_SET_TOPICMASK",     "$bTopicMask   $b %s" },
+    { "CSMSG_SET_ADVTOPIC",      "$bAdvTopic    $b %s" },
     { "CSMSG_SET_GREETING",      "$bGreeting    $b %s" },
     { "CSMSG_SET_USERGREETING",  "$bUserGreeting$b %s" },
     { "CSMSG_SET_MODES",         "$bModes       $b %s" },
@@ -263,6 +294,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" },
@@ -276,6 +308,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" },
@@ -310,7 +344,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_INVITING_YOU", "$b%s$b invites you to join %s." },
     { "CSMSG_ALREADY_PRESENT", "%s is already in $b%s$b." },
     { "CSMSG_YOU_ALREADY_PRESENT", "You are already in $b%s$b." },
-    { "CSMSG_LOW_CHANNEL_ACCESS", "You lack sufficient access in %s to use this command." },
+    { "CSMSG_LOW_CHANNEL_ACCESS", "You lack sufficient access in %s for $S to invite you." },
     { "CSMSG_INFOLINE_TOO_LONG", "Your infoline may not exceed %u characters." },
     { "CSMSG_BAD_INFOLINE", "You may not use the character \\%03o in your infoline." },
 
@@ -323,6 +357,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:" },
@@ -365,6 +400,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." },
@@ -382,6 +420,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" },
@@ -399,10 +438,15 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_CHANNEL_REGISTERED", "$bRegistered:          $b%s ago." },
     { "CSMSG_CHANNEL_VISITED", "$bVisited:             $b%s ago." },
 
+    { "CSMSG_CHANNEL_OWNERSHIP_HISTORY", "Ownership transfer history for %s" },
+    { "CSMSG_CHANNEL_OWNERSHIP_STAFF_REASON", " from %s to %s (%d access) by %s on %s (Reason: %s)" },
+    { "CSMSG_CHANNEL_OWNERSHIP_STAFF", " from %s to %s (%d access) by %s on %s" },
+    { "CSMSG_CHANNEL_OWNERSHIP_NORMAL", " from %s to %s (%d access) on %s" },
+
     { "CSMSG_PEEK_INFO", "$b%s$b Status:" },
     { "CSMSG_PEEK_TOPIC", "$bTopic:          $b%s" },
     { "CSMSG_PEEK_MODES", "$bModes:          $b%s" },
-    { "CSMSG_PEEK_USERS", "$bTotal users:    $b%d" },
+    { "CSMSG_PEEK_USERS", "$bTotal users:    $b%d (%d ops, %d voices, %d regulars)" },
     { "CSMSG_PEEK_OPS", "$bOps:$b" },
     { "CSMSG_PEEK_NO_OPS", "$bOps:            $bNone present" },
 
@@ -436,7 +480,7 @@ static const struct message_entry msgtab[] = {
 
 /* User settings */
     { "CSMSG_USER_OPTIONS", "User Options:" },
-    { "CSMSG_USER_PROTECTED", "That user is protected." },
+    { "CSMSG_USER_PROTECTED_2", "That user is protected." },
 
 /* Toys */
     { "CSMSG_UNF_RESPONSE", "I don't want to be part of your sick fantasies!" },
@@ -450,35 +494,64 @@ 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 }
 };
 
 /* eject_user and unban_user flags */
-#define ACTION_KICK            0x0001
-#define ACTION_BAN             0x0002
-#define ACTION_ADD_BAN         0x0004
-#define ACTION_ADD_TIMED_BAN           0x0008
-#define ACTION_UNBAN           0x0010
-#define ACTION_DEL_BAN         0x0020
+#define ACTION_KICK             0x0001
+#define ACTION_BAN              0x0002
+#define ACTION_ADD_BAN          0x0004
+#define ACTION_ADD_TIMED_BAN    0x0008
+#define ACTION_UNBAN            0x0010
+#define ACTION_DEL_BAN          0x0020
 
 /* The 40 allows for [+-ntlksimprD] and lots of fudge factor. */
-#define MODELEN                        40 + KEYLEN
-#define PADLEN                 21
-#define ACCESSLEN              10
+#define MODELEN     40 + KEYLEN
+#define PADLEN      21
+#define ACCESSLEN   10
 
-#define CSFUNC_ARGS            user, channel, argc, argv, cmd
+#define CSFUNC_ARGS user, channel, argc, argv, cmd
 
 #define CHANSERV_FUNC(NAME) MODCMD_FUNC(NAME)
-#define CHANSERV_SYNTAX()      svccmd_send_help(user, chanserv, cmd)
-#define REQUIRE_PARAMS(N)      if(argc < (N)) {            \
-       reply("MSG_MISSING_PARAMS", argv[0]); \
-       CHANSERV_SYNTAX(); \
-       return 0; }
+#define CHANSERV_SYNTAX() svccmd_send_help(user, chanserv, cmd)
+#define REQUIRE_PARAMS(N)   if(argc < (N)) {            \
+        reply("MSG_MISSING_PARAMS", argv[0]); \
+        CHANSERV_SYNTAX(); \
+        return 0; }
 
 DECLARE_LIST(dnrList, struct do_not_register *);
-DEFINE_LIST(dnrList, struct do_not_register *);
+DEFINE_LIST(dnrList, struct do_not_register *)
+
+#define chanserv_notice(target, format...) send_message(target , chanserv , ## format)
+#define chanserv_oper_message(format...) do { if(chanserv_conf.oper_channel) send_channel_message(chanserv_conf.oper_channel , chanserv , ## format); } while(0)
 
 static int eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, char *argv[], struct svccmd *cmd, int action);
 
@@ -493,26 +566,31 @@ static struct
     struct channelList  support_channels;
     struct mod_chanmode default_modes;
 
-    unsigned long      db_backup_frequency;
-    unsigned long      channel_expire_frequency;
-    unsigned long      dnr_expire_frequency;
+    unsigned long   db_backup_frequency;
+    unsigned long   channel_expire_frequency;
+    unsigned long   dnr_expire_frequency;
+    
+    unsigned long   invited_timeout;
 
-    long               info_delay;
-    unsigned int       adjust_delay;
-    long               channel_expire_delay;
-    unsigned int        nodelete_level;
+    unsigned long   info_delay;
+    unsigned long   adjust_delay;
+    unsigned long   channel_expire_delay;
+    unsigned int    nodelete_level;
 
-    unsigned int       adjust_threshold;
-    int                        join_flood_threshold;
+    unsigned int    adjust_threshold;
+    int             join_flood_threshold;
 
-    unsigned int       greeting_length;
-    unsigned int        refresh_period;
-    unsigned int        giveownership_period;
+    unsigned int    greeting_length;
+    unsigned int    refresh_period;
+    unsigned int    giveownership_period;
 
-    unsigned int        max_owned;
-    unsigned int       max_chan_users;
-    unsigned int       max_chan_bans;
-    unsigned int        max_userinfo_length;
+    unsigned int    max_owned;
+    unsigned int    max_chan_users;
+    unsigned int    max_chan_bans;
+    unsigned int    min_time_bans;
+    unsigned int    max_userinfo_length;
+    
+    unsigned int    revoke_mode_a;
 
     struct string_list  *set_shows;
     struct string_list  *eightball;
@@ -524,6 +602,12 @@ 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;
+
+    struct chanNode    *oper_channel;
 } chanserv_conf;
 
 struct listData
@@ -538,6 +622,12 @@ struct listData
     struct helpfile_table table;
 };
 
+struct ChanUser
+{
+       struct userNode *user;
+    struct chanNode *chan;
+};
+
 enum note_access_type
 {
     NOTE_SET_CHANNEL_ACCESS,
@@ -607,7 +697,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 {
@@ -654,9 +745,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)
@@ -702,52 +793,52 @@ _GetChannelUser(struct chanData *channel, struct handle_info *handle, int overri
     if(override && HANDLE_FLAGGED(handle, HELPING)
        && ((handle->opserv_level >= chanserv_conf.nodelete_level) || !IsProtected(channel)))
     {
-       for(uData = helperList;
-           uData && uData->handle != handle;
-           uData = uData->next);
+        for(uData = helperList;
+            uData && uData->handle != handle;
+            uData = uData->next);
 
-       if(!uData)
-       {
-           uData = calloc(1, sizeof(struct userData));
-           uData->handle = handle;
+        if(!uData)
+        {
+            uData = calloc(1, sizeof(struct userData));
+            uData->handle = handle;
 
-           uData->access = UL_HELPER;
-           uData->seen = 0;
+            uData->access = UL_HELPER;
+            uData->seen = 0;
 
-           uData->info = NULL;
+            uData->info = NULL;
 
-           uData->prev = NULL;
-           uData->next = helperList;
-           if(helperList)
-               helperList->prev = uData;
-           helperList = uData;
-       }
+            uData->prev = NULL;
+            uData->next = helperList;
+            if(helperList)
+                helperList->prev = uData;
+            helperList = uData;
+        }
 
-       head = &helperList;
+        head = &helperList;
     }
     else
     {
-       for(uData = channel->users; uData; uData = uData->next)
+        for(uData = channel->users; uData; uData = uData->next)
             if((uData->handle == handle) && (allow_suspended || !IsUserSuspended(uData)))
                 break;
 
-       head = &(channel->users);
+        head = &(channel->users);
     }
 
     if(uData && (uData != *head))
     {
-       /* Shuffle the user to the head of whatever list he was in. */
-       if(uData->next)
+        /* Shuffle the user to the head of whatever list he was in. */
+        if(uData->next)
             uData->next->prev = uData->prev;
-       if(uData->prev)
+        if(uData->prev)
             uData->prev->next = uData->next;
 
-       uData->prev = NULL;
-       uData->next = *head;
+        uData->prev = NULL;
+        uData->next = *head;
 
-       if(*head)
-           (**head).prev = uData;
-       *head = uData;
+        if(*head)
+            (**head).prev = uData;
+        *head = uData;
     }
 
     return uData;
@@ -798,7 +889,7 @@ scan_user_presence(struct userData *uData, struct userNode *user)
 }
 
 static void
-chanserv_ctcp_check(struct userNode *user, struct chanNode *channel, const char *text, UNUSED_ARG(struct userNode *bot))
+chanserv_ctcp_check(struct userNode *user, struct chanNode *channel, const char *text, UNUSED_ARG(struct userNode *bot), UNUSED_ARG(unsigned int is_notice))
 {
     unsigned int eflags, argc;
     char *argv[4];
@@ -848,13 +939,22 @@ 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)
 {
     struct note_type *ntype = data;
 
     if(--ntype->refs > 0)
-       return;
+        return;
     free(ntype);
 }
 
@@ -872,12 +972,12 @@ chanserv_truncate_notes(struct note_type *ntype)
     struct chanData *cData;
     struct note *note;
     unsigned int size = sizeof(*note) + ntype->max_length;
-    
+
     for(cData = channelList; cData; cData = cData->next) {
         note = dict_find(cData->notes, ntype->name, NULL);
         if(!note)
             continue;
-       if(strlen(note->note) <= ntype->max_length)
+        if(strlen(note->note) <= ntype->max_length)
             continue;
         dict_remove2(cData->notes, ntype->name, 1);
         note = realloc(note, size);
@@ -922,7 +1022,7 @@ static MODCMD_FUNC(cmd_createnote) {
     if((ntype = dict_find(note_types, argv[1], NULL)))
         existed = 1;
     else
-       ntype = chanserv_create_note_type(argv[arg]);
+        ntype = chanserv_create_note_type(argv[arg]);
     if(!irccasecmp(argv[++arg], "privileged"))
     {
         arg++;
@@ -935,7 +1035,7 @@ static MODCMD_FUNC(cmd_createnote) {
         if(!ulvl)
         {
             reply("CSMSG_INVALID_ACCESS", argv[arg]);
-           goto fail;
+            goto fail;
         }
         ntype->set_access_type = NOTE_SET_CHANNEL_ACCESS;
         ntype->set_access.min_ulevel = ulvl;
@@ -947,7 +1047,7 @@ static MODCMD_FUNC(cmd_createnote) {
     else
     {
         reply("CSMSG_BAD_NOTE_ACCESS", argv[arg]);
-       goto fail;
+        goto fail;
     }
 
     if(!irccasecmp(argv[++arg], "privileged"))
@@ -958,35 +1058,35 @@ static MODCMD_FUNC(cmd_createnote) {
         ntype->visible_type = NOTE_VIS_ALL;
     else {
         reply("CSMSG_BAD_NOTE_ACCESS", argv[arg]);
-       goto fail;
+        goto fail;
     }
 
     if((arg+1) >= argc) {
         reply("MSG_MISSING_PARAMS", argv[0]);
-       goto fail;
+        goto fail;
     }
     max_length = strtoul(argv[++arg], NULL, 0);
     if(max_length < 20 || max_length > 450)
     {
         reply("CSMSG_BAD_MAX_LENGTH", argv[arg]);
-       goto fail;
+        goto fail;
     }
     if(existed && (max_length < ntype->max_length))
     {
-       ntype->max_length = max_length;
-       chanserv_truncate_notes(ntype);
+        ntype->max_length = max_length;
+        chanserv_truncate_notes(ntype);
     }
     ntype->max_length = max_length;
 
     if(existed)
-        reply("CSMSG_NOTE_MODIFIED", ntype->name);    
+        reply("CSMSG_NOTE_MODIFIED", ntype->name);
     else
         reply("CSMSG_NOTE_CREATED", ntype->name);
     return 1;
 
 fail:
     if(!existed)
-       dict_remove(note_types, ntype->name);
+        dict_remove(note_types, ntype->name);
     return 0;
 }
 
@@ -1015,6 +1115,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)
 {
@@ -1082,6 +1241,7 @@ register_channel(struct chanNode *cNode, char *registrar)
     struct chanData *channel;
     enum levelOption lvlOpt;
     enum charOption chOpt;
+    int i;
 
     channel = calloc(1, sizeof(struct chanData));
 
@@ -1098,41 +1258,45 @@ register_channel(struct chanNode *cNode, char *registrar)
         channel->lvlOpts[lvlOpt] = levelOptions[lvlOpt].default_value;
     for(chOpt = 0; chOpt < NUM_CHAR_OPTIONS; ++chOpt)
         channel->chOpts[chOpt] = charOptions[chOpt].default_value;
+    for(i = 0; i < MAXADVTOPICENTRIES; i++) 
+        channel->advtopic[i] = NULL;
 
     channel->prev = NULL;
     channel->next = channelList;
 
     if(channelList)
-       channelList->prev = channel;
+        channelList->prev = channel;
     channelList = channel;
     registered_channels++;
 
     channel->channel = cNode;
     LockChannel(cNode);
     cNode->channel_info = channel;
+    
+    channel->vote = NULL;
 
     return channel;
 }
 
 static struct userData*
-add_channel_user(struct chanData *channel, struct handle_info *handle, unsigned short access, time_t seen, const char *info)
+add_channel_user(struct chanData *channel, struct handle_info *handle, unsigned short access_level, unsigned long seen, const char *info)
 {
     struct userData *ud;
 
-    if(access > UL_OWNER)
-       return NULL;
+    if(access_level > UL_OWNER)
+        return NULL;
 
     ud = calloc(1, sizeof(*ud));
     ud->channel = channel;
     ud->handle = handle;
     ud->seen = seen;
-    ud->access = access;
+    ud->access = access_level;
     ud->info = info ? strdup(info) : NULL;
 
     ud->prev = NULL;
     ud->next = channel->users;
     if(channel->users)
-       channel->users->prev = ud;
+        channel->users->prev = ud;
     channel->users = ud;
 
     channel->userCount++;
@@ -1147,8 +1311,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)
 {
@@ -1173,14 +1335,16 @@ 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*
-add_channel_ban(struct chanData *channel, const char *mask, char *owner, time_t set, time_t triggered, time_t expires, char *reason)
+struct banData*
+add_channel_ban(struct chanData *channel, const char *mask, char *owner, unsigned long set, unsigned long triggered, unsigned long expires, char *reason)
 {
     struct banData *bd;
     unsigned int ii, l1, l2;
@@ -1217,12 +1381,12 @@ add_channel_ban(struct chanData *channel, const char *mask, char *owner, time_t
     bd->reason = strdup(reason);
 
     if(expires)
-       timeq_add(expires, expire_ban, bd);
+        timeq_add(expires, expire_ban, bd);
 
     bd->prev = NULL;
     bd->next = channel->bans;
     if(channel->bans)
-       channel->bans->prev = bd;
+        channel->bans->prev = bd;
     channel->bans = bd;
     channel->banCount++;
     banCount++;
@@ -1245,7 +1409,7 @@ del_channel_ban(struct banData *ban)
         ban->next->prev = ban->prev;
 
     if(ban->expires)
-       timeq_del(0, expire_ban, ban, TIMEQ_IGNORE_WHEN);
+        timeq_del(0, expire_ban, ban, TIMEQ_IGNORE_WHEN);
 
     if(ban->reason)
         free(ban->reason);
@@ -1267,7 +1431,7 @@ expire_ban(void *data)
         for(ii=0; ii<bans.used; ii++)
         {
             if(!strcmp(bans.list[ii]->ban, bd->mask))
-           {
+            {
                 change.argc = 1;
                 change.args[0].mode = MODE_REMOVE|MODE_BAN;
                 change.args[0].u.hostmask = bd->mask;
@@ -1287,6 +1451,7 @@ unregister_channel(struct chanData *channel, const char *reason)
 {
     struct mod_chanmode change;
     char msgbuf[MAXLEN];
+    int i;
 
     /* After channel unregistration, the following must be cleaned
        up:
@@ -1298,28 +1463,35 @@ unregister_channel(struct chanData *channel, const char *reason)
     */
 
     if(!channel)
-       return;
+        return;
 
     timeq_del(0, NULL, channel, TIMEQ_IGNORE_FUNC | TIMEQ_IGNORE_WHEN);
 
-    if(off_channel > 0)
-    {
-      mod_chanmode_init(&change);
-      change.modes_clear |= MODE_REGISTERED;
-      mod_chanmode_announce(chanserv, channel->channel, &change);
+    if(off_channel > 0 || chanserv_conf.revoke_mode_a) {
+        mod_chanmode_init(&change);
+        if(off_channel > 0)
+            change.modes_clear |= MODE_REGISTERED;
+        if(chanserv_conf.revoke_mode_a)
+            change.modes_clear |= MODE_ACCESS;
+        mod_chanmode_announce(chanserv, channel->channel, &change);
     }
 
     while(channel->users)
-       del_channel_user(channel->users, 0);
+        del_channel_user(channel->users, 0);
 
     while(channel->bans)
-       del_channel_ban(channel->bans);
+        del_channel_ban(channel->bans);
 
     free(channel->topic);
     free(channel->registrar);
     free(channel->greeting);
     free(channel->user_greeting);
     free(channel->topic_mask);
+    
+    for(i = 0; i < MAXADVTOPICENTRIES; i++) {
+        if(channel->advtopic[i]) 
+            free(channel->advtopic[i]);
+    }
 
     if(channel->prev)
         channel->prev->next = channel->next;
@@ -1331,8 +1503,8 @@ unregister_channel(struct chanData *channel, const char *reason)
 
     if(channel->suspended)
     {
-       struct chanNode *cNode = channel->channel;
-       struct suspended *suspended, *next_suspended;
+        struct chanNode *cNode = channel->channel;
+        struct suspended *suspended, *next_suspended;
 
         for(suspended = channel->suspended; suspended; suspended = next_suspended)
         {
@@ -1344,23 +1516,24 @@ unregister_channel(struct chanData *channel, const char *reason)
             free(suspended);
         }
 
-       if(cNode)
-           cNode->channel_info = NULL;
+        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);
-    sprintf(msgbuf, "%s %s", channel->channel->name, reason);
     if(!IsSuspended(channel))
         DelChannelUser(chanserv, channel->channel, msgbuf, 0);
-    global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, msgbuf);
+    chanserv_oper_message(CSMSGL_CHANNEL_UNREGISTERED, channel->channel->name, reason);
     UnlockChannel(channel->channel);
     free(channel);
     registered_channels--;
 }
 
 static void
-expire_channels(UNUSED_ARG(void *data))
+expire_channels(void *data)
 {
     struct chanData *channel, *next;
     struct userData *user;
@@ -1371,7 +1544,7 @@ expire_channels(UNUSED_ARG(void *data))
 
     for(channel = channelList; channel; channel = next)
     {
-       next = channel->next;
+        next = channel->next;
 
         /* See if the channel can be expired. */
         if(((now - channel->visited) <= chanserv_conf.channel_expire_delay)
@@ -1380,46 +1553,47 @@ expire_channels(UNUSED_ARG(void *data))
 
         /* Make sure there are no high-ranking users still in the channel. */
         for(user=channel->users; user; user=user->next)
-            if(user->present && (user->access >= UL_PRESENT))
+            if(user->present && (user->access >= UL_PRESENT) && !HANDLE_FLAGGED(user->handle, BOT))
                 break;
         if(user)
             continue;
 
         /* Unregister the channel */
         log_module(CS_LOG, LOG_INFO, "(%s) Channel registration expired.", channel->channel->name);
+        spamserv_cs_unregister(NULL, channel->channel, expire, NULL);
         unregister_channel(channel, "registration expired.");
     }
 
-    if(chanserv_conf.channel_expire_frequency)
-       timeq_add(now + chanserv_conf.channel_expire_frequency, expire_channels, NULL);
+    if(chanserv_conf.channel_expire_frequency && !data)
+        timeq_add(now + chanserv_conf.channel_expire_frequency, expire_channels, NULL);
 }
 
 static void
 expire_dnrs(UNUSED_ARG(void *data))
 {
-    dict_iterator_t it;
+    dict_iterator_t it, next;
     struct do_not_register *dnr;
 
-    for(it = dict_first(handle_dnrs); it; it = iter_next(it))
+    for(it = dict_first(handle_dnrs); it; it = next)
     {
         dnr = iter_data(it);
-        if(!dnr->expires || dnr->expires > now)
-            continue;
-        dict_remove(handle_dnrs, dnr->chan_name + 1);
+        next = iter_next(it);
+        if(dnr->expires && dnr->expires <= now)
+            dict_remove(handle_dnrs, dnr->chan_name + 1);
     }
-    for(it = dict_first(plain_dnrs); it; it = iter_next(it))
+    for(it = dict_first(plain_dnrs); it; it = next)
     {
         dnr = iter_data(it);
-        if(!dnr->expires || dnr->expires > now)
-            continue;
-        dict_remove(plain_dnrs, dnr->chan_name);
+        next = iter_next(it);
+        if(dnr->expires && dnr->expires <= now)
+            dict_remove(plain_dnrs, dnr->chan_name + 1);
     }
-    for(it = dict_first(mask_dnrs); it; it = iter_next(it))
+    for(it = dict_first(mask_dnrs); it; it = next)
     {
         dnr = iter_data(it);
-        if(!dnr->expires || dnr->expires > now)
-            continue;
-        dict_remove(mask_dnrs, dnr->chan_name);
+        next = iter_next(it);
+        if(dnr->expires && dnr->expires <= now)
+            dict_remove(mask_dnrs, dnr->chan_name + 1);
     }
 
     if(chanserv_conf.dnr_expire_frequency)
@@ -1435,7 +1609,7 @@ protect_user(const struct userNode *victim, const struct userNode *aggressor, st
     /* Don't protect if no one is to be protected, someone is attacking
        himself, or if the aggressor is an IRC Operator. */
     if(protect == 'n' || victim == aggressor || IsOper(aggressor))
-       return 0;
+        return 0;
 
     /* Don't protect if the victim isn't authenticated (because they
        can't be a channel user), unless we are to protect non-users
@@ -1457,14 +1631,14 @@ protect_user(const struct userNode *victim, const struct userNode *aggressor, st
     switch(protect)
     {
     case 'l':
-       if(cs_victim->access > cs_aggressor->access)
+        if(cs_victim->access > cs_aggressor->access)
             return 1;
-       break;
+        break;
     case 'a':
     case 'e':
-       if(cs_victim->access >= cs_aggressor->access)
+        if(cs_victim->access >= cs_aggressor->access)
             return 1;
-       break;
+        break;
     }
 
     return 0;
@@ -1480,8 +1654,8 @@ validate_op(struct userNode *user, struct chanNode *channel, struct userNode *vi
         || (cs_victim->access < cData->lvlOpts[lvlGiveOps]))
        && !check_user_level(channel, user, lvlEnfOps, 0, 0))
     {
-       send_message(user, chanserv, "CSMSG_OPBY_LOCKED");
-       return 0;
+        send_message(user, chanserv, "CSMSG_OPBY_LOCKED");
+        return 0;
     }
 
     return 1;
@@ -1492,21 +1666,21 @@ validate_deop(struct userNode *user, struct chanNode *channel, struct userNode *
 {
     if(IsService(victim))
     {
-       send_message(user, chanserv, "MSG_SERVICE_IMMUNE", victim->nick);
-       return 0;
+        send_message(user, chanserv, "MSG_SERVICE_IMMUNE", victim->nick);
+        return 0;
     }
 
     if(protect_user(victim, user, channel->channel_info))
     {
-       send_message(user, chanserv, "CSMSG_USER_PROTECTED", victim->nick);
-       return 0;
+        send_message(user, chanserv, "CSMSG_USER_PROTECTED", victim->nick);
+        return 0;
     }
 
     return 1;
 }
 
 static struct do_not_register *
-chanserv_add_dnr(const char *chan_name, const char *setter, time_t expires, const char *reason)
+chanserv_add_dnr(const char *chan_name, const char *setter, unsigned long expires, const char *reason)
 {
     struct do_not_register *dnr = calloc(1, sizeof(*dnr)+strlen(reason));
     safestrncpy(dnr->chan_name, chan_name, sizeof(dnr->chan_name));
@@ -1571,13 +1745,18 @@ static int dnr_print_func(struct do_not_register *dnr, void *extra)
     struct userNode *user;
     char buf1[INTERVALLEN];
     char buf2[INTERVALLEN];
+    time_t feh;
 
     user = extra;
     if(dnr->set)
-        strftime(buf1, sizeof(buf1), "%d %b %Y", localtime(&dnr->set));
+    {
+        feh = dnr->set;
+        strftime(buf1, sizeof(buf1), "%d %b %Y", localtime(&feh));
+    }
     if(dnr->expires)
     {
-        strftime(buf2, sizeof(buf2), "%d %b %Y", localtime(&dnr->expires));
+        feh = dnr->expires;
+        strftime(buf2, sizeof(buf2), "%d %b %Y", localtime(&feh));
         send_message(user, chanserv, "CSMSG_DNR_INFO_SET_EXPIRES", dnr->chan_name, buf1, dnr->setter, buf2, dnr->reason);
     }
     else if(dnr->set)
@@ -1610,7 +1789,7 @@ chanserv_is_dnr(const char *chan_name, struct handle_info *handle)
     struct dnrList list;
     struct do_not_register *dnr;
 
-    list = chanserv_find_dnrs(chan_name, handle->handle, 1);
+    list = chanserv_find_dnrs(chan_name, handle ? handle->handle : NULL, 1);
     dnr = list.used ? list.list[0] : NULL;
     free(list.list);
     return dnr;
@@ -1641,7 +1820,8 @@ static unsigned int send_dnrs(struct userNode *user, dict_t dict)
 static CHANSERV_FUNC(cmd_noregister)
 {
     const char *target;
-    time_t expiry, duration;
+    const char *reason;
+    unsigned long expiry, duration;
     unsigned int matches;
 
     if(argc < 2)
@@ -1683,7 +1863,7 @@ static CHANSERV_FUNC(cmd_noregister)
             return 0;
         }
 
-        const char *reason = unsplit_string(argv + 3, argc - 3, NULL);
+        reason = unsplit_string(argv + 3, argc - 3, NULL);
         if((*target == '*') && !get_handle_info(target + 1))
         {
             reply("MSG_HANDLE_UNKNOWN", target + 1);
@@ -1724,8 +1904,8 @@ struct dnr_search {
     char *chan_mask;
     char *setter_mask;
     char *reason_mask;
-    time_t min_set, max_set;
-    time_t min_expires, max_expires;
+    unsigned long min_set, max_set;
+    unsigned long min_expires, max_expires;
     unsigned int limit;
 };
 
@@ -1734,14 +1914,16 @@ dnr_search_matches(const struct do_not_register *dnr, const struct dnr_search *s
 {
     return !((dnr->set < search->min_set)
              || (dnr->set > search->max_set)
-             || (dnr->expires && ((dnr->expires < search->min_expires)
-                                  || (dnr->expires > search->max_expires)))
+             || (dnr->expires < search->min_expires)
+             || (search->max_expires
+                 && ((dnr->expires == 0)
+                     || (dnr->expires > search->max_expires)))
              || (search->chan_mask
-                 && !match_ircglob(search->chan_mask, dnr->chan_name))
+                 && !match_ircglob(dnr->chan_name, search->chan_mask))
              || (search->setter_mask
-                 && !match_ircglob(search->setter_mask, dnr->setter))
+                 && !match_ircglob(dnr->setter, search->setter_mask))
              || (search->reason_mask
-                 && !match_ircglob(search->reason_mask, dnr->reason)));
+                 && !match_ircglob(dnr->reason, search->reason_mask)));
 }
 
 static struct dnr_search *
@@ -1756,7 +1938,6 @@ dnr_search_create(struct userNode *user, struct svccmd *cmd, unsigned int argc,
     discrim->setter_mask = NULL;
     discrim->reason_mask = NULL;
     discrim->max_set = INT_MAX;
-    discrim->max_expires = INT_MAX;
     discrim->limit = 50;
 
     for(ii=0; ii<argc; ++ii)
@@ -1918,6 +2099,12 @@ dnr_remove_func(struct do_not_register *match, void *extra)
     return 0;
 }
 
+static int
+dnr_count_func(struct do_not_register *match, void *extra)
+{
+    return 0; (void)match; (void)extra;
+}
+
 static MODCMD_FUNC(cmd_dnrsearch)
 {
     struct dnr_search *discrim;
@@ -1939,6 +2126,8 @@ static MODCMD_FUNC(cmd_dnrsearch)
         action = dnr_print_func;
     else if(!irccasecmp(argv[1], "remove"))
         action = dnr_remove_func;
+    else if(!irccasecmp(argv[1], "count"))
+        action = dnr_count_func;
     else
     {
         reply("CSMSG_DNR_BAD_ACTION", argv[1]);
@@ -2026,20 +2215,20 @@ static CHANSERV_FUNC(cmd_register)
 
     if(argc >= (new_channel+2))
     {
-       if(!IsHelping(user))
-       {
-           reply("CSMSG_PROXY_FORBIDDEN");
-           return 0;
-       }
+        if(!IsHelping(user))
+        {
+            reply("CSMSG_PROXY_FORBIDDEN");
+            return 0;
+        }
 
-       if(!(handle = modcmd_get_handle_info(user, argv[new_channel+1])))
+        if(!(handle = modcmd_get_handle_info(user, argv[new_channel+1])))
             return 0;
         force = (argc > (new_channel+2)) && !irccasecmp(argv[new_channel+2], "force");
         dnr = chanserv_is_dnr(chan_name, handle);
     }
     else
     {
-       handle = user->handle_info;
+        handle = user->handle_info;
         dnr = chanserv_is_dnr(chan_name, handle);
     }
     if(dnr && !force)
@@ -2081,14 +2270,14 @@ 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);
     else
         reply("CSMSG_REG_SUCCESS", channel->name);
 
-    sprintf(reason, "%s registered to %s by %s.", channel->name, handle->handle, user->handle_info->handle);
-    global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+    chanserv_oper_message(CSMSG_ALERT_REGISTERED, channel->name, handle->handle, user->handle_info->handle);
     return 1;
 }
 
@@ -2130,7 +2319,7 @@ static CHANSERV_FUNC(cmd_unregister)
         return 0;
     }
 
-    if(IsProtected(cData))
+    if(IsProtected(cData) && !IsOper(user))
     {
         reply("CSMSG_UNREG_NODELETE", channel->name);
         return 0;
@@ -2145,21 +2334,49 @@ static CHANSERV_FUNC(cmd_unregister)
             return 0;
         }
         confirm_string = make_confirmation_string(uData);
-       if((argc < 2) || strcmp(argv[1], confirm_string))
-       {
-           reply("CSMSG_CONFIRM_UNREG", confirm_string);
-           return 0;
-       }
+        if((argc < 2) || strcmp(argv[1], confirm_string))
+        {
+            reply("CSMSG_CONFIRM_UNREG", confirm_string);
+            return 0;
+        }
     }
 
     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;
@@ -2168,13 +2385,14 @@ 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);
 
     if(IsProtected(channel->channel_info))
     {
-       reply("CSMSG_MOVE_NODELETE", channel->name);
-       return 0;
+        reply("CSMSG_MOVE_NODELETE", channel->name);
+        return 0;
     }
 
     if(!IsChannelName(argv[1]))
@@ -2209,12 +2427,12 @@ 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)
     {
-       reply("CSMSG_ALREADY_REGGED", target->name);
-       return 0;
+        reply("CSMSG_ALREADY_REGGED", target->name);
+        return 0;
     }
     else if((!(mn = GetUserMode(target, user)) || !(mn->modes && MODE_CHANOP))
             && !IsHelping(user))
@@ -2223,12 +2441,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)
     {
@@ -2248,18 +2461,26 @@ static CHANSERV_FUNC(cmd_move)
     target->channel_info->channel = target;
     channel->channel_info = NULL;
 
-    reply("CSMSG_MOVE_SUCCESS", target->name);
+    /* Check whether users are present in the new channel. */
+    for(uData = target->channel_info->users; uData; uData = uData->next)
+        scan_user_presence(uData, NULL);
+
+    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))
     {
         char reason2[MAXLEN];
-       sprintf(reason2, "Channel moved to %s by %s.", target->name, user->handle_info->handle);
-       DelChannelUser(chanserv, channel, reason2, 0);
+        sprintf(reason2, "Channel moved to %s by %s.", target->name, user->handle_info->handle);
+        DelChannelUser(chanserv, channel, reason2, 0);
     }
     UnlockChannel(channel);
     LockChannel(target);
     global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+    reply("CSMSG_MOVE_SUCCESS", target->name);
     return 1;
 }
 
@@ -2274,7 +2495,7 @@ merge_users(struct chanData *source, struct chanData *target)
 
     /* Insert the source's users into the scratch area. */
     for(suData = source->users; suData; suData = suData->next)
-       dict_insert(merge, suData->handle->handle, suData);
+        dict_insert(merge, suData->handle->handle, suData);
 
     /* Iterate through the target's users, looking for
        users common to both channels. The lower access is
@@ -2282,50 +2503,58 @@ merge_users(struct chanData *source, struct chanData *target)
        list. */
     for(tuData = target->users; tuData; tuData = next)
     {
-       struct userData *choice;
+        struct userData *choice;
 
-       next = tuData->next;
+        next = tuData->next;
 
-       /* If a source user exists with the same handle as a target
-          channel's user, resolve the conflict by removing one. */
-       suData = dict_find(merge, tuData->handle->handle, NULL);
-       if(!suData)
-           continue;
+        /* If a source user exists with the same handle as a target
+           channel's user, resolve the conflict by removing one. */
+        suData = dict_find(merge, tuData->handle->handle, NULL);
+        if(!suData)
+            continue;
 
-       /* Pick the data we want to keep. */
+        /* Pick the data we want to keep. */
         /* If the access is the same, use the later seen time. */
-       if(suData->access == tuData->access)
-           choice = (suData->seen > tuData->seen) ? suData : tuData;
-       else /* Otherwise, keep the higher access level. */
-           choice = (suData->access > tuData->access) ? suData : tuData;
+        if(suData->access == tuData->access)
+            choice = (suData->seen > tuData->seen) ? suData : tuData;
+        else /* Otherwise, keep the higher access level. */
+            choice = (suData->access > tuData->access) ? suData : tuData;
+        /* Use the later seen time. */
+        if(suData->seen < tuData->seen)
+            suData->seen = tuData->seen;
+        else
+            tuData->seen = suData->seen;
 
-       /* Remove the user that wasn't picked. */
-       if(choice == tuData)
-       {
-           dict_remove(merge, suData->handle->handle);
-           del_channel_user(suData, 0);
-       }
-       else
-           del_channel_user(tuData, 0);
+        /* Remove the user that wasn't picked. */
+        if(choice == tuData)
+        {
+            dict_remove(merge, suData->handle->handle);
+            del_channel_user(suData, 0);
+        }
+        else
+            del_channel_user(tuData, 0);
     }
 
     /* Move the remaining users to the target channel. */
     for(it = dict_first(merge); it; it = iter_next(it))
     {
-       suData = iter_data(it);
+        suData = iter_data(it);
 
-       /* Insert the user into the target channel's linked list. */
-       suData->prev = NULL;
-       suData->next = target->users;
+        /* Insert the user into the target channel's linked list. */
+        suData->prev = NULL;
+        suData->next = target->users;
         suData->channel = target;
 
-       if(target->users)
-           target->users->prev = suData;
-       target->users = suData;
+        if(target->users)
+            target->users->prev = suData;
+        target->users = suData;
+
+        /* Update the user counts for the target channel; the
+           source counts are left alone. */
+        target->userCount++;
 
-       /* Update the user counts for the target channel; the
-          source counts are left alone. */
-       target->userCount++;
+        /* Check whether the user is in the target channel. */
+        scan_user_presence(suData, NULL);
     }
 
     /* Possible to assert (source->users == NULL) here. */
@@ -2345,72 +2574,72 @@ merge_bans(struct chanData *source, struct chanData *target)
     /* Perform a totally expensive O(n*m) merge, ick. */
     for(sbData = source->bans; sbData; sbData = sNext)
     {
-       /* Flag to track whether the ban's been moved
-          to the destination yet. */
-       int moved = 0;
+        /* Flag to track whether the ban's been moved
+           to the destination yet. */
+        int moved = 0;
 
-       /* Possible to assert (sbData->prev == NULL) here. */
-       sNext = sbData->next;
+        /* Possible to assert (sbData->prev == NULL) here. */
+        sNext = sbData->next;
 
-       for(tbData = tFront; tbData; tbData = tNext)
-       {
-           tNext = tbData->next;
-
-           /* Perform two comparisons between each source
-              and target ban, conflicts are resolved by
-              keeping the broader ban and copying the later
-              expiration and triggered time. */
-           if(match_ircglobs(tbData->mask, sbData->mask))
-           {
-               /* There is a broader ban in the target channel that
-                  overrides one in the source channel; remove the 
-                  source ban and break. */
-               if(sbData->expires > tbData->expires)
-                   tbData->expires = sbData->expires;
-               if(sbData->triggered > tbData->triggered)
-                   tbData->triggered = sbData->triggered;
-               del_channel_ban(sbData);
-               break;
-           }
-           else if(match_ircglobs(sbData->mask, tbData->mask))
-           {
-               /* There is a broader ban in the source channel that
-                  overrides one in the target channel; remove the
-                  target ban, fall through and move the source over. */
-               if(tbData->expires > sbData->expires)
-                   sbData->expires = tbData->expires;
-               if(tbData->triggered > sbData->triggered)
-                   sbData->triggered = tbData->triggered;
-               if(tbData == tFront)
-                   tFront = tNext;
-               del_channel_ban(tbData);
-           }
-
-           /* Source bans can override multiple target bans, so
-              we allow a source to run through this loop multiple
-              times, but we can only move it once. */
-           if(moved)
-               continue;
-           moved = 1;
-
-           /* Remove the source ban from the source ban list. */
-           if(sbData->next)
-               sbData->next->prev = sbData->prev;
-
-           /* Modify the source ban's associated channel. */
-           sbData->channel = target;
-
-           /* Insert the ban into the target channel's linked list. */
-           sbData->prev = NULL;
-           sbData->next = target->bans;
-
-           if(target->bans)
-               target->bans->prev = sbData;
-           target->bans = sbData;
-
-           /* Update the user counts for the target channel. */
-           target->banCount++;
-       }
+        for(tbData = tFront; tbData; tbData = tNext)
+        {
+            tNext = tbData->next;
+
+            /* Perform two comparisons between each source
+               and target ban, conflicts are resolved by
+               keeping the broader ban and copying the later
+               expiration and triggered time. */
+            if(match_ircglobs(tbData->mask, sbData->mask))
+            {
+                /* There is a broader ban in the target channel that
+                   overrides one in the source channel; remove the
+                   source ban and break. */
+                if(sbData->expires > tbData->expires)
+                    tbData->expires = sbData->expires;
+                if(sbData->triggered > tbData->triggered)
+                    tbData->triggered = sbData->triggered;
+                del_channel_ban(sbData);
+                break;
+            }
+            else if(match_ircglobs(sbData->mask, tbData->mask))
+            {
+                /* There is a broader ban in the source channel that
+                   overrides one in the target channel; remove the
+                   target ban, fall through and move the source over. */
+                if(tbData->expires > sbData->expires)
+                    sbData->expires = tbData->expires;
+                if(tbData->triggered > sbData->triggered)
+                    sbData->triggered = tbData->triggered;
+                if(tbData == tFront)
+                    tFront = tNext;
+                del_channel_ban(tbData);
+            }
+
+            /* Source bans can override multiple target bans, so
+               we allow a source to run through this loop multiple
+               times, but we can only move it once. */
+            if(moved)
+                continue;
+            moved = 1;
+
+            /* Remove the source ban from the source ban list. */
+            if(sbData->next)
+                sbData->next->prev = sbData->prev;
+
+            /* Modify the source ban's associated channel. */
+            sbData->channel = target;
+
+            /* Insert the ban into the target channel's linked list. */
+            sbData->prev = NULL;
+            sbData->next = target->bans;
+
+            if(target->bans)
+                target->bans->prev = sbData;
+            target->bans = sbData;
+
+            /* Update the user counts for the target channel. */
+            target->banCount++;
+        }
     }
 
     /* Possible to assert (source->bans == NULL) here. */
@@ -2425,15 +2654,17 @@ merge_data(struct chanData *source, struct chanData *target)
      * Do not touch last_refresh, ban count or user counts.
      */
     if(source->visited > target->visited)
-       target->visited = source->visited;
+        target->visited = source->visited;
     if(source->registered < target->registered)
         target->registered = source->registered;
     if(source->ownerTransfer > target->ownerTransfer)
         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
@@ -2493,6 +2724,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);
@@ -2512,6 +2744,11 @@ static CHANSERV_FUNC(cmd_opchan)
     change.argc = 1;
     change.args[0].mode = MODE_CHANOP;
     change.args[0].u.member = GetUserMode(channel, chanserv);
+    if(!change.args[0].u.member)
+    {
+        reply("CSMSG_OUT_OF_CHANNEL", channel->name);
+        return 0;
+    }
     mod_chanmode_announce(chanserv, channel, &change);
     reply("CSMSG_OPCHAN_DONE", channel->name);
     return 1;
@@ -2522,34 +2759,34 @@ static CHANSERV_FUNC(cmd_adduser)
     struct userData *actee;
     struct userData *actor, *real_actor;
     struct handle_info *handle;
-    unsigned short access, override = 0;
+    unsigned short access_level, override = 0;
 
     REQUIRE_PARAMS(3);
 
     if(channel->channel_info->userCount >= chanserv_conf.max_chan_users)
     {
-       reply("CSMSG_MAXIMUM_USERS", chanserv_conf.max_chan_users);
-       return 0;
+        reply("CSMSG_MAXIMUM_USERS", chanserv_conf.max_chan_users);
+        return 0;
     }
 
-    access = user_level_from_name(argv[2], UL_OWNER);
-    if(!access)
+    access_level = user_level_from_name(argv[2], UL_OWNER);
+    if(!access_level)
     {
-       reply("CSMSG_INVALID_ACCESS", argv[2]);
-       return 0;
+        reply("CSMSG_INVALID_ACCESS", argv[2]);
+        return 0;
     }
 
     actor = GetChannelUser(channel->channel_info, user->handle_info);
     real_actor = GetChannelAccess(channel->channel_info, user->handle_info);
 
-    if(actor->access <= access)
+    if(actor->access <= access_level)
     {
-       reply("CSMSG_NO_BUMP_ACCESS");
-       return 0;
+        reply("CSMSG_NO_BUMP_ACCESS");
+        return 0;
     }
 
     /* Trying to add someone with equal/more access? */
-    if (!real_actor || real_actor->access <= access)
+    if (!real_actor || real_actor->access <= access_level)
         override = CMD_LOG_OVERRIDE;
 
     if(!(handle = modcmd_get_handle_info(user, argv[1])))
@@ -2557,13 +2794,13 @@ static CHANSERV_FUNC(cmd_adduser)
 
     if((actee = GetTrueChannelAccess(channel->channel_info, handle)))
     {
-       reply("CSMSG_USER_EXISTS", handle->handle, channel->name, actee->access);
-       return 0;
+        reply("CSMSG_USER_EXISTS", handle->handle, channel->name, actee->access);
+        return 0;
     }
 
-    actee = add_channel_user(channel->channel_info, handle, access, 0, NULL);
+    actee = add_channel_user(channel->channel_info, handle, access_level, 0, NULL);
     scan_user_presence(actee, NULL);
-    reply("CSMSG_ADDED_USER", handle->handle, channel->name, access);
+    reply("CSMSG_ADDED_USER", handle->handle, channel->name, access_level);
     return 1 | override;
 }
 
@@ -2585,34 +2822,34 @@ static CHANSERV_FUNC(cmd_clvl)
 
     if(handle == user->handle_info && !privileged)
     {
-       reply("CSMSG_NO_SELF_CLVL");
-       return 0;
+        reply("CSMSG_NO_SELF_CLVL");
+        return 0;
     }
 
     if(!(victim = GetTrueChannelAccess(channel->channel_info, handle)))
     {
-       reply("CSMSG_NO_CHAN_USER", handle->handle, channel->name);
-       return 0;
+        reply("CSMSG_NO_CHAN_USER", handle->handle, channel->name);
+        return 0;
     }
 
     if(actor->access <= victim->access && !privileged)
     {
-       reply("MSG_USER_OUTRANKED", handle->handle);
-       return 0;
+        reply("MSG_USER_OUTRANKED", handle->handle);
+        return 0;
     }
 
     new_access = user_level_from_name(argv[2], UL_OWNER);
 
     if(!new_access)
     {
-       reply("CSMSG_INVALID_ACCESS", argv[2]);
-       return 0;
+        reply("CSMSG_INVALID_ACCESS", argv[2]);
+        return 0;
     }
 
     if(new_access >= actor->access && !privileged)
     {
-       reply("CSMSG_NO_BUMP_ACCESS");
-       return 0;
+        reply("CSMSG_NO_BUMP_ACCESS");
+        return 0;
     }
 
     /* Trying to clvl a equal/higher user? */
@@ -2635,7 +2872,7 @@ static CHANSERV_FUNC(cmd_deluser)
     struct handle_info *handle;
     struct userData *victim;
     struct userData *actor, *real_actor;
-    unsigned short access, override = 0;
+    unsigned short access_level, override = 0;
     char *chan_name;
 
     REQUIRE_PARAMS(2);
@@ -2648,33 +2885,33 @@ static CHANSERV_FUNC(cmd_deluser)
 
     if(!(victim = GetTrueChannelAccess(channel->channel_info, handle)))
     {
-       reply("CSMSG_NO_CHAN_USER", handle->handle, channel->name);
-       return 0;
+        reply("CSMSG_NO_CHAN_USER", handle->handle, channel->name);
+        return 0;
     }
 
     if(argc > 2)
     {
-        access = user_level_from_name(argv[1], UL_OWNER);
-        if(!access)
+        access_level = user_level_from_name(argv[1], UL_OWNER);
+        if(!access_level)
         {
             reply("CSMSG_INVALID_ACCESS", argv[1]);
             return 0;
         }
-       if(access != victim->access)
-       {
-           reply("CSMSG_INCORRECT_ACCESS", handle->handle, victim->access, argv[1]);
-           return 0;
-       }
+        if(access_level != victim->access)
+        {
+            reply("CSMSG_INCORRECT_ACCESS", handle->handle, victim->access, argv[1]);
+            return 0;
+        }
     }
     else
     {
-        access = victim->access;
+        access_level = victim->access;
     }
 
     if((actor->access <= victim->access) && !IsHelping(user))
     {
-       reply("MSG_USER_OUTRANKED", victim->handle->handle);
-       return 0;
+        reply("MSG_USER_OUTRANKED", victim->handle->handle);
+        return 0;
     }
 
     /* If people delete themselves it is an override, but they
@@ -2685,7 +2922,7 @@ static CHANSERV_FUNC(cmd_deluser)
 
     chan_name = strdup(channel->name);
     del_channel_user(victim, 1);
-    reply("CSMSG_DELETED_USER", handle->handle, access, chan_name);
+    reply("CSMSG_DELETED_USER", handle->handle, access_level, chan_name);
     free(chan_name);
     return 1 | override;
 }
@@ -2705,10 +2942,10 @@ cmd_mdel_user(struct userNode *user, struct chanNode *channel, unsigned short mi
         return 0;
     }
 
-    if((actor->access <= max_access) && !IsHelping(user))
+    if(actor->access <= max_access)
     {
-       reply("CSMSG_NO_ACCESS");
-       return 0;
+        reply("CSMSG_NO_ACCESS");
+        return 0;
     }
 
     if(!real_actor || real_actor->access <= max_access)
@@ -2716,12 +2953,12 @@ cmd_mdel_user(struct userNode *user, struct chanNode *channel, unsigned short mi
 
     for(uData = channel->channel_info->users; uData; uData = next)
     {
-       next = uData->next;
+        next = uData->next;
 
-       if((uData->access >= min_access)
+        if((uData->access >= min_access)
            && (uData->access <= max_access)
            && match_ircglob(uData->handle->handle, mask))
-           del_channel_user(uData, 1);
+            del_channel_user(uData, 1);
     }
 
     reply("CSMSG_DELETED_USERS", mask, min_access, max_access, channel->name);
@@ -2759,13 +2996,13 @@ cmd_trim_bans(struct userNode *user, struct chanNode *channel, unsigned long dur
     struct banData *bData, *next;
     char interval[INTERVALLEN];
     unsigned int count;
-    time_t limit;
+    unsigned long limit;
 
     count = 0;
     limit = now - duration;
     for(bData = channel->channel_info->bans; bData; bData = next)
     {
-       next = bData->next;
+        next = bData->next;
 
         if((bData->triggered && bData->triggered >= limit) || (bData->set && bData->set >= limit))
             continue;
@@ -2785,9 +3022,9 @@ cmd_trim_users(struct userNode *user, struct chanNode *channel, unsigned short m
     struct userData *actor, *uData, *next;
     char interval[INTERVALLEN];
     unsigned int count;
-    time_t limit;
+    unsigned long limit;
 
-    actor = GetChannelAccess(channel->channel_info, user->handle_info);
+    actor = GetChannelUser(channel->channel_info, user->handle_info);
     if(min_access > max_access)
     {
         send_message(user, chanserv, "CSMSG_BAD_RANGE", min_access, max_access);
@@ -2796,27 +3033,27 @@ cmd_trim_users(struct userNode *user, struct chanNode *channel, unsigned short m
 
     if(!actor || actor->access <= max_access)
     {
-       send_message(user, chanserv, "CSMSG_NO_ACCESS");
-       return 0;
+        send_message(user, chanserv, "CSMSG_NO_ACCESS");
+        return 0;
     }
 
     count = 0;
     limit = now - duration;
     for(uData = channel->channel_info->users; uData; uData = next)
     {
-       next = uData->next;
+        next = uData->next;
 
-       if((uData->seen > limit)
+        if((uData->seen > limit)
            || uData->present
            || (HANDLE_FLAGGED(uData->handle, FROZEN) && !vacation))
-           continue;
+            continue;
 
-       if(((uData->access >= min_access) && (uData->access <= max_access))
+        if(((uData->access >= min_access) && (uData->access <= max_access))
            || (!max_access && (uData->access < actor->access)))
-       {
-           del_channel_user(uData, 1);
-           count++;
-       }
+        {
+            del_channel_user(uData, 1);
+            count++;
+        }
     }
 
     if(!max_access)
@@ -2840,29 +3077,29 @@ static CHANSERV_FUNC(cmd_trim)
     duration = ParseInterval(argv[2]);
     if(duration < 60)
     {
-       reply("CSMSG_CANNOT_TRIM");
-       return 0;
+        reply("CSMSG_CANNOT_TRIM");
+        return 0;
     }
 
     if(!irccasecmp(argv[1], "bans"))
     {
-       cmd_trim_bans(user, channel, duration);
-       return 1;
+        cmd_trim_bans(user, channel, duration);
+        return 1;
     }
     else if(!irccasecmp(argv[1], "users"))
     {
-       cmd_trim_users(user, channel, 0, 0, duration, vacation);
-       return 1;
+        cmd_trim_users(user, channel, 0, 0, duration, vacation);
+        return 1;
     }
     else if(parse_level_range(&min_level, &max_level, argv[1]))
     {
-       cmd_trim_users(user, channel, min_level, max_level, duration, vacation);
-       return 1;
+        cmd_trim_users(user, channel, min_level, max_level, duration, vacation);
+        return 1;
     }
     else if((min_level = user_level_from_name(argv[1], UL_OWNER)))
     {
-       cmd_trim_users(user, channel, min_level, min_level, duration, vacation);
-       return 1;
+        cmd_trim_users(user, channel, min_level, min_level, duration, vacation);
+        return 1;
     }
     else
     {
@@ -2886,7 +3123,7 @@ static CHANSERV_FUNC(cmd_up)
     {
         if(argc)
             reply("MSG_CHANNEL_ABSENT", channel->name);
-       return 0;
+        return 0;
     }
 
     uData = GetChannelAccess(channel->channel_info, user->handle_info);
@@ -2934,14 +3171,14 @@ static CHANSERV_FUNC(cmd_down)
     {
         if(argc)
             reply("MSG_CHANNEL_ABSENT", channel->name);
-       return 0;
+        return 0;
     }
 
     if(!change.args[0].u.member->modes)
     {
         if(argc)
             reply("CSMSG_ALREADY_DOWN", channel->name);
-       return 0;
+        return 0;
     }
 
     change.args[0].mode = MODE_REMOVE | change.args[0].u.member->modes;
@@ -2955,12 +3192,12 @@ static int cmd_all(struct userNode *user, UNUSED_ARG(struct chanNode *channel),
 
     for(cList = user->handle_info->channels; cList; cList = cList->u_next)
     {
-       if(IsSuspended(cList->channel)
+        if(IsSuspended(cList->channel)
            || IsUserSuspended(cList)
            || !GetUserMode(cList->channel->channel, user))
-           continue;
+            continue;
 
-       mcmd(user, cList->channel->channel, 0, NULL, cmd);
+        mcmd(user, cList->channel->channel, 0, NULL, cmd);
     }
 
     return 1;
@@ -2990,20 +3227,20 @@ modify_users(struct userNode *user, struct chanNode *channel, unsigned int argc,
 
     for(ii=valid=0; ++ii < argc; )
     {
-       if(!(victim = GetUserH(argv[ii])))
+        if(!(victim = GetUserH(argv[ii])))
             continue;
         change->args[valid].mode = mode;
         change->args[valid].u.member = GetUserMode(channel, victim);
         if(!change->args[valid].u.member)
             continue;
         if(validate && !validate(user, channel, victim))
-           continue;
+            continue;
         valid++;
     }
 
     change->argc = valid;
     if(valid < (argc-1))
-       reply("CSMSG_PROCESS_FAILED");
+        reply("CSMSG_PROCESS_FAILED");
     if(valid)
     {
         modcmd_chanmode_announce(change);
@@ -3033,6 +3270,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)
 {
@@ -3070,9 +3345,23 @@ 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);
+        reason = unsplit_string(argv + offset, argc - offset, NULL);
         if(strlen(reason) > (TOPICLEN - (NICKLEN + 3)))
         {
             /* Truncate the reason to a length of TOPICLEN, as
@@ -3091,13 +3380,13 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
          * want to allow those (e.g.  unbans) in that case.  If we add
          * some other ejection action for in-channel users, change
          * this too. */
-       victimCount = victims[0] ? 1 : 0;
+        victimCount = victims[0] ? 1 : 0;
 
-       if(IsService(victim))
-       {
-           reply("MSG_SERVICE_IMMUNE", victim->nick);
-           return 0;
-       }
+        if(IsService(victim))
+        {
+            reply("MSG_SERVICE_IMMUNE", victim->nick);
+            return 0;
+        }
 
         if((action == ACTION_KICK) && !victimCount)
         {
@@ -3105,34 +3394,64 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
             return 0;
         }
 
-       if(protect_user(victim, user, channel->channel_info))
-       {
-           reply("CSMSG_USER_PROTECTED", victim->nick);
-           return 0;
-       }
+        if(protect_user(victim, user, channel->channel_info))
+        {
+            reply("CSMSG_USER_PROTECTED", victim->nick);
+            return 0;
+        }
 
-       ban = generate_hostmask(victim, GENMASK_STRICT_HOST|GENMASK_ANY_IDENT);
-       name = victim->nick;
+        ban = generate_hostmask(victim, GENMASK_STRICT_HOST|GENMASK_ANY_IDENT);
+        name = victim->nick;
     }
-    else
+    else if(!is_ircmask(argv[1]) && (*argv[1] == '*'))
     {
-       if(!is_ircmask(argv[1]))
-       {
-           reply("MSG_NICK_UNKNOWN", argv[1]);
-           return 0;
-       }
-
-       victims = alloca(sizeof(victims[0]) * channel->members.used);
-
-        if(bad_channel_ban(channel, user, argv[1], &victimCount, victims))
-        {
-           reply("CSMSG_MASK_PROTECTED", argv[1]);
-           return 0;
-       }
+        struct handle_info *hi;
+        extern const char *titlehost_suffix;
+        char banmask[NICKLEN + USERLEN + HOSTLEN + 3];
+        const char *accountname = argv[1] + 1;
 
-        if((victimCount > 4) && ((victimCount * 3) > channel->members.used) && !IsOper(user))
+        if(!(hi = get_handle_info(accountname)))
         {
-            reply("CSMSG_LAME_MASK", argv[1]);
+            reply("MSG_HANDLE_UNKNOWN", accountname);
+            return 0;
+        }
+
+        snprintf(banmask, sizeof(banmask), "*!*@%s.*.%s", hi->handle, titlehost_suffix);
+        victims = alloca(sizeof(victims[0]) * channel->members.used);
+
+        if(bad_channel_ban(channel, user, banmask, &victimCount, victims))
+        {
+            reply("CSMSG_MASK_PROTECTED", banmask);
+            return 0;
+        }
+
+        if((action == ACTION_KICK) && (victimCount == 0))
+        {
+            reply("CSMSG_NO_MATCHING_USERS", channel->name, banmask);
+            return 0;
+        }
+
+        name = ban = strdup(banmask);
+    }
+    else
+    {
+        if(!is_ircmask(argv[1]))
+        {
+            reply("MSG_NICK_UNKNOWN", argv[1]);
+            return 0;
+        }
+
+        victims = alloca(sizeof(victims[0]) * channel->members.used);
+
+        if(bad_channel_ban(channel, user, argv[1], &victimCount, victims))
+        {
+            reply("CSMSG_MASK_PROTECTED", argv[1]);
+            return 0;
+        }
+
+        if((victimCount > 4) && ((victimCount * 3) > channel->members.used) && !IsOper(user))
+        {
+            reply("CSMSG_LAME_MASK", argv[1]);
             return 0;
         }
 
@@ -3142,7 +3461,7 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
             return 0;
         }
 
-       name = ban = strdup(argv[1]);
+        name = ban = strdup(argv[1]);
     }
 
     /* Truncate the ban in place if necessary; we must ensure
@@ -3151,108 +3470,108 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
 
     if(action & ACTION_ADD_BAN)
     {
-       struct banData *bData, *next;
+        struct banData *bData, *next;
 
-       if(channel->channel_info->banCount >= chanserv_conf.max_chan_bans)
-       {
-           reply("CSMSG_MAXIMUM_BANS", chanserv_conf.max_chan_bans);
-           free(ban);
-           return 0;
-       }
+        if(channel->channel_info->banCount >= chanserv_conf.max_chan_bans)
+        {
+            reply("CSMSG_MAXIMUM_BANS", chanserv_conf.max_chan_bans);
+            free(ban);
+            return 0;
+        }
 
-       if(action & ACTION_ADD_TIMED_BAN)
-       {
-           duration = ParseInterval(argv[2]);
-
-           if(duration < 15)
-           {
-               reply("CSMSG_DURATION_TOO_LOW");
-               free(ban);
-               return 0;
-           }
-           else if(duration > (86400 * 365 * 2))
-           {
-               reply("CSMSG_DURATION_TOO_HIGH");
-               free(ban);
-               return 0;
-           }
-       }
+        if(action & ACTION_ADD_TIMED_BAN)
+        {
+            duration = ParseInterval(argv[2]);
 
-       for(bData = channel->channel_info->bans; bData; bData = next)
-       {
-           if(match_ircglobs(bData->mask, ban))
-           {
-               int exact = !irccasecmp(bData->mask, ban);
+            if(duration < chanserv_conf.min_time_bans)
+            {
+                reply("CSMSG_DURATION_TOO_LOW");
+                free(ban);
+                return 0;
+            }
+            else if(duration > (86400 * 365 * 2))
+            {
+                reply("CSMSG_DURATION_TOO_HIGH");
+                free(ban);
+                return 0;
+            }
+        }
 
-               /* The ban is redundant; there is already a ban
-                  with the same effect in place. */
-               if(exact)
-               {
-                   if(bData->reason)
+        for(bData = channel->channel_info->bans; bData; bData = next)
+        {
+            if(match_ircglobs(bData->mask, ban))
+            {
+                int exact = !irccasecmp(bData->mask, ban);
+
+                /* The ban is redundant; there is already a ban
+                   with the same effect in place. */
+                if(exact)
+                {
+                    if(bData->reason)
                         free(bData->reason);
-                   bData->reason = strdup(reason);
+                    bData->reason = strdup(reason);
                     safestrncpy(bData->owner, (user->handle_info ? user->handle_info->handle : user->nick), sizeof(bData->owner));
                     if(cmd)
                         reply("CSMSG_REASON_CHANGE", ban);
-                   if(!bData->expires)
+                    if(!bData->expires)
                         goto post_add_ban;
-               }
-               if(exact && bData->expires)
-               {
-                   int reset = 0;
-
-                   /* If the ban matches an existing one exactly,
-                      extend the expiration time if the provided
-                      duration is longer. */
-                   if(duration && ((time_t)(now + duration) > bData->expires))
-                   {
-                       bData->expires = now + duration;
-                       reset = 1;
-                   }
-                   else if(!duration)
-                   {
-                       bData->expires = 0;
-                       reset = 1;
-                   }
-
-                   if(reset)
-                   {
-                       /* Delete the expiration timeq entry and
-                          requeue if necessary. */
-                       timeq_del(0, expire_ban, bData, TIMEQ_IGNORE_WHEN);
-
-                       if(bData->expires)
-                           timeq_add(bData->expires, expire_ban, bData);
+                }
+                if(exact && bData->expires)
+                {
+                    int reset = 0;
+
+                    /* If the ban matches an existing one exactly,
+                       extend the expiration time if the provided
+                       duration is longer. */
+                    if(duration && (now + duration > bData->expires))
+                    {
+                        bData->expires = now + duration;
+                        reset = 1;
+                    }
+                    else if(!duration)
+                    {
+                        bData->expires = 0;
+                        reset = 1;
+                    }
+
+                    if(reset)
+                    {
+                        /* Delete the expiration timeq entry and
+                           requeue if necessary. */
+                        timeq_del(0, expire_ban, bData, TIMEQ_IGNORE_WHEN);
+
+                        if(bData->expires)
+                            timeq_add(bData->expires, expire_ban, bData);
 
                         if(!cmd)
                         {
                             /* automated kickban */
                         }
-                       else if(duration)
-                           reply("CSMSG_BAN_EXTENDED", ban, intervalString(interval, duration, user->handle_info));
-                       else
-                           reply("CSMSG_BAN_ADDED", name, channel->name);
+                        else if(duration)
+                            reply("CSMSG_BAN_EXTENDED", ban, intervalString(interval, duration, user->handle_info));
+                        else
+                            reply("CSMSG_BAN_ADDED", name, channel->name);
 
-                       goto post_add_ban;
-                   }
-               }
+                        goto post_add_ban;
+                    }
+                }
                 if(cmd)
                     reply("CSMSG_REDUNDANT_BAN", name, channel->name);
 
-               free(ban);
-               return 0;
-           }
-
-           next = bData->next;
-           if(match_ircglobs(ban, bData->mask))
-           {
-               /* The ban we are adding makes previously existing
-                  bans redundant; silently remove them. */
-               del_channel_ban(bData);
-           }
-       }
+                free(ban);
+                return 0;
+            }
+
+            next = bData->next;
+            if(match_ircglobs(ban, bData->mask))
+            {
+                /* The ban we are adding makes previously existing
+                   bans redundant; silently remove them. */
+                del_channel_ban(bData);
+            }
+        }
 
-       bData = add_channel_ban(channel->channel_info, ban, (user->handle_info ? user->handle_info->handle : user->nick), now, (victimCount ? now : 0), (duration ? now + duration : 0), reason);
+        bData = add_channel_ban(channel->channel_info, ban, (user->handle_info ? user->handle_info->handle : user->nick), now, (victimCount ? now : 0), (duration ? now + duration : 0), reason);
         free(ban);
         name = ban = strdup(bData->mask);
     }
@@ -3278,19 +3597,19 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
         }
     }
 
-  post_add_ban:
+    post_add_ban:
     if(action & ACTION_BAN)
     {
-       unsigned int exists;
+        unsigned int exists;
         struct mod_chanmode *change;
 
-       if(channel->banlist.used >= MAXBANS)
-       {
+        if(channel->banlist.used >= MAXBANS)
+        {
             if(cmd)
                 reply("CSMSG_BANLIST_FULL", channel->name);
-           free(ban);
-           return 0;
-       }
+            free(ban);
+            return 0;
+        }
 
         exists = ChannelBanExists(channel, ban);
         change = mod_chanmode_alloc(victimCount + 1);
@@ -3312,7 +3631,7 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
         mod_chanmode_free(change);
 
         if(exists && (action == ACTION_BAN))
-       {
+        {
             if(cmd)
                 reply("CSMSG_REDUNDANT_BAN", name, channel->name);
             free(ban);
@@ -3323,10 +3642,10 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
     if(action & ACTION_KICK)
     {
         char kick_reason[MAXLEN];
-       sprintf(kick_reason, "(%s) %s", user->nick, reason);
+        sprintf(kick_reason, "(%s) %s", user->nick, reason);
 
-       for(n = 0; n < victimCount; n++)
-           KickChannelUser(victims[n]->user, channel, chanserv, kick_reason);
+        for(n = 0; n < victimCount; n++)
+            KickChannelUser(victims[n]->user, channel, chanserv, kick_reason);
     }
 
     if(!cmd)
@@ -3335,17 +3654,17 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
     }
     else if(action & ACTION_ADD_BAN)
     {
-       if(duration)
-           reply("CSMSG_TIMED_BAN_ADDED", name, channel->name, intervalString(interval, duration, user->handle_info));
-       else
-           reply("CSMSG_BAN_ADDED", name, channel->name);
+        if(duration)
+            reply("CSMSG_TIMED_BAN_ADDED", name, channel->name, intervalString(interval, duration, user->handle_info));
+        else
+            reply("CSMSG_BAN_ADDED", name, channel->name);
     }
     else if((action & (ACTION_BAN | ACTION_KICK)) == (ACTION_BAN | ACTION_KICK))
-       reply("CSMSG_KICK_BAN_DONE", name, channel->name);
+        reply("CSMSG_KICK_BAN_DONE", name, channel->name);
     else if(action & ACTION_BAN)
-       reply("CSMSG_BAN_DONE", name, channel->name);
+        reply("CSMSG_BAN_DONE", name, channel->name);
     else if(action & ACTION_KICK && victimCount)
-       reply("CSMSG_KICK_DONE", name, channel->name);
+        reply("CSMSG_KICK_DONE", name, channel->name);
 
     free(ban);
     return 1;
@@ -3376,7 +3695,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;
@@ -3429,13 +3748,23 @@ unban_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
     /* may want to allow a comma delimited list of users... */
     if(!(actee = GetUserH(argv[1])))
     {
-       if(!is_ircmask(argv[1]))
-       {
-           reply("MSG_NICK_UNKNOWN", argv[1]);
-           return 0;
-       }
+        if(!is_ircmask(argv[1]) && *argv[1] == '*')
+        {
+            char banmask[NICKLEN + USERLEN + HOSTLEN + 3];
+            const char *accountname = argv[1] + 1;
 
-       mask = strdup(argv[1]);
+            snprintf(banmask, sizeof(banmask), "*!*@%s.*", accountname);
+            mask = strdup(banmask);
+        }
+        else if(!is_ircmask(argv[1]))
+        {
+            reply("MSG_NICK_UNKNOWN", argv[1]);
+            return 0;
+        }
+        else
+        {
+            mask = strdup(argv[1]);
+        }
     }
 
     /* We don't sanitize the mask here because ircu
@@ -3458,31 +3787,31 @@ unban_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
 
     if(action & ACTION_DEL_BAN)
     {
-       struct banData *ban, *next;
+        struct banData *ban, *next;
 
-       ban = channel->channel_info->bans;
-       while(ban)
-       {
-           if(actee)
-               for( ; ban && !user_matches_glob(actee, ban->mask,
+        ban = channel->channel_info->bans;
+        while(ban)
+        {
+            if(actee)
+                for( ; ban && !user_matches_glob(actee, ban->mask,
                                                  MATCH_USENICK | MATCH_VISIBLE);
-                    ban = ban->next);
-           else
-               for( ; ban && !match_ircglobs(mask, ban->mask);
-                    ban = ban->next);
-           if(!ban)
+                     ban = ban->next);
+            else
+                for( ; ban && !match_ircglobs(mask, ban->mask);
+                     ban = ban->next);
+            if(!ban)
                 break;
-           next = ban->next;
-           del_channel_ban(ban);
-           ban = next;
-           acted = 1;
-       }
+            next = ban->next;
+            del_channel_ban(ban);
+            ban = next;
+            acted = 1;
+        }
     }
 
     if(!acted)
-       reply("CSMSG_BAN_NOT_FOUND", actee ? actee->nick : mask);
+        reply("CSMSG_BAN_NOT_FOUND", actee ? actee->nick : mask);
     else
-       reply("CSMSG_BAN_REMOVED", actee ? actee->nick : mask);
+        reply("CSMSG_BAN_REMOVED", actee ? actee->nick : mask);
     if(mask)
         free(mask);
     return 1;
@@ -3507,7 +3836,7 @@ static CHANSERV_FUNC(cmd_unbanme)
 
     /* remove permanent bans if the user has the proper access. */
     if(uData->access >= UL_MASTER)
-       flags |= ACTION_DEL_BAN;
+        flags |= ACTION_DEL_BAN;
 
     argv[1] = user->nick;
     return unban_user(user, channel, 2, argv, cmd, flags);
@@ -3520,8 +3849,8 @@ static CHANSERV_FUNC(cmd_unbanall)
 
     if(!channel->banlist.used)
     {
-       reply("CSMSG_NO_BANS", channel->name);
-       return 0;
+        reply("CSMSG_NO_BANS", channel->name);
+        return 0;
     }
 
     change = mod_chanmode_alloc(channel->banlist.used);
@@ -3563,10 +3892,12 @@ 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;
-    else if(!IsHelping(user))
+    else if(!IsStaff(user))
     {
         reply("CSMSG_MYACCESS_SELF_ONLY", argv[0]);
         return 0;
@@ -3574,6 +3905,9 @@ static CHANSERV_FUNC(cmd_myaccess)
     else if(!(target_handle = modcmd_get_handle_info(user, argv[1])))
         return 0;
 
+    if(!oper_outranks(user, target_handle))
+        return 0;
+
     if(!target_handle->channels)
     {
         reply("CSMSG_SQUAT_ACCESS", target_handle->handle);
@@ -3584,17 +3918,22 @@ 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);
-        if(uData->flags != USER_AUTO_OP)
-            string_buffer_append(&sbuf, ',');
+        string_buffer_append_printf(&sbuf, "[%s (%d,", cData->channel->name, uData->access);
+        base_len = sbuf.used;
         if(IsUserSuspended(uData))
             string_buffer_append(&sbuf, 's');
         if(IsUserAutoOp(uData))
@@ -3606,6 +3945,8 @@ static CHANSERV_FUNC(cmd_myaccess)
         }
         if(IsUserAutoInvite(uData) && (uData->access >= cData->lvlOpts[lvlInviteMe]))
             string_buffer_append(&sbuf, 'i');
+        if(sbuf.used==base_len)
+            sbuf.used--;
         if(uData->info)
             string_buffer_append_printf(&sbuf, ")] %s", uData->info);
         else
@@ -3614,6 +3955,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;
 }
 
@@ -3627,7 +3974,7 @@ static CHANSERV_FUNC(cmd_access)
 
     if(argc < 2)
     {
-       target = user;
+        target = user;
         target_handle = target->handle_info;
     }
     else if((target = GetUserH(argv[1])))
@@ -3652,8 +3999,8 @@ static CHANSERV_FUNC(cmd_access)
 
     if(target == chanserv)
     {
-       reply("CSMSG_IS_CHANSERV");
-       return 1;
+        reply("CSMSG_IS_CHANSERV");
+        return 1;
     }
 
     if(!target_handle)
@@ -3663,11 +4010,11 @@ static CHANSERV_FUNC(cmd_access)
             reply("CSMSG_LAZY_SMURF_TARGET", target->nick, chanserv_conf.irc_operator_epithet);
             return 0;
         }
-       if(target != user)
-       {
-           reply("MSG_USER_AUTHENTICATE", target->nick);
-           return 0;
-       }
+        if(target != user)
+        {
+            reply("MSG_USER_AUTHENTICATE", target->nick);
+            return 0;
+        }
         reply("MSG_AUTHENTICATE");
         return 0;
     }
@@ -3739,56 +4086,6 @@ static CHANSERV_FUNC(cmd_access)
     return 1;
 }
 
-static void
-zoot_list(struct listData *list)
-{
-    struct userData *uData;
-    unsigned int start, curr, highest, lowest;
-    struct helpfile_table tmp_table;
-    const char **temp, *msg;
-
-    if(list->table.length == 1)
-    {
-        if(list->search)
-            send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER", list->channel->name, list->lowest, list->highest, list->search);
-        else
-            send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER", list->channel->name, list->lowest, list->highest);
-        msg = user_find_message(list->user, "MSG_NONE");
-        send_message_type(4, list->user, list->bot, "  %s", msg);
-    }
-    tmp_table.width = list->table.width;
-    tmp_table.flags = list->table.flags;
-    list->table.contents[0][0] = " ";
-    highest = list->highest;
-    if(list->lowest != 0)
-        lowest = list->lowest;
-    else if(highest < 100)
-        lowest = 1;
-    else
-        lowest = highest - 100;
-    for(start = curr = 1; curr < list->table.length; )
-    {
-        uData = list->users[curr-1];
-        list->table.contents[curr++][0] = " ";
-        if((curr == list->table.length) || (list->users[curr-1]->access < lowest))
-        {
-            if(list->search)
-                send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER", list->channel->name, lowest, highest, list->search);
-            else
-                send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER", list->channel->name, lowest, highest);
-            temp = list->table.contents[--start];
-            list->table.contents[start] = list->table.contents[0];
-            tmp_table.contents = list->table.contents + start;
-            tmp_table.length = curr - start;
-            table_send(list->bot, list->user->nick, 0, NULL, tmp_table);
-            list->table.contents[start] = temp;
-            start = curr;
-            highest = lowest - 1;
-            lowest = (highest < 100) ? 0 : (highest - 99);
-        }
-    }
-}
-
 static void
 def_list(struct listData *list)
 {
@@ -3834,26 +4131,25 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
     lData.highest = highest;
     lData.search = (argc > 1) ? argv[1] : NULL;
     send_list = def_list;
-    (void)zoot_list; /* since it doesn't show user levels */
 
     if(user->handle_info)
     {
-       switch(user->handle_info->userlist_style)
-       {
-       case HI_STYLE_DEF: send_list = def_list; break;
+        switch(user->handle_info->userlist_style)
+        {
+        case HI_STYLE_DEF: send_list = def_list; break;
         case HI_STYLE_ZOOT: send_list = def_list; break;
-       }
+        }
     }
 
     lData.users = alloca(channel->channel_info->userCount * sizeof(struct userData *));
     matches = 0;
     for(uData = channel->channel_info->users; uData; uData = uData->next)
     {
-       if((uData->access < lowest)
+        if((uData->access < lowest)
            || (uData->access > highest)
            || (lData.search && !match_ircglob(uData->handle->handle, lData.search)))
-           continue;
-       lData.users[matches++] = uData;
+            continue;
+        lData.users[matches++] = uData;
     }
     qsort(lData.users, matches, sizeof(lData.users[0]), userData_access_comp);
 
@@ -3869,15 +4165,17 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
     ary[3] = "Status";
     for(matches = 1; matches < lData.table.length; ++matches)
     {
-        struct userData *uData = lData.users[matches-1];
         char seen[INTERVALLEN];
 
+        uData = lData.users[matches-1];
         ary = malloc(lData.table.width*sizeof(**lData.table.contents));
         lData.table.contents[matches] = ary;
         ary[0] = strtab(uData->access);
         ary[1] = uData->handle->handle;
         if(uData->present)
             ary[2] = "Here";
+        else if(HANDLE_FLAGGED(uData->handle, NETWORK))
+             ary[2] = "Here";
         else if(!uData->seen)
             ary[2] = "Never";
         else
@@ -3885,8 +4183,16 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
         ary[2] = strdup(ary[2]);
         if(IsUserSuspended(uData))
             ary[3] = "Suspended";
+        else if(HANDLE_FLAGGED(uData->handle, OPER))
+            ary[3] = "Operator";
+        else if(HANDLE_FLAGGED(uData->handle, HELPING))
+            ary[3] = "Staff";
+        else if(HANDLE_FLAGGED(uData->handle, NETWORK))
+            ary[3] = "Network";
         else if(HANDLE_FLAGGED(uData->handle, FROZEN))
             ary[3] = "Vacation";
+        else if(HANDLE_FLAGGED(uData->handle, BOT))
+            ary[3] = "Bot";
         else
             ary[3] = "Normal";
     }
@@ -3898,6 +4204,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;
 }
 
@@ -3944,7 +4251,7 @@ static CHANSERV_FUNC(cmd_bans)
         search = NULL;
     else if(strchr(search = argv[1], '!'))
     {
-       search = argv[1];
+        search = argv[1];
         search_wilds = search[strcspn(search, "?*")];
     }
     else if(!(search_u = GetUserH(search)))
@@ -3959,13 +4266,13 @@ static CHANSERV_FUNC(cmd_bans)
             if(!user_matches_glob(search_u, ban->mask, MATCH_USENICK | MATCH_VISIBLE))
                 continue;
         }
-       else if(search)
+        else if(search)
         {
             if(search_wilds ? !match_ircglobs(search, ban->mask) : !match_ircglob(search, ban->mask))
                 continue;
         }
-       bans[matches++] = ban;
-       if(ban->expires)
+        bans[matches++] = ban;
+        if(ban->expires)
             timed = 1;
     }
 
@@ -3988,28 +4295,28 @@ static CHANSERV_FUNC(cmd_bans)
     if(!matches)
     {
         table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
-       reply("MSG_NONE");
+        reply("MSG_NONE");
         free(tbl.contents[0]);
         free(tbl.contents);
-       return 0;
+        return 0;
     }
 
     msg_never = user_find_message(user, "MSG_NEVER");
     for(ii = 0; ii < matches; )
     {
-       ban = bans[ii];
+        ban = bans[ii];
 
-       if(!timed)
-           expires = "";
-       else if(ban->expires)
-           expires = intervalString(e_buffer, ban->expires - now, user->handle_info);
-       else
-           expires = msg_never;
+        if(!timed)
+            expires = "";
+        else if(ban->expires)
+            expires = intervalString(e_buffer, ban->expires - now, user->handle_info);
+        else
+            expires = msg_never;
 
-       if(ban->triggered)
-           triggered = intervalString(t_buffer, now - ban->triggered, user->handle_info);
-       else
-           triggered = msg_never;
+        if(ban->triggered)
+            triggered = intervalString(t_buffer, now - ban->triggered, user->handle_info);
+        else
+            triggered = msg_never;
 
         tbl.contents[++ii] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
         tbl.contents[ii][0] = ban->mask;
@@ -4043,8 +4350,34 @@ bad_topic(struct chanNode *channel, struct userNode *user, const char *new_topic
     struct chanData *cData = channel->channel_info;
     if(check_user_level(channel, user, lvlEnfTopic, 1, 0))
         return 0;
-    if(cData->topic_mask)
-        return !match_ircglob(new_topic, cData->topic_mask);
+    if(cData->topic_mask) 
+    {
+        if(cData->flags & CHANNEL_ADVTOPIC) 
+        {
+            //this implementation is a little bit dirty but i haven't found a better, faster solution, yet...
+            char topicmask[TOPICLEN];
+            int skipnum, topicpos = 0;
+            char *ptr = cData->topic_mask;
+            for(;*ptr;ptr++) { //replace all the %[0-9]* variables with *
+                switch(*ptr) {
+                case '%':
+                    for(skipnum = 0; isdigit(ptr[1]) && skipnum < 3; ptr++, skipnum++) {} //skip up to 3 numbers
+                    if(skipnum)
+                        topicmask[topicpos++] = '*';
+                    else
+                        topicmask[topicpos++] = *ptr;
+                    break;
+                default:
+                    topicmask[topicpos++] = *ptr;
+                    break;
+                }
+            }
+            topicmask[topicpos] = 0;
+            return !match_ircglob(new_topic, topicmask);
+        }
+        else
+            return !match_ircglob(new_topic, cData->topic_mask);
+    }
     else if(cData->topic)
         return irccasecmp(new_topic, cData->topic);
     else
@@ -4059,12 +4392,12 @@ static CHANSERV_FUNC(cmd_topic)
     cData = channel->channel_info;
     if(argc < 2)
     {
-       if(cData->topic)
-       {
-           SetChannelTopic(channel, chanserv, cData->topic, 1);
-           reply("CSMSG_TOPIC_SET", cData->topic);
+        if(cData->topic)
+        {
+            SetChannelTopic(channel, chanserv, cData->topic, 1);
+            reply("CSMSG_TOPIC_SET", cData->topic);
             return 1;
-       }
+        }
 
         reply("CSMSG_NO_TOPIC", channel->name);
         return 0;
@@ -4082,30 +4415,96 @@ static CHANSERV_FUNC(cmd_topic)
             char new_topic[TOPICLEN+1], tchar;
             int pos=0, starpos=-1, dpos=0, len;
 
-            while((tchar = topic_mask[pos++]) && (dpos <= TOPICLEN))
+            if(cData->flags & CHANNEL_ADVTOPIC) 
             {
-                switch(tchar)
+                //first check if there is a leading 'modifier id'
+                int advtopic_index = 0;
+                char numbuf[4];
+                int numpos;
+                for(; topic[pos]; pos++) 
                 {
-                case '*':
-                    if(starpos != -1)
-                        goto bad_mask;
-                    len = strlen(topic);
-                    if((dpos + len) > TOPICLEN)
-                        len = TOPICLEN + 1 - dpos;
-                    memcpy(new_topic+dpos, topic, len);
-                    dpos += len;
-                    starpos = pos;
-                    break;
-                case '\\': tchar = topic_mask[pos++]; /* and fall through */
-                default: new_topic[dpos++] = tchar; break;
+                    if(topic[pos] == ' ') 
+                    {
+                        //leading number found, cut off and store value in advtopic_index
+                        topic[pos] = 0;
+                        advtopic_index = atoi(topic) - 1; //no zerobase
+                        topic = &topic[pos+1];
+                        /* If they say "!topic 2 *", unset advtopic id 2. */
+                        if((topic[0] == '*') && (topic[1] == 0))
+                            topic[0] = 0;
+                    }
+                    if(!isdigit(topic[pos]))
+                        break;
+                }
+                if(advtopic_index < 0 || advtopic_index >= MAXADVTOPICENTRIES)
+                {
+                    //invalid id!
+                    reply("CSMSG_ADVTOPIC_INVALID_ID", advtopic_index+1);
+                    return 0;
+                }
+                if(cData->advtopic[advtopic_index])
+                    free(cData->advtopic[advtopic_index]);
+                cData->advtopic[advtopic_index] = (topic[0] ? strdup(topic) : NULL);
+                char *ptr = topic_mask;
+                while(*ptr && (dpos <= TOPICLEN))
+                {
+                    switch(*ptr)
+                    {
+                    case '%':
+                        ptr++;
+                        for(numpos = 0; isdigit(*ptr) && numpos < 3; ptr++) {
+                            numbuf[numpos++] = *ptr;
+                        }
+                        numbuf[numpos] = 0;
+                        if(!numpos || (advtopic_index = atoi(numbuf)) <= 0 || advtopic_index > MAXADVTOPICENTRIES) {
+                            ptr -= numpos+1;
+                            new_topic[dpos++] = *ptr; //is % again
+                            break;
+                        }
+                        advtopic_index--; //no zero base
+                        if(!cData->advtopic[advtopic_index])
+                            break; //just leave it empty
+                        len = strlen(cData->advtopic[advtopic_index]);
+                        if((dpos + len) > TOPICLEN)
+                            len = TOPICLEN + 1 - dpos;
+                        memcpy(new_topic+dpos, cData->advtopic[advtopic_index], len);
+                        dpos += len;
+                        break;
+                    case '\\': 
+                        ptr++; /* and fall through */
+                        if(!*ptr) break;
+                    default:
+                        new_topic[dpos++] = *ptr;
+                        ptr++;
+                        break;
+                    }
+                }
+            } else {
+                while((tchar = topic_mask[pos++]) && (dpos <= TOPICLEN))
+                {
+                    switch(tchar)
+                    {
+                    case '*':
+                        if(starpos != -1)
+                            goto bad_mask;
+                        len = strlen(topic);
+                        if((dpos + len) > TOPICLEN)
+                            len = TOPICLEN + 1 - dpos;
+                        memcpy(new_topic+dpos, topic, len);
+                        dpos += len;
+                        starpos = pos;
+                        break;
+                    case '\\': tchar = topic_mask[pos++]; /* and fall through */
+                    default: new_topic[dpos++] = tchar; break;
+                    }
+                }
+                if((dpos > TOPICLEN) || tchar)
+                {
+                bad_mask:
+                    reply("CSMSG_TOPICMASK_CONFLICT1", channel->name, topic_mask);
+                    reply("CSMSG_TOPICMASK_CONFLICT2", TOPICLEN);
+                    return 0;
                 }
-            }
-            if((dpos > TOPICLEN) || tchar)
-            {
-            bad_mask:
-                reply("CSMSG_TOPICMASK_CONFLICT1", channel->name, topic_mask);
-                reply("CSMSG_TOPICMASK_CONFLICT2", TOPICLEN);
-                return 0;
             }
             new_topic[dpos] = 0;
             SetChannelTopic(channel, chanserv, new_topic, 1);
@@ -4132,16 +4531,17 @@ static CHANSERV_FUNC(cmd_mode)
     struct userData *uData;
     struct mod_chanmode *change;
     short base_oplevel;
+    char fmt[MAXLEN];
 
     if(argc < 2)
     {
         change = &channel->channel_info->modes;
-       if(change->modes_set || change->modes_clear) {
+        if(change->modes_set || change->modes_clear) {
             modcmd_chanmode_announce(change);
             reply("CSMSG_DEFAULTED_MODES", channel->name);
-       } else
-           reply("CSMSG_NO_MODES", channel->name);
-       return 1;
+        } else
+            reply("CSMSG_NO_MODES", channel->name);
+        return 1;
     }
 
     uData = GetChannelUser(channel->channel_info, user->handle_info);
@@ -4151,11 +4551,11 @@ 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, 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));
-       return 0;
+        reply("MSG_INVALID_MODES", unsplit_string(argv+1, argc-1, NULL));
+        return 0;
     }
 
     if(!check_user_level(channel, user, lvlEnfModes, 1, 0)
@@ -4168,22 +4568,38 @@ static CHANSERV_FUNC(cmd_mode)
     }
 
     modcmd_chanmode_announce(change);
+    mod_chanmode_format(change, fmt);
     mod_chanmode_free(change);
-    reply("CSMSG_MODES_SET", unsplit_string(argv+1, argc-1, NULL));
+    reply("CSMSG_MODES_SET", fmt);
     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 userData *uData;
     struct userNode *invite;
-
-    uData = GetChannelUser(channel->channel_info, user->handle_info);
+    struct ChanUser *chanuser;
+    unsigned int i;
 
     if(argc > 1)
     {
         if(!(invite = GetUserH(argv[1])))
-       {
+        {
             reply("MSG_NICK_UNKNOWN", argv[1]);
             return 0;
         }
@@ -4193,8 +4609,16 @@ static CHANSERV_FUNC(cmd_invite)
 
     if(GetUserMode(channel, invite))
     {
-       reply("CSMSG_ALREADY_PRESENT", invite->nick, channel->name);
-       return 0;
+        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)
@@ -4209,7 +4633,13 @@ static CHANSERV_FUNC(cmd_invite)
     }
     irc_invite(chanserv, invite, channel);
     if(argc > 1)
-       reply("CSMSG_INVITED_USER", argv[1], channel->name);
+        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;
 }
@@ -4218,8 +4648,8 @@ static CHANSERV_FUNC(cmd_inviteme)
 {
     if(GetUserMode(channel, user))
     {
-       reply("CSMSG_YOU_ALREADY_PRESENT", channel->name);
-       return 0;
+        reply("CSMSG_YOU_ALREADY_PRESENT", channel->name);
+        return 0;
     }
     if(channel->channel_info
        && !check_user_level(channel, user, lvlInviteMe, 1, 0))
@@ -4231,6 +4661,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)
 {
@@ -4286,6 +4738,30 @@ show_suspension_info(struct svccmd *cmd, struct userNode *user, struct suspended
     }
 }
 
+static void
+show_giveownership_info(struct svccmd *cmd, struct userNode *user, struct giveownership *giveownership)
+{
+    char buf[MAXLEN];
+    const char *fmt = "%a %b %d %H:%M %Y";
+    strftime(buf, sizeof(buf), fmt, localtime(&giveownership->issued));
+
+    if(giveownership->staff_issuer)
+    {
+        if(giveownership->reason)
+            reply("CSMSG_CHANNEL_OWNERSHIP_STAFF_REASON", giveownership->old_owner,
+                  giveownership->target, giveownership->target_access,
+                  giveownership->staff_issuer, buf, giveownership->reason);
+        else
+            reply("CSMSG_CHANNEL_OWNERSHIP_STAFF", giveownership->old_owner,
+                  giveownership->target, giveownership->target_access,
+                  giveownership->staff_issuer, buf);
+    }
+    else
+    {
+        reply("CSMSG_CHANNEL_OWNERSHIP_NORMAL", giveownership->old_owner, giveownership->target, giveownership->target_access, buf);
+    }
+}
+
 static CHANSERV_FUNC(cmd_info)
 {
     char modes[MAXLEN], buffer[INTERVALLEN];
@@ -4303,8 +4779,8 @@ static CHANSERV_FUNC(cmd_info)
     if(uData && (uData->access >= cData->lvlOpts[lvlGiveOps]))
     {
         mod_chanmode_format(&cData->modes, modes);
-       reply("CSMSG_CHANNEL_TOPIC", cData->topic);
-       reply("CSMSG_CHANNEL_MODES", modes[0] ? modes : user_find_message(user, "MSG_NONE"));
+        reply("CSMSG_CHANNEL_TOPIC", cData->topic);
+        reply("CSMSG_CHANNEL_MODES", modes[0] ? modes : user_find_message(user, "MSG_NONE"));
     }
 
     for(it = dict_first(cData->notes); it; it = iter_next(it))
@@ -4319,7 +4795,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);
@@ -4348,12 +4828,20 @@ static CHANSERV_FUNC(cmd_info)
         reply("CSMSG_CHANNEL_SUSPENDED", channel->name);
         show_suspension_info(cmd, user, cData->suspended);
     }
+    
+    if(cData->giveownership && ((uData && (uData->access >= UL_COOWNER)) || IsStaff(user)))
+    {
+        struct giveownership *giveownership;
+        reply("CSMSG_CHANNEL_OWNERSHIP_HISTORY", channel->name);
+        for(giveownership = cData->giveownership; giveownership; giveownership = giveownership->previous)
+            show_giveownership_info(cmd, user, giveownership);
+    }
     return 1;
 }
 
 static CHANSERV_FUNC(cmd_netinfo)
 {
-    extern time_t boot_time;
+    extern unsigned long boot_time;
     extern unsigned long burst_length;
     char interval[INTERVALLEN];
 
@@ -4388,6 +4876,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))
         {
@@ -4430,13 +4920,13 @@ static CHANSERV_FUNC(cmd_peek)
     char modes[MODELEN];
     unsigned int n;
     struct helpfile_table table;
+    int opcount = 0, voicecount = 0, srvcount = 0;
 
     irc_make_chanmode(channel, modes);
 
     reply("CSMSG_PEEK_INFO", channel->name);
     reply("CSMSG_PEEK_TOPIC", channel->topic);
     reply("CSMSG_PEEK_MODES", modes);
-    reply("CSMSG_PEEK_USERS", channel->members.used);
 
     table.length = 0;
     table.width = 1;
@@ -4444,13 +4934,24 @@ static CHANSERV_FUNC(cmd_peek)
     table.contents = alloca(channel->members.used*sizeof(*table.contents));
     for(n = 0; n < channel->members.used; n++)
     {
-       mn = channel->members.list[n];
-       if(!(mn->modes & MODE_CHANOP) || IsLocal(mn->user))
+        mn = channel->members.list[n];
+        if(IsLocal(mn->user))
+            srvcount++;
+        else if(mn->modes & MODE_CHANOP)
+            opcount++;
+        else if(mn->modes & MODE_VOICE)
+            voicecount++;
+
+        if(!(mn->modes & MODE_CHANOP) || IsLocal(mn->user))
             continue;
         table.contents[table.length] = alloca(sizeof(**table.contents));
         table.contents[table.length][0] = mn->user->nick;
         table.length++;
     }
+
+    reply("CSMSG_PEEK_USERS", channel->members.used, opcount, voicecount,
+          (channel->members.used - opcount - voicecount - srvcount));
+
     if(table.length)
     {
         reply("CSMSG_PEEK_OPS");
@@ -4512,8 +5013,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]
@@ -4524,7 +5033,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;
@@ -4556,24 +5065,24 @@ static CHANSERV_FUNC(cmd_seen)
 
     if(!irccasecmp(argv[1], chanserv->nick))
     {
-       reply("CSMSG_IS_CHANSERV");
-       return 1;
+        reply("CSMSG_IS_CHANSERV");
+        return 1;
     }
 
     if(!(handle = get_handle_info(argv[1])))
     {
-       reply("MSG_HANDLE_UNKNOWN", argv[1]);
-       return 0;
+        reply("MSG_HANDLE_UNKNOWN", argv[1]);
+        return 0;
     }
 
     if(!(uData = GetTrueChannelAccess(channel->channel_info, handle)))
     {
-       reply("CSMSG_NO_CHAN_USER", handle->handle, channel->name);
-       return 0;
+        reply("CSMSG_NO_CHAN_USER", handle->handle, channel->name);
+        return 0;
     }
 
     if(uData->present)
-       reply("CSMSG_USER_PRESENT", handle->handle);
+        reply("CSMSG_USER_PRESENT", handle->handle);
     else if(uData->seen)
         reply("CSMSG_USER_SEEN", handle->handle, channel->name, intervalString(seen, now - uData->seen, user->handle_info));
     else
@@ -4773,9 +5282,9 @@ static CHANSERV_FUNC(cmd_events)
     reply("CSMSG_EVENT_SEARCH_RESULTS");
     matches = log_entry_search(&discrim, log_report_entry, &report);
     if(matches)
-       reply("MSG_MATCH_COUNT", matches);
+        reply("MSG_MATCH_COUNT", matches);
     else
-       reply("MSG_NO_MATCHES");
+        reply("MSG_NO_MATCHES");
     return 1;
 }
 
@@ -4788,6 +5297,23 @@ static CHANSERV_FUNC(cmd_say)
         msg = unsplit_string(argv + 1, argc - 1, NULL);
         send_channel_message(channel, cmd->parent->bot, "%s", msg);
     }
+    else if(*argv[1] == '*' && argv[1][1] != '\0')
+    {
+        struct handle_info *hi;
+        struct userNode *authed;
+
+        REQUIRE_PARAMS(3);
+        msg = unsplit_string(argv + 2, argc - 2, NULL);
+
+        if (!(hi = get_handle_info(argv[1] + 1)))
+        {
+            reply("MSG_HANDLE_UNKNOWN", argv[1] + 1);
+            return 0;
+        }
+
+        for (authed = hi->users; authed; authed = authed->next_authed)
+            send_target_message(5, authed->nick, cmd->parent->bot, "%s", msg);
+    }
     else if(GetUserH(argv[1]))
     {
         REQUIRE_PARAMS(3);
@@ -4812,6 +5338,23 @@ static CHANSERV_FUNC(cmd_emote)
         msg = unsplit_string(argv + 1, argc - 1, NULL);
         send_channel_message(channel, cmd->parent->bot, "\001ACTION %s\001", msg);
     }
+    else if(*argv[1] == '*' && argv[1][1] != '\0')
+    {
+        struct handle_info *hi;
+        struct userNode *authed;
+
+        REQUIRE_PARAMS(3);
+        msg = unsplit_string(argv + 2, argc - 2, NULL);
+
+        if (!(hi = get_handle_info(argv[1] + 1)))
+        {
+            reply("MSG_HANDLE_UNKNOWN", argv[1] + 1);
+            return 0;
+        }
+
+        for (authed = hi->users; authed; authed = authed->next_authed)
+            send_target_message(5, authed->nick, cmd->parent->bot, "\001ACTION %s\001", msg);
+    }
     else if(GetUserH(argv[1]))
     {
         msg = unsplit_string(argv + 2, argc - 2, NULL);
@@ -4834,7 +5377,7 @@ chanserv_support_channels(void)
 static CHANSERV_FUNC(cmd_expire)
 {
     int channel_count = registered_channels;
-    expire_channels(NULL);
+    expire_channels(chanserv);
     reply("CSMSG_CHANNELS_EXPIRED", channel_count - registered_channels);
     return 1;
 }
@@ -4844,28 +5387,39 @@ chanserv_expire_suspension(void *data)
 {
     struct suspended *suspended = data;
     struct chanNode *channel;
+    unsigned int ii;
 
+    /* Update the channel registration data structure. */
     if(!suspended->expires || (now < suspended->expires))
         suspended->revoked = now;
     channel = suspended->cData->channel;
     suspended->cData->channel = channel;
     suspended->cData->flags &= ~CHANNEL_SUSPENDED;
+
+    /* 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);
     }
-}
 
-static CHANSERV_FUNC(cmd_csuspend)
+    /* Mark everyone currently in the channel as present. */
+    for(ii = 0; ii < channel->members.used; ++ii)
+    {
+        struct userData *uData = GetChannelAccess(suspended->cData, channel->members.list[ii]->user->handle_info);
+        if(uData)
+        {
+            uData->present = 1;
+            uData->seen = now;
+        }
+    }
+}
+
+static CHANSERV_FUNC(cmd_csuspend)
 {
     struct suspended *suspended;
     char reason[MAXLEN];
-    time_t expiry, duration;
+    unsigned long expiry, duration;
     struct userData *uData;
 
     REQUIRE_PARAMS(3);
@@ -4880,9 +5434,9 @@ static CHANSERV_FUNC(cmd_csuspend)
         argv[1]++;
     else if(IsSuspended(channel->channel_info))
     {
-       reply("CSMSG_ALREADY_SUSPENDED", channel->name);
+        reply("CSMSG_ALREADY_SUSPENDED", channel->name);
         show_suspension_info(cmd, user, channel->channel_info->suspended);
-       return 0;
+        return 0;
     }
 
     if(!strcmp(argv[1], "0"))
@@ -4932,6 +5486,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);
@@ -4967,8 +5522,8 @@ typedef struct chanservSearch
     char *name;
     char *registrar;
 
-    time_t unvisited;
-    time_t registered;
+    unsigned long unvisited;
+    unsigned long registered;
 
     unsigned long flags;
     unsigned int limit;
@@ -4988,49 +5543,49 @@ chanserv_search_create(struct userNode *user, unsigned int argc, char *argv[])
 
     for(i = 0; i < argc; i++)
     {
-       /* Assume all criteria require arguments. */
-       if(i == (argc - 1))
-       {
-           send_message(user, chanserv, "MSG_MISSING_PARAMS", argv[i]);
+        /* Assume all criteria require arguments. */
+        if(i == (argc - 1))
+        {
+            send_message(user, chanserv, "MSG_MISSING_PARAMS", argv[i]);
             goto fail;
-       }
+        }
 
-       if(!irccasecmp(argv[i], "name"))
-           search->name = argv[++i];
-       else if(!irccasecmp(argv[i], "registrar"))
-           search->registrar = argv[++i];
-       else if(!irccasecmp(argv[i], "unvisited"))
-           search->unvisited = ParseInterval(argv[++i]);
-       else if(!irccasecmp(argv[i], "registered"))
-           search->registered = ParseInterval(argv[++i]);
-       else if(!irccasecmp(argv[i], "flags"))
-       {
-           i++;
-           if(!irccasecmp(argv[i], "nodelete"))
-               search->flags |= CHANNEL_NODELETE;
-           else if(!irccasecmp(argv[i], "suspended"))
-               search->flags |= CHANNEL_SUSPENDED;
+        if(!irccasecmp(argv[i], "name"))
+            search->name = argv[++i];
+        else if(!irccasecmp(argv[i], "registrar"))
+            search->registrar = argv[++i];
+        else if(!irccasecmp(argv[i], "unvisited"))
+            search->unvisited = ParseInterval(argv[++i]);
+        else if(!irccasecmp(argv[i], "registered"))
+            search->registered = ParseInterval(argv[++i]);
+        else if(!irccasecmp(argv[i], "flags"))
+        {
+            i++;
+            if(!irccasecmp(argv[i], "nodelete"))
+                search->flags |= CHANNEL_NODELETE;
+            else if(!irccasecmp(argv[i], "suspended"))
+                search->flags |= CHANNEL_SUSPENDED;
             else if(!irccasecmp(argv[i], "unreviewed"))
                 search->flags |= CHANNEL_UNREVIEWED;
-           else
-           {
-               send_message(user, chanserv, "CSMSG_INVALID_CFLAG", argv[i]);
-               goto fail;
-           }
-       }
-       else if(!irccasecmp(argv[i], "limit"))
-           search->limit = strtoul(argv[++i], NULL, 10);
-       else
-       {
-           send_message(user, chanserv, "MSG_INVALID_CRITERIA", argv[i]);
-           goto fail;
-       }
+            else
+            {
+                send_message(user, chanserv, "CSMSG_INVALID_CFLAG", argv[i]);
+                goto fail;
+            }
+        }
+        else if(!irccasecmp(argv[i], "limit"))
+            search->limit = strtoul(argv[++i], NULL, 10);
+        else
+        {
+            send_message(user, chanserv, "MSG_INVALID_CRITERIA", argv[i]);
+            goto fail;
+        }
     }
 
     if(search->name && !strcmp(search->name, "*"))
-       search->name = 0;
+        search->name = 0;
     if(search->registrar && !strcmp(search->registrar, "*"))
-       search->registrar = 0;
+        search->registrar = 0;
 
     return search;
   fail:
@@ -5048,7 +5603,7 @@ chanserv_channel_match(struct chanData *channel, search_t search)
        (search->unvisited && (now - channel->visited) < search->unvisited) ||
        (search->registered && (now - channel->registered) > search->registered) ||
        (search->flags && ((search->flags & channel->flags) != search->flags)))
-       return 0;
+        return 0;
 
     return 1;
 }
@@ -5061,10 +5616,10 @@ chanserv_channel_search(search_t search, channel_search_func smf, void *data)
 
     for(channel = channelList; channel && matches < search->limit; channel = channel->next)
     {
-       if(!chanserv_channel_match(channel, search))
+        if(!chanserv_channel_match(channel, search))
             continue;
-       matches++;
-       smf(channel, data);
+        matches++;
+        smf(channel, data);
     }
 
     return matches;
@@ -5090,13 +5645,13 @@ static CHANSERV_FUNC(cmd_search)
     REQUIRE_PARAMS(3);
 
     if(!irccasecmp(argv[1], "count"))
-       action = search_count;
+        action = search_count;
     else if(!irccasecmp(argv[1], "print"))
-       action = search_print;
+        action = search_print;
     else
     {
-       reply("CSMSG_ACTION_INVALID", argv[1]);
-       return 0;
+        reply("CSMSG_ACTION_INVALID", argv[1]);
+        return 0;
     }
 
     search = chanserv_search_create(user, argc - 2, argv + 2);
@@ -5104,17 +5659,17 @@ static CHANSERV_FUNC(cmd_search)
         return 0;
 
     if(action == search_count)
-       search->limit = INT_MAX;
+        search->limit = INT_MAX;
 
     if(action == search_print)
-       reply("CSMSG_CHANNEL_SEARCH_RESULTS");
+        reply("CSMSG_CHANNEL_SEARCH_RESULTS");
 
     matches = chanserv_channel_search(search, action, user);
 
     if(matches)
-       reply("MSG_MATCH_COUNT", matches);
+        reply("MSG_MATCH_COUNT", matches);
     else
-       reply("MSG_NO_MATCHES");
+        reply("MSG_NO_MATCHES");
 
     free(search);
     return 1;
@@ -5123,14 +5678,14 @@ static CHANSERV_FUNC(cmd_search)
 static CHANSERV_FUNC(cmd_unvisited)
 {
     struct chanData *cData;
-    time_t interval = chanserv_conf.channel_expire_delay;
+    unsigned long interval = chanserv_conf.channel_expire_delay;
     char buffer[INTERVALLEN];
     unsigned int limit = 25, matches = 0;
 
     if(argc > 1)
     {
-       interval = ParseInterval(argv[1]);
-       if(argc > 2)
+        interval = ParseInterval(argv[1]);
+        if(argc > 2)
             limit = atoi(argv[2]);
     }
 
@@ -5139,12 +5694,12 @@ static CHANSERV_FUNC(cmd_unvisited)
 
     for(cData = channelList; cData && matches < limit; cData = cData->next)
     {
-       if((now - cData->visited) < interval)
+        if((now - cData->visited) < interval)
             continue;
 
-       intervalString(buffer, now - cData->visited, user->handle_info);
-       reply("CSMSG_UNVISITED_DATA", cData->channel->name, buffer);
-       matches++;
+        intervalString(buffer, now - cData->visited, user->handle_info);
+        reply("CSMSG_UNVISITED_DATA", cData->channel->name, buffer);
+        matches++;
     }
 
     return 1;
@@ -5162,20 +5717,20 @@ static MODCMD_FUNC(chan_opt_defaulttopic)
             return 0;
         }
 
-       topic = unsplit_string(argv+1, argc-1, NULL);
+        topic = unsplit_string(argv+1, argc-1, NULL);
 
         free(channel->channel_info->topic);
-       if(topic[0] == '*' && topic[1] == 0)
-       {
+        if(topic[0] == '*' && topic[1] == 0)
+        {
             topic = channel->channel_info->topic = NULL;
-       }
-       else
-       {
-           topic = channel->channel_info->topic = strdup(topic);
+        }
+        else
+        {
+            topic = channel->channel_info->topic = strdup(topic);
             if(channel->channel_info->topic_mask
                && !match_ircglob(channel->channel_info->topic, channel->channel_info->topic_mask))
                 reply("CSMSG_TOPIC_MISMATCH", channel->name);
-       }
+        }
         SetChannelTopic(channel, chanserv, topic ? topic : "", 1);
     }
 
@@ -5199,22 +5754,22 @@ static MODCMD_FUNC(chan_opt_topicmask)
             return 0;
         }
 
-       mask = unsplit_string(argv+1, argc-1, NULL);
+        mask = unsplit_string(argv+1, argc-1, NULL);
 
         if(cData->topic_mask)
             free(cData->topic_mask);
-       if(mask[0] == '*' && mask[1] == 0)
-       {
-           cData->topic_mask = 0;
-       }
-       else
-       {
+        if(mask[0] == '*' && mask[1] == 0)
+        {
+            cData->topic_mask = 0;
+        }
+        else
+        {
             cData->topic_mask = strdup(mask);
             if(!cData->topic)
                 reply("CSMSG_MASK_BUT_NO_TOPIC", channel->name);
             else if(!match_ircglob(cData->topic, cData->topic_mask))
                 reply("CSMSG_TOPIC_MISMATCH", channel->name);
-       }
+        }
     }
 
     if(channel->channel_info->topic_mask)
@@ -5232,18 +5787,18 @@ int opt_greeting_common(struct userNode *user, struct svccmd *cmd, int argc, cha
         char *previous;
 
         previous = *data;
-       if(greeting[0] == '*' && greeting[1] == 0)
-           *data = NULL;
-       else
-       {
-           unsigned int length = strlen(greeting);
-           if(length > chanserv_conf.greeting_length)
-           {
-               reply("CSMSG_GREETING_TOO_LONG", length, chanserv_conf.greeting_length);
-               return 0;
-           }
-           *data = strdup(greeting);
-       }
+        if(greeting[0] == '*' && greeting[1] == 0)
+            *data = NULL;
+        else
+        {
+            unsigned int length = strlen(greeting);
+            if(length > chanserv_conf.greeting_length)
+            {
+                reply("CSMSG_GREETING_TOO_LONG", length, chanserv_conf.greeting_length);
+                return 0;
+            }
+            *data = strdup(greeting);
+        }
         if(previous)
             free(previous);
     }
@@ -5268,7 +5823,7 @@ static MODCMD_FUNC(chan_opt_usergreeting)
 static MODCMD_FUNC(chan_opt_modes)
 {
     struct mod_chanmode *new_modes;
-    char modes[MODELEN];
+    char modes[MAXLEN];
 
     if(argc > 1)
     {
@@ -5277,12 +5832,12 @@ static MODCMD_FUNC(chan_opt_modes)
             reply("CSMSG_NO_ACCESS");
             return 0;
         }
-       if(argv[1][0] == '*' && argv[1][1] == 0)
-       {
+        if(argv[1][0] == '*' && argv[1][1] == 0)
+        {
             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, 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;
         }
@@ -5317,27 +5872,27 @@ channel_binary_option(char *name, unsigned long mask, struct userNode *user, str
 
     if(argc > 1)
     {
-       /* Set flag according to value. */
-       if(enabled_string(argv[1]))
-       {
-           cData->flags |= mask;
-           value = 1;
-       }
-       else if(disabled_string(argv[1]))
-       {
-           cData->flags &= ~mask;
-           value = 0;
-       }
-       else
-       {
-           reply("MSG_INVALID_BINARY", argv[1]);
-           return 0;
-       }
+        /* Set flag according to value. */
+        if(enabled_string(argv[1]))
+        {
+            cData->flags |= mask;
+            value = 1;
+        }
+        else if(disabled_string(argv[1]))
+        {
+            cData->flags &= ~mask;
+            value = 0;
+        }
+        else
+        {
+            reply("MSG_INVALID_BINARY", argv[1]);
+            return 0;
+        }
     }
     else
     {
-       /* Find current option value. */
-       value = (cData->flags & mask) ? 1 : 0;
+        /* Find current option value. */
+        value = (cData->flags & mask) ? 1 : 0;
     }
 
     if(value)
@@ -5351,8 +5906,8 @@ static MODCMD_FUNC(chan_opt_nodelete)
 {
     if((argc > 1) && (!IsOper(user) || !user->handle_info || (user->handle_info->opserv_level < chanserv_conf.nodelete_level)))
     {
-       reply("MSG_SETTING_PRIVILEGED", argv[0]);
-       return 0;
+        reply("MSG_SETTING_PRIVILEGED", argv[0]);
+        return 0;
     }
 
     CHANNEL_BINARY_OPTION("CSMSG_SET_NODELETE", CHANNEL_NODELETE);
@@ -5363,6 +5918,11 @@ static MODCMD_FUNC(chan_opt_dynlimit)
     CHANNEL_BINARY_OPTION("CSMSG_SET_DYNLIMIT", CHANNEL_DYNAMIC_LIMIT);
 }
 
+static MODCMD_FUNC(chan_opt_advtopic)
+{
+    CHANNEL_BINARY_OPTION("CSMSG_SET_ADVTOPIC", CHANNEL_ADVTOPIC);
+}
+
 static MODCMD_FUNC(chan_opt_offchannel)
 {
     struct chanData *cData = channel->channel_info;
@@ -5370,16 +5930,16 @@ static MODCMD_FUNC(chan_opt_offchannel)
 
     if(argc > 1)
     {
-       /* Set flag according to value. */
-       if(enabled_string(argv[1]))
-       {
+        /* Set flag according to value. */
+        if(enabled_string(argv[1]))
+        {
             if(!IsOffChannel(cData))
                 DelChannelUser(chanserv, channel, "Going off-channel.", 0);
-           cData->flags |= CHANNEL_OFFCHANNEL;
-           value = 1;
-       }
-       else if(disabled_string(argv[1]))
-       {
+            cData->flags |= CHANNEL_OFFCHANNEL;
+            value = 1;
+        }
+        else if(disabled_string(argv[1]))
+        {
             if(IsOffChannel(cData))
             {
                 struct mod_chanmode change;
@@ -5389,19 +5949,19 @@ static MODCMD_FUNC(chan_opt_offchannel)
                 change.args[0].u.member = AddChannelUser(chanserv, channel);
                 mod_chanmode_announce(chanserv, channel, &change);
             }
-           cData->flags &= ~CHANNEL_OFFCHANNEL;
-           value = 0;
-       }
-       else
-       {
-           reply("MSG_INVALID_BINARY", argv[1]);
-           return 0;
-       }
+            cData->flags &= ~CHANNEL_OFFCHANNEL;
+            value = 0;
+        }
+        else
+        {
+            reply("MSG_INVALID_BINARY", argv[1]);
+            return 0;
+        }
     }
     else
     {
-       /* Find current option value. */
-       value = (cData->flags & CHANNEL_OFFCHANNEL) ? 1 : 0;
+        /* Find current option value. */
+        value = (cData->flags & CHANNEL_OFFCHANNEL) ? 1 : 0;
     }
 
     if(value)
@@ -5426,10 +5986,10 @@ static MODCMD_FUNC(chan_opt_unreviewed)
         else if(disabled_string(argv[1]))
             new_value = 0;
         else
-       {
-           reply("MSG_INVALID_BINARY", argv[1]);
-           return 0;
-       }
+        {
+            reply("MSG_INVALID_BINARY", argv[1]);
+            return 0;
+        }
 
         if (new_value != value)
         {
@@ -5513,8 +6073,8 @@ channel_level_option(enum levelOption option, struct userNode *user, struct chan
         }
         value = user_level_from_name(argv[1], UL_OWNER+1);
         if(!value && strcmp(argv[1], "0"))
-       {
-           reply("CSMSG_INVALID_ACCESS", argv[1]);
+        {
+            reply("CSMSG_INVALID_ACCESS", argv[1]);
             return 0;
         }
         uData = GetChannelUser(cData, user->handle_info);
@@ -5609,6 +6169,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);
@@ -5618,31 +6183,31 @@ static int
 channel_multiple_option(enum charOption option, struct userNode *user, struct chanNode *channel, int argc, char *argv[], struct svccmd *cmd)
 {
     struct chanData *cData = channel->channel_info;
-    int count = charOptions[option].count, index;
+    int count = charOptions[option].count, idx;
 
     if(argc > 1)
     {
-        index = atoi(argv[1]);
+        idx = atoi(argv[1]);
 
-       if(!isdigit(argv[1][0]) || (index < 0) || (index >= count))
-       {
-           reply("CSMSG_INVALID_NUMERIC", index);
+        if(!isdigit(argv[1][0]) || (idx < 0) || (idx >= count))
+        {
+            reply("CSMSG_INVALID_NUMERIC", idx);
             /* Show possible values. */
-            for(index = 0; index < count; index++)
-                reply(charOptions[option].format_name, index, user_find_message(user, charOptions[option].values[index].format_name));
-           return 0;
-       }
+            for(idx = 0; idx < count; idx++)
+                reply(charOptions[option].format_name, idx, user_find_message(user, charOptions[option].values[idx].format_name));
+            return 0;
+        }
 
-       cData->chOpts[option] = charOptions[option].values[index].value;
+        cData->chOpts[option] = charOptions[option].values[idx].value;
     }
     else
     {
-       /* Find current option value. */
+        /* Find current option value. */
       find_value:
-       for(index = 0;
-            (index < count) && (cData->chOpts[option] != charOptions[option].values[index].value);
-            index++);
-        if(index == count)
+        for(idx = 0;
+            (idx < count) && (cData->chOpts[option] != charOptions[option].values[idx].value);
+            idx++);
+        if(idx == count)
         {
             /* Somehow, the option value is corrupt; reset it to the default. */
             cData->chOpts[option] = charOptions[option].default_value;
@@ -5650,7 +6215,7 @@ channel_multiple_option(enum charOption option, struct userNode *user, struct ch
         }
     }
 
-    reply(charOptions[option].format_name, index, user_find_message(user, charOptions[option].values[index].format_name));
+    reply(charOptions[option].format_name, idx, user_find_message(user, charOptions[option].values[idx].format_name));
     return 1;
 }
 
@@ -5714,13 +6279,13 @@ static CHANSERV_FUNC(cmd_set)
 
     if(argc < 2)
     {
-       reply("CSMSG_CHANNEL_OPTIONS");
+        reply("CSMSG_CHANNEL_OPTIONS");
         for(ii = 0; ii < set_shows_list.used; ii++)
         {
             subcmd = set_shows_list.list[ii];
             subcmd->command->func(user, channel, 1, argv+1, subcmd);
         }
-       return 1;
+        return 1;
     }
 
     sprintf(buf, "%s %s", argv[0], argv[1]);
@@ -5755,7 +6320,7 @@ user_binary_option(char *name, unsigned long mask, struct userNode *user, struct
 
     if(argc < 2)
     {
-       /* Just show current option value. */
+        /* Just show current option value. */
     }
     else if(enabled_string(argv[1]))
     {
@@ -5793,6 +6358,10 @@ static MODCMD_FUNC(user_opt_noautoop)
 
 static MODCMD_FUNC(user_opt_autoinvite)
 {
+    if((argc > 1) && !check_user_level(channel, user, lvlInviteMe, 1, 0))
+    {
+        reply("CSMSG_LOW_CHANNEL_ACCESS", channel->name);
+    }
     return user_binary_option("CSMSG_USET_AUTOINVITE", USER_AUTO_INVITE, CSFUNC_ARGS);
 }
 
@@ -5805,11 +6374,11 @@ static MODCMD_FUNC(user_opt_info)
 
     if(!uData)
     {
-       /* If they got past the command restrictions (which require access)
+        /* If they got past the command restrictions (which require access)
          * but fail this test, we have some fool with security override on.
          */
-       reply("CSMSG_NOT_USER", channel->name);
-       return 0;
+        reply("CSMSG_NOT_USER", channel->name);
+        return 0;
     }
 
     if(argc > 1)
@@ -5878,11 +6447,11 @@ static CHANSERV_FUNC(cmd_uset)
 
     if(argc < 2)
     {
-       /* Do this so options are presented in a consistent order. */
-       reply("CSMSG_USER_OPTIONS");
+        /* Do this so options are presented in a consistent order. */
+        reply("CSMSG_USER_OPTIONS");
         for(ii = 0; ii < uset_shows_list.used; ii++)
             uset_shows_list.list[ii]->command->func(user, channel, 1, argv+1, uset_shows_list.list[ii]);
-       return 1;
+        return 1;
     }
 
     sprintf(buf, "%s %s", argv[0], argv[1]);
@@ -5905,13 +6474,21 @@ static CHANSERV_FUNC(cmd_giveownership)
     struct chanData *cData = channel->channel_info;
     struct do_not_register *dnr;
     const char *confirm;
-    unsigned int force;
-    unsigned short co_access;
-    char reason[MAXLEN];
+    struct giveownership *giveownership;
+    unsigned int force, override;
+    unsigned short co_access, new_owner_old_access;
+    char reason[MAXLEN], transfer_reason[MAXLEN];
 
     REQUIRE_PARAMS(2);
     curr_user = GetChannelAccess(cData, user->handle_info);
     force = IsHelping(user) && (argc > 2) && !irccasecmp(argv[2], "force");
+
+    struct userData *uData = _GetChannelUser(channel->channel_info, user->handle_info, 1, 0);
+    override = ((cmd->effective_flags & MODCMD_REQUIRE_CHANUSER)
+                && (uData->access > 500)
+                && (!(uData = _GetChannelUser(channel->channel_info, user->handle_info, 0, 0))
+                    || uData->access < 500));
+
     if(!curr_user || (curr_user->access != UL_OWNER))
     {
         struct userData *owner = NULL;
@@ -5930,7 +6507,7 @@ static CHANSERV_FUNC(cmd_giveownership)
         }
         curr_user = owner;
     }
-    else if(!force && (now < (time_t)(cData->ownerTransfer + chanserv_conf.giveownership_period)))
+    else if(!force && (now < cData->ownerTransfer + chanserv_conf.giveownership_period))
     {
         char delay[INTERVALLEN];
         intervalString(delay, cData->ownerTransfer + chanserv_conf.giveownership_period - now, user->handle_info);
@@ -5944,162 +6521,566 @@ static CHANSERV_FUNC(cmd_giveownership)
         reply("CSMSG_NO_TRANSFER_SELF");
         return 0;
     }
-    new_owner = GetChannelAccess(cData, new_owner_hi);
-    if(!new_owner)
-    {
-        if(force)
-        {
-            new_owner = add_channel_user(cData, new_owner_hi, UL_COOWNER, 0, NULL);
-        }
-        else
-        {
-            reply("CSMSG_NO_CHAN_USER", new_owner_hi->handle, channel->name);
+    new_owner = GetChannelAccess(cData, new_owner_hi);
+    if(!new_owner)
+    {
+        if(force)
+        {
+            new_owner = add_channel_user(cData, new_owner_hi, UL_OWNER - 1, 0, NULL);
+        }
+        else
+        {
+            reply("CSMSG_NO_CHAN_USER", new_owner_hi->handle, channel->name);
+            return 0;
+        }
+    }
+    if((chanserv_get_owned_count(new_owner_hi) >= chanserv_conf.max_owned) && !force)
+    {
+        reply("CSMSG_OWN_TOO_MANY", new_owner_hi->handle, chanserv_conf.max_owned);
+        return 0;
+    }
+    if((dnr = chanserv_is_dnr(NULL, new_owner_hi)) && !force) {
+        if(!IsHelping(user))
+            reply("CSMSG_DNR_ACCOUNT", new_owner_hi->handle);
+        else
+            chanserv_show_dnrs(user, cmd, NULL, new_owner_hi->handle);
+        return 0;
+    }
+    invoker = GetChannelUser(cData, user->handle_info);
+    if(invoker->access <= UL_OWNER)
+    {
+        confirm = make_confirmation_string(curr_user);
+        if((argc < 3) || strcmp(argv[2], confirm))
+        {
+            reply("CSMSG_CONFIRM_GIVEOWNERSHIP", new_owner_hi->handle, confirm);
+            return 0;
+        }
+    }
+    new_owner_old_access = new_owner->access;
+    if(new_owner->access >= UL_COOWNER)
+        co_access = new_owner->access;
+    else
+        co_access = UL_COOWNER;
+    new_owner->access = UL_OWNER;
+    if(curr_user)
+        curr_user->access = co_access;
+    cData->ownerTransfer = now;
+    giveownership = calloc(1, sizeof(*giveownership));
+    giveownership->issued = now;
+    giveownership->old_owner = curr_user->handle->handle;
+    giveownership->target = new_owner_hi->handle;
+    giveownership->target_access = new_owner_old_access;
+    if(override)
+    {
+        if(argc > (2 + force))
+        {
+            unsplit_string(argv + 2 + force, argc - 2 - force, transfer_reason);
+            giveownership->reason = strdup(transfer_reason);
+        }
+        giveownership->staff_issuer = strdup(user->handle_info->handle);
+    }
+
+    giveownership->previous = channel->channel_info->giveownership;
+    channel->channel_info->giveownership = giveownership;
+    reply("CSMSG_OWNERSHIP_GIVEN", channel->name, new_owner_hi->handle);
+    sprintf(reason, "%s ownership transferred to %s by %s.", channel->name, new_owner_hi->handle, user->handle_info->handle);
+    global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+    return 1;
+}
+
+static void
+chanserv_expire_user_suspension(void *data)
+{
+    struct userData *target = data;
+
+       target->expires = 0;
+       target->flags &= ~USER_SUSPENDED;       
+}
+
+static CHANSERV_FUNC(cmd_suspend)
+{
+    struct handle_info *hi;
+    struct userData *actor, *real_actor, *target;
+    unsigned int override = 0;
+    time_t expiry;
+
+    REQUIRE_PARAMS(3);
+    if(!(hi = modcmd_get_handle_info(user, argv[1]))) return 0;
+    actor = GetChannelUser(channel->channel_info, user->handle_info);
+    real_actor = GetChannelAccess(channel->channel_info, user->handle_info);
+    if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+    {
+        reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+        return 0;
+    }
+    if(target->access >= actor->access)
+    {
+        reply("MSG_USER_OUTRANKED", hi->handle);
+        return 0;
+    }
+    if(target->flags & USER_SUSPENDED)
+    {
+        reply("CSMSG_ALREADY_SUSPENDED", hi->handle);
+        return 0;
+    }
+    if(target->present)
+    {
+        target->present = 0;
+        target->seen = now;
+    }
+    if(!real_actor || target->access >= real_actor->access)
+        override = CMD_LOG_OVERRIDE;
+       if(!strcmp(argv[2], "0"))
+        expiry = 0;
+    else
+    {
+        unsigned int duration;
+        if(!(duration = ParseInterval(argv[2])))
+        {
+            reply("MSG_INVALID_DURATION", argv[2]);
+            return 0;
+        }
+        expiry = now + duration;
+    }
+
+       target->expires = expiry;
+
+       if(target->expires)
+               timeq_add(target->expires, chanserv_expire_user_suspension, target);
+
+    target->flags |= USER_SUSPENDED;
+    reply("CSMSG_USER_SUSPENDED", hi->handle, channel->name);
+    return 1 | override;
+}
+
+static CHANSERV_FUNC(cmd_unsuspend)
+{
+    struct handle_info *hi;
+    struct userData *actor, *real_actor, *target;
+    unsigned int override = 0;
+
+    REQUIRE_PARAMS(2);
+    if(!(hi = modcmd_get_handle_info(user, argv[1]))) return 0;
+    actor = GetChannelUser(channel->channel_info, user->handle_info);
+    real_actor = GetChannelAccess(channel->channel_info, user->handle_info);
+    if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
+    {
+        reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
+        return 0;
+    }
+    if(target->access >= actor->access)
+    {
+        reply("MSG_USER_OUTRANKED", hi->handle);
+        return 0;
+    }
+    if(!(target->flags & USER_SUSPENDED))
+    {
+        reply("CSMSG_NOT_SUSPENDED", hi->handle);
+        return 0;
+    }
+    if(!real_actor || target->access >= real_actor->access)
+        override = CMD_LOG_OVERRIDE;
+    timeq_del(target->expires, chanserv_expire_user_suspension, target, 0);
+    target->flags &= ~USER_SUSPENDED;
+    scan_user_presence(target, NULL);
+    reply("CSMSG_USER_UNSUSPENDED", hi->handle, channel->name);
+    return 1 | override;
+}
+
+static MODCMD_FUNC(cmd_deleteme)
+{
+    struct handle_info *hi;
+    struct userData *target;
+    const char *confirm_string;
+    unsigned short access_level;
+    char *channel_name;
+
+    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 == UL_OWNER)
+    {
+        reply("CSMSG_NO_OWNER_DELETEME", channel->name);
+        return 0;
+    }
+    confirm_string = make_confirmation_string(target);
+    if((argc < 2) || strcmp(argv[1], confirm_string))
+    {
+        reply("CSMSG_CONFIRM_DELETEME", confirm_string);
+        return 0;
+    }
+    access_level = target->access;
+    channel_name = strdup(channel->name);
+    del_channel_user(target, 1);
+    reply("CSMSG_DELETED_YOU", access_level, channel_name);
+    free(channel_name);
+    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;
         }
     }
-    if((chanserv_get_owned_count(new_owner_hi) >= chanserv_conf.max_owned) && !force)
-    {
-        reply("CSMSG_OWN_TOO_MANY", new_owner_hi->handle, chanserv_conf.max_owned);
+    
+    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;
     }
-    if((dnr = chanserv_is_dnr(NULL, new_owner_hi)) && !force) {
-        if(!IsHelping(user))
-            reply("CSMSG_DNR_ACCOUNT", new_owner_hi->handle);
-        else
-            chanserv_show_dnrs(user, cmd, NULL, new_owner_hi->handle);
+}
+
+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;
     }
-    invoker = GetChannelUser(cData, user->handle_info);
-    if(invoker->access <= UL_OWNER)
-    {
-        confirm = make_confirmation_string(curr_user);
-        if((argc < 3) || strcmp(argv[2], confirm))
+    if(argc > 1 && cData->vote_start) {
+        hi = user->handle_info;
+        if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
         {
-            reply("CSMSG_CONFIRM_GIVEOWNERSHIP", new_owner_hi->handle, confirm);
+            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(new_owner->access >= UL_COOWNER)
-        co_access = new_owner->access;
-    else
-        co_access = UL_COOWNER;
-    new_owner->access = UL_OWNER;
-    if(curr_user)
-        curr_user->access = co_access;
-    cData->ownerTransfer = now;
-    reply("CSMSG_OWNERSHIP_GIVEN", channel->name, new_owner_hi->handle);
-    sprintf(reason, "%s ownership transferred to %s by %s.", channel->name, new_owner_hi->handle, user->handle_info->handle);
-    global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+    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_suspend)
+static CHANSERV_FUNC(cmd_startvote)
 {
+    struct chanData *cData = channel->channel_info;
+    struct userData *target;
     struct handle_info *hi;
-    struct userData *self, *real_self, *target;
-    unsigned int override = 0;
-
-    REQUIRE_PARAMS(2);
-    if(!(hi = modcmd_get_handle_info(user, argv[1]))) return 0;
-    self = GetChannelUser(channel->channel_info, user->handle_info);
-    real_self = GetChannelAccess(channel->channel_info, user->handle_info);
+    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 >= self->access)
-    {
-        reply("MSG_USER_OUTRANKED", hi->handle);
+    if(target->access < 300) {
+        reply("CSMSG_NO_ACCESS");
         return 0;
     }
-    if(target->flags & USER_SUSPENDED)
-    {
-        reply("CSMSG_ALREADY_SUSPENDED", hi->handle);
+    if (!cData->vote) {
+        reply("CSMSG_NO_VOTE");
         return 0;
     }
-    if(target->present)
-    {
-        target->present = 0;
-        target->seen = now;
+    if(cData->vote_start) {
+        reply("CSMSG_STARTVOTE_RUNNING");
+        return 0;
     }
-    if(!real_self || target->access >= real_self->access)
-        override = CMD_LOG_OVERRIDE;
-    target->flags |= USER_SUSPENDED;
-    reply("CSMSG_USER_SUSPENDED", hi->handle, channel->name);
-    return 1 | override;
+    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_unsuspend)
+static CHANSERV_FUNC(cmd_endvote)
 {
+    struct chanData *cData = channel->channel_info;
+    struct userData *target;
     struct handle_info *hi;
-    struct userData *self, *real_self, *target;
-    unsigned int override = 0;
-
-    REQUIRE_PARAMS(2);
-    if(!(hi = modcmd_get_handle_info(user, argv[1]))) return 0;
-    self = GetChannelUser(channel->channel_info, user->handle_info);
-    real_self = GetChannelAccess(channel->channel_info, user->handle_info);
+    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 >= self->access)
-    {
-        reply("MSG_USER_OUTRANKED", hi->handle);
+    if(target->access < 300) {
+        reply("CSMSG_NO_ACCESS");
         return 0;
     }
-    if(!(target->flags & USER_SUSPENDED))
-    {
-        reply("CSMSG_NOT_SUSPENDED", hi->handle);
+    if (!cData->vote) {
+        reply("CSMSG_NO_VOTE");
         return 0;
     }
-    if(!real_self || target->access >= real_self->access)
-        override = CMD_LOG_OVERRIDE;
-    target->flags &= ~USER_SUSPENDED;
-    scan_user_presence(target, NULL);
-    reply("CSMSG_USER_UNSUSPENDED", hi->handle, channel->name);
-    return 1 | override;
+    if(!cData->vote_start) {
+        reply("CSMSG_ENDVOTE_STOPPED");
+        return 0;
+    }
+    cData->vote_start = 0;
+    reply("CSMSG_ENDVOTE_DONE");
+    return 1;
 }
 
-static MODCMD_FUNC(cmd_deleteme)
+static CHANSERV_FUNC(cmd_voteresults)
 {
-    struct handle_info *hi;
+    struct chanData *cData = channel->channel_info;
     struct userData *target;
-    const char *confirm_string;
-    unsigned short access;
-    char *channel_name;
-
-    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 == UL_OWNER)
-    {
-        reply("CSMSG_NO_OWNER_DELETEME", channel->name);
+    struct handle_info *hi;
+    if (!cData) return 0;
+    if (!cData->vote) {
+        reply("CSMSG_NO_VOTE");
         return 0;
     }
-    confirm_string = make_confirmation_string(target);
-    if((argc < 2) || strcmp(argv[1], confirm_string))
-    {
-        reply("CSMSG_CONFIRM_DELETEME", confirm_string);
-        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);
+        }
     }
-    access = target->access;
-    channel_name = strdup(channel->name);
-    del_channel_user(target, 1);
-    reply("CSMSG_DELETED_YOU", access, channel_name);
-    free(channel_name);
     return 1;
 }
 
 static void
 chanserv_refresh_topics(UNUSED_ARG(void *data))
 {
-    unsigned int refresh_num = (now - self->link) / chanserv_conf.refresh_period;
+    unsigned int refresh_num = (now - self->link_time) / chanserv_conf.refresh_period;
     struct chanData *cData;
     char opt;
 
@@ -6296,6 +7277,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
@@ -6310,13 +7301,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:
@@ -6381,11 +7382,9 @@ handle_join(struct modeNode *mNode)
         }
     }
 
-    /* ChanServ will not modify the limits in join-flooded channels.
-       It will also skip DynLimit processing when the user (or srvx)
-       is bursting in, because there are likely more incoming. */
+    /* ChanServ will not modify the limits in join-flooded channels,
+       or when there are enough slots left below the limit. */
     if((cData->flags & CHANNEL_DYNAMIC_LIMIT)
-       && !user->uplink->burst
        && !channel->join_flooded
        && (channel->limit - channel->members.used) < chanserv_conf.adjust_threshold)
     {
@@ -6436,7 +7435,7 @@ handle_join(struct modeNode *mNode)
                 else if(uData->access >= cData->lvlOpts[lvlGiveVoice])
                     modes |= MODE_VOICE;
             }
-            if(uData->access >= UL_PRESENT)
+            if(uData->access >= UL_PRESENT && !HANDLE_FLAGGED(uData->handle, BOT))
                 cData->visited = now;
             if(cData->user_greeting)
                 greeting = cData->user_greeting;
@@ -6465,7 +7464,7 @@ handle_join(struct modeNode *mNode)
         }
         if(greeting)
             send_message_type(4, user, chanserv, "(%s) %s", channel->name, greeting);
-        if(uData && info)
+        if(uData && info && (modes || !(channel->modes & MODE_DELAYJOINS)))
             send_target_message(5, channel->name, chanserv, "[%s] %s", user->nick, uData->info);
     }
     return 0;
@@ -6479,7 +7478,7 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
     unsigned int ii, jj;
 
     if(!user->handle_info)
-       return;
+        return;
 
     mod_chanmode_init(&change);
     change.argc = 1;
@@ -6504,8 +7503,8 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
             continue;
         }
 
-       if(channel->access >= UL_PRESENT)
-           channel->channel->visited = now;
+        if(channel->access >= UL_PRESENT && !HANDLE_FLAGGED(channel->handle, BOT))
+            channel->channel->visited = now;
 
         if(IsUserAutoOp(channel))
         {
@@ -6520,34 +7519,34 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
                 mod_chanmode_announce(chanserv, cn, &change);
         }
 
-       channel->seen = now;
-       channel->present = 1;
+        channel->seen = now;
+        channel->present = 1;
     }
 
     for(ii = 0; ii < user->channels.used; ++ii)
     {
-        struct chanNode *channel = user->channels.list[ii]->channel;
+        struct chanNode *chan = user->channels.list[ii]->channel;
         struct banData *ban;
 
         if((user->channels.list[ii]->modes & (MODE_CHANOP|MODE_VOICE))
-           || !channel->channel_info
-           || IsSuspended(channel->channel_info))
+           || !chan->channel_info
+           || IsSuspended(chan->channel_info))
             continue;
-        for(jj = 0; jj < channel->banlist.used; ++jj)
-            if(user_matches_glob(user, channel->banlist.list[jj]->ban, MATCH_USENICK))
+        for(jj = 0; jj < chan->banlist.used; ++jj)
+            if(user_matches_glob(user, chan->banlist.list[jj]->ban, MATCH_USENICK))
                 break;
-        if(jj < channel->banlist.used)
+        if(jj < chan->banlist.used)
             continue;
-        for(ban = channel->channel_info->bans; ban; ban = ban->next)
+        for(ban = chan->channel_info->bans; ban; ban = ban->next)
         {
             char kick_reason[MAXLEN];
             if(!user_matches_glob(user, ban->mask, MATCH_USENICK | MATCH_VISIBLE))
                 continue;
             change.args[0].mode = MODE_BAN;
             change.args[0].u.hostmask = ban->mask;
-            mod_chanmode_announce(chanserv, channel, &change);
+            mod_chanmode_announce(chanserv, chan, &change);
             sprintf(kick_reason, "(%s) %s", ban->owner, ban->reason);
-            KickChannelUser(user, channel, chanserv, kick_reason);
+            KickChannelUser(user, chan, chanserv, kick_reason);
             ban->triggered = now;
             break;
         }
@@ -6578,32 +7577,36 @@ handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason))
 
     if((cData->flags & CHANNEL_DYNAMIC_LIMIT) && !mn->channel->join_flooded)
     {
-       /* Allow for a bit of padding so that the limit doesn't
-          track the user count exactly, which could get annoying. */
-       if((mn->channel->limit - mn->channel->members.used) > chanserv_conf.adjust_threshold + 5)
-       {
-           timeq_del(0, chanserv_adjust_limit, cData, TIMEQ_IGNORE_WHEN);
-           timeq_add(now + chanserv_conf.adjust_delay, chanserv_adjust_limit, cData);
-       }
+        /* Allow for a bit of padding so that the limit doesn't
+           track the user count exactly, which could get annoying. */
+        if((mn->channel->limit - mn->channel->members.used) > chanserv_conf.adjust_threshold + 5)
+        {
+            timeq_del(0, chanserv_adjust_limit, cData, TIMEQ_IGNORE_WHEN);
+            timeq_add(now + chanserv_conf.adjust_delay, chanserv_adjust_limit, cData);
+        }
     }
 
     if((uData = GetTrueChannelAccess(cData, mn->user->handle_info)))
     {
-       scan_user_presence(uData, mn->user);
+        scan_user_presence(uData, mn->user);
         uData->seen = now;
-        if (uData->access >= UL_PRESENT)
+        if (uData->access >= UL_PRESENT && !HANDLE_FLAGGED(uData->handle, BOT))
             cData->visited = now;
     }
 
     if(IsHelping(mn->user) && IsSupportHelper(mn->user))
     {
-        unsigned int ii, jj;
-        for(ii = 0; ii < chanserv_conf.support_channels.used; ++ii)
-        {
-            for(jj = 0; jj < mn->user->channels.used; ++jj)
-                if(mn->user->channels.list[jj]->channel == chanserv_conf.support_channels.list[ii])
-                    break;
-            if(jj < mn->user->channels.used)
+        unsigned int ii;
+        for(ii = 0; ii < chanserv_conf.support_channels.used; ++ii) {
+            struct chanNode *channel;
+            struct userNode *exclude;
+            /* When looking at the channel that is being /part'ed, we
+             * have to skip over the client that is leaving.  For
+             * other channels, we must not do that.
+             */
+            channel = chanserv_conf.support_channels.list[ii];
+            exclude = (channel == mn->channel) ? mn->user : NULL;
+            if(find_handle_in_channel(channel, mn->user->handle_info, exclude))
                 break;
         }
         if(ii == chanserv_conf.support_channels.used)
@@ -6623,8 +7626,8 @@ handle_kick(struct userNode *kicker, struct userNode *victim, struct chanNode *c
 
     if(protect_user(victim, kicker, channel->channel_info))
     {
-        const char *reason = user_find_message(kicker, "CSMSG_USER_PROTECTED");
-       KickChannelUser(kicker, channel, chanserv, reason);
+        const char *reason = user_find_message(kicker, "CSMSG_USER_PROTECTED_2");
+        KickChannelUser(kicker, channel, chanserv, reason);
     }
 
     if((uData = GetTrueChannelAccess(channel->channel_info, victim->handle_info)))
@@ -6836,8 +7839,8 @@ chanserv_conf_read(void)
 
     if(!(conf_node = conf_get_data(CHANSERV_CONF_NAME, RECDB_OBJECT)))
     {
-       log_module(CS_LOG, LOG_ERROR, "Invalid config node `%s'.", CHANSERV_CONF_NAME);
-       return;
+        log_module(CS_LOG, LOG_ERROR, "Invalid config node `%s'.", CHANSERV_CONF_NAME);
+        return;
     }
     for(ii = 0; ii < chanserv_conf.support_channels.used; ++ii)
         UnlockChannel(chanserv_conf.support_channels.list[ii]);
@@ -6880,12 +7883,18 @@ 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_REVOKE_MODE_A, RECDB_QSTRING);
+    chanserv_conf.revoke_mode_a = str ? atoi(str) : 1;
     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);
     chanserv_conf.max_chan_users = str ? atoi(str) : 512;
     str = database_get_data(conf_node, KEY_MAX_CHAN_BANS, RECDB_QSTRING);
     chanserv_conf.max_chan_bans = str ? atoi(str) : 512;
+    str = database_get_data(conf_node, KEY_MIN_TIME_BANS, RECDB_QSTRING);
+    chanserv_conf.min_time_bans = str ? atoi(str) : 5;
     str = database_get_data(conf_node, KEY_MAX_USERINFO_LENGTH, RECDB_QSTRING);
     chanserv_conf.max_userinfo_length = str ? atoi(str) : 400;
     str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
@@ -6907,12 +7916,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, 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;
@@ -6933,11 +7948,10 @@ chanserv_conf_read(void)
             /* multiple choice options */
             "CtcpReaction", "Protect", "Toys", "TopicRefresh",
             /* binary options */
-            "DynLimit", "NoDelete",
+            "DynLimit", "NoDelete", "expire", "Vote",
             /* delimiter */
             NULL
         };
-        unsigned int ii;
         strlist = alloc_string_list(ArrayLength(list)-1);
         for(ii=0; list[ii]; ii++)
             string_list_append(strlist, strdup(list[ii]));
@@ -6971,6 +7985,16 @@ chanserv_conf_read(void)
     chanserv_conf.old_ban_names = strlist;
     str = database_get_data(conf_node, "off_channel", RECDB_QSTRING);
     off_channel = str ? atoi(str) : 0;
+
+    str = database_get_data(conf_node, "oper_chan", RECDB_QSTRING);
+       if(str)
+       {
+               chanserv_conf.oper_channel = AddChannel(str, now, "+tinms", NULL);
+       }
+       else
+       {
+               chanserv_conf.oper_channel = NULL;
+       }
 }
 
 static void
@@ -7029,32 +8053,56 @@ 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;
-    time_t last_seen;
-    unsigned short access;
+    char *seen, *inf, *flags, *voted, *votefor, *expires;
+    unsigned long last_seen;
+    unsigned short access_level;
 
     if(rd->type != RECDB_OBJECT || !dict_size(rd->d.object))
     {
-       log_module(CS_LOG, LOG_ERROR, "Invalid user in %s.", chan->channel->name);
-       return;
+        log_module(CS_LOG, LOG_ERROR, "Invalid user in %s.", chan->channel->name);
+        return;
     }
 
-    access = atoi(database_get_data(rd->d.object, KEY_LEVEL, RECDB_QSTRING));
-    if(access > UL_OWNER)
+    access_level = atoi(database_get_data(rd->d.object, KEY_LEVEL, RECDB_QSTRING));
+    if(access_level > UL_OWNER)
     {
-       log_module(CS_LOG, LOG_ERROR, "Invalid access level for %s in %s.", key, chan->channel->name);
-       return;
+        log_module(CS_LOG, LOG_ERROR, "Invalid access level for %s in %s.", key, chan->channel->name);
+        return;
     }
 
     inf = database_get_data(rd->d.object, KEY_INFO, RECDB_QSTRING);
     seen = database_get_data(rd->d.object, KEY_SEEN, RECDB_QSTRING);
-    last_seen = seen ? (signed)strtoul(seen, NULL, 0) : now;
+    last_seen = seen ? strtoul(seen, NULL, 0) : now;
     flags = database_get_data(rd->d.object, KEY_FLAGS, RECDB_QSTRING);
+    expires = database_get_data(rd->d.object, KEY_EXPIRES, 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)
     {
@@ -7062,21 +8110,36 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
         return;
     }
 
-    uData = add_channel_user(chan, handle, access, last_seen, inf);
+    uData = add_channel_user(chan, handle, access_level, last_seen, inf);
     uData->flags = flags ? strtoul(flags, NULL, 0) : 0;
+       uData->expires = expires ? (signed)strtoul(expires, NULL, 0) : 0;
+
+       if((uData->flags & USER_SUSPENDED) && uData->expires)
+       {
+         if(uData->expires > now)
+                 timeq_add(uData->expires, chanserv_expire_user_suspension, uData);
+         else
+                 uData->flags &= ~USER_SUSPENDED;
+       }
+    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
 ban_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
 {
-    struct banData *bData;
     char *set, *triggered, *s_duration, *s_expires, *reason, *owner;
-    time_t set_time, triggered_time, expires_time;
+    unsigned long set_time, triggered_time, expires_time;
 
     if(rd->type != RECDB_OBJECT || !dict_size(rd->d.object))
     {
-       log_module(CS_LOG, LOG_ERROR, "Invalid ban in %s.", chan->channel->name);
-       return;
+        log_module(CS_LOG, LOG_ERROR, "Invalid ban in %s.", chan->channel->name);
+        return;
     }
 
     set = database_get_data(rd->d.object, KEY_SET, RECDB_QSTRING);
@@ -7088,10 +8151,10 @@ ban_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
     if (!reason || !owner)
         return;
 
-    set_time = set ? (time_t)strtoul(set, NULL, 0) : now;
-    triggered_time = triggered ? (time_t)strtoul(triggered, NULL, 0) : 0;
+    set_time = set ? strtoul(set, NULL, 0) : now;
+    triggered_time = triggered ? strtoul(triggered, NULL, 0) : 0;
     if(s_expires)
-        expires_time = (time_t)strtoul(s_expires, NULL, 0);
+        expires_time = strtoul(s_expires, NULL, 0);
     else if(s_duration)
         expires_time = set_time + atoi(s_duration);
     else
@@ -7100,7 +8163,7 @@ ban_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
     if(!reason || (expires_time && (expires_time < now)))
         return;
 
-    bData = add_channel_ban(chan, key, owner, set_time, triggered_time, expires_time, reason);
+    add_channel_ban(chan, key, owner, set_time, triggered_time, expires_time, reason);
 }
 
 static struct suspended *
@@ -7111,11 +8174,11 @@ chanserv_read_suspended(dict_t obj)
     dict_t previous;
 
     str = database_get_data(obj, KEY_EXPIRES, RECDB_QSTRING);
-    suspended->expires = str ? (time_t)strtoul(str, NULL, 0) : 0;
+    suspended->expires = str ? strtoul(str, NULL, 0) : 0;
     str = database_get_data(obj, KEY_REVOKED, RECDB_QSTRING);
-    suspended->revoked = str ? (time_t)strtoul(str, NULL, 0) : 0;
+    suspended->revoked = str ? strtoul(str, NULL, 0) : 0;
     str = database_get_data(obj, KEY_ISSUED, RECDB_QSTRING);
-    suspended->issued = str ? (time_t)strtoul(str, NULL, 0) : 0;
+    suspended->issued = str ? strtoul(str, NULL, 0) : 0;
     suspended->suspender = strdup(database_get_data(obj, KEY_SUSPENDER, RECDB_QSTRING));
     suspended->reason = strdup(database_get_data(obj, KEY_REASON, RECDB_QSTRING));
     previous = database_get_data(obj, KEY_PREVIOUS, RECDB_OBJECT);
@@ -7123,10 +8186,36 @@ chanserv_read_suspended(dict_t obj)
     return suspended;
 }
 
+static struct giveownership *
+chanserv_read_giveownership(dict_t obj)
+{
+    struct giveownership *giveownership = calloc(1, sizeof(*giveownership));
+    char *str;
+    dict_t previous;
+
+    str = database_get_data(obj, KEY_STAFF_ISSUER, RECDB_QSTRING);
+    giveownership->staff_issuer = str ? strdup(str) : NULL;
+
+    giveownership->old_owner = strdup(database_get_data(obj, KEY_OLD_OWNER, RECDB_QSTRING));
+
+    giveownership->target = strdup(database_get_data(obj, KEY_TARGET, RECDB_QSTRING));
+    giveownership->target_access = atoi(database_get_data(obj, KEY_TARGET_ACCESS, RECDB_QSTRING));
+
+    str = database_get_data(obj, KEY_REASON, RECDB_QSTRING);
+    giveownership->reason = str ? strdup(str) : NULL;
+    str = database_get_data(obj, KEY_ISSUED, RECDB_QSTRING);
+    giveownership->issued = str ? (time_t)strtoul(str, NULL, 0) : 0;
+
+    previous = database_get_data(obj, KEY_PREVIOUS, RECDB_OBJECT);
+    giveownership->previous = previous ? chanserv_read_giveownership(previous) : NULL;
+    return giveownership;
+}
+
 static int
 chanserv_channel_read(const char *key, struct record_data *hir)
 {
     struct suspended *suspended;
+    struct giveownership *giveownership;
     struct mod_chanmode *modes;
     struct chanNode *cNode;
     struct chanData *cData;
@@ -7150,7 +8239,7 @@ chanserv_channel_read(const char *key, struct record_data *hir)
     if(!cData)
     {
         log_module(CS_LOG, LOG_ERROR, "Unable to register channel %s from database.", key);
-       return 0;
+        return 0;
     }
 
     if((obj = database_get_data(channel, KEY_OPTIONS, RECDB_OBJECT)))
@@ -7188,7 +8277,7 @@ chanserv_channel_read(const char *key, struct record_data *hir)
         enum charOption chOpt;
         unsigned int count;
 
-       cData->flags = base64toint(str, 5);
+        cData->flags = base64toint(str, 5);
         count = strlen(str += 5);
         for(lvlOpt = 0; lvlOpt < NUM_LEVEL_OPTIONS; ++lvlOpt)
         {
@@ -7215,7 +8304,21 @@ chanserv_channel_read(const char *key, struct record_data *hir)
         for(chOpt = 0; chOpt < NUM_CHAR_OPTIONS; ++chOpt)
             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);
@@ -7251,6 +8354,12 @@ chanserv_channel_read(const char *key, struct record_data *hir)
             cData->flags &= ~CHANNEL_SUSPENDED;
     }
 
+    if((obj = database_get_data(hir->d.object, KEY_GIVEOWNERSHIP, RECDB_OBJECT)))
+    {
+        giveownership = chanserv_read_giveownership(obj);
+        cData->giveownership = giveownership;
+    }
+
     if((!off_channel || !IsOffChannel(cData)) && !IsSuspended(cData)) {
         struct mod_chanmode change;
         mod_chanmode_init(&change);
@@ -7261,13 +8370,15 @@ chanserv_channel_read(const char *key, struct record_data *hir)
     }
 
     str = database_get_data(channel, KEY_REGISTERED, RECDB_QSTRING);
-    cData->registered = str ? (time_t)strtoul(str, NULL, 0) : now;
+    cData->registered = str ? strtoul(str, NULL, 0) : now;
     str = database_get_data(channel, KEY_VISITED, RECDB_QSTRING);
-    cData->visited = str ? (time_t)strtoul(str, NULL, 0) : now;
+    cData->visited = str ? strtoul(str, NULL, 0) : now;
     str = database_get_data(channel, KEY_OWNER_TRANSFER, RECDB_QSTRING);
-    cData->ownerTransfer = str ? (time_t)strtoul(str, NULL, 0) : 0;
+    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);
@@ -7277,12 +8388,36 @@ 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);
+        }
+    }
+
+    obj = database_get_data(channel, KEY_ADVTOPIC_ENTRIES, RECDB_OBJECT);
+    for(it = dict_first(obj); it; it = iter_next(it))
+    {
+        struct record_data *rd = iter_data(it);
+        if(rd->type != RECDB_QSTRING) continue;
+        int advtopic_index = atoi(iter_key(it));
+        if(advtopic_index < 0 || advtopic_index >= MAXADVTOPICENTRIES) continue;
+        cData->advtopic[advtopic_index] = (rd ? strdup(rd->d.qstring) : NULL);
+    }
+
     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, 0))) {
+       && (modes = mod_chanmode_parse(cNode, NULL, argv, argc, MCP_KEY_FREE|MCP_NO_APASS, 0))) {
         cData->modes = *modes;
-       if(off_channel > 0)
+        if(off_channel > 0)
           cData->modes.modes_set |= MODE_REGISTERED;
         if(cData->modes.argc > 1)
             cData->modes.argc = 1;
@@ -7292,12 +8427,12 @@ chanserv_channel_read(const char *key, struct record_data *hir)
 
     obj = database_get_data(channel, KEY_USERS, RECDB_OBJECT);
     for(it = dict_first(obj); it; it = iter_next(it))
-       user_read_helper(iter_key(it), iter_data(it), cData);
+        user_read_helper(iter_key(it), iter_data(it), cData);
 
     if(!cData->users && !IsProtected(cData))
     {
         log_module(CS_LOG, LOG_ERROR, "Channel %s had no users in database, unregistering it.", key);
-       unregister_channel(cData, "has empty user list.");
+        unregister_channel(cData, "has empty user list.");
         return 0;
     }
 
@@ -7340,7 +8475,7 @@ chanserv_dnr_read(const char *key, struct record_data *hir)
 {
     const char *setter, *reason, *str;
     struct do_not_register *dnr;
-    time_t expiry;
+    unsigned long expiry;
 
     setter = database_get_data(hir->d.object, KEY_DNR_SETTER, RECDB_QSTRING);
     if(!setter)
@@ -7355,7 +8490,7 @@ chanserv_dnr_read(const char *key, struct record_data *hir)
         return;
     }
     str = database_get_data(hir->d.object, KEY_EXPIRES, RECDB_QSTRING);
-    expiry = str ? (time_t)strtoul(str, NULL, 0) : 0;
+    expiry = str ? strtoul(str, NULL, 0) : 0;
     if(expiry && expiry <= now)
         return;
     dnr = chanserv_add_dnr(key, setter, expiry, reason);
@@ -7379,8 +8514,8 @@ chanserv_saxdb_read(struct dict *database)
             chanserv_note_type_read(iter_key(it), iter_data(it));
 
     if((section = database_get_data(database, KEY_CHANNELS, RECDB_OBJECT)))
-       for(it = dict_first(section); it; it = iter_next(it))
-           chanserv_channel_read(iter_key(it), iter_data(it));
+        for(it = dict_first(section); it; it = iter_next(it))
+            chanserv_channel_read(iter_key(it), iter_data(it));
 
     if((section = database_get_data(database, KEY_DNR, RECDB_OBJECT)))
         for(it = dict_first(section); it; it = iter_next(it))
@@ -7396,14 +8531,20 @@ chanserv_write_users(struct saxdb_context *ctx, struct userData *uData)
     saxdb_start_record(ctx, KEY_USERS, 1);
     for(; uData; uData = uData->next)
     {
-        if((uData->access >= UL_PRESENT) && uData->present)
+        if((uData->access >= UL_PRESENT) && uData->present && !HANDLE_FLAGGED(uData->handle, BOT))
             high_present = 1;
         saxdb_start_record(ctx, uData->handle->handle, 0);
         saxdb_write_int(ctx, KEY_LEVEL, uData->access);
         saxdb_write_int(ctx, KEY_SEEN, uData->seen);
         if(uData->flags)
             saxdb_write_int(ctx, KEY_FLAGS, uData->flags);
-       if(uData->info)
+        if(uData->expires)
+            saxdb_write_int(ctx, KEY_EXPIRES, uData->expires);
+        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);
     }
@@ -7451,6 +8592,27 @@ chanserv_write_suspended(struct saxdb_context *ctx, const char *name, struct sus
     saxdb_end_record(ctx);
 }
 
+static void
+chanserv_write_giveownership(struct saxdb_context *ctx, const char *name, struct giveownership *giveownership)
+{
+    saxdb_start_record(ctx, name, 0);
+    if(giveownership->staff_issuer)
+      saxdb_write_string(ctx, KEY_STAFF_ISSUER, giveownership->staff_issuer);
+    if(giveownership->old_owner)
+      saxdb_write_string(ctx, KEY_OLD_OWNER, giveownership->old_owner);
+    if(giveownership->target)
+      saxdb_write_string(ctx, KEY_TARGET, giveownership->target);
+    if(giveownership->target_access)
+      saxdb_write_int(ctx, KEY_TARGET_ACCESS, giveownership->target_access);
+    if(giveownership->reason)
+      saxdb_write_string(ctx, KEY_REASON, giveownership->reason);
+    if(giveownership->issued)
+        saxdb_write_int(ctx, KEY_ISSUED, giveownership->issued);
+    if(giveownership->previous)
+        chanserv_write_giveownership(ctx, KEY_PREVIOUS, giveownership->previous);
+    saxdb_end_record(ctx);
+}
+
 static void
 chanserv_write_channel(struct saxdb_context *ctx, struct chanData *channel)
 {
@@ -7458,11 +8620,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)
@@ -7475,6 +8639,31 @@ 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->giveownership)
+        chanserv_write_giveownership(ctx, "giveownership", channel->giveownership);
+    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);
@@ -7490,13 +8679,23 @@ chanserv_write_channel(struct saxdb_context *ctx, struct chanData *channel)
 
     if(channel->modes.modes_set || channel->modes.modes_clear)
     {
-       mod_chanmode_format(&channel->modes, buf);
+        mod_chanmode_format(&channel->modes, buf);
         saxdb_write_string(ctx, KEY_MODES, buf);
     }
 
     high_present = chanserv_write_users(ctx, channel->users);
     chanserv_write_bans(ctx, channel->bans);
 
+    if(channel->flags & CHANNEL_ADVTOPIC) {
+        saxdb_start_record(ctx, KEY_ADVTOPIC_ENTRIES, 0);
+        int advtopic_index;
+        for(advtopic_index = 0; advtopic_index < MAXADVTOPICENTRIES; advtopic_index++) {
+            if(channel->advtopic[advtopic_index])
+                saxdb_write_string(ctx, strtab(advtopic_index), channel->advtopic[advtopic_index]);
+        }
+        saxdb_end_record(ctx);
+    }
+
     if(dict_size(channel->notes))
     {
         dict_iterator_t it;
@@ -7504,7 +8703,7 @@ chanserv_write_channel(struct saxdb_context *ctx, struct chanData *channel)
         saxdb_start_record(ctx, KEY_NOTES, 1);
         for(it = dict_first(channel->notes); it; it = iter_next(it))
         {
-           struct note *note = iter_data(it);
+            struct note *note = iter_data(it);
             saxdb_start_record(ctx, iter_key(it), 0);
             saxdb_write_string(ctx, KEY_NOTE_SETTER, note->setter);
             saxdb_write_string(ctx, KEY_NOTE_NOTE, note->note);
@@ -7628,7 +8827,11 @@ chanserv_db_cleanup(void) {
     }
 }
 
-#define DEFINE_COMMAND(NAME, MIN_ARGC, FLAGS, OPTIONS...) modcmd_register(chanserv_module, #NAME, cmd_##NAME, MIN_ARGC, FLAGS, ## OPTIONS)
+#if defined(GCC_VARMACROS)
+# define DEFINE_COMMAND(NAME, MIN_ARGC, FLAGS, ARGS...) modcmd_register(chanserv_module, #NAME, cmd_##NAME, MIN_ARGC, FLAGS, ARGS)
+#elif defined(C99_VARMACROS)
+# define DEFINE_COMMAND(NAME, MIN_ARGC, FLAGS, ...) modcmd_register(chanserv_module, #NAME, cmd_##NAME, MIN_ARGC, FLAGS, __VA_ARGS__)
+#endif
 #define DEFINE_CHANNEL_OPTION(NAME) modcmd_register(chanserv_module, "set "#NAME, chan_opt_##NAME, 1, 0, NULL)
 #define DEFINE_USER_OPTION(NAME) modcmd_register(chanserv_module, "uset "#NAME, user_opt_##NAME, 1, MODCMD_REQUIRE_REGCHAN, NULL)
 
@@ -7669,6 +8872,7 @@ init_chanserv(const char *nick)
     DEFINE_COMMAND(dnrsearch, 3, 0, "template", "noregister", NULL);
     modcmd_register(chanserv_module, "dnrsearch print", NULL, 0, 0, NULL);
     modcmd_register(chanserv_module, "dnrsearch remove", NULL, 0, 0, NULL);
+    modcmd_register(chanserv_module, "dnrsearch count", NULL, 0, 0, NULL);
     DEFINE_COMMAND(move, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_REGCHAN, "template", "register", NULL);
     DEFINE_COMMAND(csuspend, 2, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_REGCHAN, "flags", "+helping", NULL);
     DEFINE_COMMAND(cunsuspend, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_REGCHAN, "flags", "+helping", NULL);
@@ -7714,6 +8918,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);
@@ -7757,10 +8962,21 @@ init_chanserv(const char *nick)
     DEFINE_COMMAND(unf, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
     DEFINE_COMMAND(ping, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
     DEFINE_COMMAND(wut, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
-    DEFINE_COMMAND(8ball, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
-    DEFINE_COMMAND(d, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
+    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);
@@ -7777,6 +8993,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);
@@ -7784,9 +9001,11 @@ init_chanserv(const char *nick)
     DEFINE_CHANNEL_OPTION(ctcpusers);
     DEFINE_CHANNEL_OPTION(ctcpreaction);
     DEFINE_CHANNEL_OPTION(inviteme);
+    DEFINE_CHANNEL_OPTION(advtopic);
     DEFINE_CHANNEL_OPTION(unreviewed);
-    modcmd_register(chanserv_module, "set unreviewed on", NULL, 0, 0, NULL);
-    modcmd_register(chanserv_module, "set unreviewed off", NULL, 0, 0, NULL);
+    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)
         DEFINE_CHANNEL_OPTION(offchannel);
     modcmd_register(chanserv_module, "set defaults", chan_opt_defaults, 1, 0, "access", "owner", NULL);
@@ -7814,14 +9033,14 @@ init_chanserv(const char *nick)
     saxdb_register("ChanServ", chanserv_saxdb_read, chanserv_saxdb_write);
 
     if(chanserv_conf.channel_expire_frequency)
-       timeq_add(now + chanserv_conf.channel_expire_frequency, expire_channels, NULL);
+        timeq_add(now + chanserv_conf.channel_expire_frequency, expire_channels, NULL);
 
     if(chanserv_conf.dnr_expire_frequency)
         timeq_add(now + chanserv_conf.dnr_expire_frequency, expire_dnrs, NULL);
 
     if(chanserv_conf.refresh_period)
     {
-        time_t next_refresh;
+        unsigned long next_refresh;
         next_refresh = (now + chanserv_conf.refresh_period - 1) / chanserv_conf.refresh_period * chanserv_conf.refresh_period;
         timeq_add(next_refresh, chanserv_refresh_topics, NULL);
     }