]> jfr.im git - irc/evilnet/x3.git/blobdiff - src/chanserv.c
Merge pull request #44 from evilnet/bugfix/master/43-duplicate-definitions
[irc/evilnet/x3.git] / src / chanserv.c
index ca5e2684147641e9c9bd46598449d44ccf6a8124..5931cd0fc731b9654e409ba6bd5d5cdf5bb52b34 100644 (file)
@@ -3,9 +3,9 @@
  *
  * This file is part of x3.
  *
- * srvx is free software; you can redistribute it and/or modify
+ * x3 is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.
  *
  * This program is distributed in the hope that it will be useful,
 #include "chanserv.h"
 #include "conf.h"
 #include "global.h"
+#include "gline.h"
+#include "ioset.h"
 #include "modcmd.h"
 #include "opserv.h" /* for opserv_bad_channel() */
+#include "nickserv.h" /* for oper_outranks() */
 #include "saxdb.h"
+#include "shun.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_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_BAN_TIMEOUT_FREQ        "ban_timeout_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_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_VALID_CHANNEL_REGEX     "valid_channel_regex"
 
 /* ChanServ database */
-#define KEY_VERSION_CONTROL     "version_control"
-#define KEY_CHANNELS           "channels"
-#define KEY_NOTE_TYPES          "note_types"
+#define KEY_VERSION_CONTROL         "version_control"
+#define KEY_CHANNELS                "channels"
+#define KEY_NOTE_TYPES              "note_types"
 
 /* version control paramiter */
-#define KEY_VERSION_NUMBER      "version_number"
+#define KEY_VERSION_NUMBER          "version_number"
 
 /* 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" /* for lamers */
-#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" /* for lamers */
+#define KEY_MAX                     "max"
+#define KEY_NOTES                   "notes"
+#define KEY_TOPIC_MASK              "topic_mask"
+#define KEY_OWNER_TRANSFER          "owner_transfer"
+#define KEY_MAXSETINFO              "maxsetinfo"
 
 /* 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"
+#define KEY_ACCESSEXPIRY            "accessexpiry"
+#define KEY_CLVLEXPIRY              "clvlexpiry"
+#define KEY_LASTLEVEL               "lastlevel"
 
 /* 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 CHANNEL_DEFAULT_FLAGS   (CHANNEL_OFFCHANNEL)
+#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_GOD_TIMEOUT             "god_timeout"
+
+#define CHANNEL_DEFAULT_FLAGS   (CHANNEL_OFFCHANNEL | CHANNEL_UNREVIEWED)
+#define CHANNEL_PRESERVED_FLAGS (CHANNEL_UNREVIEWED)
 #define CHANNEL_DEFAULT_OPTIONS "lmoooanpcnat"
 
 /* Administrative messages */
@@ -138,8 +158,9 @@ static const struct message_entry msgtab[] = {
 /* Do-not-register channels */
     { "CSMSG_NOT_DNR", "$b%s$b is not a valid channel name or *account." },
     { "CSMSG_DNR_SEARCH_RESULTS", "$bDo-Not-Register Channels$b" },
-    { "CSMSG_DNR_INFO", "$b%s$b (set by $b%s$b): %s" },
-    { "CSMSG_DNR_INFO_SET", "$b%s$b (set %s ago by $b%s$b): %s" },
+    { "CSMSG_DNR_INFO", "$b%s$b is do-not-register (by $b%s$b): %s" },
+    { "CSMSG_DNR_INFO_SET", "$b%s$b is do-not-register (set %s by $b%s$b): %s" },
+    { "CSMSG_DNR_INFO_SET_EXPIRES", "$b%s$b is do-not-register (set %s by $b%s$b; expires %s): %s" },
     { "CSMSG_MORE_DNRS", "%d more do-not-register entries skipped." },
     { "CSMSG_DNR_CHANNEL", "Only network staff may register $b%s$b." },
     { "CSMSG_DNR_CHANNEL_MOVE", "Only network staff may move $b%s$b." },
@@ -147,12 +168,14 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_NOREGISTER_CHANNEL", "$b%s$b has been added to the do-not-register list." },
     { "CSMSG_NO_SUCH_DNR", "$b%s$b is not in the do-not-register list." },
     { "CSMSG_DNR_REMOVED", "$b%s$b has been removed from the do-not-register list." },
+    { "CSMSG_DNR_BAD_ACTION", "$b%s$b is not a recognized do-not-register action." },
+    { "CSMSG_DNR_SEARCH_RESULTS", "The following do-not-registers were found:" },
 
 /* Channel unregistration */
     { "CSMSG_UNREG_SUCCESS", "$b%s$b has been unregistered." },
     { "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'." },
+    { "CSMSG_CONFIRM_UNREG", "To confirm this unregistration, you must use 'unregister %s %s'." },
 
 /* Channel moving */
     { "CSMSG_MOVE_SUCCESS", "Channel registration has been moved to $b%s$b." },
@@ -182,7 +205,9 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_ALREADY_VOICED", "You are already voiced in $b%s$b." },
     { "CSMSG_ALREADY_DOWN", "You are not opped, halfopped, 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." },
+    { "CSMSG_NOT_IN_CHANNEL", "I am not in %s." },
 
 /* Removing yourself from a channel. */
     { "CSMSG_NO_OWNER_DELETEME", "You cannot delete your owner access in $b%s$b." },
@@ -190,6 +215,8 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_DELETED_YOU", "Your $b%d$b access has been deleted from $b%s$b." },
 
 /* User management */
+    { "CSMSG_AUTO_DELETED", "Your %s access has expired in %s." },
+    { "CSMSG_CLVL_EXPIRED", "Your CLVL access has expired in %s." },
     { "CSMSG_ADDED_USER", "Added %s to the %s user list with access %s (%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)." },
@@ -197,7 +224,7 @@ static const struct message_entry msgtab[] = {
     { "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%s$b, not %s." },
     { "CSMSG_USER_EXISTS", "%s is already on the $b%s$b user list (with %s access)." },
-    { "CSMSG_ADDUSER_PENDING", "I have sent him/her a message letting them know, and if they auth or register soon, I will finish adding them automatically." },
+    { "CSMSG_ADDUSER_PENDING", "I have sent %s a message letting them know, and if they auth or register soon, I will finish adding them automatically." },
     { "CSMSG_ADDUSER_PENDING_ALREADY", "He or she is already pending addition to %s once he/she auths with $b$N$b." },
     { "CSMSG_ADDUSER_PENDING_HEADER", "Users to add to channels pending logins:" }, /* Remove after testing? */
     { "CSMSG_ADDUSER_PENDING_LIST", "Channel %s user %s" },             /* Remove after testing? */
@@ -208,9 +235,12 @@ static const struct message_entry msgtab[] = {
 
     { "CSMSG_NO_SELF_CLVL", "You cannot change your own access." },
     { "CSMSG_NO_BUMP_ACCESS", "You cannot give users access greater than or equal to your own." },
+    { "CSMSG_NO_BUMP_EXPIRY", "You cannot give users timed $bCLVL$b's when they already have timed access." },
     { "CSMSG_MULTIPLE_OWNERS", "There is more than one owner in %s; please use $bCLVL$b, $bDELOWNER$b and/or $bADDOWNER$b instead." },
+    { "CSMSG_NO_OWNER", "There is no owner for %s; please use $bCLVL$b and/or $bADDOWNER$b instead." },
     { "CSMSG_TRANSFER_WAIT", "You must wait %s before you can give ownership of $b%s$b to someone else." },
     { "CSMSG_NO_TRANSFER_SELF", "You cannot give ownership to your own account." },
+    { "CSMSG_CONFIRM_GIVEOWNERSHIP", "To really give ownership to $b%1$s$b, you must use 'giveownership *%1$s %2$s'." },
     { "CSMSG_OWNERSHIP_GIVEN", "Ownership of $b%s$b has been transferred to account $b%s$b." },
 
 /* Ban management */
@@ -220,17 +250,19 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_BAN_DONE", "Banned $b%s$b from %s." },
     { "CSMSG_REASON_CHANGE", "Reason for LAMER $b%s$b changed." },
     { "CSMSG_LAMER_EXTENDED", "Extended LAMER for $b%s$b, now expires in %s." },
-    { "CSMSG_BAN_REMOVED", "Matching ban(s) and LAMER(s) in $b%s$b were removed." },
+    { "CSMSG_BAN_REMOVED", "Ban(s) and LAMER(s) matching $b%s$b were removed." },
     { "CSMSG_TRIMMED_LAMERS", "Trimmed $b%d LAMERs$b from the %s LAMER list that were inactive for at least %s." },
     { "CSMSG_REDUNDANT_BAN", "$b%s$b is already banned in %s." },
     { "CSMSG_REDUNDANT_LAMER", "$b%s$b is already LAMER'd in %s." },
     { "CSMSG_DURATION_TOO_LOW", "Timed bans must last for at least 15 seconds." },
     { "CSMSG_DURATION_TOO_HIGH", "Timed bans must last for less than 2 years." },
     { "CSMSG_LAME_MASK", "$b%s$b is a little too general. Try making it more specific." },
+    { "CSMSG_NO_EXTBANS", "$b%s$b is an extended ban, which are not allowed." },
     { "CSMSG_MASK_PROTECTED", "Sorry, ban for $b%s$b conflicts with a protected user's hostmask." },
     { "CSMSG_NO_MATCHING_USERS", "No one in $b%s$b has a hostmask matching $b%s$b." },
     { "CSMSG_BAN_NOT_FOUND", "Sorry, no ban or LAMER found: $b%s$b." },
     { "CSMSG_BANLIST_FULL", "The $b%s$b channel ban list is $bfull$b." },
+    { "CSMSG_BAD_BAN", "The given ban $b%s$b is invalid." },
 
     { "CSMSG_INVALID_TRIM", "$b%s$b isn't a valid trim target." },
 
@@ -256,11 +288,6 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_CONFIRM_DEFAULTS", "To reset %s's settings to the defaults, you must use 'set defaults %s'." },
     { "CSMSG_SETTINGS_DEFAULTED", "All settings for %s have been reset to default values." },
     { "CSMSG_BAD_SETLEVEL", "You cannot change any setting to above your level." },
-    /*
-    { "CSMSG_BAD_GIVEVOICE", "You cannot change GiveVoice to above GiveHalfOps (%d)." },
-    { "CSMSG_BAD_GIVEHOPS", "You cannot change GiveHalfOps to below GiveOps (%d)." },
-    { "CSMSG_BAD_GIVEOPS", "You cannot change GiveOps to below GiveVoice (%d)." },
-    */
     { "CSMSG_BAD_SETTERS", "You cannot change Setters to above your level." },
     { "CSMSG_INVALID_MODE_LOCK", "$b%s$b is an invalid mode lock." },
     { "CSMSG_INVALID_NUMERIC",   "$b%d$b is not a valid choice.  Choose one:" },
@@ -270,36 +297,35 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_SET_USERGREETING",  "$bUserGreeting$b %s" },
     { "CSMSG_SET_MODES",         "$bModes       $b %s" },
     { "CSMSG_SET_NODELETE",      "$bNoDelete    $b %s" },
-    { "CSMSG_SET_DYNLIMIT",      "$bDynLimit    $b %s" },
+    { "CSMSG_SET_UNREVIEWED",    "$bUnreviewed  $b %s" },
+    { "CSMSG_SET_DYNLIMIT",      "$bDynLimit    $b %s - +l joinflood protection." },
     { "CSMSG_SET_OFFCHANNEL",    "$bOffChannel  $b %s" },
-    { "CSMSG_SET_USERINFO",      "$bUserInfo    $b %d" },
-    /*
-    { "CSMSG_SET_GIVE_VOICE",    "$bGiveVoice   $b %d" },
-    { "CSMSG_SET_GIVE_HALFOPS",  "$bGiveHalfOps $b %d" },
-    */
+    { "CSMSG_SET_USERINFO",      "$bUserInfo    $b %d - and above userinfos are shown." },
     { "CSMSG_SET_TOPICSNARF",    "$bTopicSnarf  $b %d" },
-    { "CSMSG_SET_INVITEME",      "$bInviteMe    $b %d" },
-    { "CSMSG_SET_ENFOPS",        "$bEnfOps      $b %d" },
-    { "CSMSG_SET_ENFHALFOPS",    "$bEnfHalfOps  $b %d" },
-    /*
-    { "CSMSG_SET_GIVE_OPS",      "$bGiveOps     $b %d" },
-    */
-    { "CSMSG_SET_ENFMODES",      "$bEnfModes    $b %d" },
-    { "CSMSG_SET_ENFTOPIC",      "$bEnfTopic    $b %d" },
-    { "CSMSG_SET_PUBCMD",        "$bPubCmd      $b %d" },
-    { "CSMSG_SET_SETTERS",       "$bSetters     $b %d" },
-    { "CSMSG_SET_CTCPUSERS",     "$bCTCPUsers   $b %d" },
-    { "CSMSG_SET_VOICE",         "$bvoice       $b %d - %s" },
+    { "CSMSG_SET_INVITEME",      "$bInviteMe    $b %d - Userlevel required to invite self." },
+    { "CSMSG_SET_ENFOPS",        "$bEnfOps      $b %d - level and above can op unknown users." },
+    { "CSMSG_SET_ENFHALFOPS",    "$bEnfHalfOps  $b %d - level and above can hop unknown users." },
+    { "CSMSG_SET_ENFMODES",      "$bEnfModes    $b %d - and above can change channel modes." },
+    { "CSMSG_SET_ENFTOPIC",      "$bEnfTopic    $b %d - and above can set the topic." },
+    { "CSMSG_SET_PUBCMD",        "$bPubCmd      $b %d - and above can use public commands." },
+    { "CSMSG_SET_SETTERS",       "$bSetters     $b %d - and above can change these settings." },
+    { "CSMSG_SET_AUTOMODE",      "$bAutoMode    $b %d - %s" },
     { "CSMSG_SET_PROTECT",       "$bProtect     $b %d - %s" },
     { "CSMSG_SET_TOYS",          "$bToys        $b %d - %s" },
     { "CSMSG_SET_CTCPREACTION",  "$bCTCPReaction$b %d - %s" },
     { "CSMSG_SET_TOPICREFRESH",  "$bTopicRefresh$b %d - %s" },
+    { "CSMSG_SET_RESYNC",        "$bResync      $b %d - %s" },
+    { "CSMSG_SET_BANTIMEOUT",    "$bBanTimeout  $b %d - %s" },
+    { "CSMSG_SET_MAXSETINFO",    "$bMaxSetInfo  $b %d - maximum characters in a setinfo line." },
+
     { "CSMSG_USET_AUTOOP",       "$bAutoOp      $b %s" },
     { "CSMSG_USET_AUTOVOICE",    "$bAutoVoice   $b %s" },
     { "CSMSG_USET_AUTOINVITE",   "$bAutoInvite  $b %s" },
+    { "CSMSG_USET_AUTOJOIN",     "$bAutoJoin    $b %s" },
     { "CSMSG_USET_INFO",         "$bInfo        $b %s" },
 
     { "CSMSG_USER_PROTECTED", "Sorry, $b%s$b is protected." },
+    { "CSMSG_USER_PROTECTED_KICK", "That user is protected." }, /* No $ or %s replacements! */
     { "CSMSG_OPBY_LOCKED", "You may not op users who lack op or greater access." },
     { "CSMSG_HOPBY_LOCKED", "You may not halfop users who lack halfop or greater access." },
     { "CSMSG_PROCESS_FAILED", "$b$C$b could not process some of the nicks you provided." },
@@ -309,9 +335,15 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_DEHALFOPPED_USERS", "DeHalfopped users in $b%s$b." },
     { "CSMSG_VOICED_USERS", "Voiced users in $b%s$b." },
     { "CSMSG_DEVOICED_USERS", "Devoiced users in $b%s$b." },
-    { "CSMSG_VOICE_NONE", "Noone will be auto-voiced" },
-    { "CSMSG_VOICE_PEON", "PEONs will be auto-voiced" },
-    { "CSMSG_VOICE_ALL", "Everyone will be auto-voiced" },
+
+    { "CSMSG_AUTOMODE_NONE", "Noone will be automatically oped, half-oped, or voiced." },
+    { "CSMSG_AUTOMODE_NORMAL", "Give voice to pals, half-op to halfops, and op to ops." },
+    { "CSMSG_AUTOMODE_VOICE", "#1 plus give voice to everyone." },
+    { "CSMSG_AUTOMODE_HOP", "#1 plus give halfops to everyone." },
+    { "CSMSG_AUTOMODE_OP", "#1 plus give ops to everyone (not advised)" },
+    { "CSMSG_AUTOMODE_MUTE", "Give half-op to halfops, and op to ops only." },
+    { "CSMSG_AUTOMODE_ONLYVOICE", "Just voice everyone with access." },
+
     { "CSMSG_PROTECT_ALL", "Non-users and users will be protected from those of equal or lower access." },
     { "CSMSG_PROTECT_EQUAL", "Users will be protected from those of equal or lower access." },
     { "CSMSG_PROTECT_LOWER", "Users will be protected from those of lower access." },
@@ -319,22 +351,39 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_TOYS_DISABLED", "Toys are completely disabled." },
     { "CSMSG_TOYS_PRIVATE", "Toys will only reply privately." },
     { "CSMSG_TOYS_PUBLIC", "Toys will reply publicly." },
+
     { "CSMSG_TOPICREFRESH_NEVER", "Never refresh topic." },
     { "CSMSG_TOPICREFRESH_3_HOURS", "Refresh every 3 hours." },
     { "CSMSG_TOPICREFRESH_6_HOURS", "Refresh every 6 hours." },
     { "CSMSG_TOPICREFRESH_12_HOURS", "Refresh every 12 hours." },
     { "CSMSG_TOPICREFRESH_24_HOURS", "Refresh every 24 hours." },
+
+    { "CSMSG_RESYNC_NEVER", "Never automaticly resync userlist." },
+    { "CSMSG_RESYNC_3_HOURS", "Resync userlist every 3 hours." },
+    { "CSMSG_RESYNC_6_HOURS", "Resync userlist every 6 hours." },
+    { "CSMSG_RESYNC_12_HOURS", "Resync userlist every 12 hours." },
+    { "CSMSG_RESYNC_24_HOURS", "Resync userlist every 24 hours." },
+
+    { "CSMSG_CTCPREACTION_NONE", "CTCPs are allowed" },
     { "CSMSG_CTCPREACTION_KICK", "Kick on disallowed CTCPs" },
     { "CSMSG_CTCPREACTION_KICKBAN", "Kickban on disallowed CTCPs" },
     { "CSMSG_CTCPREACTION_SHORTBAN",  "Short timed ban on disallowed CTCPs" },
     { "CSMSG_CTCPREACTION_LONGBAN", "Long timed ban on disallowed CTCPs" },
+    
+    { "CSMSG_BANTIMEOUT_NONE", "Bans will not be removed automatically."},
+    { "CSMSG_BANTIMEOUT_10M", "Bans will be removed after 10 minutes."},
+    { "CSMSG_BANTIMEOUT_2H", "Bans will be removed after 2 hours."},
+    { "CSMSG_BANTIMEOUT_4H", "Bans will be removed after 4 hours."},
+    { "CSMSG_BANTIMEOUT_1D", "Bans will be removed after 24 hours."},
+    { "CSMSG_BANTIMEOUT_1W", "Bans will be removed after 1 week."},
 
     { "CSMSG_INVITED_USER", "Invited $b%s$b to join %s." },
     { "CSMSG_INVITING_YOU_REASON", "$b%s$b invites you to join %s: %s" },
     { "CSMSG_INVITING_YOU", "$b%s$b invites you to join %s." },
+    { "CSMSG_CANNOT_INVITE", "You cannot invite %s to %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." },
 
@@ -343,8 +392,16 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_BANS_REMOVED", "Removed all channel bans from $b%s$b." },
 
 /* Channel userlist */
-    { "CSMSG_ACCESS_ALL_HEADER", "$b%s Users From Level %s To %s$b" },
-    { "CSMSG_ACCESS_SEARCH_HEADER", "$b%s Users From Level %s To %s Matching %s$b" },
+    { "CSMSG_ACCESS_ALL_HEADER_NORMAL",      "$b%s Users From Level %s To %s$b" },
+    { "CSMSG_ACCESS_SEARCH_HEADER_NORMAL",   "$b%s Users From Level %s To %s Matching %s$b" },
+    /* uncomment if needed to adujust styles (and change code below)
+    { "CSMSG_ACCESS_ALL_HEADER_CLEAN",       "$b%s Users From Level %s To %s$b" },
+    { "CSMSG_ACCESS_SEARCH_HEADER_CLEAN",    "$b%s Users From Level %s To %s Matching %s$b" },
+    { "CSMSG_ACCESS_ALL_HEADER_ADVANCED",    "$b%s Users From Level %s To %s$b" },
+    { "CSMSG_ACCESS_SEARCH_HEADER_ADVANCED", "$b%s Users From Level %s To %s Matching %s$b" },
+    { "CSMSG_ACCESS_ALL_HEADER_CLASSIC",     "$b%s Users From Level %s To %s$b" },
+    { "CSMSG_ACCESS_SEARCH_HEADER_CLASSIC",  "$b%s Users From Level %s To %s Matching %s$b" },
+    */
     { "CSMSG_INVALID_ACCESS", "$b%s$b is an invalid access level." },
     { "CSMSG_CHANGED_ACCESS", "%s now has access $b%s$b (%u) in %s." },
     { "CSMSG_LAMERS_HEADER", "$bLamers in %s$b" },
@@ -385,7 +442,10 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_HELPER_NO_ACCESS", "%s lacks access to %s but has $bsecurity override$b enabled." },
     { "CSMSG_HELPER_HAS_ACCESS", "%s has $b%s$b access (%d) in %s and has $bsecurity override$b enabled." },
     { "CSMSG_LAZY_SMURF_TARGET", "%s is %s ($bIRCOp$b; not logged in)." },
-    { "CSMSG_SMURF_TARGET", "%s is %s ($b%s$b)." },
+    { "CSMSG_SMURF_TARGET", "%s %s ($b%s$b)." },
+    { "CSMSG_OPERATOR_TITLE", "IRC operator" },
+    { "CSMSG_UC_H_TITLE", "network helper" },
+    { "CSMSG_LC_H_TITLE", "support helper" },
     { "CSMSG_LAME_SMURF_TARGET", "%s is an IRC operator." },
 
 /* Seen information */
@@ -421,7 +481,12 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_CHANNEL_SUSPENDED_7", " %s ago by %s; revoked %s ago: %s" },
     { "CSMSG_CHANNEL_REGISTERED", "$bRegistered:          $b%s ago." },
     { "CSMSG_CHANNEL_VISITED", "$bVisited:             $b%s ago." },
+    { "CSMSG_CHANNEL_OWNERSHIP_HISTORY", "Ownership transfer history for $b%s$b" },
+    { "CSMSG_CHANNEL_OWNERSHIP_NORMAL", "from %s to %s (%d access) on %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_END",  "---------------End of Info--------------"},
+    { "CSMSG_CHANNEL_END_CLEAN",  "End of Info"},
 
     { "CSMSG_PEEK_INFO", "$bStatus of %s$b" },
     { "CSMSG_PEEK_TOPIC", "$bTopic:          $b%s" },
@@ -455,13 +520,14 @@ static const struct message_entry msgtab[] = {
 
 /* Channel configuration */
     { "CSMSG_INVALID_OPTION", "$b%s$b is not a valid %s option." },
+    { "CSMSG_INVALID_CFLAG", "$b%s$b is not a recognized channel flag." },
     { "CSMSG_CHANNEL_OPTIONS", "$bChannel Options for %s$b" },
     { "CSMSG_CHANNEL_OPTIONS_END", "-------------End of Options-------------" },
     { "CSMSG_GREETING_TOO_LONG", "Your greeting ($b%d$b characters) must be shorter than $b%d$b characters." },
 
 /* User settings */
     { "CSMSG_USER_OPTIONS", "User Options:" },
-    { "CSMSG_USER_PROTECTED", "That user is protected." },
+//    { "CSMSG_USER_PROTECTED", "That user is protected." },
 
 /* Toys */
     { "CSMSG_UNF_RESPONSE", "I don't want to be part of your sick fantasies!" },
@@ -474,75 +540,110 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_DIE_ROLL", "A $b%lu$b shows on the %lu-sided die." },
     { "CSMSG_HUGGLES_HIM", "\001ACTION huggles %s\001" },
     { "CSMSG_HUGGLES_YOU", "\001ACTION huggles you\001" },
+    { "CSMSG_ROULETTE_LOADS",  "\001ACTION loads the gun and sets it on the table\001" },
+    { "CSMSG_ROULETTE_NEW", "Please type %croulette to start a new round" } ,
+    { "CSMSG_ROULETTE_BETTER_LUCK", "Better luck next time, %s" },
+    { "CSMSG_ROULETTE_BANG", "Bang!!!" } ,
+    { "CSMSG_ROULETTE_CLICK", "Click" } ,
+
+    { "CSMSG_SPIN_WHEEL1", "\001ACTION spins the wheel of misfortune for: %s\001" } ,
+    { "CSMSG_SPIN_WHEEL2", "Round and round she goes, where she stops, nobody knows...!" } ,
+    { "CSMSG_SPIN_WHEEL3", "The wheel of misfortune has stopped on..." } ,
+
+    { "CSMSG_SPIN_PEER", "Peer: Peer's gonna eat you!!!!" } ,
+    { "CSMSG_SPIN_PARTALL", "Part all: Part all channels" } ,
+    { "CSMSG_SPIN_Gline", "Gline: /gline for random amount of time" } ,
+    { "CSMSG_SPIN_SHUN", "Shun: /shun for random amount of time" } ,
+    { "CSMSG_SPIN_NOTHING", "Nothing: Absolutely nothing" } ,
+    { "CSMSG_SPIN_RANDJOIN", "Random join: Join a bunch of random channels, then /part all of 'em several times" } ,
+    { "CSMSG_SPIN_ABUSEWHOIS", "Abuse whois: Abuse line added to /whois info" } ,
+    { "CSMSG_SPIN_KICKALL", "Kick all: /kick from each channel you're in" } ,
+    { "CSMSG_SPIN_NICKCHANGE", "Nick change: Random Nick Change" } ,
+    { "CSMSG_SPIN_KILL", "Kill: /kill" } ,
+    { "CSMSG_SPIN_SVSIGNORE", "Ignore: Services ignore for random amount of time" } ,
+    { "CSMSG_SPIN_SVSIGNORE_OPER", "Ignore: I'm trying REALLY hard to ignore you, but your IRCOp smell is overwhelming!" } ,
+    { "CSMSG_SPIN_KICKBANALL", "Kickban all: /kick and ban from each channel your're in" } ,
+    { "CSMSG_SPIN_UNKNOWN", "Error: I don't know how to '%s' you, so you live for now..." },
 
 /* Other things */
     { "CSMSG_EVENT_SEARCH_RESULTS", "$bChannel Events for %s$b" },
+    { "CSMSG_LAST_INVALID", "Invalid argument.  must be 1-200" },
+    { "CSMSG_DEFCON_NO_NEW_CHANNELS", "You cannot register new channels at this time, please try again soon." },
+    { "CSMSG_DEFCON_NO_MODE_CHANGE", "You cannot change the MODE at this time, please try again soon." },
     { NULL, NULL }
 };
 
 /* eject_user and unban_user flags */
-#define ACTION_KICK            0x0001
-#define ACTION_BAN             0x0002
-#define ACTION_ADD_LAMER       0x0004
-#define ACTION_ADD_TIMED_LAMER         0x0008
-#define ACTION_UNBAN           0x0010
-#define ACTION_DEL_LAMER       0x0020
+#define ACTION_KICK     0x0001
+#define ACTION_BAN      0x0002
+#define ACTION_ADD_LAMER    0x0004
+#define ACTION_ADD_TIMED_LAMER  0x0008
+#define ACTION_UNBAN        0x0010
+#define ACTION_DEL_LAMER    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_brief(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_brief(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 *)
 
 static int eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, char *argv[], struct svccmd *cmd, int action);
 
 struct userNode *chanserv;
 dict_t note_types;
 int off_channel;
+extern struct string_list *autojoin_channels;
 static dict_t plain_dnrs, mask_dnrs, handle_dnrs;
 static struct log_type *CS_LOG;
 struct adduserPending* adduser_pendings = NULL;
 unsigned int adduser_pendings_count = 0;
+unsigned long god_timeout;
 
 static struct
 {
     struct channelList  support_channels;
     struct mod_chanmode default_modes;
 
-    unsigned long      db_backup_frequency;
-    unsigned long      channel_expire_frequency;
+    unsigned long   db_backup_frequency;
+    unsigned long   channel_expire_frequency;
+    unsigned long       ban_timeout_frequency;
+    unsigned long   dnr_expire_frequency;
 
-    long               info_delay;
-    unsigned int       adjust_delay;
-    long               channel_expire_delay;
+    long        info_delay;
+    unsigned int    adjust_delay;
+    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    greeting_length;
     unsigned int        refresh_period;
     unsigned int        giveownership_period;
 
     unsigned int        max_owned;
-    unsigned int       max_chan_users;
-    unsigned int       max_chan_bans; /* lamers */
+    unsigned int    max_chan_users;
+    unsigned int    max_chan_bans; /* lamers */
     unsigned int        max_userinfo_length;
+    unsigned int        valid_channel_regex_set : 1;
+
+    regex_t             valid_channel_regex;
 
     struct string_list  *set_shows;
     struct string_list  *eightball;
     struct string_list  *old_ban_names;
+    struct string_list  *wheel;
 
     const char          *ctcp_short_ban_duration;
     const char          *ctcp_long_ban_duration;
@@ -578,6 +679,9 @@ enum note_visible_type
     NOTE_VIS_PRIVILEGED
 };
 
+extern struct io_fd *socket_io_fd;
+extern struct cManagerNode cManager;
+
 struct note_type
 {
     enum note_access_type set_access_type;
@@ -607,6 +711,7 @@ static const struct {
     unsigned short level;
     char ch;
 } accessLevels[] = { /* MUST be orderd less to most! */
+    { "pal", "Pal", UL_PEON, '+' },
     { "peon", "Peon", UL_PEON, '+' },
     { "halfop", "HalfOp", UL_HALFOP, '%' },
     { "op", "Op", UL_OP, '@' },
@@ -616,6 +721,7 @@ static const struct {
     { "helper", "BUG:", UL_HELPER, 'X' }
 };
 
+/* If you change this, change the enum in chanserv.h also, or stack smashing will commence. */
 static const struct {
     char *format_name;
     char *db_name;
@@ -624,28 +730,28 @@ static const struct {
     unsigned int old_flag;
     unsigned short flag_value;
 } levelOptions[] = {
-    { "CSMSG_SET_GIVE_VOICE", "givevoice", 100, ~0, CHANNEL_VOICE_ALL, 0 },
-    { "CSMSG_SET_GIVE_HALFOPS", "givehalfops", 150, ~0, CHANNEL_HOP_ALL, 0 },
-    { "CSMSG_SET_GIVE_OPS", "giveops", 200, 2, 0, 0 },
-    { "CSMSG_SET_ENFOPS", "enfops", 300, 1, 0, 0 },
-    { "CSMSG_SET_ENFHALFOPS", "enfhalfops", 300, 1, 0, 0 },
-    { "CSMSG_SET_ENFMODES", "enfmodes", 200, 3, 0, 0 },
-    { "CSMSG_SET_ENFTOPIC", "enftopic", 200, 4, 0, 0 },
-    { "CSMSG_SET_PUBCMD", "pubcmd", 0, 5, 0, 0 },
-    { "CSMSG_SET_SETTERS", "setters", 400, 7, 0, 0 },
-    { "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_ENFOPS",     "enfops",     300,  1, 0, 0 },
+    { "CSMSG_SET_ENFHALFOPS", "enfhalfops", 300,  1, 0, 0 },
+    { "CSMSG_SET_ENFMODES",   "enfmodes",   200,  3, 0, 0 },
+    { "CSMSG_SET_ENFTOPIC",   "enftopic",   200,  4, 0, 0 },
+    { "CSMSG_SET_PUBCMD",     "pubcmd",       0,  5, 0, 0 },
+    { "CSMSG_SET_SETTERS",    "setters",    400,  7, 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 }
 };
 
 struct charOptionValues {
     char value;
     char *format_name;
-} voiceValues[] = {
-    { 'n', "CSMSG_VOICE_NONE" },
-    { 'p', "CSMSG_VOICE_PEON" },
-    { 'a', "CSMSG_VOICE_ALL" }
+} automodeValues[] = {
+    { 'n', "CSMSG_AUTOMODE_NONE" },
+    { 'y', "CSMSG_AUTOMODE_NORMAL" },
+    { 'v', "CSMSG_AUTOMODE_VOICE" },
+    { 'h', "CSMSG_AUTOMODE_HOP" },
+    { 'o', "CSMSG_AUTOMODE_OP" },
+    { 'm', "CSMSG_AUTOMODE_MUTE" },
+    { 'l', "CSMSG_AUTOMODE_ONLYVOICE" }
 }, protectValues[] = {
     { 'a', "CSMSG_PROTECT_ALL" },
     { 'e', "CSMSG_PROTECT_EQUAL" },
@@ -662,10 +768,25 @@ struct charOptionValues {
     { '3', "CSMSG_TOPICREFRESH_12_HOURS" },
     { '4', "CSMSG_TOPICREFRESH_24_HOURS" }
 }, ctcpReactionValues[] = {
+    { 'n', "CSMSG_CTCPREACTION_NONE" },
     { 'k', "CSMSG_CTCPREACTION_KICK" },
     { 'b', "CSMSG_CTCPREACTION_KICKBAN" },
     { 't', "CSMSG_CTCPREACTION_SHORTBAN" },
     { 'T', "CSMSG_CTCPREACTION_LONGBAN" }
+}, banTimeoutValues[] = {
+    { '0', "CSMSG_BANTIMEOUT_NONE" },
+    { '1', "CSMSG_BANTIMEOUT_10M" },
+    { '2', "CSMSG_BANTIMEOUT_2H" },
+    { '3', "CSMSG_BANTIMEOUT_4H" },
+    { '4', "CSMSG_BANTIMEOUT_1D" },
+    { '5', "CSMSG_BANTIMEOUT_1W" }
+},
+resyncValues[] = {
+    { 'n', "CSMSG_RESYNC_NEVER" },
+    { '1', "CSMSG_RESYNC_3_HOURS" },
+    { '2', "CSMSG_RESYNC_6_HOURS" },
+    { '3', "CSMSG_RESYNC_12_HOURS" },
+    { '4', "CSMSG_RESYNC_24_HOURS" }
 };
 
 static const struct {
@@ -676,11 +797,13 @@ static const struct {
     unsigned char count;
     struct charOptionValues *values;
 } charOptions[] = {
-    { "CSMSG_SET_VOICE",        "voice",        'p', 99, ArrayLength(voiceValues), voiceValues },
+    { "CSMSG_SET_AUTOMODE",     "automode",     'y', 99, ArrayLength(automodeValues), automodeValues },
     { "CSMSG_SET_PROTECT",      "protect",      'l', 0, ArrayLength(protectValues), protectValues },
     { "CSMSG_SET_TOYS",         "toys",         'p', 6, ArrayLength(toysValues), toysValues },
     { "CSMSG_SET_TOPICREFRESH", "topicrefresh", 'n', 8, ArrayLength(topicRefreshValues), topicRefreshValues },
-    { "CSMSG_SET_CTCPREACTION", "ctcpreaction", 't', 10, ArrayLength(ctcpReactionValues), ctcpReactionValues }
+    { "CSMSG_SET_CTCPREACTION", "ctcpreaction", 'n', 10, ArrayLength(ctcpReactionValues), ctcpReactionValues },
+    { "CSMSG_SET_BANTIMEOUT",   "bantimeout",   '0', 11, ArrayLength(banTimeoutValues), banTimeoutValues },
+    { "CSMSG_SET_RESYNC",       "resync",       'n', 12, ArrayLength(resyncValues), resyncValues },
 };
 
 struct userData *helperList;
@@ -691,10 +814,31 @@ unsigned int chanserv_read_version = 0; /* db version control */
 
 #define CHANSERV_DB_VERSION 2
 
-#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)
 
+void sputsock(const char *text, ...) PRINTF_LIKE(1, 2);
+
+void
+sputsock(const char *text, ...)
+{
+    va_list arg_list;
+    char buffer[MAXLEN];
+    int pos;
+
+    if (!cManager.uplink || cManager.uplink->state == DISCONNECTED) return;
+    buffer[0] = '\0';
+    va_start(arg_list, text);
+    pos = vsnprintf(buffer, MAXLEN - 2, text, arg_list);
+    va_end(arg_list);
+    if (pos < 0 || pos > (MAXLEN - 2)) pos = MAXLEN - 2;
+    buffer[pos] = 0;
+    log_replay(MAIN_LOG, true, buffer);
+    buffer[pos++] = '\n';
+    buffer[pos] = 0;
+    ioset_write(socket_io_fd, buffer, pos);
+}
+
 unsigned short
 user_level_from_name(const char *name, unsigned short clamp_level)
 {
@@ -717,7 +861,7 @@ user_level_name_from_level(int level)
 
     highest = "None";
     if(level >= 1)
-        highest = "Peon";
+        highest = "Pal";
     for(ii = 0; (ii < ArrayLength(accessLevels)); ii++)
         if(level >= accessLevels[ii].level)
             highest = accessLevels[ii].title;
@@ -755,52 +899,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;
@@ -851,7 +995,7 @@ scan_user_presence(struct userData *uData, struct userNode *user)
 }
 
 static void
-chanserv_ctcp_check(struct userNode *user, struct chanNode *channel, 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), UNUSED_ARG(void *extra))
 {
     unsigned int eflags, argc;
     char *argv[4];
@@ -863,18 +1007,27 @@ chanserv_ctcp_check(struct userNode *user, struct chanNode *channel, char *text,
        || IsService(user)
        || !ircncasecmp(text, "ACTION ", 7))
         return;
-    /* Figure out the minimum level needed to CTCP the channel */
-    if(check_user_level(channel, user, lvlCTCPUsers, 1, 0))
+    /* We dont punish people we know -Rubin 
+     *    * Figure out the minimum level needed to CTCP the channel *
+     *
+     *      if(check_user_level(channel, user, lvlCTCPUsers, 1, 0))
+     *      return;
+     */
+    /* If they are a user of the channel, they are exempt */
+    if(_GetChannelUser(channel->channel_info, user->handle_info, 0, 0))
         return;
     /* We need to enforce against them; do so. */
     eflags = 0;
-    argv[0] = text;
+    argv[0] = (char*)text;
     argv[1] = user->nick;
     argc = 2;
     if(GetUserMode(channel, user))
         eflags |= ACTION_KICK;
     switch(channel->channel_info->chOpts[chCTCPReaction]) {
-    default: case 'k': /* just do the kick */ break;
+    default: case 'n': return;
+    case 'k':
+        eflags |= ACTION_KICK; 
+        break;
     case 'b':
         eflags |= ACTION_BAN;
         break;
@@ -907,7 +1060,7 @@ chanserv_deref_note_type(void *data)
     struct note_type *ntype = data;
 
     if(--ntype->refs > 0)
-       return;
+    return;
     free(ntype);
 }
 
@@ -930,7 +1083,7 @@ chanserv_truncate_notes(struct note_type *ntype)
         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);
@@ -975,7 +1128,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++;
@@ -988,7 +1141,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;
@@ -1000,7 +1153,7 @@ static MODCMD_FUNC(cmd_createnote) {
     else
     {
         reply("CSMSG_BAD_NOTE_ACCESS", argv[arg]);
-       goto fail;
+    goto fail;
     }
 
     if(!irccasecmp(argv[++arg], "privileged"))
@@ -1011,23 +1164,23 @@ 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;
 
@@ -1039,7 +1192,7 @@ static MODCMD_FUNC(cmd_createnote) {
 
 fail:
     if(!existed)
-       dict_remove(note_types, ntype->name);
+    dict_remove(note_types, ntype->name);
     return 0;
 }
 
@@ -1156,7 +1309,7 @@ register_channel(struct chanNode *cNode, char *registrar)
     channel->next = channelList;
 
     if(channelList)
-       channelList->prev = channel;
+    channelList->prev = channel;
     channelList = channel;
     registered_channels++;
 
@@ -1168,24 +1321,27 @@ register_channel(struct chanNode *cNode, char *registrar)
 }
 
 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, time_t seen, const char *info, time_t accessexpiry)
 {
     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->accessexpiry = accessexpiry ? accessexpiry : 0;
+    ud->clvlexpiry = 0;
+    ud->lastaccess = 0;
 
     ud->prev = NULL;
     ud->next = channel->users;
     if(channel->users)
-       channel->users->prev = ud;
+    channel->users->prev = ud;
     channel->users = ud;
 
     channel->userCount++;
@@ -1203,6 +1359,110 @@ add_channel_user(struct chanData *channel, struct handle_info *handle, unsigned
 
 static void unregister_channel(struct chanData *channel, const char *reason);
 
+static void
+chanserv_expire_tempuser(void *data)
+{
+    struct userData *uData = data;
+    char *handle;
+
+    if (data) {
+        handle = strdup(uData->handle->handle);
+        if (uData->accessexpiry > 0) {
+            if (uData->present) {
+                struct userNode *user, *next_un = NULL;
+                struct handle_info *hi;
+
+                hi = get_handle_info(handle);
+                for (user = hi->users; user; user = next_un) {
+                    struct mod_chanmode *change;
+                    struct modeNode *mn;
+                    unsigned int count = 0;
+
+                    send_message(user, chanserv, "CSMSG_AUTO_DELETED", chanserv->nick, uData->channel->channel->name);
+                    if (!(mn = GetUserMode(uData->channel->channel, user)) || !(mn->modes & MODE_CHANOP)) {
+                        next_un = user->next_authed;
+                        continue;
+                    }
+
+                    change = mod_chanmode_alloc(2);
+                    change->args[count].mode = MODE_REMOVE | MODE_CHANOP;
+                    change->args[count++].u.member = mn;
+
+                    if (count) {
+                        change->argc = count;
+                        mod_chanmode_announce(chanserv, uData->channel->channel, change);
+                    }
+                    mod_chanmode_free(change);
+                    next_un = user->next_authed;
+                }
+            }
+            del_channel_user(uData, 1);
+        }
+    }
+}
+
+static void
+chanserv_expire_tempclvl(void *data)
+{
+    struct userData *uData = data;
+    char *handle;
+
+    if (data) {
+        handle = strdup(uData->handle->handle);
+        if (uData->clvlexpiry > 0) {
+            int changemodes = 0;
+            unsigned int mode = 0;
+            if (((uData->lastaccess == UL_PEON) || (uData->lastaccess == UL_HALFOP)) && (uData->access >= UL_OP)) {
+                changemodes = 1;
+                mode = MODE_REMOVE | MODE_CHANOP;
+            } else if ((uData->lastaccess == UL_PEON) && (uData->access == UL_HALFOP)) {
+                changemodes = 1;
+                mode = MODE_REMOVE | MODE_HALFOP;
+            } else
+                changemodes = 0;
+
+            if (uData->present) {
+                struct userNode *user, *next_un = NULL;
+                struct handle_info *hi;
+
+                hi = get_handle_info(handle);
+                for (user = hi->users; user; user = next_un) {
+                    struct mod_chanmode *change;
+                    struct modeNode *mn;
+                    unsigned int count = 0;
+
+                    send_message(user, chanserv, "CSMSG_CLVL_EXPIRED", uData->channel->channel->name);
+                    if (!(mn = GetUserMode(uData->channel->channel, user))) {
+                        next_un = user->next_authed;
+                        continue;
+                    }
+
+                    if (changemodes == 0) {
+                        next_un = user->next_authed;
+                        continue;
+                    }
+                    change = mod_chanmode_alloc(2);
+                    change->args[count].mode = mode;
+                    change->args[count++].u.member = mn;
+
+                    if (count) {
+                        change->argc = count;
+                        mod_chanmode_announce(chanserv, uData->channel->channel, change);
+                    }
+                    mod_chanmode_free(change);
+                    next_un = user->next_authed;
+                }
+            }
+
+            uData->access = uData->lastaccess;
+            uData->lastaccess = 0;
+            uData->clvlexpiry = 0;
+        }
+    }
+}
+
 void
 del_channel_user(struct userData *user, int do_gc)
 {
@@ -1211,6 +1471,9 @@ del_channel_user(struct userData *user, int do_gc)
     channel->userCount--;
     userCount--;
 
+    timeq_del(0, chanserv_expire_tempuser, user, TIMEQ_IGNORE_WHEN);
+    timeq_del(0, chanserv_expire_tempclvl, user, TIMEQ_IGNORE_WHEN);
+
     if(user->prev)
         user->prev->next = user->next;
     else
@@ -1227,8 +1490,10 @@ 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 struct adduserPending* 
@@ -1319,7 +1584,7 @@ process_adduser_pending(struct userNode *user)
         }
         else
         {
-            actee = add_channel_user(ap->channel->channel_info, ap->user->handle_info, ap->level, 0, NULL);
+            actee = add_channel_user(ap->channel->channel_info, ap->user->handle_info, ap->level, 0, NULL, 0);
             scan_user_presence(actee, NULL);
         }
         del_adduser_pending(ap);
@@ -1346,7 +1611,7 @@ expire_adduser_pending()
 
 static void expire_ban(void *data);
 
-static struct banData*
+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 *bd;
@@ -1384,12 +1649,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; /* lamers */
     if(channel->bans)
-       channel->bans->prev = bd;
+    channel->bans->prev = bd;
     channel->bans = bd;
     channel->banCount++;
     banCount++;
@@ -1412,7 +1677,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);
@@ -1434,7 +1699,7 @@ expire_ban(void *data) /* lamer.. */
         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;
@@ -1466,7 +1731,7 @@ unregister_channel(struct chanData *channel, const char *reason)
     */
 
     if(!channel)
-       return;
+    return;
 
     timeq_del(0, NULL, channel, TIMEQ_IGNORE_FUNC | TIMEQ_IGNORE_WHEN);
 
@@ -1480,10 +1745,10 @@ unregister_channel(struct chanData *channel, const char *reason)
     wipe_adduser_pending(channel->channel, NULL);
 
     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);
@@ -1501,8 +1766,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)
         {
@@ -1514,8 +1779,8 @@ unregister_channel(struct chanData *channel, const char *reason)
             free(suspended);
         }
 
-       if(cNode)
-           cNode->channel_info = NULL;
+    if(cNode)
+        cNode->channel_info = NULL;
     }
     channel->channel->channel_info = NULL;
 
@@ -1541,7 +1806,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)
@@ -1557,33 +1822,84 @@ expire_channels(UNUSED_ARG(void *data))
 
         /* Unregister the channel */
         log_module(CS_LOG, LOG_INFO, "(%s) Channel registration expired.", channel->channel->name);
+        spamserv_cs_unregister(NULL, channel->channel, expire, NULL);
         unregister_channel(channel, "registration expired.");
     }
 
     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);
+}
+
+static void
+expire_dnrs(UNUSED_ARG(void *data))
+{
+    dict_iterator_t it, next;
+    struct do_not_register *dnr;
+
+    for(it = dict_first(handle_dnrs); it; it = next)
+    {
+        dnr = iter_data(it);
+        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 = next)
+    {
+        dnr = iter_data(it);
+        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 = next)
+    {
+        dnr = iter_data(it);
+        next = iter_next(it);
+        if(dnr->expires && dnr->expires <= now)
+            dict_remove(mask_dnrs, dnr->chan_name + 1);
+    }
+
+    if(chanserv_conf.dnr_expire_frequency)
+        timeq_add(now + chanserv_conf.dnr_expire_frequency, expire_dnrs, NULL);
 }
 
 static int
-protect_user(const struct userNode *victim, const struct userNode *aggressor, struct chanData *channel)
+protect_user(const struct userNode *victim, const struct userNode *aggressor, struct chanData *channel, int protect_invitables)
 {
     char protect = channel->chOpts[chProtect];
     struct userData *cs_victim, *cs_aggressor;
 
-    /* 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 /* Opers dont get special treatment :/  || IsOper(aggressor) */)
-       return 0;
+    /* If victim access level is greater than set invitelevel, don't let
+     * us kick them, but don't consider it punishment if someone else does
+     */
+
 
+    if(victim == aggressor)
+        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
        also. */
+
     cs_victim = GetChannelAccess(channel, victim->handle_info);
+
+    /* If they have enough access to invite themselvs through a ban,
+     * and its us kicking them, don't. -Rubin */
+    if(protect_invitables==true && cs_victim && (cs_victim->access >= channel->lvlOpts[lvlInviteMe]))
+        return 1;
+
+    if(protect == 'n')
+    return 0;
+
     if(protect != 'a' && !cs_victim)
         return 0;
 
     /* Protect if the aggressor isn't a user because at this point,
        the aggressor can only be less than or equal to the victim. */
+
+    /* Not protected from chanserv except above */
+    /* XXX: need to generic-ize chanserv to "one of x3's services" somehow.. */
+    if(aggressor == chanserv)
+        return 0;
+
     cs_aggressor = GetChannelAccess(channel, aggressor->handle_info);
     if(!cs_aggressor)
         return 1;
@@ -1595,21 +1911,21 @@ 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;
 }
 
 static int
-validate_op(struct userNode *user, struct chanNode *channel, struct userNode *victim)
+validate_op(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, struct userNode *victim)
 {
     struct chanData *cData = channel->channel_info;
     struct userData *cs_victim;
@@ -1618,15 +1934,18 @@ validate_op(struct userNode *user, struct chanNode *channel, struct userNode *vi
         || (cs_victim->access < UL_OP /* cData->lvlOpts[lvlGiveOps]*/))
        && !check_user_level(channel, user, lvlEnfOps, 0, 0))
     {
-       send_message(user, chanserv, "CSMSG_OPBY_LOCKED");
-       return 0;
+        if(cmd)
+        reply("CSMSG_OPBY_LOCKED");
+        else
+            send_message(user, chanserv, "CSMSG_OPBY_LOCKED");
+    return 0;
     }
 
     return 1;
 }
 
 static int
-validate_halfop(struct userNode *user, struct chanNode *channel, struct userNode *victim)
+validate_halfop(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, struct userNode *victim)
 {
     struct chanData *cData = channel->channel_info;
     struct userData *cs_victim;
@@ -1635,7 +1954,7 @@ validate_halfop(struct userNode *user, struct chanNode *channel, struct userNode
         || (cs_victim->access < UL_HALFOP /* cData->lvlOpts[lvlGiveHalfOps] */))
        && !check_user_level(channel, user, lvlEnfHalfOps, 0, 0))
     {
-        send_message(user, chanserv, "CSMSG_HOPBY_LOCKED");
+        reply("CSMSG_HOPBY_LOCKED");
         return 0;
     }
 
@@ -1644,35 +1963,35 @@ validate_halfop(struct userNode *user, struct chanNode *channel, struct userNode
 
 
 static int
-validate_deop(struct userNode *user, struct chanNode *channel, struct userNode *victim)
+validate_deop(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, struct userNode *victim)
 {
     if(IsService(victim))
     {
-       send_message(user, chanserv, "MSG_SERVICE_IMMUNE", victim->nick);
-       return 0;
+    reply("MSG_SERVICE_IMMUNE", victim->nick);
+    return 0;
     }
 
-    if(protect_user(victim, user, channel->channel_info))
+    if(protect_user(victim, user, channel->channel_info, false))
     {
-       send_message(user, chanserv, "CSMSG_USER_PROTECTED", victim->nick);
-       return 0;
+    reply("CSMSG_USER_PROTECTED", victim->nick);
+    return 0;
     }
 
     return 1;
 }
 
 static int
-validate_dehop(struct userNode *user, struct chanNode *channel, struct userNode *victim)
+validate_dehop(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, struct userNode *victim)
 {
     if(IsService(victim))
     {
-        send_message(user, chanserv, "MSG_SERVICE_IMMUNE", victim->nick);
+        reply("MSG_SERVICE_IMMUNE", victim->nick);
         return 0;
     }
 
-    if(protect_user(victim, user, channel->channel_info))
+    if(protect_user(victim, user, channel->channel_info, false))
     {
-        send_message(user, chanserv, "CSMSG_USER_PROTECTED", victim->nick);
+        reply("CSMSG_USER_PROTECTED", victim->nick);
         return 0;
     }
 
@@ -1680,13 +1999,14 @@ validate_dehop(struct userNode *user, struct chanNode *channel, struct userNode
 }
 
 static struct do_not_register *
-chanserv_add_dnr(const char *chan_name, const char *setter, const char *reason)
+chanserv_add_dnr(const char *chan_name, const char *setter, time_t 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));
     safestrncpy(dnr->setter, setter, sizeof(dnr->setter));
     strcpy(dnr->reason, reason);
     dnr->set = now;
+    dnr->expires = expires;
     if(dnr->chan_name[0] == '*')
         dict_insert(handle_dnrs, dnr->chan_name+1, dnr);
     else if(strpbrk(dnr->chan_name, "*?"))
@@ -1697,44 +2017,78 @@ chanserv_add_dnr(const char *chan_name, const char *setter, const char *reason)
 }
 
 static struct dnrList
-chanserv_find_dnrs(const char *chan_name, const char *handle)
+chanserv_find_dnrs(const char *chan_name, const char *handle, unsigned int max)
 {
     struct dnrList list;
-    dict_iterator_t it;
+    dict_iterator_t it, next;
     struct do_not_register *dnr;
 
     dnrList_init(&list);
+
     if(handle && (dnr = dict_find(handle_dnrs, handle, NULL)))
-        dnrList_append(&list, dnr);
+    {
+        if(dnr->expires && dnr->expires <= now)
+            dict_remove(handle_dnrs, handle);
+        else if (list.used < max)
+            dnrList_append(&list, dnr);
+    }
+
     if(chan_name && (dnr = dict_find(plain_dnrs, chan_name, NULL)))
-        dnrList_append(&list, dnr);
+    {
+        if(dnr->expires && dnr->expires <= now)
+            dict_remove(plain_dnrs, chan_name);
+        else if (list.used < max)
+            dnrList_append(&list, dnr);
+    }
     if(chan_name)
-        for(it = dict_first(mask_dnrs); it; it = iter_next(it))
-            if(match_ircglob(chan_name, iter_key(it)))
-                dnrList_append(&list, iter_data(it));
+    {
+        for(it = dict_first(mask_dnrs); it && list.used < max; it = next)
+        {
+            next = iter_next(it);
+            if(!match_ircglob(chan_name, iter_key(it)))
+                continue;
+            dnr = iter_data(it);
+            if(dnr->expires && dnr->expires <= now)
+                dict_remove(mask_dnrs, iter_key(it));
+            else
+                dnrList_append(&list, dnr);
+        }
+    }
     return list;
 }
 
+static int dnr_print_func(struct do_not_register *dnr, void *extra)
+{
+    struct userNode *user;
+    char buf1[INTERVALLEN];
+    char buf2[INTERVALLEN];
+
+    user = extra;
+    if(dnr->set)
+        strftime(buf1, sizeof(buf1), "%d %b %Y", localtime(&dnr->set));
+    if(dnr->expires)
+    {
+        strftime(buf2, sizeof(buf2), "%d %b %Y", localtime(&dnr->expires));
+        send_message(user, chanserv, "CSMSG_DNR_INFO_SET_EXPIRES", dnr->chan_name, buf1, dnr->setter, buf2, dnr->reason);
+    }
+    else if(dnr->set)
+    {
+        send_message(user, chanserv, "CSMSG_DNR_INFO_SET", dnr->chan_name, buf1, dnr->setter, dnr->reason);
+    }
+    else
+        send_message(user, chanserv, "CSMSG_DNR_INFO", dnr->chan_name, dnr->setter, dnr->reason);
+    return 0;
+}
+
 static unsigned int
 chanserv_show_dnrs(struct userNode *user, struct svccmd *cmd, const char *chan_name, const char *handle)
 {
     struct dnrList list;
-    struct do_not_register *dnr;
     unsigned int ii;
-    char buf[INTERVALLEN];
 
-    list = chanserv_find_dnrs(chan_name, handle);
+    list = chanserv_find_dnrs(chan_name, handle, UINT_MAX);
     for(ii = 0; (ii < list.used) && (ii < 10); ++ii)
-    {
-        dnr = list.list[ii];
-        if(dnr->set)
-        {
-            strftime(buf, sizeof(buf), "%Y %b %d", localtime(&dnr->set));
-            reply("CSMSG_DNR_INFO_SET", dnr->chan_name, buf, dnr->setter, dnr->reason);
-        }
-        else
-            reply("CSMSG_DNR_INFO", dnr->chan_name, dnr->setter, dnr->reason);
-    }
+        dnr_print_func(list.list[ii], user);
     if(ii < list.used)
         reply("CSMSG_MORE_DNRS", list.used - ii);
     free(list.list);
@@ -1744,64 +2098,50 @@ chanserv_show_dnrs(struct userNode *user, struct svccmd *cmd, const char *chan_n
 struct do_not_register *
 chanserv_is_dnr(const char *chan_name, struct handle_info *handle)
 {
+    struct dnrList list;
     struct do_not_register *dnr;
-    dict_iterator_t it;
 
-    if(handle && (dnr = dict_find(handle_dnrs, handle->handle, NULL)))
-        return dnr;
-    if(chan_name)
+    list = chanserv_find_dnrs(chan_name, handle ? handle->handle : NULL, 1);
+    dnr = list.used ? list.list[0] : NULL;
+    free(list.list);
+    return dnr;
+}
+
+static unsigned int send_dnrs(struct userNode *user, dict_t dict)
+{
+    struct do_not_register *dnr;
+    dict_iterator_t it, next;
+    unsigned int matches = 0;
+
+    for(it = dict_first(dict); it; it = next)
     {
-        if((dnr = dict_find(plain_dnrs, chan_name, NULL)))
-            return dnr;
-        for(it = dict_first(mask_dnrs); it; it = iter_next(it))
-            if(match_ircglob(chan_name, iter_key(it)))
-                return iter_data(it);
+        dnr = iter_data(it);
+        next = iter_next(it);
+        if(dnr->expires && dnr->expires <= now)
+        {
+            dict_remove(dict, iter_key(it));
+            continue;
+        }
+        dnr_print_func(dnr, user);
+        matches++;
     }
-    return NULL;
+
+    return matches;
 }
 
 static CHANSERV_FUNC(cmd_noregister)
 {
     const char *target;
-    struct do_not_register *dnr;
-    char buf[INTERVALLEN];
+    const char *reason;
+    time_t expiry, duration;
     unsigned int matches;
 
     if(argc < 2)
     {
-        dict_iterator_t it;
-
         reply("CSMSG_DNR_SEARCH_RESULTS");
-        reply("CSMSG_BAR");
-        matches = 0;
-        for(it = dict_first(handle_dnrs); it; it = iter_next(it))
-        {
-            dnr = iter_data(it);
-            if(dnr->set)
-                reply("CSMSG_DNR_INFO_SET", dnr->chan_name, intervalString(buf, now - dnr->set, user->handle_info), dnr->setter, dnr->reason);
-            else
-                reply("CSMSG_DNR_INFO", dnr->chan_name, dnr->setter, dnr->reason);
-            matches++;
-        }
-        for(it = dict_first(plain_dnrs); it; it = iter_next(it))
-        {
-            dnr = iter_data(it);
-            if(dnr->set)
-                reply("CSMSG_DNR_INFO_SET", dnr->chan_name, intervalString(buf, now - dnr->set, user->handle_info), dnr->setter, dnr->reason);
-            else
-                reply("CSMSG_DNR_INFO", dnr->chan_name, dnr->setter, dnr->reason);
-            matches++;
-        }
-        for(it = dict_first(mask_dnrs); it; it = iter_next(it))
-        {
-            dnr = iter_data(it);
-            if(dnr->set)
-                reply("CSMSG_DNR_INFO_SET", dnr->chan_name, intervalString(buf, now - dnr->set, user->handle_info), dnr->setter, dnr->reason);
-            else
-                reply("CSMSG_DNR_INFO", dnr->chan_name, dnr->setter, dnr->reason);
-            matches++;
-        }
-
+        matches = send_dnrs(user, handle_dnrs);
+        matches += send_dnrs(user, plain_dnrs);
+        matches += send_dnrs(user, mask_dnrs);
         if(matches)
             reply("MSG_MATCH_COUNT", matches);
         else
@@ -1819,19 +2159,37 @@ static CHANSERV_FUNC(cmd_noregister)
 
     if(argc > 2)
     {
-        const char *reason = unsplit_string(argv + 2, argc - 2, NULL);
+        if(argc == 3)
+        {
+            reply("MSG_INVALID_DURATION", argv[2]);
+            return 0;
+        }
+
+        if(!strcmp(argv[2], "0"))
+            expiry = 0;
+        else if((duration = ParseInterval(argv[2])))
+            expiry = now + duration;
+        else
+        {
+            reply("MSG_INVALID_DURATION", argv[2]);
+            return 0;
+        }
+
+        reason = unsplit_string(argv + 3, argc - 3, NULL);
+
         if((*target == '*') && !get_handle_info(target + 1))
         {
             reply("MSG_HANDLE_UNKNOWN", target + 1);
             return 0;
         }
-        chanserv_add_dnr(target, user->handle_info->handle, reason);
+        chanserv_add_dnr(target, user->handle_info->handle, expiry, reason);
         reply("CSMSG_NOREGISTER_CHANNEL", target);
         return 1;
     }
 
     reply("CSMSG_DNR_SEARCH_RESULTS");
-    reply("CSMSG_BAR");
+    if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
+        reply("CSMSG_BAR");
     if(*target == '*')
         matches = chanserv_show_dnrs(user, cmd, NULL, target + 1);
     else
@@ -1845,112 +2203,354 @@ static CHANSERV_FUNC(cmd_allowregister)
 {
     const char *chan_name = argv[1];
 
-    if((chan_name[0] == '*') && dict_find(handle_dnrs, chan_name+1, NULL))
-    {
-        dict_remove(handle_dnrs, chan_name+1);
-        reply("CSMSG_DNR_REMOVED", chan_name);
-    }
-    else if(dict_find(plain_dnrs, chan_name, NULL))
+    if(((chan_name[0] == '*') && dict_remove(handle_dnrs, chan_name+1))
+       || dict_remove(plain_dnrs, chan_name)
+       || dict_remove(mask_dnrs, chan_name))
     {
-        dict_remove(plain_dnrs, chan_name);
         reply("CSMSG_DNR_REMOVED", chan_name);
+        return 1;
     }
-    else if(dict_find(mask_dnrs, chan_name, NULL))
-    {
-        dict_remove(mask_dnrs, chan_name);
-        reply("CSMSG_DNR_REMOVED", chan_name);
-    }
-    else
-    {
-        reply("CSMSG_NO_SUCH_DNR", chan_name);
-        return 0;
-    }
-    return 1;
+    reply("CSMSG_NO_SUCH_DNR", chan_name);
+    return 0;
 }
 
-unsigned int
-chanserv_get_owned_count(struct handle_info *hi)
-{
-    struct userData *cList;
-    unsigned int owned;
-
-    for(owned=0, cList=hi->channels; cList; cList=cList->u_next)
-        if(cList->access == UL_OWNER)
-            owned++;
-    return owned;
-}
+struct dnr_search {
+    struct userNode *source;
+    char *chan_mask;
+    char *setter_mask;
+    char *reason_mask;
+    time_t min_set, max_set;
+    time_t min_expires, max_expires;
+    unsigned int limit;
+};
 
-static CHANSERV_FUNC(cmd_register)
-{
-    struct handle_info *handle;
-    struct chanData *cData;
-    struct modeNode *mn;
-    char reason[MAXLEN];
-    char *chan_name;
-    unsigned int new_channel, force=0;
-    struct do_not_register *dnr;
-    unsigned int n;
+static int
+dnr_search_matches(const struct do_not_register *dnr, const struct dnr_search *search)
+{
+    return !((dnr->set < search->min_set)
+             || (dnr->set > search->max_set)
+             || (dnr->expires < search->min_expires)
+             || (search->max_expires
+                 && ((dnr->expires == 0)
+                     || (dnr->expires > search->max_expires)))
+             || (search->chan_mask
+                 && !match_ircglob(dnr->chan_name, search->chan_mask))
+             || (search->setter_mask
+                 && !match_ircglob(dnr->setter, search->setter_mask))
+             || (search->reason_mask
+                 && !match_ircglob(dnr->reason, search->reason_mask)));
+}
+
+static struct dnr_search *
+dnr_search_create(struct userNode *user, struct svccmd *cmd, unsigned int argc, char *argv[])
+{
+    struct dnr_search *discrim;
+    unsigned int ii;
 
+    discrim = calloc(1, sizeof(*discrim));
+    discrim->source = user;
+    discrim->chan_mask = NULL;
+    discrim->setter_mask = NULL;
+    discrim->reason_mask = NULL;
+    discrim->max_set = INT_MAX;
+    discrim->limit = 50;
 
-    if(channel)
+    for(ii=0; ii<argc; ++ii)
     {
-        if(channel->channel_info)
+        if(ii == argc - 1)
         {
-            reply("CSMSG_ALREADY_REGGED", channel->name);
-            return 0;
+            reply("MSG_MISSING_PARAMS", argv[ii]);
+            goto fail;
         }
-
-        if(channel->bad_channel)
+        else if(0 == irccasecmp(argv[ii], "channel"))
         {
-            reply("CSMSG_ILLEGAL_CHANNEL", channel->name);
-            return 0;
+            discrim->chan_mask = argv[++ii];
         }
-
-        if(!IsHelping(user) && (!(mn = GetUserMode(channel, user)) || !(mn->modes & MODE_CHANOP)))
+        else if(0 == irccasecmp(argv[ii], "setter"))
         {
-            reply("CSMSG_MUST_BE_OPPED", channel->name);
-            return 0;
+            discrim->setter_mask = argv[++ii];
         }
-
-        new_channel = 0;
-        chan_name = channel->name;
-    }
-    else
-    {
-        if(argc < 2)
+        else if(0 == irccasecmp(argv[ii], "reason"))
         {
-          reply("MSG_MISSING_PARAMS", cmd->name);
-          svccmd_send_help_brief(user, chanserv, cmd);
-          return 0;
+            discrim->reason_mask = argv[++ii];
         }
-        if(!IsChannelName(argv[1]))
+        else if(0 == irccasecmp(argv[ii], "limit"))
         {
-            reply("MSG_NOT_CHANNEL_NAME");
-            return 0;
+            discrim->limit = strtoul(argv[++ii], NULL, 0);
         }
-
-        if(opserv_bad_channel(argv[1]))
+        else if(0 == irccasecmp(argv[ii], "set"))
         {
-            reply("CSMSG_ILLEGAL_CHANNEL", argv[1]);
-            return 0;
+            const char *cmp = argv[++ii];
+            if(cmp[0] == '<') {
+                if(cmp[1] == '=')
+                    discrim->min_set = now - ParseInterval(cmp + 2);
+                else
+                    discrim->min_set = now - (ParseInterval(cmp + 1) - 1);
+            } else if(cmp[0] == '=') {
+                discrim->min_set = discrim->max_set = now - ParseInterval(cmp + 1);
+            } else if(cmp[0] == '>') {
+                if(cmp[1] == '=')
+                    discrim->max_set = now - ParseInterval(cmp + 2);
+                else
+                    discrim->max_set = now - (ParseInterval(cmp + 1) - 1);
+            } else {
+                discrim->max_set = now - (ParseInterval(cmp) - 1);
+            }
+        }
+        else if(0 == irccasecmp(argv[ii], "expires"))
+        {
+            const char *cmp = argv[++ii];
+            if(cmp[0] == '<') {
+                if(cmp[1] == '=')
+                    discrim->max_expires = now + ParseInterval(cmp + 2);
+                else
+                    discrim->max_expires = now + (ParseInterval(cmp + 1) - 1);
+            } else if(cmp[0] == '=') {
+                discrim->min_expires = discrim->max_expires = now + ParseInterval(cmp + 1);
+            } else if(cmp[0] == '>') {
+                if(cmp[1] == '=')
+                    discrim->min_expires = now + ParseInterval(cmp + 2);
+                else
+                    discrim->min_expires = now + (ParseInterval(cmp + 1) - 1);
+            } else {
+                discrim->min_expires = now + (ParseInterval(cmp) - 1);
+            }
+        }
+        else
+        {
+            reply("MSG_INVALID_CRITERIA", argv[ii]);
+            goto fail;
         }
-
-        new_channel = 1;
-        chan_name = argv[1];
     }
+    return discrim;
 
-    if(argc >= (new_channel+2))
-    {
-       if(!IsHelping(user))
-       {
-           reply("CSMSG_PROXY_FORBIDDEN");
-           return 0;
-       }
+  fail:
+    free(discrim);
+    return NULL;
+}
 
-       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);
+typedef int (*dnr_search_func)(struct do_not_register *match, void *extra);
+
+static unsigned int
+dnr_search(struct dnr_search *discrim, dnr_search_func dsf, void *data)
+{
+    struct do_not_register *dnr;
+    dict_iterator_t next;
+    dict_iterator_t it;
+    unsigned int count;
+    int target_fixed;
+
+    /* Initialize local variables. */
+    count = 0;
+    target_fixed = 0;
+    if(discrim->chan_mask)
+    {
+        int shift = (discrim->chan_mask[0] == '\\' && discrim->chan_mask[1] == '*') ? 2 : 0;
+        if('\0' == discrim->chan_mask[shift + strcspn(discrim->chan_mask+shift, "*?")])
+            target_fixed = 1;
+    }
+
+    if(target_fixed && discrim->chan_mask[0] == '\\' && discrim->chan_mask[1] == '*')
+    {
+        /* Check against account-based DNRs. */
+        dnr = dict_find(handle_dnrs, discrim->chan_mask + 2, NULL);
+        if(dnr && dnr_search_matches(dnr, discrim) && (count++ < discrim->limit))
+            dsf(dnr, data);
+    }
+    else if(target_fixed)
+    {
+        /* Check against channel-based DNRs. */
+        dnr = dict_find(plain_dnrs, discrim->chan_mask, NULL);
+        if(dnr && dnr_search_matches(dnr, discrim) && (count++ < discrim->limit))
+            dsf(dnr, data);
+    }
+    else
+    {
+        /* Exhaustively search account DNRs. */
+        for(it = dict_first(handle_dnrs); it; it = next)
+        {
+            next = iter_next(it);
+            dnr = iter_data(it);
+            if(dnr_search_matches(dnr, discrim) && (count++ < discrim->limit) && dsf(dnr, data))
+                break;
+        }
+
+        /* Do the same for channel DNRs. */
+        for(it = dict_first(plain_dnrs); it; it = next)
+        {
+            next = iter_next(it);
+            dnr = iter_data(it);
+            if(dnr_search_matches(dnr, discrim) && (count++ < discrim->limit) && dsf(dnr, data))
+                break;
+        }
+
+        /* Do the same for wildcarded channel DNRs. */
+        for(it = dict_first(mask_dnrs); it; it = next)
+        {
+            next = iter_next(it);
+            dnr = iter_data(it);
+            if(dnr_search_matches(dnr, discrim) && (count++ < discrim->limit) && dsf(dnr, data))
+                break;
+        }
+    }
+    return count;
+}
+
+static int
+dnr_remove_func(struct do_not_register *match, void *extra)
+{
+    struct userNode *user;
+    char *chan_name;
+
+    chan_name = alloca(strlen(match->chan_name) + 1);
+    strcpy(chan_name, match->chan_name);
+    user = extra;
+    if(((chan_name[0] == '*') && dict_remove(handle_dnrs, chan_name+1))
+       || dict_remove(plain_dnrs, chan_name)
+       || dict_remove(mask_dnrs, chan_name))
+    {
+        send_message(user, chanserv, "CSMSG_DNR_REMOVED", chan_name);
+    }
+    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;
+    dnr_search_func action;
+    struct svccmd *subcmd;
+    unsigned int matches;
+    char buf[MAXLEN];
+
+    sprintf(buf, "dnrsearch %s", argv[1]);
+    subcmd = dict_find(cmd->parent->commands, buf, NULL);
+    if(!subcmd)
+    {
+        reply("CSMSG_DNR_BAD_ACTION", argv[1]);
+        return 0;
+    }
+    if(!svccmd_can_invoke(user, cmd->parent->bot, subcmd, channel, SVCCMD_NOISY))
+        return 0;
+    if(!irccasecmp(argv[1], "print"))
+        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]);
+        return 0;
+    }
+
+    discrim = dnr_search_create(user, cmd, argc-2, argv+2);
+    if(!discrim)
+        return 0;
+
+    if(action == dnr_print_func)
+        reply("CSMSG_DNR_SEARCH_RESULTS");
+    matches = dnr_search(discrim, action, user);
+    if(matches)
+        reply("MSG_MATCH_COUNT", matches);
+    else
+        reply("MSG_NO_MATCHES");
+    free(discrim);
+    return 1;
+}
+
+unsigned int
+chanserv_get_owned_count(struct handle_info *hi)
+{
+    struct userData *cList;
+    unsigned int owned;
+
+    for(owned=0, cList=hi->channels; cList; cList=cList->u_next)
+        if(cList->access == UL_OWNER)
+            owned++;
+    return owned;
+}
+
+static CHANSERV_FUNC(cmd_register)
+{
+    struct handle_info *handle;
+    struct chanData *cData;
+    struct modeNode *mn;
+    char reason[MAXLEN];
+    char *chan_name;
+    unsigned int new_channel, force=0;
+    struct do_not_register *dnr;
+    unsigned int n;
+
+    if (checkDefCon(DEFCON_NO_NEW_CHANNELS) && !IsOper(user)) {
+        reply("CSMSG_DEFCON_NO_NEW_CHANNELS");
+        return 0;
+    }
+
+    if(channel)
+    {
+        if(channel->channel_info)
+        {
+            reply("CSMSG_ALREADY_REGGED", channel->name);
+            return 0;
+        }
+
+        if(channel->bad_channel)
+        {
+            reply("CSMSG_ILLEGAL_CHANNEL", channel->name);
+            return 0;
+        }
+
+        if(!IsHelping(user) && (!(mn = GetUserMode(channel, user)) || !(mn->modes & MODE_CHANOP)))
+        {
+            reply("CSMSG_MUST_BE_OPPED", channel->name);
+            return 0;
+        }
+
+        new_channel = 0;
+        chan_name = channel->name;
+    }
+    else
+    {
+        if(argc < 2)
+        {
+          reply("MSG_MISSING_PARAMS", cmd->name);
+          svccmd_send_help_brief(user, chanserv, cmd);
+          return 0;
+        }
+        if(!IsChannelName(argv[1]))
+        {
+            reply("MSG_NOT_CHANNEL_NAME");
+            return 0;
+        }
+
+        if(opserv_bad_channel(argv[1]))
+        {
+            reply("CSMSG_ILLEGAL_CHANNEL", argv[1]);
+            return 0;
+        }
+
+        new_channel = 1;
+        chan_name = argv[1];
+    }
+
+    if(argc >= (new_channel+2))
+    {
+    if(!IsHelping(user))
+    {
+        reply("CSMSG_PROXY_FORBIDDEN");
+        return 0;
+    }
+
+    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);
 
         /* Check if they are over the limit.. */
         if((chanserv_get_owned_count(handle) >= chanserv_conf.max_owned) && !force)
@@ -1999,11 +2599,24 @@ static CHANSERV_FUNC(cmd_register)
     }
     */
 
+    if (chanserv_conf.valid_channel_regex_set) {
+        int err = regexec(&chanserv_conf.valid_channel_regex, chan_name, 0, 0, 0);
+        if (err) {
+            char buff[256];
+            buff[regerror(err, &chanserv_conf.valid_channel_regex, buff, sizeof(buff))] = 0;
+            log_module(CS_LOG, LOG_INFO, "regexec error: %s (%d)", buff, err);
+        }
+        if(err == REG_NOMATCH) {
+            reply("CSMSG_ILLEGAL_CHANNEL", chan_name);
+            return 0;
+        }
+    }
+
     if(new_channel)
-        channel = AddChannel(argv[1], now, NULL, NULL, NULL);
+        channel = AddChannel(chan_name, now, NULL, NULL, NULL);
 
     cData = register_channel(channel, user->handle_info->handle);
-    scan_user_presence(add_channel_user(cData, handle, UL_OWNER, 0, NULL), NULL);
+    scan_user_presence(add_channel_user(cData, handle, UL_OWNER, 0, NULL, 0), NULL);
     cData->modes = chanserv_conf.default_modes;
     if(off_channel > 0)
       cData->modes.modes_set |= MODE_REGISTERED;
@@ -2023,14 +2636,15 @@ static CHANSERV_FUNC(cmd_register)
 
     /* Initialize the channel's max user record. */
     cData->max = channel->members.used;
+    cData->maxsetinfo = chanserv_conf.max_userinfo_length;
 
     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);
+    global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_REGISTERED_TO", channel->name, 
+                        handle->handle, user->handle_info->handle);
     return 1;
 }
 
@@ -2087,36 +2701,64 @@ 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", channel->name, 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;
     struct chanNode *target;
     struct modeNode *mn;
     struct userData *uData;
-    char reason[MAXLEN];
     struct do_not_register *dnr;
+    int chanserv_join = 0, spamserv_join;
 
     REQUIRE_PARAMS(2);
 
     if(IsProtected(channel->channel_info))
     {
-       reply("CSMSG_MOVE_NODELETE", channel->name);
-       return 0;
+    reply("CSMSG_MOVE_NODELETE", channel->name);
+    return 0;
     }
 
     if(!IsChannelName(argv[1]))
@@ -2151,12 +2793,12 @@ static CHANSERV_FUNC(cmd_move)
     {
         target = AddChannel(argv[1], now, NULL, 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))
@@ -2165,12 +2807,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)
     {
@@ -2190,18 +2827,24 @@ static CHANSERV_FUNC(cmd_move)
     target->channel_info->channel = target;
     channel->channel_info = NULL;
 
-    reply("CSMSG_MOVE_SUCCESS", target->name);
+    spamserv_join = spamserv_cs_move_merge(user, channel, target, 1);
+
+    if (chanserv_join)
+        ss_cs_join_channel(target, spamserv_join);
 
-    sprintf(reason, "%s moved to %s by %s.", channel->name, target->name, user->handle_info->handle);
     if(!IsSuspended(target->channel_info))
     {
         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);
+    global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_CHANNEL_MOVED",
+                        channel->name, target->name, user->handle_info->handle);
+
+    reply("CSMSG_MOVE_SUCCESS", target->name);
     return 1;
 }
 
@@ -2216,7 +2859,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
@@ -2224,50 +2867,50 @@ 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. */
-        /* 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;
+        /* 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;
 
-       /* 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++;
     }
 
     /* Possible to assert (source->users == NULL) here. */
@@ -2287,72 +2930,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;
-
-       /* 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++;
-       }
+        /* 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;
+
+        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. */
@@ -2362,8 +3005,20 @@ merge_bans(struct chanData *source, struct chanData *target)
 static void
 merge_data(struct chanData *source, struct chanData *target)
 {
+    /* Use more recent visited and owner-transfer time; use older
+     * registered time.  Bitwise or may_opchan.  Use higher max.
+     * 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)
+        target->max = source->max;
 }
 
 static void
@@ -2379,6 +3034,7 @@ static CHANSERV_FUNC(cmd_merge)
     struct userData *target_user;
     struct chanNode *target;
     char reason[MAXLEN];
+    int nodelete = 0;
 
     REQUIRE_PARAMS(2);
 
@@ -2390,6 +3046,11 @@ static CHANSERV_FUNC(cmd_merge)
         return 0;
     }
 
+    if (argc > 2) {
+        if (!irccasecmp("nodelete", argv[2]))
+            nodelete = 1;
+    }
+
     if(!target->channel_info)
     {
         reply("CSMSG_NOT_REGISTERED", target->name);
@@ -2423,8 +3084,10 @@ 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);
+    if (!nodelete)
+        unregister_channel(channel->channel_info, reason);
     reply("CSMSG_MERGE_SUCCESS", target->name);
     return 1;
 }
@@ -2437,11 +3100,20 @@ static CHANSERV_FUNC(cmd_opchan)
         reply("CSMSG_ALREADY_OPCHANNED", channel->name);
         return 0;
     }
+    if(!IsInChannel(channel,chanserv)) {
+       reply("CSMSG_NOT_IN_CHANNEL", channel->name);
+       return 0;
+    }
     channel->channel_info->may_opchan = 0;
     mod_chanmode_init(&change);
     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;
@@ -2450,35 +3122,42 @@ static CHANSERV_FUNC(cmd_opchan)
 static CHANSERV_FUNC(cmd_adduser)
 {
     struct userData *actee;
-    struct userData *actor;
-    struct handle_info *handle;
-    unsigned short access;
+    struct userData *actor, *real_actor;
+    struct handle_info *handle = NULL;
+    //struct adduserPending *tmp;
+    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);
-    if(actor->access <= access)
+    real_actor = GetTrueChannelAccess(channel->channel_info, user->handle_info);
+
+    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_level)
+        override = CMD_LOG_OVERRIDE;
+
     if(!(handle = modcmd_get_handle_info(user, argv[1])))
     {
-        // 'kevin must first authenticate with AuthServ.' is sent to user
+        /* 'kevin must first authenticate with AuthServ'. is sent to user */
         struct userNode *unode;
         unode = GetUserH(argv[1]); /* find user struct by nick */
         if(unode)
@@ -2488,8 +3167,8 @@ static CHANSERV_FUNC(cmd_adduser)
             }
             else {
                 if(IsInChannel(channel, unode)) {
-                   reply("CSMSG_ADDUSER_PENDING");
-                   add_adduser_pending(channel, unode, access);
+                   reply("CSMSG_ADDUSER_PENDING", unode->nick);
+                   add_adduser_pending(channel, unode, access_level);
                    send_message_type(1,unode, chanserv, "CSMSG_ADDUSER_PENDING_TARGET", user->nick, channel->name);
                 }
                 /* this results in user must auth AND not in chan errors. too confusing..
@@ -2504,128 +3183,177 @@ static CHANSERV_FUNC(cmd_adduser)
 
     if((actee = GetTrueChannelAccess(channel->channel_info, handle)))
     {
-       reply("CSMSG_USER_EXISTS", handle->handle, channel->name, user_level_name_from_level(actee->access));
-       return 0;
+        reply("CSMSG_USER_EXISTS", handle->handle, channel->name, user_level_name_from_level(actee->access));
+        return 0;
     }
 
-    actee = add_channel_user(channel->channel_info, handle, access, 0, NULL);
+    time_t accessexpiry = 0;
+    unsigned int duration = 0;
+    if (argc > 3) {
+        if ((duration = ParseInterval(argv[3])))
+            accessexpiry = now + duration;
+    }
+
+    actee = add_channel_user(channel->channel_info, handle, access_level, 0, NULL, accessexpiry);
     scan_user_presence(actee, NULL);
-    reply("CSMSG_ADDED_USER", handle->handle, channel->name, user_level_name_from_level(access), access);
-    return 1;
+
+    if (duration > 0)
+        timeq_add(accessexpiry, chanserv_expire_tempuser, actee);
+
+    reply("CSMSG_ADDED_USER", handle->handle, channel->name, user_level_name_from_level(access_level), access_level);
+    return 1 | override;
 }
 
 static CHANSERV_FUNC(cmd_clvl)
 {
     struct handle_info *handle;
     struct userData *victim;
-    struct userData *actor;
-    unsigned short new_access;
+    struct userData *actor, *real_actor;
+    unsigned short new_access, override = 0;
     int privileged = IsHelping(user) && ((user->handle_info->opserv_level >= chanserv_conf.nodelete_level) || !IsProtected(channel->channel_info));
 
     REQUIRE_PARAMS(3);
 
     actor = GetChannelUser(channel->channel_info, user->handle_info);
+    real_actor = GetChannelAccess(channel->channel_info, user->handle_info);
 
     if(!(handle = modcmd_get_handle_info(user, argv[1])))
         return 0;
 
     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;
+    }
+
+    time_t clvlexpiry = 0;
+    unsigned int duration = 0;
+    if (argc > 3) {
+        if ((duration = ParseInterval(argv[3])))
+            clvlexpiry = now + duration;
+    }
+
+    if (duration > 0) {
+        if (victim->accessexpiry > 0) {
+            reply("CSMSG_NO_BUMP_EXPIRY");
+            return 0;
+        }
+
+        victim->clvlexpiry = clvlexpiry;
+        victim->lastaccess = victim->access;
+        timeq_add(clvlexpiry, chanserv_expire_tempclvl, victim);
     }
 
+    /* Trying to clvl a equal/higher user */
+    if(!real_actor || (real_actor->access <= victim->access && handle != user->handle_info))
+        override = CMD_LOG_OVERRIDE;
+    /* Trying to clvl someone to equal/higher access */
+    if(!real_actor || new_access >= real_actor->access)
+        override = CMD_LOG_OVERRIDE;
+    /* Helpers clvling themselves get caught by the "clvl someone to equal/higher access" check.
+     * If they lower their own access it's not a big problem. 
+     */
     victim->access = new_access;
     reply("CSMSG_CHANGED_ACCESS", handle->handle, user_level_name_from_level(new_access), new_access, channel->name);
-    return 1;
+    return 1 | override;
 }
 
 static CHANSERV_FUNC(cmd_deluser)
 {
     struct handle_info *handle;
     struct userData *victim;
-    struct userData *actor;
-    unsigned short access;
+    struct userData *actor, *real_actor;
+    unsigned short access_level, override = 0;
+    unsigned short access_level_user = 0;
     char *chan_name;
 
     REQUIRE_PARAMS(2);
 
     actor = GetChannelUser(channel->channel_info, user->handle_info);
-
+    real_actor = GetChannelAccess(channel->channel_info, user->handle_info);
     if(!(handle = modcmd_get_handle_info(user, argv[argc-1])))
         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(argc > 2)
     {
-        access = user_level_from_name(argv[1], UL_OWNER);
-        if(!access)
+        access_level = user_level_from_name(argv[1], UL_OWNER);
+        char *useraccess = user_level_name_from_level(victim->access);
+        access_level_user = user_level_from_name(useraccess, UL_OWNER);
+        if(!access_level)
         {
             reply("CSMSG_INVALID_ACCESS", argv[1]);
             return 0;
         }
-       if(access != victim->access)
-       {
-           reply("CSMSG_INCORRECT_ACCESS", handle->handle, user_level_name_from_level(victim->access), argv[1]);
-           return 0;
-       }
+        if(access_level != access_level_user)
+        {
+            reply("CSMSG_INCORRECT_ACCESS", handle->handle, useraccess, 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 could've used deleteme so we don't log it as an override */
+    if(!real_actor || (real_actor->access <= victim->access && real_actor != victim))
+        override = CMD_LOG_OVERRIDE;
+
     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;
+    return 1 | override;
 }
 
 static int
 cmd_mdel_user(struct userNode *user, struct chanNode *channel, unsigned short min_access, unsigned short max_access, char *mask, struct svccmd *cmd)
 {
-    struct userData *actor, *uData, *next;
+    struct userData *actor, *real_actor, *uData, *next;
+    unsigned int override = 0;
 
     actor = GetChannelUser(channel->channel_info, user->handle_info);
-
+    real_actor = GetChannelAccess(channel->channel_info, user->handle_info);
     if(min_access > max_access)
     {
         reply("CSMSG_BAD_RANGE", min_access, max_access);
@@ -2634,22 +3362,27 @@ cmd_mdel_user(struct userNode *user, struct chanNode *channel, unsigned short mi
 
     if((actor->access <= max_access) && !IsHelping(user))
     {
-       reply("CSMSG_NO_ACCESS");
-       return 0;
+    reply("CSMSG_NO_ACCESS");
+    return 0;
     }
 
+    if(!real_actor || real_actor->access <= max_access)
+        override = CMD_LOG_OVERRIDE;
+
     for(uData = channel->channel_info->users; uData; uData = next)
     {
-       next = uData->next;
+        next = uData->next;
 
-       if((uData->access >= min_access)
-           && (uData->access <= max_access)
-           && match_ircglob(uData->handle->handle, mask))
-           del_channel_user(uData, 1);
+        if((uData->access >= min_access) 
+               && (uData->access <= max_access)
+               && match_ircglob(uData->handle->handle, mask))
+        {
+            del_channel_user(uData, 1);
+        }
     }
 
     reply("CSMSG_DELETED_USERS", mask, min_access, max_access, channel->name);
-    return 1;
+    return 1 | override;
 }
 
 static CHANSERV_FUNC(cmd_mdelowner)
@@ -2659,33 +3392,103 @@ static CHANSERV_FUNC(cmd_mdelowner)
 
 static CHANSERV_FUNC(cmd_mdelcoowner)
 {
-    return cmd_mdel_user(user, channel, UL_COOWNER, UL_COOWNER, argv[1], cmd);
+    return cmd_mdel_user(user, channel, UL_COOWNER, UL_OWNER-1, argv[1], cmd);
 }
 
 static CHANSERV_FUNC(cmd_mdelmanager)
 {
-    return cmd_mdel_user(user, channel, UL_MANAGER, UL_MANAGER, argv[1], cmd);
+    return cmd_mdel_user(user, channel, UL_MANAGER, UL_COOWNER-1, argv[1], cmd);
 }
 
 static CHANSERV_FUNC(cmd_mdelop)
 {
-    return cmd_mdel_user(user, channel, UL_OP, UL_OP, argv[1], cmd);
+    return cmd_mdel_user(user, channel, UL_OP, UL_MANAGER-1, argv[1], cmd);
+}
+
+static CHANSERV_FUNC(cmd_mdelhalfop)
+{
+    return cmd_mdel_user(user, channel, UL_HALFOP, UL_OP-1, argv[1], cmd);
 }
 
 static CHANSERV_FUNC(cmd_mdelpeon)
 {
-    return cmd_mdel_user(user, channel, UL_PEON, UL_PEON, argv[1], cmd);
+    return cmd_mdel_user(user, channel, UL_PEON, UL_HALFOP-1, argv[1], cmd);
 }
 
-static CHANSERV_FUNC(cmd_mdelhalfop)
+static CHANSERV_FUNC(cmd_mdelpal)
 {
-    return cmd_mdel_user(user, channel, UL_HALFOP, UL_HALFOP, argv[1], cmd);
+    return cmd_mdel_user(user, channel, UL_PEON, UL_HALFOP-1, argv[1], cmd);
 }
 
+static CHANSERV_FUNC(cmd_levels)
+{
+    struct helpfile_table tbl;
+    int ii = 0;
+
+    tbl.length = 6 + 1; // 6 levels
+    tbl.width = 4;
+    tbl.flags = 0;
+    tbl.contents = calloc(tbl.length,sizeof(tbl.contents[0]));
+    tbl.contents[0] = calloc(tbl.width,sizeof(tbl.contents[0][0]));
+    tbl.contents[0][0] = "Level";
+    tbl.contents[0][1] = "From";
+    tbl.contents[0][2] = "-";
+    tbl.contents[0][3] = "To";
+
+    tbl.contents[++ii] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
+    tbl.contents[ii][0] = strdup(user_level_name_from_level(UL_OWNER));
+    tbl.contents[ii][1] = msnprintf(4, "%d", UL_OWNER);
+    tbl.contents[ii][2] = msnprintf(2, " ");
+    tbl.contents[ii][3] = msnprintf(1, "");
+
+    tbl.contents[++ii] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
+    tbl.contents[ii][0] = strdup(user_level_name_from_level(UL_COOWNER));
+    tbl.contents[ii][1] = msnprintf(4, "%d", UL_COOWNER);
+    tbl.contents[ii][2] = msnprintf(2, "-");
+    tbl.contents[ii][3] = msnprintf(4, "%d", UL_OWNER-1);
+
+    tbl.contents[++ii] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
+    tbl.contents[ii][0] = strdup(user_level_name_from_level(UL_MANAGER));
+    tbl.contents[ii][1] = msnprintf(4, "%d", UL_MANAGER);
+    tbl.contents[ii][2] = msnprintf(2, "-");
+    tbl.contents[ii][3] = msnprintf(4, "%d", UL_COOWNER-1);
+
+    tbl.contents[++ii] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
+    tbl.contents[ii][0] = strdup(user_level_name_from_level(UL_OP));
+    tbl.contents[ii][1] = msnprintf(4, "%d", UL_OP);
+    tbl.contents[ii][2] = msnprintf(2, "-");
+    tbl.contents[ii][3] = msnprintf(4, "%d", UL_MANAGER-1);
+
+    tbl.contents[++ii] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
+    tbl.contents[ii][0] = strdup(user_level_name_from_level(UL_HALFOP));
+    tbl.contents[ii][1] = msnprintf(4, "%d", UL_HALFOP);
+    tbl.contents[ii][2] = msnprintf(2, "-");
+    tbl.contents[ii][3] = msnprintf(4, "%d", UL_OP-1);
+
+    tbl.contents[++ii] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
+    tbl.contents[ii][0] = strdup(user_level_name_from_level(UL_PEON));
+    tbl.contents[ii][1] = msnprintf(4, "%d", UL_PEON);
+    tbl.contents[ii][2] = msnprintf(2, "-");
+    tbl.contents[ii][3] = msnprintf(4, "%d", UL_HALFOP-1);
+
+    table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
+    return 0;
+
+/*
+    reply("CSMSG_LEVELS_HEADER");
+    reply("CSMSG_LEVELS",  user_level_name_from_level(UL_OWNER), UL_OWNER, UL_OWNER);
+    reply("CSMSG_LEVELS",  user_level_name_from_level(UL_COOWNER), UL_COOWNER, UL_OWNER-1);
+    reply("CSMSG_LEVELS",  user_level_name_from_level(UL_MANAGER), UL_MANAGER, UL_COOWNER-1);
+    reply("CSMSG_LEVELS",  user_level_name_from_level(UL_OP), UL_OP, UL_MANAGER-1);
+    reply("CSMSG_LEVELS",  user_level_name_from_level(UL_HALFOP), UL_HALFOP, UL_OP-1);
+    reply("CSMSG_LEVELS",  user_level_name_from_level(UL_PEON), UL_PEON, UL_HALFOP-1);
+    reply("CSMSG_BAR");
+ */
+}
 
 /* trim_lamers.. */
 static int
-cmd_trim_bans(struct userNode *user, struct chanNode *channel, unsigned long duration)
+cmd_trim_bans(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, unsigned long duration)
 {
     struct banData *bData, *next;
     char interval[INTERVALLEN];
@@ -2696,7 +3499,7 @@ cmd_trim_bans(struct userNode *user, struct chanNode *channel, unsigned long dur
     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;
@@ -2706,46 +3509,48 @@ cmd_trim_bans(struct userNode *user, struct chanNode *channel, unsigned long dur
     }
 
     intervalString(interval, duration, user->handle_info);
-    send_message(user, chanserv, "CSMSG_TRIMMED_LAMERS", count, channel->name, interval);
+    reply("CSMSG_TRIMMED_LAMERS", count, channel->name, interval);
     return 1;
 }
 
 static int
-cmd_trim_users(struct userNode *user, struct chanNode *channel, unsigned short min_access, unsigned short max_access, unsigned long duration)
+cmd_trim_users(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, unsigned short min_access, unsigned short max_access, unsigned long duration, int vacation)
 {
     struct userData *actor, *uData, *next;
     char interval[INTERVALLEN];
     unsigned int count;
     time_t limit;
 
-    actor = GetChannelUser(channel->channel_info, user->handle_info);
+    actor = GetChannelAccess(channel->channel_info, user->handle_info);
     if(min_access > max_access)
     {
-        send_message(user, chanserv, "CSMSG_BAD_RANGE", min_access, max_access);
+        reply("CSMSG_BAD_RANGE", min_access, max_access);
         return 0;
     }
 
-    if((actor->access <= max_access) && !IsHelping(user))
+    if(!actor || actor->access <= max_access)
     {
-       send_message(user, chanserv, "CSMSG_NO_ACCESS");
-       return 0;
+        reply("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) || uData->present)
-           continue;
+        if((uData->seen > limit)
+           || uData->present
+           || (HANDLE_FLAGGED(uData->handle, FROZEN) && !vacation))
+        continue;
 
-       if(((uData->access >= min_access) && (uData->access <= max_access))
-           || (!max_access && (uData->access < actor->access)))
-       {
-           del_channel_user(uData, 1);
-           count++;
-       }
+        if(((uData->access >= min_access) && (uData->access <= max_access))
+               || (!max_access && (uData->access < actor->access)))
+        {
+            del_channel_user(uData, 1);
+            count++;
+        }
     }
 
     if(!max_access)
@@ -2753,7 +3558,7 @@ cmd_trim_users(struct userNode *user, struct chanNode *channel, unsigned short m
         min_access = 1;
         max_access = (actor->access > UL_OWNER) ? UL_OWNER : (actor->access - 1);
     }
-    send_message(user, chanserv, "CSMSG_TRIMMED_USERS", count, min_access, max_access, channel->name, intervalString(interval, duration, user->handle_info));
+    reply("CSMSG_TRIMMED_USERS", count, min_access, max_access, channel->name, intervalString(interval, duration, user->handle_info));
     return 1;
 }
 
@@ -2761,35 +3566,37 @@ static CHANSERV_FUNC(cmd_trim)
 {
     unsigned long duration;
     unsigned short min_level, max_level;
+    int vacation;
 
     REQUIRE_PARAMS(3);
 
+    vacation = argc > 3 && !strcmp(argv[3], "vacation");
     duration = ParseInterval(argv[2]);
     if(duration < 60)
     {
-       reply("CSMSG_CANNOT_TRIM");
-       return 0;
+        reply("CSMSG_CANNOT_TRIM");
+        return 0;
     }
 
     if(!irccasecmp(argv[1], "lamers"))
     {
-       cmd_trim_bans(user, channel, duration); /* trim_lamers.. */
-       return 1;
+        cmd_trim_bans(cmd, user, channel, duration); /* trim_lamers.. */
+        return 1;
     }
     else if(!irccasecmp(argv[1], "users"))
     {
-       cmd_trim_users(user, channel, 0, 0, duration);
-       return 1;
+        cmd_trim_users(cmd, 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);
-       return 1;
+        cmd_trim_users(cmd, 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);
-       return 1;
+        cmd_trim_users(cmd, user, channel, min_level, min_level, duration, vacation);
+        return 1;
     }
     else
     {
@@ -2813,7 +3620,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);
@@ -2823,17 +3630,17 @@ static CHANSERV_FUNC(cmd_up)
             reply("CSMSG_GODMODE_UP", argv[0]);
         return 0;
     }
-    else if(uData->access >= UL_OP /*channel->channel_info->lvlOpts[lvlGiveOps]*/)
+    else if(uData->access >= UL_OP)
     {
         change.args[0].mode = MODE_CHANOP;
         errmsg = "CSMSG_ALREADY_OPPED";
     }
-    else if(uData->access >= UL_HALFOP /*channel->channel_info->lvlOpts[lvlGiveHalfOps]*/)
+    else if(uData->access >= UL_HALFOP )
     {
         change.args[0].mode = MODE_HALFOP;
         errmsg = "CSMSG_ALREADY_HALFOPPED";
     }
-    else if(uData->access >= UL_PEON && (channel->channel_info->chOpts[chVoice] == 'p' || channel->channel_info->chOpts[chVoice] == 'a'))
+    else if(uData->access >= UL_PEON && (channel->channel_info->chOpts[chAutomode] != 'm' ))
     {
         change.args[0].mode = MODE_VOICE;
         errmsg = "CSMSG_ALREADY_VOICED";
@@ -2866,14 +3673,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;
@@ -2887,12 +3694,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)
-           || IsUserSuspended(cList)
-           || !GetUserMode(cList->channel->channel, user))
-           continue;
+        if(IsSuspended(cList->channel)
+               || IsUserSuspended(cList)
+               || !GetUserMode(cList->channel->channel, user))
+            continue;
 
-       mcmd(user, cList->channel->channel, 0, NULL, cmd);
+        mcmd(user, cList->channel->channel, 0, NULL, cmd);
     }
 
     return 1;
@@ -2908,7 +3715,7 @@ static CHANSERV_FUNC(cmd_downall)
     return cmd_all(CSFUNC_ARGS, cmd_down);
 }
 
-typedef int validate_func_t(struct userNode *user, struct chanNode *channel, struct userNode *victim);
+typedef int validate_func_t(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, struct userNode *victim);
 typedef void process_func_t(unsigned int num, struct userNode **newops, struct chanNode *channel, struct userNode *who, int announce);
 
 static int
@@ -2922,20 +3729,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;
+        if(validate && !validate(cmd, user, channel, victim))
+        continue;
         valid++;
     }
 
     change->argc = valid;
     if(valid < (argc-1))
-       reply("CSMSG_PROCESS_FAILED");
+    reply("CSMSG_PROCESS_FAILED");
     if(valid)
     {
         modcmd_chanmode_announce(change);
@@ -2976,9 +3783,10 @@ static CHANSERV_FUNC(cmd_devoice)
 }
 
 static int
-bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban, int *victimCount, struct modeNode **victims)
+bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban, unsigned int *victimCount, struct modeNode **victims)
 {
     unsigned int ii;
+    int b = 0;
 
     if(victimCount)
         *victimCount = 0;
@@ -2989,10 +3797,13 @@ bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban
         if(IsService(mn->user))
             continue;
 
-        if(!user_matches_glob(mn->user, ban, 1))
+        b = user_matches_glob(mn->user, ban, MATCH_USENICK | MATCH_VISIBLE, 0);
+        if (b == -1)
+            return -1;
+        else if (b == 0)
             continue;
 
-        if(protect_user(mn->user, user, channel->channel_info))
+        if(protect_user(mn->user, user, channel->channel_info, false))
             return 1;
 
         if(victims)
@@ -3001,12 +3812,20 @@ bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban
     return 0;
 }
 
+int is_extban(char *b) {
+    if(*b == '~') {
+        return 1;
+    }
+    return 0;
+}
+
 static int
 eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, char *argv[], struct svccmd *cmd, int action)
 {
     struct userNode *victim;
     struct modeNode **victims;
     unsigned int offset, n, victimCount, duration = 0;
+    int b = 0;
     char *reason = "Bye.", *ban, *name;
     char interval[INTERVALLEN];
 
@@ -3014,7 +3833,7 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
     REQUIRE_PARAMS(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
@@ -3033,174 +3852,232 @@ 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))
+        {
+            if(cmd)
+                reply("MSG_SERVICE_IMMUNE", victim->nick);
+            return 0;
+        }
+
+        if((action == ACTION_KICK) && !victimCount)
+        {
+            if(cmd)
+                reply("MSG_CHANNEL_USER_ABSENT", victim->nick, channel->name);
+            return 0;
+        }
+
+        if(protect_user(victim, user, channel->channel_info, false))
+        {
+            // This translates to  send_message(user, cmd->parent->bot, ...)
+            // if user is x3 (ctcp action) cmd is null and segfault.
+            if(cmd)
+                reply("CSMSG_USER_PROTECTED", victim->nick);
+            return 0;
+        }
+
+        ban = generate_hostmask(victim, GENMASK_STRICT_HOST|GENMASK_ANY_IDENT);
+        name = victim->nick;
+    }
+    else if(!is_ircmask(argv[1]) && (*argv[1] == '*'))
+    {
+        struct handle_info *hi;
+        char banmask[NICKLEN + USERLEN + HOSTLEN + 3];
+        const char *accountname = argv[1] + 1;
+
+        if(!(hi = get_handle_info(accountname)))
+        {
+            reply("MSG_HANDLE_UNKNOWN", accountname);
+            return 0;
+        }
 
-       if(IsService(victim))
-       {
-           reply("MSG_SERVICE_IMMUNE", victim->nick);
-           return 0;
-       }
+        snprintf(banmask, sizeof(banmask), "*!*@%s.*", hi->handle);
+        victims = alloca(sizeof(victims[0]) * channel->members.used);
 
-        if((action == ACTION_KICK) && !victimCount)
+        b = bad_channel_ban(channel, user, banmask, &victimCount, victims);
+        if(b == 1)
         {
-            reply("MSG_CHANNEL_USER_ABSENT", victim->nick, channel->name);
+            reply("CSMSG_MASK_PROTECTED", banmask);
+            return 0;
+        }
+        else if(b == -1)
+        {
+            reply("CSMSG_BAD_BAN", banmask);
             return 0;
         }
 
-       if(protect_user(victim, user, channel->channel_info))
-       {
-           reply("CSMSG_USER_PROTECTED", victim->nick);
-           return 0;
-       }
+        if((action == ACTION_KICK) && (victimCount == 0))
+        {
+            reply("CSMSG_NO_MATCHING_USERS", channel->name, banmask);
+            return 0;
+        }
 
-       ban = generate_hostmask(victim, GENMASK_STRICT_HOST|GENMASK_ANY_IDENT);
-       name = victim->nick;
+        name = ban = strdup(banmask);
     }
     else
     {
-       if(!is_ircmask(argv[1]))
-       {
-           reply("MSG_NICK_UNKNOWN", argv[1]);
-           return 0;
-       }
+        if(!is_ircmask(argv[1]))
+        {
+            if(cmd)
+               reply("MSG_NICK_UNKNOWN", argv[1]);
+            return 0;
+        }
 
-       victims = alloca(sizeof(victims[0]) * channel->members.used);
+        victims = alloca(sizeof(victims[0]) * channel->members.used);
 
-        if(bad_channel_ban(channel, user, argv[1], &victimCount, victims))
+        b = bad_channel_ban(channel, user, argv[1], &victimCount, victims);
+        if(cmd && (b == 1)) {
+            reply("CSMSG_MASK_PROTECTED", argv[1]);
+            return 0;
+        }
+        else if(cmd && (b == -1)) {
+            reply("CSMSG_BAD_BAN", argv[1]);
+            return 0;
+        }
+/* If i want to ban *.nl and theres 5 of them, what is it to the bot?!? 
+//      if((victimCount > 4) && ((victimCount * 3) > channel->members.used) && !IsOper(user)) 
+                                                                           And, ^^^^^^^^^ BAH! 
+   We use x2 style over-mask detection instead because it doesnt stop channel owners from doing 
+   reasonable bans, but does stop *@*, *@*a* *@*b* etc type masks.  Yes, you can defeat it with 
+   some creativity, but its not x3's job to be the ban censor anyway.  */
+        if(is_overmask(argv[1]))
         {
-           reply("CSMSG_MASK_PROTECTED", argv[1]);
-           return 0;
-       }
-        /* We dont actually want a victem count if were banning a mask manually, IMO -Rubin*/
-        if(cmd)
-            victimCount = 0;  /* Dont deop etc ppl who match this */
-
-#ifdef entropy_lameness
-        if((victimCount > 4) && ((victimCount * 3) > channel->members.used) && !IsOper(user))
+            if(cmd)
+                reply("CSMSG_LAME_MASK", argv[1]);
+            return 0;
+        }
+        //TODO: We have no support to do protection etc etc so for now we dont let you use x3 to set extended bans.
+        if(is_extban(argv[1]))
         {
-            reply("CSMSG_LAME_MASK", argv[1]);
+            if(cmd)
+                reply("CSMSG_NO_EXTBANS", argv[1]);
             return 0;
         }
-#endif
 
         if((action == ACTION_KICK) && (victimCount == 0))
         {
-            reply("CSMSG_NO_MATCHING_USERS", channel->name, argv[1]);
+            if(cmd)
+                reply("CSMSG_NO_MATCHING_USERS", channel->name, argv[1]);
             return 0;
         }
 
-       name = ban = strdup(argv[1]);
+        name = ban = strdup(argv[1]);
     }
 
     /* Truncate the ban in place if necessary; we must ensure
        that 'ban' is a valid ban mask before sanitizing it. */
-    sanitize_ircmask(ban);
+    if (*ban != '~')
+        sanitize_ircmask(ban);
 
     if(action & ACTION_ADD_LAMER)
     {
-       struct banData *bData, *next;
-
-       if(channel->channel_info->banCount >= chanserv_conf.max_chan_bans) /* ..lamers.. */
-       {
-           reply("CSMSG_MAXIMUM_LAMERS", chanserv_conf.max_chan_bans); /* ..lamers.. */
-           free(ban);
-           return 0;
-       }
-
-       if(action & ACTION_ADD_TIMED_LAMER)
-       {
-           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;
-           }
-       }
+        struct banData *bData, *next;
+
+        if(channel->channel_info->banCount >= chanserv_conf.max_chan_bans) /* ..lamers.. */
+        {
+            if(cmd)
+                reply("CSMSG_MAXIMUM_LAMERS", chanserv_conf.max_chan_bans); /* ..lamers.. */
+            free(ban);
+            return 0;
+        }
+
+        if(action & ACTION_ADD_TIMED_LAMER)
+        {
+            duration = ParseInterval(argv[2]);
+
+            if(duration < 15)
+            {
+                if(cmd)
+                    reply("CSMSG_DURATION_TOO_LOW");
+                free(ban);
+                return 0;
+            }
+            else if(duration > (86400 * 365 * 2))
+            {
+                if(cmd)
+                    reply("CSMSG_DURATION_TOO_HIGH");
+                free(ban);
+                return 0;
+            }
+        }
 
         /* lamers... */
-       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)
+        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 && ((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(!cmd)
                         {
-                            /* automated kickban */
+                                /* automated kickban, dont reply */
                         }
-                       else if(duration)
-                           reply("CSMSG_LAMER_EXTENDED", ban, intervalString(interval, duration, user->handle_info));
-                       else
-                           reply("CSMSG_LAMER_ADDED", name, channel->name);
-
-                       goto post_add_ban;
-                   }
-               }
+                        else if(duration)
+                            reply("CSMSG_LAMER_EXTENDED", ban, intervalString(interval, duration, user->handle_info));
+                        else
+                            reply("CSMSG_LAMER_ADDED", name, channel->name);
+
+                        goto post_add_ban;
+                    }
+                }
                 if(cmd)
                     reply("CSMSG_REDUNDANT_LAMER", name, channel->name);
 
-               free(ban);
-               return 0;
-           }
+                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);
-           }
-       }
+            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);
     }
@@ -3227,19 +4104,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);
@@ -3261,7 +4138,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);
@@ -3269,13 +4146,28 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
         }
     }
 
-    if(action & ACTION_KICK)
+    if(action & ACTION_ADD_LAMER)
+    {
+        char kick_reason[MAXLEN];
+        sprintf(kick_reason, "(%s) %s", user->nick, reason);
+
+        for(n = 0; n < victimCount; n++) 
+        {
+            if(!protect_user(victims[n]->user, user, channel->channel_info, true)) 
+            {
+                KickChannelUser(victims[n]->user, channel, chanserv, kick_reason);
+            }
+        }
+    }
+    else 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)
@@ -3284,17 +4176,17 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
     }
     else if(action & ACTION_ADD_LAMER)
     {
-       if(duration)
-           reply("CSMSG_TIMED_LAMER_ADDED", name, channel->name, intervalString(interval, duration, user->handle_info));
-       else
-           reply("CSMSG_LAMER_ADDED", name, channel->name);
+        if(duration)
+            reply("CSMSG_TIMED_LAMER_ADDED", name, channel->name, intervalString(interval, duration, user->handle_info));
+        else
+            reply("CSMSG_LAMER_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;
@@ -3337,7 +4229,8 @@ find_matching_bans(struct banList *bans, struct userNode *actee, const char *mas
     {
         for(ii = count = 0; ii < bans->used; ++ii)
         {
-            match[ii] = user_matches_glob(actee, bans->list[ii]->ban, 1);
+            match[ii] = user_matches_glob(actee, bans->list[ii]->ban,
+                                          MATCH_USENICK | MATCH_VISIBLE, 0);
             if(match[ii])
                 count++;
         }
@@ -3365,6 +4258,70 @@ find_matching_bans(struct banList *bans, struct userNode *actee, const char *mas
     return change;
 }
 
+void expire_bans(UNUSED_ARG(void* data)) /* Real bans, not lamers */
+{
+    unsigned int jj, ii, count;
+    struct banNode *bn;
+    struct chanData *channel;
+    time_t bantimeout;
+    struct mod_chanmode *change;
+
+    log_module(CS_LOG, LOG_DEBUG, "Checking for expired bans");
+    /* Walk through every channel */
+    for(channel = channelList; channel; channel = channel->next) {
+        switch(channel->chOpts[chBanTimeout])
+        {
+            default: case '0': continue; /* Dont remove bans in this chan */
+            case '1': bantimeout = now - (10 * 60);          break; /* 10 minutes */
+            case '2': bantimeout = now - (2 * 60 * 60);      break; /* 2 hours */
+            case '3': bantimeout = now - (4 * 60 * 60);      break; /* 4 hours */
+            case '4': bantimeout = now - (24 * 60 * 60);     break; /* 24 hours */
+            case '5': bantimeout = now - (7 * 24 * 60 * 60); break; /* 1 week */
+        }
+        count = 0;
+        /* First find out how many bans were going to unset */
+        for (jj=0; jj < channel->channel->banlist.used; ++jj) {
+            //TODO: for now, were just not removing extended bans, but ultimately some types we should, some shouldn't...see below
+            if(channel->channel->banlist.list[jj]->set < bantimeout && !is_extban(channel->channel->banlist.list[jj]->ban))
+                count++;
+        }
+        if(count > 0) {
+            /* At least one ban, so setup a removal */
+            change = mod_chanmode_alloc(count);
+            ii = 0;
+            /* Walk over every ban in this channel.. */
+            for (jj=0; jj < channel->channel->banlist.used; ++jj) {
+                bn = channel->channel->banlist.list[jj];
+                //TODO: for now, were just not removing extended bans, but ultimately some types we should, some shouldn't...see above
+                if (bn->set < bantimeout && !is_extban(bn->ban)) {
+                    log_module(CS_LOG, LOG_DEBUG, "Removing ban %s from %s", bn->ban, channel->channel->name);
+
+                    /* Add this ban to the mode change */
+                    change->args[ii].mode = MODE_REMOVE | MODE_BAN;
+                    change->args[ii].u.hostmask = strdup(bn->ban);
+                    ii++;
+                    /* Pull this ban out of the list */
+                    banList_remove(&(channel->channel->banlist), bn);
+                    jj--;
+                    free(bn);
+                }
+            }
+            /* Send the modes to IRC */
+            mod_chanmode_announce(chanserv, channel->channel, change);
+
+            /* free memory from strdup above */
+            for(ii = 0; ii < count; ++ii)
+                free((char*)change->args[ii].u.hostmask);
+
+            mod_chanmode_free(change);
+        }
+    } 
+    /* Set this function to run again */
+    if(chanserv_conf.ban_timeout_frequency)
+        timeq_add(now + chanserv_conf.ban_timeout_frequency, expire_bans, NULL);
+}
+
+
 static int
 unban_user(struct userNode *user, struct chanNode *channel, unsigned int argc, char *argv[], struct svccmd *cmd, int action)
 {
@@ -3377,13 +4334,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
@@ -3406,30 +4373,30 @@ unban_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
 
     if(action & ACTION_DEL_LAMER)
     {
-       struct banData *ban, *next;
-
-       ban = channel->channel_info->bans; /* lamers */
-       while(ban)
-       {
-           if(actee)
-               for( ; ban && !user_matches_glob(actee, ban->mask, 1);
-                    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;
-       }
+        struct banData *ban, *next;
+
+        ban = channel->channel_info->bans; /* lamers */
+        while(ban)
+        {
+            if(actee)
+                   for( ; ban && !user_matches_glob(actee, ban->mask, MATCH_USENICK | MATCH_VISIBLE, 0);
+                 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;
+        }
     }
 
     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;
@@ -3454,7 +4421,7 @@ static CHANSERV_FUNC(cmd_unbanme)
 
     /* remove permanent bans if the user has the proper access. */
     if(uData->access >= UL_MANAGER)
-       flags |= ACTION_DEL_LAMER;
+    flags |= ACTION_DEL_LAMER;
 
     argv[1] = user->nick;
     return unban_user(user, channel, 2, argv, cmd, flags);
@@ -3467,10 +4434,11 @@ 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;
     }
 
+    // TODO: dont remove some kinds of extended bans such as ~c
     change = mod_chanmode_alloc(channel->banlist.used);
     for(ii=0; ii<channel->banlist.used; ii++)
     {
@@ -3513,7 +4481,7 @@ static CHANSERV_FUNC(cmd_myaccess)
 
     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;
@@ -3521,6 +4489,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);
@@ -3540,21 +4511,23 @@ static CHANSERV_FUNC(cmd_myaccess)
             continue;
         sbuf.used = 0;
         string_buffer_append_printf(&sbuf, "[%s (%d", cData->channel->name, uData->access);
-        if(uData->flags == USER_AUTO_OP)
+        if(uData->flags != 0)
             string_buffer_append(&sbuf, ',');
         if(IsUserSuspended(uData))
             string_buffer_append(&sbuf, 's');
         if(IsUserAutoOp(uData))
         {
-            if(uData->access >= UL_OP /*cData->lvlOpts[lvlGiveOps]*/)
+            if(uData->access >= UL_OP )
                 string_buffer_append(&sbuf, 'o');
-            else if(uData->access >= UL_HALFOP /*cData->lvlOpts[lvlGiveHalfOps]*/)
+            else if(uData->access >= UL_HALFOP )
                 string_buffer_append(&sbuf, 'h');
-            else if(uData->access >= UL_PEON /*cData->lvlOpts[lvlGiveVoice]*/)
+            else if(uData->access >= UL_PEON )
                 string_buffer_append(&sbuf, 'v');
         }
         if(IsUserAutoInvite(uData) && (uData->access >= cData->lvlOpts[lvlInviteMe]))
             string_buffer_append(&sbuf, 'i');
+        if(IsUserAutoJoin(uData) && (uData->access >= cData->lvlOpts[lvlInviteMe]))
+            string_buffer_append(&sbuf, 'j');
         if(uData->info)
             string_buffer_append_printf(&sbuf, ")] %s", uData->info);
         else
@@ -3576,7 +4549,7 @@ static CHANSERV_FUNC(cmd_access)
 
     if(argc < 2)
     {
-       target = user;
+    target = user;
         target_handle = target->handle_info;
     }
     else if((target = GetUserH(argv[1])))
@@ -3601,8 +4574,8 @@ static CHANSERV_FUNC(cmd_access)
 
     if(target == chanserv)
     {
-       reply("CSMSG_IS_CHANSERV");
-       return 1;
+        reply("CSMSG_IS_CHANSERV");
+        return 1;
     }
 
     if(!target_handle)
@@ -3612,13 +4585,13 @@ 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;
-       }
-        reply("MSG_AUTHENTICATE");
-        return 0;
+        if(target != user)
+        {
+            reply("MSG_USER_AUTHENTICATE", target->nick);
+            return 0;
+        }
+            reply("MSG_AUTHENTICATE");
+            return 0;
     }
 
     if(target)
@@ -3627,17 +4600,17 @@ static CHANSERV_FUNC(cmd_access)
         if(IsOper(target))
         {
             epithet = chanserv_conf.irc_operator_epithet;
-            type = "IRCOp";
+            type = user_find_message(user, "CSMSG_OPERATOR_TITLE");
         }
         else if(IsNetworkHelper(target))
         {
             epithet = chanserv_conf.network_helper_epithet;
-            type = "network helper";
+            type = user_find_message(user, "CSMSG_UC_H_TITLE");
         }
         else if(IsSupportHelper(target))
         {
             epithet = chanserv_conf.support_helper_epithet;
-            type = "support helper";
+            type = user_find_message(user, "CSMSG_LC_H_TITLE");
         }
         if(epithet)
         {
@@ -3688,7 +4661,7 @@ static CHANSERV_FUNC(cmd_access)
     return 1;
 }
 
-/* This is never used... */
+/* This is never used...
 static void
 zoot_list(struct listData *list)
 {
@@ -3738,22 +4711,77 @@ zoot_list(struct listData *list)
         }
     }
 }
+*/
+
+static void
+normal_list(struct listData *list)
+{
+    const char *msg;
+    if(list->search)
+        send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER_NORMAL", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest), list->search);
+    else
+        send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER_NORMAL", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest));
+    if(list->table.length == 1)
+    {
+        msg = user_find_message(list->user, "MSG_NONE");
+        send_message_type(4, list->user, list->bot, "  %s", msg);
+    }
+    else
+        table_send(list->bot, list->user->nick, 0, NULL, list->table);
+}
+
+/* if these need changed, uncomment and customize 
+static void
+clean_list(struct listData *list)
+{
+    const char *msg;
+    if(list->search)
+        send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER_CLEAN", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest), list->search);
+    else
+        send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER_CLEAN", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest));
+    if(list->table.length == 1)
+    {
+        msg = user_find_message(list->user, "MSG_NONE");
+        send_message_type(4, list->user, list->bot, "  %s", msg);
+    }
+    else
+        table_send(list->bot, list->user->nick, 0, NULL, list->table);
+}
+
+static void
+advanced_list(struct listData *list)
+{
+    const char *msg;
+    if(list->search)
+        send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER_ADVANCED", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest), list->search);
+    else
+        send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER_ADVANCED", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest));
+    if(list->table.length == 1)
+    {
+        msg = user_find_message(list->user, "MSG_NONE");
+        send_message_type(4, list->user, list->bot, "  %s", msg);
+    }
+    else
+        table_send(list->bot, list->user->nick, 0, NULL, list->table);
+} 
 
 static void
-def_list(struct listData *list)
+classic_list(struct listData *list)
 {
     const char *msg;
     if(list->search)
-        send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest), list->search);
+        send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER_CLASSIC", list->channel->name, list->lowest, list->highest, list->search);
     else
-        send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER", list->channel->name, user_level_name_from_level(list->lowest), user_level_name_from_level(list->highest));
-    table_send(list->bot, list->user->nick, 0, NULL, list->table);
+        send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER_CLASSIC", list->channel->name, list->lowest, list->highest);
     if(list->table.length == 1)
     {
         msg = user_find_message(list->user, "MSG_NONE");
         send_message_type(4, list->user, list->bot, "  %s", msg);
     }
+    else
+        table_send(list->bot, list->user->nick, 0, NULL, list->table);
 }
+*/
 
 static int
 userData_access_comp(const void *arg_a, const void *arg_b)
@@ -3776,6 +4804,8 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
     struct listData lData;
     unsigned int matches;
     const char **ary;
+    int i = 0;
+    int seen_index;
 
     lData.user = user;
     lData.bot = cmd->parent->bot;
@@ -3783,73 +4813,149 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
     lData.lowest = lowest;
     lData.highest = highest;
     lData.search = (argc > 1) ? argv[1] : NULL;
-    send_list = def_list;
+    send_list = normal_list;
     /* What does the following line do exactly?? */
-    (void)zoot_list; /* since it doesn't show user levels */
+    /*(void)zoot_list; ** since it doesn't show user levels */
 
-    /* this does nothing!! -rubin
+    /*
     if(user->handle_info)
     {
-       switch(user->handle_info->userlist_style)
-       {
-       case HI_STYLE_DEF: send_list = def_list; break;
-        case HI_STYLE_ZOOT: send_list = def_list; break;
-       }
+        switch(user->handle_info->userlist_style)
+        {
+            case HI_STYLE_CLEAN: 
+                send_list = clean_list; 
+                break;
+            case HI_STYLE_ADVANCED: 
+                send_list = advanced_list; 
+                break;
+            case HI_STYLE_CLASSIC: 
+                send_list = classic_list; 
+                break;
+            case HI_STYLE_NORMAL: 
+            default: 
+                send_list = normal_list; 
+                break;
+        }
     }
     */
+    send_list = normal_list;
 
     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)
-           || (uData->access > highest)
-           || (lData.search && !match_ircglob(uData->handle->handle, lData.search)))
-           continue;
-       lData.users[matches++] = uData;
+        if((uData->access < lowest)
+               || (uData->access > highest)
+               || (lData.search && !match_ircglob(uData->handle->handle, lData.search)))
+            continue;
+        lData.users[matches++] = uData;
     }
     qsort(lData.users, matches, sizeof(lData.users[0]), userData_access_comp);
 
     lData.table.length = matches+1;
-    lData.table.width = 5;
     lData.table.flags = TABLE_NO_FREE;
     lData.table.contents = malloc(lData.table.length*sizeof(*lData.table.contents));
+
+    if(user->handle_info && user->handle_info->userlist_style == HI_STYLE_ADVANCED)
+        lData.table.width = 6; /* with level = 6 */
+    else
+        lData.table.width = 5; /* without = 5 */
     ary = malloc(lData.table.width*sizeof(**lData.table.contents));
     lData.table.contents[0] = ary;
-    ary[0] = "Access";
-    ary[1] = "Level";
-    ary[2] = "Account";
-    ary[3] = "Last Seen";
-    ary[4] = "Status";
+    if(user->handle_info) {
+       switch(user->handle_info->userlist_style) {
+          case HI_STYLE_CLASSIC:
+            ary[i++] = "Level";
+       break;
+          case HI_STYLE_ADVANCED:
+            ary[i++] = "Access";
+            ary[i++] = "Level";
+       break;
+          case HI_STYLE_CLEAN:
+            ary[i++] = "Access";
+       break;
+          case HI_STYLE_NORMAL:
+            default:
+            ary[i++] = "Access";
+       break;
+        }
+    }
+    else {
+      ary[i++] = "Access";
+    }  
+    ary[i++] = "Account";
+    ary[i] = "Last Seen";
+    seen_index = i++;
+    ary[i++] = "Status";
+    ary[i++] = "Expiry";
     for(matches = 1; matches < lData.table.length; ++matches)
     {
-        struct userData *uData = lData.users[matches-1];
         char seen[INTERVALLEN];
 
+
+        uData = lData.users[matches-1];
+        i = 0;
         ary = malloc(lData.table.width*sizeof(**lData.table.contents));
         lData.table.contents[matches] = ary;
-        /* ary[0] = strtab(uData->access);*/
-        ary[0] = user_level_name_from_level(uData->access);
-        ary[1] = strtab(uData->access);
-        ary[2] = uData->handle->handle;
+        if(user->handle_info) {
+           switch(user->handle_info->userlist_style) {
+              case HI_STYLE_CLASSIC:
+                ary[i++] = strtab(uData->access);
+           break;
+              case HI_STYLE_ADVANCED:
+                ary[i++] = user_level_name_from_level(uData->access);
+                ary[i++] = strtab(uData->access);
+           break;
+              case HI_STYLE_CLEAN:
+                ary[i++] = user_level_name_from_level(uData->access);
+           break;
+              case HI_STYLE_NORMAL:
+                default:
+                ary[i++] = user_level_name_from_level(uData->access);
+           break;
+            }
+        }
+        else {
+           ary[i++] = user_level_name_from_level(uData->access);
+        }
+        ary[i++] = uData->handle->handle;
         if(uData->present)
-            ary[3] = "Here";
+            ary[i] = "Here";
         else if(!uData->seen)
-            ary[3] = "Never";
+            ary[i] = "Never";
         else
-            ary[3] = intervalString(seen, now - uData->seen, user->handle_info);
-        ary[3] = strdup(ary[3]);
+            ary[i] = intervalString(seen, now - uData->seen, user->handle_info);
+        ary[i] = strdup(ary[i]);
+        i++;
         if(IsUserSuspended(uData))
-            ary[4] = "Suspended";
+            ary[i++] = "Suspended";
         else if(HANDLE_FLAGGED(uData->handle, FROZEN))
-            ary[4] = "Vacation";
+            ary[i++] = "Vacation";
+        else
+            ary[i++] = "Normal";
+
+        if ((uData->accessexpiry > 0) || (uData->clvlexpiry > 0)) {
+            char delay[INTERVALLEN];
+            time_t diff;
+
+            if (uData->accessexpiry > 0) {
+                diff = uData->accessexpiry - now;
+                intervalString(delay, diff, user->handle_info);
+            } else {
+                diff = uData->clvlexpiry - now;
+                intervalString(delay, diff, user->handle_info);
+            }
+            ary[i++] = delay;
+        } 
         else
-            ary[4] = "Normal";
+            ary[i++] = "Never";
     }
+
     send_list(&lData);
     for(matches = 1; matches < lData.table.length; ++matches)
     {
-        free((char*)lData.table.contents[matches][3]);
+        /* Free strdup above */
+        free((char*)lData.table.contents[matches][seen_index]);
         free(lData.table.contents[matches]);
     }
     free(lData.table.contents[0]);
@@ -3863,7 +4969,8 @@ static CHANSERV_FUNC(cmd_pending)
 {
     struct adduserPending *ap;
     reply("CSMSG_ADDUSER_PENDING_HEADER");
-    reply("CSMSG_BAR");
+    if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
+        reply("CSMSG_BAR");
     for(ap = adduser_pendings;ap;ap = ap->next)
         reply("CSMSG_ADDUSER_PENDING_LIST", ap->channel->name, ap->user->nick);
     reply("CSMSG_ADDUSER_PENDING_FOOTER");
@@ -3907,16 +5014,22 @@ static CHANSERV_FUNC(cmd_plist)
 
 static CHANSERV_FUNC(cmd_lamers)
 {
+    struct userNode *search_u = NULL;
     struct helpfile_table tbl;
-    unsigned int matches = 0, timed = 0, ii;
+    unsigned int matches = 0, timed = 0, search_wilds = 0, ii;
     char t_buffer[INTERVALLEN], e_buffer[INTERVALLEN], *search;
     const char *msg_never, *triggered, *expires;
     struct banData *ban, **bans; /* lamers */
 
-    if(argc > 1)
-       search = argv[1];
-    else
+    if(argc < 2)
         search = NULL;
+    else if(strchr(search = argv[1], '!'))
+    {
+        search = argv[1];
+        search_wilds = search[strcspn(search, "?*")];
+    }
+    else if(!(search_u = GetUserH(search)))
+        reply("MSG_NICK_UNKNOWN", search);
 
     reply("CSMSG_LAMERS_HEADER", channel->name);
     bans = alloca(channel->channel_info->banCount * sizeof(struct banData *)); /* lamers */
@@ -3924,10 +5037,18 @@ static CHANSERV_FUNC(cmd_lamers)
     /* lamers */
     for(ban = channel->channel_info->bans; ban; ban = ban->next)
     {
-       if(search && !match_ircglobs(search, ban->mask))
-           continue;
-       bans[matches++] = ban;
-       if(ban->expires)
+        if(search_u)
+        {
+            if(!user_matches_glob(search_u, ban->mask, MATCH_USENICK | MATCH_VISIBLE, 0))
+                continue;
+        }
+        else if(search)
+        {
+            if(search_wilds ? !match_ircglobs(search, ban->mask) : !match_ircglob(search, ban->mask))
+                continue;
+        }
+        bans[matches++] = ban;
+        if(ban->expires)
             timed = 1;
     }
 
@@ -3950,28 +5071,28 @@ static CHANSERV_FUNC(cmd_lamers)
     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;
@@ -4068,13 +5189,19 @@ static CHANSERV_FUNC(cmd_topic)
 {
     struct chanData *cData;
     char *topic;
+    int p10 = 0;
+
+#ifdef WITH_PROTOCOL_P10
+    p10 = 1;
+#endif
 
     cData = channel->channel_info;
     if(argc < 2)
     {
         if(cData->topic)
         {
-            SetChannelTopic(channel, chanserv, cData->topic, 1);
+            /*XXX Why would we ever want to send chanserv as the setter? I dont understand  -Rubin */
+            SetChannelTopic(channel, chanserv, p10 ? user : chanserv, cData->topic, 1);
             reply("CSMSG_TOPIC_SET", cData->topic);
             return 1;
         }
@@ -4110,10 +5237,10 @@ static CHANSERV_FUNC(cmd_topic)
                 reply("CSMSG_TOPICMASK_CONFLICT2", TOPICLEN);
                 return 0;
             }
-            SetChannelTopic(channel, chanserv, new_topic, 1);
+            SetChannelTopic(channel, chanserv, p10 ? user : chanserv, new_topic, 1);
         }
         else /* No mask set, just set the topic */
-            SetChannelTopic(channel, chanserv, topic, 1);
+            SetChannelTopic(channel, chanserv, p10 ? user : chanserv, topic, 1);
     }
 
     if(check_user_level(channel, user, lvlTopicSnarf, 1, 0))
@@ -4128,24 +5255,41 @@ static CHANSERV_FUNC(cmd_topic)
 
 static CHANSERV_FUNC(cmd_mode)
 {
+    struct userData *uData;
     struct mod_chanmode *change;
+    short base_oplevel;
     
     if(argc < 2)
     {
+        if (checkDefCon(DEFCON_NO_MODE_CHANGE) && !IsOper(user)) 
+        {
+            reply("CSMSG_DEFCON_NO_MODE_CHANGE");
+            return 0;
+        }
+
         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;
     }
 
-    change = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED);
+    uData = GetChannelUser(channel->channel_info, user->handle_info);
+    if (!uData)
+        base_oplevel = MAXOPLEVEL;
+    else if (uData->access >= UL_OWNER)
+        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);
+
     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)
@@ -4165,15 +5309,15 @@ static CHANSERV_FUNC(cmd_mode)
 
 static CHANSERV_FUNC(cmd_invite)
 {
-    struct userData *uData;
+    //struct userData *uData;
     struct userNode *invite;
 
-    uData = GetChannelUser(channel->channel_info, user->handle_info);
+    //uData = GetChannelUser(channel->channel_info, user->handle_info);
 
     if(argc > 1)
     {
         if(!(invite = GetUserH(argv[1])))
-       {
+        {
             reply("MSG_NICK_UNKNOWN", argv[1]);
             return 0;
         }
@@ -4183,8 +5327,8 @@ 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;
     }
 
     if(user != invite)
@@ -4197,9 +5341,22 @@ static CHANSERV_FUNC(cmd_invite)
         else
             send_message(invite, chanserv, "CSMSG_INVITING_YOU", user->nick, channel->name);
     }
+
+    if (invite->handle_info && invite->handle_info->ignores->used && (argc > 1)) {
+        unsigned int i;
+        for (i=0; i < invite->handle_info->ignores->used; i++) 
+        {
+            if (user_matches_glob(user, invite->handle_info->ignores->list[i], MATCH_USENICK, 0)) 
+            {
+                reply("CSMSG_CANNOT_INVITE", argv[1], channel->name);
+                return 0;
+            }
+        }
+    }
+
     irc_invite(chanserv, invite, channel);
     if(argc > 1)
-       reply("CSMSG_INVITED_USER", argv[1], channel->name);
+        reply("CSMSG_INVITED_USER", argv[1], channel->name);
 
     return 1;
 }
@@ -4208,8 +5365,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))
@@ -4236,46 +5393,71 @@ show_suspension_info(struct svccmd *cmd, struct userNode *user, struct suspended
     combo = (suspended->issued ? 4 : 0)
         + (suspended->revoked ? 3 : suspended->expires ? ((suspended->expires < now) ? 2 : 1) : 0);
     switch(combo) {
-    case 0: /* no issue time, indefinite expiration */
-        reply("CSMSG_CHANNEL_SUSPENDED_0", suspended->suspender, suspended->reason);
-        break;
-    case 1: /* no issue time, expires in future */
-        intervalString(buf1, suspended->expires-now, user->handle_info);
-        reply("CSMSG_CHANNEL_SUSPENDED_1", suspended->suspender, buf1, suspended->reason);
-        break;
-    case 2: /* no issue time, expired */
-        intervalString(buf1, now-suspended->expires, user->handle_info);
-        reply("CSMSG_CHANNEL_SUSPENDED_2", suspended->suspender, buf1, suspended->reason);
-        break;
-    case 3: /* no issue time, revoked */
-        intervalString(buf1, now-suspended->revoked, user->handle_info);
-        reply("CSMSG_CHANNEL_SUSPENDED_3", suspended->suspender, buf1, suspended->reason);
-        break;
-    case 4: /* issue time set, indefinite expiration */
-        intervalString(buf1, now-suspended->issued, user->handle_info);
-        reply("CSMSG_CHANNEL_SUSPENDED_4", buf1, suspended->suspender, suspended->reason);
-        break;
-    case 5: /* issue time set, expires in future */
-        intervalString(buf1, now-suspended->issued, user->handle_info);
-        intervalString(buf2, suspended->expires-now, user->handle_info);
-        reply("CSMSG_CHANNEL_SUSPENDED_5", buf1, suspended->suspender, buf2, suspended->reason);
-        break;
-    case 6: /* issue time set, expired */
-        intervalString(buf1, now-suspended->issued, user->handle_info);
-        intervalString(buf2, now-suspended->expires, user->handle_info);
-        reply("CSMSG_CHANNEL_SUSPENDED_6", buf1, suspended->suspender, buf2, suspended->reason);
-        break;
-    case 7: /* issue time set, revoked */
-        intervalString(buf1, now-suspended->issued, user->handle_info);
-        intervalString(buf2, now-suspended->revoked, user->handle_info);
-        reply("CSMSG_CHANNEL_SUSPENDED_7", buf1, suspended->suspender, buf2, suspended->reason);
-        break;
-    default:
-        log_module(CS_LOG, LOG_ERROR, "Invalid combo value %d in show_suspension_info()", combo);
-        return;
+        case 0: /* no issue time, indefinite expiration */
+            reply("CSMSG_CHANNEL_SUSPENDED_0", suspended->suspender, suspended->reason);
+            break;
+        case 1: /* no issue time, expires in future */
+            intervalString(buf1, suspended->expires-now, user->handle_info);
+            reply("CSMSG_CHANNEL_SUSPENDED_1", suspended->suspender, buf1, suspended->reason);
+            break;
+        case 2: /* no issue time, expired */
+            intervalString(buf1, now-suspended->expires, user->handle_info);
+            reply("CSMSG_CHANNEL_SUSPENDED_2", suspended->suspender, buf1, suspended->reason);
+            break;
+        case 3: /* no issue time, revoked */
+            intervalString(buf1, now-suspended->revoked, user->handle_info);
+            reply("CSMSG_CHANNEL_SUSPENDED_3", suspended->suspender, buf1, suspended->reason);
+            break;
+        case 4: /* issue time set, indefinite expiration */
+            intervalString(buf1, now-suspended->issued, user->handle_info);
+            reply("CSMSG_CHANNEL_SUSPENDED_4", buf1, suspended->suspender, suspended->reason);
+            break;
+        case 5: /* issue time set, expires in future */
+            intervalString(buf1, now-suspended->issued, user->handle_info);
+            intervalString(buf2, suspended->expires-now, user->handle_info);
+            reply("CSMSG_CHANNEL_SUSPENDED_5", buf1, suspended->suspender, buf2, suspended->reason);
+            break;
+        case 6: /* issue time set, expired */
+            intervalString(buf1, now-suspended->issued, user->handle_info);
+            intervalString(buf2, now-suspended->expires, user->handle_info);
+            reply("CSMSG_CHANNEL_SUSPENDED_6", buf1, suspended->suspender, buf2, suspended->reason);
+            break;
+        case 7: /* issue time set, revoked */
+            intervalString(buf1, now-suspended->issued, user->handle_info);
+            intervalString(buf2, now-suspended->revoked, user->handle_info);
+            reply("CSMSG_CHANNEL_SUSPENDED_7", buf1, suspended->suspender, buf2, suspended->reason);
+            break;
+        default:
+            log_module(CS_LOG, LOG_ERROR, "Invalid combo value %d in show_suspension_info()", combo);
+            return;
+    }
+}
+
+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];
@@ -4288,14 +5470,15 @@ static CHANSERV_FUNC(cmd_info)
 
     cData = channel->channel_info;
     reply("CSMSG_CHANNEL_INFO", channel->name);
-    reply("CSMSG_BAR");
+    if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
+        reply("CSMSG_BAR");
 
     uData = GetChannelUser(cData, user->handle_info);
     if(uData && (uData->access >= UL_OP /*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))
@@ -4317,10 +5500,10 @@ static CHANSERV_FUNC(cmd_info)
     reply("CSMSG_CHANNEL_USERS", cData->userCount);
     reply("CSMSG_CHANNEL_LAMERS", cData->banCount);
     reply("CSMSG_CHANNEL_VISITED", intervalString(buffer, now - cData->visited, user->handle_info));
-    reply("CSMSG_CHANNEL_REGISTERED", intervalString(buffer, now - cData->registered, user->handle_info));
 
     privileged = IsStaff(user);
-    if(((uData && uData->access >= UL_COOWNER) || privileged) && cData->registrar)
+    reply("CSMSG_CHANNEL_REGISTERED", intervalString(buffer, now - cData->registered, user->handle_info));
+    if(cData->registrar)
         reply("CSMSG_CHANNEL_REGISTRAR", cData->registrar);
 
     if(privileged && (dnr = chanserv_is_dnr(channel->name, NULL)))
@@ -4338,7 +5521,17 @@ static CHANSERV_FUNC(cmd_info)
         reply("CSMSG_CHANNEL_SUSPENDED", channel->name);
         show_suspension_info(cmd, user, cData->suspended);
     }
-    reply("CSMSG_CHANNEL_END");
+    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);
+    }
+    if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
+        reply("CSMSG_CHANNEL_END");
+    else
+        reply("CSMSG_CHANNEL_END_CLEAN");
     return 1;
 }
 
@@ -4351,7 +5544,7 @@ static CHANSERV_FUNC(cmd_netinfo)
     reply("CSMSG_NETWORK_INFO");
     reply("CSMSG_NETWORK_SERVERS", dict_size(servers));
     reply("CSMSG_NETWORK_USERS", dict_size(clients));
-    reply("CSMSG_NETWORK_OPERS", curr_opers.used);
+    reply("CSMSG_NETWORK_OPERS", count_opers);
     reply("CSMSG_NETWORK_CHANNELS", registered_channels);
     reply("CSMSG_NETWORK_LAMERS", banCount);
     reply("CSMSG_NETWORK_CHANUSERS", userCount);
@@ -4377,7 +5570,7 @@ send_staff_list(struct userNode *to, struct userList *list, int skip_flags)
         user = list->list[nn];
         if(user->modes & skip_flags)
             continue;
-        if(IsBot(user))
+        if(IsBot(user) || IsHideOper(user))
             continue;
         table.contents[table.length] = alloca(table.width*sizeof(**table.contents));
         if(IsAway(user))
@@ -4396,14 +5589,14 @@ send_staff_list(struct userNode *to, struct userList *list, int skip_flags)
 static CHANSERV_FUNC(cmd_ircops)
 {
     reply("CSMSG_STAFF_OPERS");
-    send_staff_list(user, &curr_opers, FLAGS_SERVICE);
+    send_staff_list(user, &curr_opers, FLAGS_SERVICE | FLAGS_BOT);
     return 1;
 }
 
 static CHANSERV_FUNC(cmd_helpers)
 {
     reply("CSMSG_STAFF_HELPERS");
-    send_staff_list(user, &curr_helpers, FLAGS_OPER);
+    send_staff_list(user, &curr_helpers, FLAGS_OPER | FLAGS_SERVICE | FLAGS_BOT);
     return 1;
 }
 
@@ -4425,7 +5618,8 @@ static CHANSERV_FUNC(cmd_peek)
     irc_make_chanmode(channel, modes);
 
     reply("CSMSG_PEEK_INFO", channel->name);
-    reply("CSMSG_BAR");
+    if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
+        reply("CSMSG_BAR");
     reply("CSMSG_PEEK_TOPIC", channel->topic);
     reply("CSMSG_PEEK_MODES", modes);
     reply("CSMSG_PEEK_USERS", channel->members.used);
@@ -4436,9 +5630,9 @@ 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))
-            continue;
+        mn = channel->members.list[n];
+        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++;
@@ -4457,10 +5651,12 @@ static CHANSERV_FUNC(cmd_peek)
 static MODCMD_FUNC(cmd_wipeinfo)
 {
     struct handle_info *victim;
-    struct userData *ud, *actor;
+    struct userData *ud, *actor, *real_actor;
+    unsigned int override = 0;
 
     REQUIRE_PARAMS(2);
     actor = GetChannelUser(channel->channel_info, user->handle_info);
+    real_actor = GetChannelAccess(channel->channel_info, user->handle_info);
     if(!(victim = modcmd_get_handle_info(user, argv[1])))
         return 0;
     if(!(ud = GetTrueChannelAccess(channel->channel_info, victim)))
@@ -4473,20 +5669,24 @@ static MODCMD_FUNC(cmd_wipeinfo)
         reply("MSG_USER_OUTRANKED", victim->handle);
         return 0;
     }
+    if((ud != real_actor) && (!real_actor || (ud->access >= real_actor->access)))
+        override = CMD_LOG_OVERRIDE;
     if(ud->info)
         free(ud->info);
     ud->info = NULL;
     reply("CSMSG_WIPED_INFO_LINE", argv[1], channel->name);
-    return 1;
+    return 1 | override;
 }
 
-static CHANSERV_FUNC(cmd_resync)
+static void
+resync_channel(struct chanNode *channel)
 {
     struct mod_chanmode *changes;
     struct chanData *cData = channel->channel_info;
     unsigned int ii, used;
 
-    changes = mod_chanmode_alloc(channel->members.used * 2);
+    /* 6 = worst case -ovh+ovh on everyone */
+    changes = mod_chanmode_alloc(channel->members.used * 6);
     for(ii = used = 0; ii < channel->members.used; ++ii)
     {
         struct modeNode *mn = channel->members.list[ii];
@@ -4495,65 +5695,150 @@ static CHANSERV_FUNC(cmd_resync)
         if(IsService(mn->user))
             continue;
 
+
         uData = GetChannelAccess(cData, mn->user->handle_info);
-        if(uData && uData->access >= UL_OP /* cData->lvlOpts[lvlGiveOps]*/)
+        
+        /* If the channel is in no-mode mode, de-mode EVERYONE */
+        if(cData->chOpts[chAutomode] == 'n')
         {
-            if(!(mn->modes & MODE_CHANOP))
-            {
-                changes->args[used].mode = MODE_CHANOP;
-                changes->args[used++].u.member = mn;
-            }
+                if(mn->modes)
+                {
+                    changes->args[used].mode = MODE_REMOVE | mn->modes;
+                    changes->args[used++].u.member = mn;
+                }
         }
-        else if(uData && uData->access >= UL_HALFOP /*cData->lvlOpts[lvlGiveHalfOps]*/)
+        else /* Give various userlevels their modes.. */
         {
-            if(mn->modes & MODE_CHANOP)
-            {
-                changes->args[used].mode = MODE_REMOVE |  MODE_CHANOP;
-                changes->args[used++].u.member = mn;
-            }
-            if(!(mn->modes & MODE_HALFOP))
-            {
-                changes->args[used].mode = MODE_HALFOP;
-                changes->args[used++].u.member = mn;
-            }
-            /* why cant halfops keep voice
-            if(mn->modes & MODE_VOICE)
+            /* If the user has autoop/autovoice disabled then ignore them */
+            if(uData && !IsUserAutoOp(uData))
+              continue;
+            if(uData && uData->access >= UL_PEON && cData->chOpts[chAutomode] == 'l')
             {
-                changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_VOICE);
-                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(uData && uData->access >= UL_PEON /* cData->lvlOpts[lvlGiveVoice]*/)
-        {
-            if(mn->modes & MODE_CHANOP)
+            else if(uData && uData->access >= UL_OP )
             {
-                changes->args[used].mode = MODE_REMOVE | MODE_CHANOP;
-                changes->args[used++].u.member = mn;
+                if(!(mn->modes & MODE_CHANOP))
+                {
+                    changes->args[used].mode = MODE_CHANOP;
+                    changes->args[used++].u.member = mn;
+                }
             }
-            if(mn->modes & MODE_HALFOP)
+            else if(uData && uData->access >= UL_HALFOP)
             {
-                changes->args[used].mode = MODE_REMOVE | MODE_HALFOP;
-                changes->args[used++].u.member = mn;
+                if(mn->modes & MODE_CHANOP)
+                {
+                    changes->args[used].mode = MODE_REMOVE |  MODE_CHANOP;
+                    changes->args[used++].u.member = mn;
+                }
+                if(!(mn->modes & MODE_HALFOP))
+                {
+                    changes->args[used].mode = MODE_HALFOP;
+                    changes->args[used++].u.member = mn;
+                }
             }
-            if(!(mn->modes & MODE_VOICE))
+            else if(uData && uData->access >= UL_PEON )
             {
-                changes->args[used].mode = MODE_VOICE;
-                changes->args[used++].u.member = mn;
+                if(mn->modes & MODE_CHANOP)
+                {
+                    changes->args[used].mode = MODE_REMOVE | MODE_CHANOP;
+                    changes->args[used++].u.member = mn;
+                }
+                if(mn->modes & MODE_HALFOP)
+                {
+                    changes->args[used].mode = MODE_REMOVE | MODE_HALFOP;
+                    changes->args[used++].u.member = mn;
+                }
+                /* Don't voice peons if were in mode m */
+                if( cData->chOpts[chAutomode] == 'm')
+                {
+                    if(mn->modes & MODE_VOICE)
+                    {
+                        changes->args[used].mode = MODE_REMOVE | MODE_VOICE;
+                        changes->args[used++].u.member = mn;
+                    }
+                }
+                /* otherwise, make user they do have voice */
+                else if(!(mn->modes & MODE_VOICE))
+                {
+                    changes->args[used].mode = MODE_VOICE;
+                    changes->args[used++].u.member = mn;
+                }
             }
-        }
-        else
-        {
-            if(mn->modes)
+            else /* They arnt on the userlist.. */
             {
-                changes->args[used].mode = MODE_REMOVE | mn->modes;
-                changes->args[used++].u.member = mn;
+                /* If we voice everyone, but they dont.. */
+                if(cData->chOpts[chAutomode] == 'v')
+                {
+                    /* Remove anything except v */
+                    if(mn->modes & ~MODE_VOICE)
+                    {
+                        changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_VOICE);
+                        changes->args[used++].u.member = mn;
+                    }
+                    /* Add v */
+                    if(!(mn->modes & MODE_VOICE))
+                    {
+                        changes->args[used].mode = MODE_VOICE;
+                        changes->args[used++].u.member = mn;
+                    }
+                }
+                /* If we hop everyone, but they dont.. */
+                else if(cData->chOpts[chAutomode] == 'h')
+                {
+                    /* Remove anything except h */
+                    if(mn->modes & ~MODE_HALFOP)
+                    {
+                        changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_HALFOP);
+                        changes->args[used++].u.member = mn;
+                    }
+                    /* Add h */
+                    if(!(mn->modes & MODE_HALFOP))
+                    {
+                        changes->args[used].mode = MODE_HALFOP;
+                        changes->args[used++].u.member = mn;
+                    }
+                }
+                /* If we op everyone, but they dont.. */
+                else if(cData->chOpts[chAutomode] == 'o')
+                {
+                    /* Remove anything except h */
+                    if(mn->modes & ~MODE_CHANOP)
+                    {
+                        changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_CHANOP);
+                        changes->args[used++].u.member = mn;
+                    }
+                    /* Add h */
+                    if(!(mn->modes & MODE_CHANOP))
+                    {
+                        changes->args[used].mode = MODE_CHANOP;
+                        changes->args[used++].u.member = mn;
+                    }
+                }
+                /* they have no excuse for having modes, de-everything them */
+                else
+                {
+                    if(mn->modes)
+                    {
+                        changes->args[used].mode = MODE_REMOVE | mn->modes;
+                        changes->args[used++].u.member = mn;
+                    }
+                }
             }
         }
     }
     changes->argc = used;
-    modcmd_chanmode_announce(changes);
+    mod_chanmode_announce(chanserv, channel, changes);
     mod_chanmode_free(changes);
+}
+
+static CHANSERV_FUNC(cmd_resync)
+{
+    resync_channel(channel);
     reply("CSMSG_RESYNCED_USERS", channel->name);
     return 1;
 }
@@ -4568,24 +5853,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
@@ -4632,9 +5917,9 @@ note_type_visible_to_user(struct chanData *channel, struct note_type *ntype, str
 {
     switch(ntype->visible_type)
     {
-    case NOTE_VIS_ALL: return 1;
-    case NOTE_VIS_CHANNEL_USERS: return !channel || !user || (user->handle_info && GetChannelUser(channel, user->handle_info));
-    case NOTE_VIS_PRIVILEGED: default: return user && (IsOper(user) || IsSupportHelper(user) || IsNetworkHelper(user));
+        case NOTE_VIS_ALL: return 1;
+        case NOTE_VIS_CHANNEL_USERS: return !channel || !user || (user->handle_info && GetChannelUser(channel, user->handle_info));
+        case NOTE_VIS_PRIVILEGED: default: return user && (IsOper(user) || IsSupportHelper(user) || IsNetworkHelper(user));
     }
 }
 
@@ -4645,16 +5930,16 @@ note_type_settable_by_user(struct chanNode *channel, struct note_type *ntype, st
 
     switch(ntype->set_access_type)
     {
-    case NOTE_SET_CHANNEL_ACCESS:
-        if(!user->handle_info)
-            return 0;
-        if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)))
-            return 0;
-        return uData->access >= ntype->set_access.min_ulevel;
-    case NOTE_SET_CHANNEL_SETTER:
-        return check_user_level(channel, user, lvlSetters, 1, 0);
-    case NOTE_SET_PRIVILEGED: default:
-        return IsHelping(user) && (user->handle_info->opserv_level >= ntype->set_access.min_opserv);
+        case NOTE_SET_CHANNEL_ACCESS:
+            if(!user->handle_info)
+                return 0;
+            if(!(uData = GetChannelUser(channel->channel_info, user->handle_info)))
+                return 0;
+            return uData->access >= ntype->set_access.min_ulevel;
+        case NOTE_SET_CHANNEL_SETTER:
+            return check_user_level(channel, user, lvlSetters, 1, 0);
+        case NOTE_SET_PRIVILEGED: default:
+            return IsHelping(user) && (user->handle_info->opserv_level >= ntype->set_access.min_opserv);
     }
 }
 
@@ -4762,6 +6047,23 @@ static CHANSERV_FUNC(cmd_delnote)
     return 1;
 }
 
+static CHANSERV_FUNC(cmd_last)
+{
+   int numoflines;
+
+   REQUIRE_PARAMS(1);
+
+   numoflines = (argc > 1) ? atoi(argv[1]) : 10;
+
+   if(numoflines < 1 || numoflines > 200)
+   {
+       reply("CSMSG_LAST_INVALID");
+       return 0;
+   }
+   ShowLog(user, channel, "*", "*", "*", "*", numoflines);
+   return 1;
+}
+
 static CHANSERV_FUNC(cmd_events)
 {
     struct logSearch discrim;
@@ -4783,12 +6085,13 @@ static CHANSERV_FUNC(cmd_events)
     report.reporter = chanserv;
     report.user = user;
     reply("CSMSG_EVENT_SEARCH_RESULTS", channel->name);
-    reply("CSMSG_BAR");
+    if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
+        reply("CSMSG_BAR");
     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;
 }
 
@@ -4801,6 +6104,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);
@@ -4825,6 +6145,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);
@@ -4857,20 +6194,31 @@ 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);
+    }
+
+    /* 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;
+        }
     }
 }
 
@@ -4893,9 +6241,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"))
@@ -4928,8 +6276,9 @@ static CHANSERV_FUNC(cmd_csuspend)
         suspended->previous->revoked = now;
         if(suspended->previous->expires)
             timeq_del(suspended->previous->expires, chanserv_expire_suspension, suspended->previous, 0);
-        sprintf(reason, "%s suspension modified by %s.", channel->name, suspended->suspender);
-        global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+
+        global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_SUSPENSION_MODIFIED",
+                            channel->name, suspended->suspender);
     }
     else
     {
@@ -4945,10 +6294,11 @@ 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);
-        global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+        global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_SUSPENDED_BY",
+                            channel->name, suspended->suspender);
     }
     return 1;
 }
@@ -4956,7 +6306,6 @@ static CHANSERV_FUNC(cmd_csuspend)
 static CHANSERV_FUNC(cmd_cunsuspend)
 {
     struct suspended *suspended;
-    char message[MAXLEN];
 
     if(!IsSuspended(channel->channel_info))
     {
@@ -4970,8 +6319,8 @@ static CHANSERV_FUNC(cmd_cunsuspend)
     timeq_del(suspended->expires, chanserv_expire_suspension, suspended, 0);
     chanserv_expire_suspension(suspended);
     reply("CSMSG_UNSUSPENDED", channel->name);
-    sprintf(message, "%s unsuspended by %s.", channel->name, user->handle_info->handle);
-    global_message(MESSAGE_RECIPIENT_OPERS|MESSAGE_RECIPIENT_HELPERS, message);
+    global_message_args(MESSAGE_RECIPIENT_OPERS|MESSAGE_RECIPIENT_HELPERS, "CSMSG_UNSUSPENDED_BY",
+                        channel->name, user->handle_info->handle);
     return 1;
 }
 
@@ -4990,7 +6339,7 @@ typedef struct chanservSearch
 typedef void (*channel_search_func)(struct chanData *channel, void *data);
 
 static search_t
-chanserv_search_create(struct userNode *user, unsigned int argc, char *argv[])
+chanserv_search_create(struct svccmd *cmd, struct userNode *user, unsigned int argc, char *argv[])
 {
     search_t search;
     unsigned int i;
@@ -5001,47 +6350,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))
+        {
+            reply("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;
+            else if(!irccasecmp(argv[i], "unreviewed"))
+                search->flags |= CHANNEL_UNREVIEWED;
+            else
+            {
+                reply("CSMSG_INVALID_CFLAG", argv[i]);
+                goto fail;
+            }
+        }
+        else if(!irccasecmp(argv[i], "limit"))
+            search->limit = strtoul(argv[++i], NULL, 10);
+        else
+        {
+            reply("MSG_INVALID_CRITERIA", 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;
-           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:
@@ -5059,7 +6410,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;
 }
@@ -5072,10 +6423,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;
@@ -5101,34 +6452,35 @@ 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);
+    search = chanserv_search_create(cmd, user, argc - 2, argv + 2);
     if(!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_BAR");
+        reply("CSMSG_CHANNEL_SEARCH_RESULTS");
+        if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
+            reply("CSMSG_BAR");
     }
 
     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;
@@ -5143,8 +6495,8 @@ static CHANSERV_FUNC(cmd_unvisited)
 
     if(argc > 1)
     {
-       interval = ParseInterval(argv[1]);
-       if(argc > 2)
+        interval = ParseInterval(argv[1]);
+        if(argc > 2)
             limit = atoi(argv[2]);
     }
 
@@ -5153,14 +6505,68 @@ 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;
+}
+
+static MODCMD_FUNC(chan_opt_unreviewed)
+{
+    struct chanData *cData = channel->channel_info;
+    int value = (cData->flags & CHANNEL_UNREVIEWED) ? 1 : 0;
+
+    if(argc > 1)
+    {
+        int new_value;
+
+        /* The two directions can have different ACLs. */
+        if(enabled_string(argv[1]))
+            new_value = 1;
+        else if(disabled_string(argv[1]))
+            new_value = 0;
+        else
+        {
+            reply("MSG_INVALID_BINARY", argv[1]);
+            return 0;
+        }
+
+        if (new_value != value)
+        {
+            struct svccmd *subcmd;
+            char subcmd_name[32];
+
+            snprintf(subcmd_name, sizeof(subcmd_name), "%s %s", argv[0], (new_value ? "on" : "off"));
+            subcmd = dict_find(cmd->parent->commands, subcmd_name, NULL);
+            if(!subcmd)
+            {
+                reply("MSG_COMMAND_DISABLED", subcmd_name);
+                return 0;
+            }
+            else if(!svccmd_can_invoke(user, cmd->parent->bot, subcmd, channel, SVCCMD_NOISY))
+                return 0;
+
+            if (new_value)
+                cData->flags |= CHANNEL_UNREVIEWED;
+            else
+            {
+                free(cData->registrar);
+                cData->registrar = strdup(user->handle_info->handle);
+                cData->flags &= ~CHANNEL_UNREVIEWED;
+            }
+            value = new_value;
+        }
     }
 
+    if(value)
+        reply("CSMSG_SET_UNREVIEWED", user_find_message(user, "MSG_ON"));
+    else
+        reply("CSMSG_SET_UNREVIEWED", user_find_message(user, "MSG_OFF"));
     return 1;
 }
 
@@ -5176,21 +6582,21 @@ 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);
+        }
+        SetChannelTopic(channel, chanserv, user, topic ? topic : "", 1);
     }
 
     if(channel->channel_info->topic)
@@ -5213,22 +6619,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)
@@ -5246,18 +6652,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);
     }
@@ -5279,6 +6685,21 @@ static MODCMD_FUNC(chan_opt_usergreeting)
     return opt_greeting_common(user, cmd, argc, argv, "CSMSG_SET_USERGREETING", &channel->channel_info->user_greeting);
 }
 
+static MODCMD_FUNC(chan_opt_maxsetinfo)
+{
+   unsigned int charmax;
+
+   if(argc > 1) 
+   {
+     charmax = atoi(argv[1]);
+     if ((charmax > 0) && (charmax <= chanserv_conf.max_userinfo_length))
+       channel->channel_info->maxsetinfo = charmax;
+   }
+
+   reply("CSMSG_SET_MAXSETINFO", channel->channel_info->maxsetinfo);
+   return 1;
+}
+
 static MODCMD_FUNC(chan_opt_modes)
 {
     struct mod_chanmode *new_modes;
@@ -5286,17 +6707,22 @@ static MODCMD_FUNC(chan_opt_modes)
 
     if(argc > 1)
     {
+        if (checkDefCon(DEFCON_NO_MODE_CHANGE) && !IsOper(user)) {
+            reply("CSMSG_DEFCON_NO_MODE_CHANGE");
+            return 0;
+        }
+
         if(!check_user_level(channel, user, lvlEnfModes, 1, 0))
         {
             reply("CSMSG_NO_ACCESS");
             return 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)))
-       {
+        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)))
+        {
             reply("CSMSG_INVALID_MODE_LOCK", unsplit_string(argv+1, argc-1, NULL));
             return 0;
         }
@@ -5331,27 +6757,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)
@@ -5365,8 +6791,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);
@@ -5374,6 +6800,18 @@ static MODCMD_FUNC(chan_opt_nodelete)
 
 static MODCMD_FUNC(chan_opt_dynlimit)
 {
+    struct mod_chanmode change;
+
+    if (argc > 1) 
+    {
+        if (disabled_string(argv[1])) 
+        {
+            mod_chanmode_init(&change);
+            change.modes_clear |= MODE_LIMIT;
+            mod_chanmode_announce(chanserv, channel, &change);
+        }
+    }
+
     CHANNEL_BINARY_OPTION("CSMSG_SET_DYNLIMIT", CHANNEL_DYNAMIC_LIMIT);
 }
 
@@ -5384,38 +6822,38 @@ static MODCMD_FUNC(chan_opt_offchannel)
 
     if(argc > 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]))
-       {
-            if(IsOffChannel(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);
-            }
-           cData->flags &= ~CHANNEL_OFFCHANNEL;
-           value = 0;
-       }
-       else
-       {
-           reply("MSG_INVALID_BINARY", argv[1]);
-           return 0;
-       }
+        /* 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]))
+        {
+                if(IsOffChannel(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);
+                }
+            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)
@@ -5446,7 +6884,8 @@ static MODCMD_FUNC(chan_opt_defaults)
         reply("CSMSG_CONFIRM_DEFAULTS", channel->name, confirm);
         return 0;
     }
-    cData->flags = CHANNEL_DEFAULT_FLAGS;
+    cData->flags = (CHANNEL_DEFAULT_FLAGS & ~CHANNEL_PRESERVED_FLAGS)
+        | (cData->flags & CHANNEL_PRESERVED_FLAGS);
     cData->modes = chanserv_conf.default_modes;
     for(lvlOpt = 0; lvlOpt < NUM_LEVEL_OPTIONS; ++lvlOpt)
         cData->lvlOpts[lvlOpt] = levelOptions[lvlOpt].default_value;
@@ -5472,8 +6911,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);
@@ -5484,42 +6923,19 @@ channel_level_option(enum levelOption option, struct userNode *user, struct chan
         }
         switch(option)
         {
-            /* removing these level sets..
-        case lvlGiveVoice:
-            if(value > cData->lvlOpts[lvlGiveOps])
-            {
-                reply("CSMSG_BAD_GIVEVOICE", cData->lvlOpts[lvlGiveOps]);
-                return 0;
-            }
-            break;
-        case lvlGiveHalfOps:
-            if(value < cData->lvlOpts[lvlGiveVoice])
-            {
-                reply("CSMSG_BAD_GIVEHOPS", cData->lvlOpts[lvlGiveHalfOps]);
-                return 0;
-            }
-            break;
-        case lvlGiveOps:
-            if(value < cData->lvlOpts[lvlGiveVoice])
-            {
-                reply("CSMSG_BAD_GIVEOPS", cData->lvlOpts[lvlGiveVoice]);
-                return 0;
-            }
-            break;
-            */
-        case lvlSetters:
-            /* This test only applies to owners, since non-owners
-             * trying to set an option to above their level get caught
-             * by the CSMSG_BAD_SETLEVEL test above.
-             */
-            if(value > uData->access)
-            {
-                reply("CSMSG_BAD_SETTERS");
-                return 0;
-            }
-            break;
-        default:
-            break;
+            case lvlSetters:
+                /* This test only applies to owners, since non-owners
+                 * trying to set an option to above their level get caught
+                 * by the CSMSG_BAD_SETLEVEL test above.
+                 */
+                if(value > uData->access)
+                {
+                    reply("CSMSG_BAD_SETTERS");
+                    return 0;
+                }
+                break;
+            default:
+                break;
         }
         cData->lvlOpts[option] = value;
     }
@@ -5536,17 +6952,6 @@ static MODCMD_FUNC(chan_opt_enfhalfops)
 {
     return channel_level_option(lvlEnfHalfOps, CSFUNC_ARGS);
 }
-/*
-static MODCMD_FUNC(chan_opt_giveops)
-{
-    return channel_level_option(lvlGiveOps, CSFUNC_ARGS);
-}
-
-static MODCMD_FUNC(chan_opt_givehalfops)
-{
-    return channel_level_option(lvlGiveHalfOps, CSFUNC_ARGS);
-}
-*/
 static MODCMD_FUNC(chan_opt_enfmodes)
 {
     return channel_level_option(lvlEnfModes, CSFUNC_ARGS);
@@ -5567,22 +6972,10 @@ static MODCMD_FUNC(chan_opt_setters)
     return channel_level_option(lvlSetters, CSFUNC_ARGS);
 }
 
-static MODCMD_FUNC(chan_opt_ctcpusers)
-{
-    return channel_level_option(lvlCTCPUsers, CSFUNC_ARGS);
-}
-
-static MODCMD_FUNC(chan_opt_userinfo)
-{
-    return channel_level_option(lvlUserInfo, CSFUNC_ARGS);
-}
-
-/*
-static MODCMD_FUNC(chan_opt_givevoice)
+static MODCMD_FUNC(chan_opt_userinfo)
 {
-    return channel_level_option(lvlGiveVoice, CSFUNC_ARGS);
+    return channel_level_option(lvlUserInfo, CSFUNC_ARGS);
 }
-*/
 
 static MODCMD_FUNC(chan_opt_topicsnarf)
 {
@@ -5594,35 +6987,48 @@ static MODCMD_FUNC(chan_opt_inviteme)
     return channel_level_option(lvlInviteMe, CSFUNC_ARGS);
 }
 
+/* TODO:  Make look like this when no args are 
+ *        given:
+ *  -X3- -------------------------------
+ *  -X3- BanTimeout: Bans are removed: 
+ *  -X3- ----- * indicates current -----
+ *  -X3-   0: [*] Never.
+ *  -X3-   1: [ ] After 10 minutes.
+ *  -X3-   2: [ ] After 2 hours.
+ *  -X3-   3: [ ] After 4 hours.
+ *  -X3-   4: [ ] After 24 hours.
+ *  -X3-   5: [ ] After one week.
+ *  -X3- ------------- End -------------
+ */
 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;
@@ -5630,13 +7036,13 @@ 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;
 }
 
-static MODCMD_FUNC(chan_opt_voice)
+static MODCMD_FUNC(chan_opt_automode)
 {
-    return channel_multiple_option(chVoice, CSFUNC_ARGS);
+    return channel_multiple_option(chAutomode, CSFUNC_ARGS);
 }
 
 static MODCMD_FUNC(chan_opt_protect)
@@ -5654,15 +7060,25 @@ static MODCMD_FUNC(chan_opt_ctcpreaction)
     return channel_multiple_option(chCTCPReaction, CSFUNC_ARGS);
 }
 
+static MODCMD_FUNC(chan_opt_bantimeout)
+{
+    return channel_multiple_option(chBanTimeout, CSFUNC_ARGS);
+}
+
 static MODCMD_FUNC(chan_opt_topicrefresh)
 {
     return channel_multiple_option(chTopicRefresh, CSFUNC_ARGS);
 }
 
+static MODCMD_FUNC(chan_opt_resync)
+{
+    return channel_multiple_option(chResync, CSFUNC_ARGS);
+}
+
 static struct svccmd_list set_shows_list;
 
 static void
-handle_svccmd_unbind(struct svccmd *target) {
+handle_svccmd_unbind(struct svccmd *target, UNUSED_ARG(void *extra)) {
     unsigned int ii;
     for(ii=0; ii<set_shows_list.used; ++ii)
         if(target == set_shows_list.list[ii])
@@ -5699,15 +7115,16 @@ static CHANSERV_FUNC(cmd_set)
 
     if(argc < 2)
     {
-       reply("CSMSG_CHANNEL_OPTIONS", channel->name);
-        reply("CSMSG_BAR");
+        reply("CSMSG_CHANNEL_OPTIONS", channel->name);
+        if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
+            reply("CSMSG_BAR");
         for(ii = 0; ii < set_shows_list.used; ii++)
         {
             subcmd = set_shows_list.list[ii];
             subcmd->command->func(user, channel, 1, argv+1, subcmd);
         }
         reply("CSMSG_CHANNEL_OPTIONS_END");
-       return 1;
+    return 1;
     }
 
     sprintf(buf, "%s %s", argv[0], argv[1]);
@@ -5723,6 +7140,8 @@ static CHANSERV_FUNC(cmd_set)
         return 0;
     }
 
+    argv[0] = "";
+    argv[1] = buf;
     return subcmd->command->func(user, channel, argc - 1, argv + 1, subcmd);
 }
 
@@ -5740,7 +7159,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]))
     {
@@ -5770,18 +7189,26 @@ static MODCMD_FUNC(user_opt_autoop)
         reply("CSMSG_NOT_USER", channel->name);
         return 0;
     }
-    if(uData->access < UL_OP /*channel->channel_info->lvlOpts[lvlGiveOps]*/)
+    if(uData->access < UL_HALFOP /*channel->channel_info->lvlOpts[lvlGiveOps]*/)
         return user_binary_option("CSMSG_USET_AUTOVOICE", USER_AUTO_OP, CSFUNC_ARGS);
     else
         return user_binary_option("CSMSG_USET_AUTOOP", USER_AUTO_OP, CSFUNC_ARGS);
-    /* TODO: add halfops error message? or is the op one generic enough? */
 }
 
 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);
 }
 
+static MODCMD_FUNC(user_opt_autojoin)
+{
+    return user_binary_option("CSMSG_USET_AUTOJOIN", USER_AUTO_JOIN, CSFUNC_ARGS);
+}
+
 static MODCMD_FUNC(user_opt_info)
 {
     struct userData *uData;
@@ -5791,20 +7218,20 @@ 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)
     {
         size_t bp;
         infoline = unsplit_string(argv + 1, argc - 1, NULL);
-        if(strlen(infoline) > chanserv_conf.max_userinfo_length)
+        if(strlen(infoline) > channel->channel_info->maxsetinfo)
         {
-            reply("CSMSG_INFOLINE_TOO_LONG", chanserv_conf.max_userinfo_length);
+            reply("CSMSG_INFOLINE_TOO_LONG", channel->channel_info->maxsetinfo);
             return 0;
         }
         bp = strcspn(infoline, "\001");
@@ -5840,7 +7267,7 @@ static CHANSERV_FUNC(cmd_uset)
     {
         char *options[] =
         {
-            "AutoOp", "AutoInvite", "Info"
+            "AutoOp", "AutoInvite", "AutoJoin", "Info"
         };
 
         if(!uset_shows_list.size)
@@ -5864,11 +7291,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]);
@@ -5885,16 +7312,28 @@ static CHANSERV_FUNC(cmd_uset)
 static CHANSERV_FUNC(cmd_giveownership)
 {
     struct handle_info *new_owner_hi;
-    struct userData *new_owner, *curr_user;
+    struct userData *new_owner;
+    struct userData *curr_user;
+    struct userData *invoker;
     struct chanData *cData = channel->channel_info;
     struct do_not_register *dnr;
-    unsigned int force;
-    unsigned short co_access;
-    char reason[MAXLEN];
+    struct giveownership *giveownership;
+    const char *confirm;
+    unsigned int force, override;
+    unsigned short co_access, new_owner_old_access;
+    char 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;
@@ -5913,13 +7352,17 @@ static CHANSERV_FUNC(cmd_giveownership)
         }
         curr_user = owner;
     }
-    else if (!force && (now < (time_t)(cData->ownerTransfer + chanserv_conf.giveownership_period)))
+    else if(!force && (now < (time_t)(cData->ownerTransfer + chanserv_conf.giveownership_period)))
     {
         char delay[INTERVALLEN];
         intervalString(delay, cData->ownerTransfer + chanserv_conf.giveownership_period - now, user->handle_info);
         reply("CSMSG_TRANSFER_WAIT", delay, channel->name);
         return 0;
     }
+    if (!curr_user) {
+        reply("CSMSG_NO_OWNER", channel->name);
+        return 0;
+    }
     if(!(new_owner_hi = modcmd_get_handle_info(user, argv[1])))
         return 0;
     if(new_owner_hi == user->handle_info)
@@ -5932,7 +7375,7 @@ static CHANSERV_FUNC(cmd_giveownership)
     {
         if(force)
         {
-            new_owner = add_channel_user(cData, new_owner_hi, UL_COOWNER, 0, NULL);
+            new_owner = add_channel_user(cData, new_owner_hi, UL_OWNER - 1, 0, NULL, 0);
         }
         else
         {
@@ -5952,6 +7395,19 @@ static CHANSERV_FUNC(cmd_giveownership)
             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
@@ -5960,26 +7416,57 @@ static CHANSERV_FUNC(cmd_giveownership)
     if(curr_user)
         curr_user->access = co_access;
     cData->ownerTransfer = now;
+
+    giveownership = calloc(1, sizeof(*giveownership));
+    giveownership->issued = now;
+    giveownership->old_owner = strdup(curr_user->handle->handle);
+    giveownership->target = strdup(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);
+    global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_OWNERSHIP_TRANSFERRED",
+                       channel->name, new_owner_hi->handle, user->handle_info->handle);
     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 *self, *target;
+    struct userData *actor, *real_actor, *target;
+    unsigned int override = 0;
+    time_t expiry;
 
-    REQUIRE_PARAMS(2);
+    REQUIRE_PARAMS(3);
     if(!(hi = modcmd_get_handle_info(user, argv[1]))) return 0;
-    self = GetChannelUser(channel->channel_info, user->handle_info);
+    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 >= self->access)
+    if(target->access >= actor->access)
     {
         reply("MSG_USER_OUTRANKED", hi->handle);
         return 0;
@@ -5994,25 +7481,48 @@ static CHANSERV_FUNC(cmd_suspend)
         target->present = 0;
         target->seen = now;
     }
+    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);
+
+    if(!real_actor || target->access >= real_actor->access)
+        override = CMD_LOG_OVERRIDE;
     target->flags |= USER_SUSPENDED;
     reply("CSMSG_USER_SUSPENDED", hi->handle, channel->name);
-    return 1;
+    return 1 | override;
 }
 
 static CHANSERV_FUNC(cmd_unsuspend)
 {
     struct handle_info *hi;
-    struct userData *self, *target;
+    struct userData *actor = NULL, *real_actor = NULL, *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);
+    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 >= self->access)
+    if(target->access >= actor->access)
     {
         reply("MSG_USER_OUTRANKED", hi->handle);
         return 0;
@@ -6022,10 +7532,13 @@ static CHANSERV_FUNC(cmd_unsuspend)
         reply("CSMSG_NOT_SUSPENDED", hi->handle);
         return 0;
     }
+    if(!real_actor || target->access >= real_actor->access)
+        override = CMD_LOG_OVERRIDE;
     target->flags &= ~USER_SUSPENDED;
     scan_user_presence(target, NULL);
+    timeq_del(target->expires, chanserv_expire_user_suspension, target, 0);
     reply("CSMSG_USER_UNSUSPENDED", hi->handle, channel->name);
-    return 1;
+    return 1 | override;
 }
 
 static MODCMD_FUNC(cmd_deleteme)
@@ -6064,7 +7577,7 @@ static MODCMD_FUNC(cmd_deleteme)
 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;
 
@@ -6078,12 +7591,34 @@ chanserv_refresh_topics(UNUSED_ARG(void *data))
         if((refresh_num - cData->last_refresh) < (unsigned int)(1 << (opt - '1')))
             continue;
         if(cData->topic)
-            SetChannelTopic(cData->channel, chanserv, cData->topic, 1);
+            SetChannelTopic(cData->channel, chanserv, chanserv, cData->topic, 1);
         cData->last_refresh = refresh_num;
     }
     timeq_add(now + chanserv_conf.refresh_period, chanserv_refresh_topics, NULL);
 }
 
+static void
+chanserv_auto_resync(UNUSED_ARG(void *data))
+{
+    unsigned int refresh_num = (now - self->link_time) / chanserv_conf.refresh_period;
+    struct chanData *cData;
+    char opt;
+
+    for(cData = channelList; cData; cData = cData->next)
+    {
+        if(IsSuspended(cData)) 
+            continue;
+        opt = cData->chOpts[chResync];
+        if(opt == 'n') 
+            continue;
+        if((refresh_num - cData->last_resync) < (unsigned int)(1 << (opt - '1'))) 
+            continue;
+        resync_channel(cData->channel);
+        cData->last_resync = refresh_num;
+    }
+    timeq_add(now + chanserv_conf.refresh_period, chanserv_auto_resync, NULL);
+}
+
 static CHANSERV_FUNC(cmd_unf)
 {
     if(channel)
@@ -6126,6 +7661,340 @@ static CHANSERV_FUNC(cmd_wut)
     return 1;
 }
 
+static CHANSERV_FUNC(cmd_roulette)
+{
+    if(channel) 
+    {
+        struct chanData *cData = channel->channel_info;
+
+        if (cData) 
+        {
+            if (cData->roulette_chamber) 
+            {
+                DelUser(user, chanserv, 1, "BANG - Don't stuff bullets into a loaded gun");
+                return 1;
+            }
+        
+            send_target_message(1, channel->name, cmd->parent->bot, "CSMSG_ROULETTE_LOADS");
+            cData->roulette_chamber = 1 + rand() % 6;
+        }
+    }
+
+    return 1;
+}
+static CHANSERV_FUNC(cmd_shoot)
+{
+    if(channel) 
+    {
+        struct chanData *cData = channel->channel_info;
+
+        if (cData->roulette_chamber <= 0) 
+        {
+            struct service *service;
+            if ((service = service_find(chanserv->nick))) 
+            {
+                reply("CSMSG_ROULETTE_NEW", service->trigger);
+            }
+            return 1;
+        }
+
+        cData->roulette_chamber--;
+
+        if (cData->roulette_chamber == 0) 
+        {
+            reply("CSMSG_ROULETTE_BANG");
+            reply("CSMSG_ROULETTE_BETTER_LUCK", user->nick);
+            DelUser(user, chanserv, 1, "BANG!!!!");
+        } 
+        else
+            reply("CSMSG_ROULETTE_CLICK");
+    }
+
+    return 1;
+}
+
+static void
+chanserv_remove_abuse(void *data)
+{
+    char *remnick = data;
+    struct userNode *user;
+    /* sometimes the clone was killed and maybe even the user took their nick back 
+     * (ie, an oper) so dont kill them here after all unless they are local. */
+    if( (user = GetUserH(remnick)) )
+       if(IsLocal(user) )
+         DelUser(user, NULL, 1, "");
+}
+
+int lamepart(struct userNode *nick) {
+    struct modeNode *mn;
+    unsigned int count, n;
+
+    for (n=count=0; n<nick->channels.used; n++) 
+    {
+        mn = nick->channels.list[n];
+        irc_svspart(chanserv, nick, mn->channel);
+    }
+
+    return 0;
+}
+
+static CHANSERV_FUNC(cmd_spin)
+{
+    if(!channel)
+        return 1;
+      
+    int type = 0, lamep = 1;
+    char *tstr;
+
+    tstr = conf_get_data("server/type", RECDB_QSTRING);
+    if(tstr) {
+        type = atoi(tstr);
+        if (type > 6)
+            lamep = 0;
+    }
+
+
+    send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_WHEEL1", user->nick);
+    send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_WHEEL2");
+    send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_WHEEL3");
+
+    if(chanserv_conf.wheel->used < 1) 
+    {
+        /* wheel actions not defined! eek */
+        return 1;
+    }
+
+    const char *wheel = chanserv_conf.wheel->list[ (int) ( (chanserv_conf.wheel->used) * (rand() / (RAND_MAX + 1.0)) ) ];
+    if(!wheel && *wheel) 
+        return 1;
+
+    /* connection reset by peer */
+    if (!strcasecmp(wheel, "peer")) 
+    {
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_PEER");
+         if (type < 7)
+              irc_kill(chanserv, user, "Connection reset by peer");
+         else
+              irc_svsquit(chanserv, user, "Connection reset by peer");
+    }
+    /* part all channels */
+    else if (!strcasecmp(wheel, "partall")) 
+    {
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_PARTALL");
+         if (lamep)
+             lamepart(user);
+         else
+             sputsock("%s SJ %s 0 "FMT_TIME_T, self->numeric, user->numeric, now);
+    }
+    /* random time gline */
+    else if (!strcasecmp(wheel, "gline")) 
+    {
+         char target[HOSTLEN + 3];
+         int wtime = 120 + rand() % 600;
+
+         strcpy(target, "*@");
+         strcat(target, user->hostname);
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_GLINE");
+
+         gline_add(chanserv->nick, target, wtime, "Reward for spinning the wheel of misfortune!", now, 1, 0);
+    }
+    /* random shun */
+    else if (!strcasecmp(wheel, "shun")) 
+    {
+         char target[HOSTLEN + 3];
+         int wtime = 120 + rand() % 600;
+
+         strcpy(target, "*@");
+         strcat(target, user->hostname);
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_SHUN");
+
+         shun_add(chanserv->nick, target, wtime, "Reward for spinning the wheel of misfortune!", now, 1);
+    }
+    /* absolutely nothing */
+    else if (!strcasecmp(wheel, "nothing")) 
+    {
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_NOTHING");
+    }
+    /* join random chans and part em several times */
+    else if (!strcasecmp(wheel, "randjoin")) 
+    {
+         int complete = 0;
+         int rndchans = 0;
+         int chango = 0;
+         int roundz0r = 0;
+
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_RANDJOIN");
+         while(complete != 1)  
+         {
+            if (rndchans != 15) 
+            {
+                chango = 120 + rand() % 600;
+                sputsock("%s SJ %s #%d "FMT_TIME_T, self->numeric, user->numeric, chango, now);
+                rndchans++;
+            } 
+            else 
+            {
+                if (roundz0r != 1) 
+                {
+                     if (lamep)
+                         lamepart(user);
+                     else
+                         sputsock("%s SJ %s 0 "FMT_TIME_T, self->numeric, user->numeric, now);
+                     roundz0r = 1;
+                     rndchans = 0;
+                } 
+                else 
+                {
+                     if (lamep)
+                         lamepart(user);
+                     else
+                         sputsock("%s SJ %s 0 "FMT_TIME_T, self->numeric, user->numeric, now);
+                     complete = 1;
+                }
+            }
+        }
+    }
+    /* abuse line added to /whois */
+    else if (!strcasecmp(wheel, "abusewhois")) 
+    {
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_ABUSEWHOIS");
+         irc_swhois(chanserv, user, "is being defecated on by services");
+    }
+    /* kick from each channel your in */
+    else if (!strcasecmp(wheel, "kickall")) 
+    {
+         unsigned int count, n;
+         struct modeNode *mn;
+
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_KICKALL");
+
+         for (n=count=0; n<user->channels.used; n++) {
+             mn = user->channels.list[n];
+             irc_kick(chanserv, user, mn->channel, "Reward for spinning the wheel of misfortune!");
+         }
+    }
+    /* random nick change */
+    else if (!strcasecmp(wheel, "nickchange")) 
+    {
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_NICKCHANGE");
+
+         char *oldnick = NULL;
+         char *oldident = NULL;
+         char abusednick[NICKLEN] = "";
+         int abusednum = 1 + (int) (10000.0 * (rand() / (RAND_MAX + 1.0)));
+         struct userNode *clone;
+
+         oldnick = strdup(user->nick);
+         oldident = strdup(user->ident);
+
+         //snprintf(abusednick, NICKLEN, "Abused%d", abusednum+(1 + rand() % 120));
+         while (1) 
+         {
+             snprintf(abusednick, NICKLEN, "Abused%d", abusednum+(1 + rand() % 120));
+             log_module(MAIN_LOG, LOG_DEBUG, "Abused Nick: %s, Client Nick: %s", abusednick, user->nick);
+             if(!GetUserH(abusednick))
+               break;
+         }
+
+         SVSNickChange(user, abusednick);
+         irc_svsnick(chanserv, user, abusednick);
+         clone = AddLocalUser(oldnick, oldident, "abused.by.wheel.of.misfortune", "I got abused by the wheel of misfortune :D", "+i");
+         timeq_add(now + 300, chanserv_remove_abuse, clone->nick);
+    }
+    /* kill */
+    else if (!strcasecmp(wheel, "kill")) 
+    {
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_KILL");
+
+         DelUser(user, chanserv, 1, "Reward for spinning the wheel of misfortune!");
+         //irc_kill(chanserv, user, "Reward for spinning the wheel of misfortune!");
+    }
+    /* service ignore */
+    else if (!strcasecmp(wheel, "svsignore")) 
+    {
+         //int gagged;
+         int ignoretime = 0;
+         char target[HOSTLEN + 13];
+
+         if(IsOper(user)) {
+            /* we cant gag opers, so just verbally abuse them */
+            send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_SVSIGNORE_OPER");
+            return 1;
+         }
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_SVSIGNORE");
+
+         strcpy(target, "*!*@");
+         strcat(target, user->hostname);
+         ignoretime = now + (1 + rand() % 120);
+
+         gag_create(target, "wheelofabuse", "Reward for spinning the wheel of misfortune!", ignoretime);
+    }
+    /* kick and ban from each channel your in */
+    else if (!strcasecmp(wheel, "kickbanall")) 
+    {
+         unsigned int count, n;
+         struct modeNode *mn;
+         //char ban[HOSTLEN + 1];
+
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_KICKBANALL");
+
+         //snprintf(ban, sizeof(ban), "*!*@%s", user->hostname);
+         for (n=count=0; n<user->channels.used; n++) 
+         {
+             struct mod_chanmode *change;
+/*           struct banData *bData; */
+             unsigned int exists;
+/*           int duration = 300; */
+             char *ban;
+
+             ban = generate_hostmask(user, GENMASK_STRICT_HOST|GENMASK_ANY_IDENT|GENMASK_USENICK);
+
+             log_module(MAIN_LOG, LOG_DEBUG, "Generated ban %s", ban);
+             mn = user->channels.list[n];
+             if(mn->channel->banlist.used >= MAXBANS) 
+             {
+                 reply("CSMSG_BANLIST_FULL", mn->channel->name);
+                 free(ban);
+                 continue;
+             }
+
+/*           bData = add_channel_ban(mn->channel->channel_info, ban, chanserv->nick, now, now, now + duration, "Reward for spinning the wheel of misfortune!"); */
+
+             change = mod_chanmode_alloc(1);
+             change->args[0].mode = MODE_REMOVE|MODE_CHANOP|MODE_HALFOP|MODE_VOICE;
+             change->args[0].u.member = GetUserMode(mn->channel, user);
+             change->argc = 1;
+
+             mod_chanmode_announce(chanserv, mn->channel, change);
+             mod_chanmode_free(change);
+
+             exists = ChannelBanExists(mn->channel, ban);
+             if(!exists) {
+                 change = mod_chanmode_alloc(1);
+                 change->args[0].mode = MODE_BAN;
+                 change->args[0].u.hostmask = ban;
+                 change->argc = 1;
+                 mod_chanmode_announce(chanserv, mn->channel, change);
+                 mod_chanmode_free(change);
+             }
+
+             if(exists) {
+                 reply("CSMSG_REDUNDANT_BAN", ban, mn->channel->name);
+                 free(ban);
+             }
+
+             irc_kick(chanserv, user, mn->channel, "Reward for spinning the wheel of misfortune!");
+         }
+    }
+    else 
+    {
+       send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_UNKNOWN", wheel);
+    }
+
+    return 1;
+}
+
+#ifdef lame8ball
 static CHANSERV_FUNC(cmd_8ball)
 {
     unsigned int i, j, accum;
@@ -6140,13 +8009,177 @@ static CHANSERV_FUNC(cmd_8ball)
     if(channel)
     {
         char response[MAXLEN];
-        sprintf(response, "\ 2%s\ 2: %s", user->nick, resp);
+        sprintf(response, "\ 2%s\ 2: %s", user->nick, resp);
+        irc_privmsg(cmd->parent->bot, channel->name, response);
+    }
+    else
+        send_message_type(4, user, cmd->parent->bot, "%s", resp);
+    return 1;
+}
+
+#else /* Use cool 8ball instead */
+
+void eightball(char *outcome, int method, unsigned int seed)
+{
+   int answer = 0;
+
+#define NUMOFCOLORS 18
+   char ballcolors[50][50] = {
+        "blue", "red", "green", "yellow",
+        "white", "black", "grey", "brown",
+        "yellow", "pink", "purple", "orange", "teal", "burgundy",
+        "fuchsia","turquoise","magenta", "cyan"};
+#define NUMOFLOCATIONS 50
+   char balllocations[50][55] = { 
+        "Locke's house", "Oregon", "California", "Indiana", "Canada",
+        "Russia", "Japan", "Florida", "the Bahamas", "Hiroshima",
+        "the Caribbean", "the Everglades", "your head", "your pants", "your school",
+        "the Statue of Liberty", "Mt. Fugi", "your mother's house", "IRC", "OSU",
+        "Locke's cat", "the closet", "the washroom", "the lake", "Spain",
+        "the bathtub", "the toilet", "the sewer", "a horse", "Jupiter",
+        "Uranus", "Pluto", "a dark place", "your undies", "your shirt",
+        "your bra", "your hair", "your bed", "the couch", "the wall", 
+        "Reed", "here --> [X]", "your brain", "Italy", "the Netherlands", 
+        "Mars", "my hardware", "the bar", "Neverland Ranch", "Germany" };
+#define NUMOFPREPS 15
+   char ballpreps[50][50] = { 
+        "Near", "Somewhere near", "In", "In", "In", 
+        "In", "Hiding in", "Under", "Next to", "Over", 
+        "Crying in", "Right beside", "Nowhere near", "North of", "Trying to find"};
+#define NUMOFNUMS 34
+   char ballnums[50][50] = { 
+        "A hundred", "A thousand", "A few", "42",
+        "About 1", "About 2", "About 3", "About 4", "About 5", "About 6", "About 7", "About 8", "About 9", "About 10",
+        "1", "2", "3", "4", "5", "6", "7", "8", "9", "Ten",
+        "1", "2", "3", "4", "5", "6", "7", "8", "9", "Ten",
+        };
+#define NUMOFMULTS 8
+   char ballmults[50][50] = { " million", " or so", " thousand", "", " or less", " or more", "", ""};
+
+   /* Method:
+    * 0: normal  (Not used in x3)
+    * 1: color
+    * 2: where is
+    * 3: how many
+    */
+
+    srand(seed);
+    if (method == 1) /* A Color */
+    {
+      char tmp[MAXLEN];
+
+      answer = (rand() % 12); /* Make sure this is the # of entries */
+      switch(answer)
+      {
+        case 0: strcpy(tmp, "Very bright %s, I'd say.");
+                break;
+        case 1: strcpy(tmp, "Sort of a light %s color.");
+                break;
+        case 2: strcpy(tmp, "Dark and dreary %s.");
+                break;
+        case 3: strcpy(tmp, "Quite a pale shade of %s.");
+                break;
+        case 4: strcpy(tmp, "A gross kind of mucky %s.");
+                break;
+        case 5: strcpy(tmp, "Brilliant whiteish %s.");
+                break;
+        case 6: case 7: case 8: case 9: strcpy(tmp, "%s.");
+                break;
+        case 10: strcpy(tmp, "Solid %s.");
+                break;
+        case 11: strcpy(tmp, "Transparent %s.");
+                break;
+        default: strcpy(outcome, "An invalid random number was generated.");
+                return;
+      }
+      sprintf(outcome, tmp, ballcolors[rand() % NUMOFCOLORS]);
+      return;
+    }
+    else if (method == 2)  /* Location */
+    {
+       sprintf(outcome, "%s %s.", ballpreps[rand() % NUMOFPREPS], balllocations[rand() % NUMOFLOCATIONS]);
+    }
+    else if (method == 3)  /* Number of ___ */
+    {
+       sprintf(outcome, "%s%s.", ballnums[rand() % NUMOFNUMS], ballmults[rand() % NUMOFMULTS]);
+    }
+    else
+    {
+      //Debug(DBGWARNING, "Error in 8ball.");
+    }
+    return;
+}
+
+static CHANSERV_FUNC(cmd_8ball)
+{
+    char *word1, *word2, *word3;
+    static char eb[MAXLEN];
+    unsigned int accum, i, j;
+
+    REQUIRE_PARAMS(2);
+    accum = 0;
+    for(i=1; i<argc; i++)
+        for(j=0; argv[i][j]; j++)
+            accum = (accum << 5) - accum + toupper(argv[i][j]);
+
+    accum += time(NULL)/3600;
+    word1 = argv[1];
+    word2 = argc>2?argv[2]:"";
+    word3 = argc>3?argv[3]:"";
+
+/*** COLOR *****/
+    if((word2) && strcasecmp(word1, "what") == 0 && ((strcasecmp(word2, "color") == 0) || (strcasecmp(word2, "colour") == 0)))
+        eightball(eb, 1, accum);
+    else if((word3) && strcasecmp(word1, "what's") == 0 && strcasecmp(word2, "the") == 0 && ((strcasecmp(word2, "color") == 0) || (strcasecmp(word2, "colour") == 0)))
+        eightball(eb, 1, accum);
+    else if((word3) && strcasecmp(word1, "whats") == 0 && strcasecmp(word2, "the") == 0 && ((strcasecmp(word2, "color") == 0) || (strcasecmp(word2, "colour") == 0)))
+        eightball(eb, 1, accum);
+/*** LOCATION *****/
+    else if(
+           (
+             word2 &&
+             (
+                (strcasecmp(word1, "where") == 0) &&
+                (strcasecmp(word2, "is") == 0)
+             )
+           ) ||
+             (
+                   strcasecmp(word1, "where's") == 0
+             )
+         )
+     eightball(eb, 2, accum);
+/*** NUMBER *****/
+    else if((word2) && strcasecmp(word1, "how") == 0 && strcasecmp(word2, "many") == 0)
+        eightball(eb, 3, accum);
+/*** GENERIC *****/
+    else
+    {
+        /* Generic 8ball question.. so pull from x3.conf srvx style */
+        const char *resp;
+
+        resp = chanserv_conf.eightball->list[accum % chanserv_conf.eightball->used];
+        if(channel)
+        {
+            char response[MAXLEN];
+            sprintf(response, "\002%s\002: %s", user->nick, resp);
+            irc_privmsg(cmd->parent->bot, channel->name, response);
+        }
+        else
+            send_message_type(4, user, cmd->parent->bot, "%s", resp);
+        return 1;
+  }
+
+  if(channel)
+  {
+        char response[MAXLEN];
+        sprintf(response, "\002%s\002: %s", user->nick, eb);
         irc_privmsg(cmd->parent->bot, channel->name, response);
-    }
-    else
-        send_message_type(4, user, cmd->parent->bot, "%s", resp);
-    return 1;
+  }
+  else
+        send_message_type(4, user, cmd->parent->bot, "%s", eb);
+  return 1;
 }
+#endif
 
 static CHANSERV_FUNC(cmd_d)
 {
@@ -6233,6 +8266,19 @@ static CHANSERV_FUNC(cmd_calc)
     return 1;
 }
 
+static CHANSERV_FUNC(cmd_reply)
+{
+
+    REQUIRE_PARAMS(2);
+    unsplit_string(argv + 1, argc - 1, NULL);
+
+    if(channel)
+        send_channel_message(channel, cmd->parent->bot, "$b%s$b: %s", user->nick, unsplit_string(argv + 1, argc - 1, NULL));
+    else
+        send_message_type(4, user, cmd->parent->bot, "%s", unsplit_string(argv + 1, argc - 1, NULL));
+    return 1;
+}
+
 static void
 chanserv_adjust_limit(void *data)
 {
@@ -6261,7 +8307,7 @@ chanserv_adjust_limit(void *data)
 }
 
 static void
-handle_new_channel(struct chanNode *channel)
+handle_new_channel(struct chanNode *channel, UNUSED_ARG(void *extra))
 {
     struct chanData *cData;
 
@@ -6272,13 +8318,108 @@ handle_new_channel(struct chanNode *channel)
         mod_chanmode_announce(chanserv, cData->channel, &cData->modes);
 
     if(self->uplink && !self->uplink->burst && channel->channel_info->topic)
-        SetChannelTopic(channel, chanserv, channel->channel_info->topic, 1);
+        SetChannelTopic(channel, chanserv, chanserv, channel->channel_info->topic, 1);
+}
+
+int
+trace_check_bans(struct userNode *user, struct chanNode *chan)
+{
+    struct banData *bData;
+    struct mod_chanmode *change;
+
+    change = find_matching_bans(&chan->banlist, user, NULL);
+    if (change)
+       return 1;
+
+    /* lamer list */
+    if (chan->channel_info) {
+        for(bData = chan->channel_info->bans; bData; bData = bData->next) {
+
+            if(!user_matches_glob(user, bData->mask, MATCH_USENICK, 0))
+                continue;
+
+            if(bData)
+               return 1;
+        }
+    }
+
+    return 0;
+}
+
+int
+check_bans(struct userNode *user, const char *channel)
+{
+    struct chanNode *chan;
+    struct mod_chanmode change;
+    struct chanData *cData;
+    struct banData *bData;
+
+    if (!(chan = GetChannel(channel)))
+        return 0;
+
+    if(!(cData = chan->channel_info))
+        return 0;
+
+    mod_chanmode_init(&change);
+    change.argc = 1;
+
+    if(chan->banlist.used < MAXBANS)
+    {
+        /* Not joining through a ban. */
+        for(bData = cData->bans;
+            bData && !user_matches_glob(user, bData->mask, MATCH_USENICK, 0);
+            bData = bData->next);
+
+        if(bData)
+        {
+            char kick_reason[MAXLEN];
+            sprintf(kick_reason, "(%s) %s", bData->owner, bData->reason);
+
+            bData->triggered = now;
+            if(bData != cData->bans)
+            {
+                /* Shuffle the ban to the head of the list. */
+                if(bData->next)
+                    bData->next->prev = bData->prev;
+                if(bData->prev)
+                    bData->prev->next = bData->next;
+
+                bData->prev = NULL;
+                bData->next = cData->bans;
+
+                if(cData->bans)
+                    cData->bans->prev = bData;
+                
+                cData->bans = bData;
+            }
+
+            change.args[0].mode = MODE_BAN;
+            change.args[0].u.hostmask = bData->mask;
+            mod_chanmode_announce(chanserv, chan, &change);
+            KickChannelUser(user, chan, chanserv, kick_reason);
+            return 1;
+        }
+    }
+    return 0;
+}
+
+int
+channel_user_is_exempt(struct userNode *user, struct chanNode *channel)
+{
+   unsigned int ii;
+   for(ii = 0; ii < channel->exemptlist.used; ii++)
+   {
+       if(user_matches_glob(user, channel->exemptlist.list[ii]->exempt, MATCH_USENICK, 0))
+           return true;
+   }
+   return false;
 }
 
+
 /* Welcome to my worst nightmare. Warning: Read (or modify)
    the code below at your own risk. */
 static int
-handle_join(struct modeNode *mNode)
+handle_join(struct modeNode *mNode, UNUSED_ARG(void *extra))
 {
     struct mod_chanmode change;
     struct userNode *user = mNode->user;
@@ -6290,13 +8431,14 @@ handle_join(struct modeNode *mNode)
     unsigned int modes = 0, info = 0;
     char *greeting;
 
-    if(IsLocal(user) || !channel->channel_info || IsSuspended(channel->channel_info))
+    if(IsLocal(user) || !channel || !channel->channel_info || IsSuspended(channel->channel_info))
         return 0;
 
     cData = channel->channel_info;
     if(channel->members.used > cData->max)
         cData->max = channel->members.used;
 
+#ifdef notdef
     /* Check for bans.  If they're joining through a ban, one of two
      * cases applies:
      *   1: Join during a netburst, by riding the break.  Kick them
@@ -6307,62 +8449,73 @@ handle_join(struct modeNode *mNode)
      * full, see if they're on the banlist for the channel.  If so,
      * kickban them.
      */
-    /* This is really, really stupid. not all banned people are kicked. 
-     * sometimes we like to leave them unkicked. 
-     * I tried to explain this to the srvx developers and 
-     * got insulted.. hence one reason for this fork.
-     *
     if(user->uplink->burst && !mNode->modes)
     {
         unsigned int ii;
         for(ii = 0; ii < channel->banlist.used; ii++)
         {
-            if(user_matches_glob(user, channel->banlist.list[ii]->ban, 1))
+            if(user_matches_glob(user, channel->banlist.list[ii]->ban, MATCH_USENICK, 0))
             {
-                ** Riding a netburst.  Naughty. **
+                /* Riding a netburst.  Naughty. */
                 KickChannelUser(user, channel, chanserv, "User from far side of netsplit should have been banned - bye.");
                 return 1;
             }
         }
     }
-    */
+#endif
+
+    if(user->handle_info)
+    {
+        handle = user->handle_info;
+        if(handle)
+        {
+            uData = GetTrueChannelAccess(cData, handle);
+        }
+    }
+
 
     mod_chanmode_init(&change);
     change.argc = 1;
-    if(channel->banlist.used < MAXBANS)
-    {
-        /* Not joining through a ban. */
-        for(bData = cData->bans;
-                bData && !user_matches_glob(user, bData->mask, 1);
-                bData = bData->next);
 
-        if(bData)
+    /* TODO: maybe only people above inviteme level? -Rubin */
+    /* We don't kick people with access */
+    if(!uData && !channel_user_is_exempt(user, channel))
+    {
+        if(channel->banlist.used < MAXBANS)
         {
-            char kick_reason[MAXLEN];
-            sprintf(kick_reason, "(%s) %s", bData->owner, bData->reason);
+            /* Not joining through a ban. */
+            for(bData = cData->bans;
+                bData && !user_matches_glob(user, bData->mask, MATCH_USENICK, 0);
+                bData = bData->next);
 
-            bData->triggered = now;
-            if(bData != cData->bans)
+            if(bData)
             {
-                /* Shuffle the ban to the head of the list. */
-                if(bData->next)
-                    bData->next->prev = bData->prev;
-                if(bData->prev)
-                    bData->prev->next = bData->next;
+                char kick_reason[MAXLEN];
+                sprintf(kick_reason, "(%s) %s", bData->owner, bData->reason);
 
-                bData->prev = NULL;
-                bData->next = cData->bans;
+                bData->triggered = now;
+                if(bData != cData->bans)
+                {
+                    /* Shuffle the ban to the head of the list. */
+                    if(bData->next)
+                        bData->next->prev = bData->prev;
+                    if(bData->prev)
+                        bData->prev->next = bData->next;
+
+                    bData->prev = NULL;
+                    bData->next = cData->bans;
+
+                    if(cData->bans)
+                        cData->bans->prev = bData;
+                    cData->bans = bData;
+                }
 
-                if(cData->bans)
-                    cData->bans->prev = bData;
-                cData->bans = bData;
+                change.args[0].mode = MODE_BAN;
+                change.args[0].u.hostmask = bData->mask;
+                mod_chanmode_announce(chanserv, channel, &change);
+                KickChannelUser(user, channel, chanserv, kick_reason);
+                return 1;
             }
-
-            change.args[0].mode = MODE_BAN;
-            change.args[0].u.hostmask = bData->mask;
-            mod_chanmode_announce(chanserv, channel, &change);
-            KickChannelUser(user, channel, chanserv, kick_reason);
-            return 1;
         }
     }
 
@@ -6383,18 +8536,21 @@ handle_join(struct modeNode *mNode)
         timeq_add(now + chanserv_conf.adjust_delay, chanserv_adjust_limit, cData);
     }
 
-    if(channel->join_flooded)
+    /* Give automodes exept during join-floods */
+    if(!channel->join_flooded)
     {
-        /* don't automatically give non users ops or voice during a join flood */
+        if(cData->chOpts[chAutomode] == 'v')
+            modes |= MODE_VOICE;
+        else if(cData->chOpts[chAutomode] == 'h')
+            modes |= MODE_HALFOP;
+        else if(cData->chOpts[chAutomode] == 'o')
+            modes |= MODE_CHANOP;
     }
-    /* EVERYONE is to get voice */
-    else if(cData->chOpts[chVoice] == 'a')
-        modes |= MODE_VOICE;
 
     greeting = cData->greeting;
     if(user->handle_info)
     {
-        handle = user->handle_info;
+/*        handle = user->handle_info; */
 
         if(IsHelper(user) && !IsHelping(user))
         {
@@ -6409,17 +8565,21 @@ handle_join(struct modeNode *mNode)
             }
         }
 
-        uData = GetTrueChannelAccess(cData, handle);
+/*        uData = GetTrueChannelAccess(cData, handle); */
         if(uData && !IsUserSuspended(uData))
         {
-            /* non users getting voice are handled above. */
-            if(IsUserAutoOp(uData))
+            /* non users getting automodes are handled above. */
+            if(IsUserAutoOp(uData) && cData->chOpts[chAutomode] != 'n')
             {
-                if(uData->access >= UL_OP /*cData->lvlOpts[lvlGiveOps]*/)
+                /* just op everyone with access */
+                if(uData->access >= UL_PEON && cData->chOpts[chAutomode] == 'l')
+                    modes |= MODE_VOICE;
+                /* or do their access level */
+                else if(uData->access >= UL_OP )
                     modes |= MODE_CHANOP;
-                if(uData->access >= UL_HALFOP /*cData->lvlOpts[lvlGiveHalfOps]*/)
+                else if(uData->access >= UL_HALFOP )
                     modes |= MODE_HALFOP;
-                else if(uData->access >= UL_PEON && cData->chOpts[chVoice] == 'p')
+                else if(uData->access >= UL_PEON && cData->chOpts[chAutomode] != 'm')
                     modes |= MODE_VOICE;
             }
             if(uData->access >= UL_PRESENT)
@@ -6435,19 +8595,25 @@ handle_join(struct modeNode *mNode)
             uData->present = 1;
         }
     }
+
+    /* If user joining normally (not during burst), apply op or voice,
+     * and send greeting/userinfo as appropriate.
+     */
     if(!user->uplink->burst)
     {
         if(modes)
         {
+            /* -- I'd rather have ops get voice too, if automode is v. -Rubin
             if(modes & MODE_CHANOP) {
                 modes &= ~MODE_HALFOP;
                 modes &= ~MODE_VOICE;
             }
+            */
             change.args[0].mode = modes;
             change.args[0].u.member = mNode;
             mod_chanmode_announce(chanserv, channel, &change);
         }
-        if(greeting && !user->uplink->burst)
+        if(greeting)
             send_message_type(4, user, chanserv, "(%s) %s", channel->name, greeting);
         if(uData && info)
             send_target_message(5, channel->name, chanserv, "[%s] %s", user->nick, uData->info);
@@ -6456,26 +8622,56 @@ handle_join(struct modeNode *mNode)
 }
 
 static void
-handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
+chanserv_autojoin_channels(struct userNode *user)
+{
+    struct userData *channel;
+
+    for(channel = user->handle_info->channels; channel; channel = channel->u_next)
+    {
+        struct chanNode *cn;
+        struct modeNode *mn;
+
+        if(IsUserSuspended(channel)
+           || IsSuspended(channel->channel)
+           || !(cn = channel->channel->channel))
+            continue;
+
+        mn = GetUserMode(cn, user);
+        if(!mn)
+        {
+            if(!IsUserSuspended(channel)
+               && IsUserAutoJoin(channel)
+               && (channel->access >= channel->channel->lvlOpts[lvlInviteMe])
+               && !self->burst
+               && !user->uplink->burst)
+                irc_svsjoin(chanserv, user, cn);
+        }
+    }
+}
+
+static void
+handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle), UNUSED_ARG(void *extra))
 {
     struct mod_chanmode change;
     struct userData *channel;
-    unsigned int ii, jj;
+    unsigned int ii, jj, i;
 
     if(!user->handle_info)
-       return;
+    return;
 
     mod_chanmode_init(&change);
     change.argc = 1;
     for(channel = user->handle_info->channels; channel; channel = channel->u_next)
     {
         struct chanNode *cn;
+        struct chanData *cData;
         struct modeNode *mn;
         if(IsUserSuspended(channel)
            || IsSuspended(channel->channel)
            || !(cn = channel->channel->channel))
             continue;
 
+        cData = cn->channel_info;
         mn = GetUserMode(cn, user);
         if(!mn)
         {
@@ -6488,16 +8684,18 @@ 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)
+        channel->channel->visited = now;
 
-        if(IsUserAutoOp(channel))
+        if(IsUserAutoOp(channel) && cData->chOpts[chAutomode] != 'n')
         {
-            if(channel->access >= UL_OP /* cn->channel_info->lvlOpts[lvlGiveOps] */)
+            if (channel->access >= UL_PEON && cData->chOpts[chAutomode] == 'l')
+                change.args[0].mode = MODE_VOICE;
+            else if(channel->access >= UL_OP )
                 change.args[0].mode = MODE_CHANOP;
-            else if(channel->access >= UL_HALFOP /* cn->channel_info->lvlOpts[lvlGiveHalfOps]*/)
+            else if(channel->access >= UL_HALFOP )
                 change.args[0].mode = MODE_HALFOP;
-            else if(channel->access >= UL_PEON /* cn->channel_info->lvlOpts[lvlGiveVoice]*/)
+            else if(channel->access >= UL_PEON )
                 change.args[0].mode = MODE_VOICE;
             else
                 change.args[0].mode = 0;
@@ -6506,34 +8704,36 @@ 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_HALFOP|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, 1))
+        if(protect_user(user, chanserv, chan->channel_info, true))
+            continue;
+        for(jj = 0; jj < chan->banlist.used; ++jj)
+            if(user_matches_glob(user, chan->banlist.list[jj]->ban, MATCH_USENICK, 0))
                 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, 1))
+            if(!user_matches_glob(user, ban->mask,MATCH_USENICK | MATCH_VISIBLE, 0))
                 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;
         }
@@ -6550,10 +8750,24 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
             }
         }
     }
+
+    if (user->handle_info->ignores->used) {
+        for (i=0; i < user->handle_info->ignores->used; i++) {
+            irc_silence(user, user->handle_info->ignores->list[i], 1);
+        }
+    }
+
+    if (user->handle_info->epithet)
+        irc_swhois(chanserv, user, user->handle_info->epithet);
+
+    /* process autojoin channels 5 seconds later as this sometimes 
+       happens before autohide */
+    //timeq_add(now + 5, chanserv_autojoin_channels, user);
+    chanserv_autojoin_channels(user);
 }
 
 static void
-handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason))
+handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason), UNUSED_ARG(void *extra))
 {
     struct chanData *cData;
     struct userData *uData;
@@ -6564,30 +8778,37 @@ 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)
+            cData->visited = now; 
     }
 
     if(IsHelping(mn->user) && IsSupportHelper(mn->user))
     {
-        unsigned int ii, jj;
-        for(ii = 0; ii < chanserv_conf.support_channels.used; ++ii)
+        unsigned int ii;
+        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)
+            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)
@@ -6596,7 +8817,7 @@ handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason))
 }
 
 static void
-handle_kick(struct userNode *kicker, struct userNode *victim, struct chanNode *channel)
+handle_kick(struct userNode *kicker, struct userNode *victim, struct chanNode *channel, UNUSED_ARG(void *extra))
 {
     struct userData *uData;
 
@@ -6605,10 +8826,10 @@ handle_kick(struct userNode *kicker, struct userNode *victim, struct chanNode *c
        || (kicker->handle_info && kicker->handle_info == victim->handle_info))
         return;
 
-    if(protect_user(victim, kicker, channel->channel_info))
+    if(protect_user(victim, kicker, channel->channel_info, false))
     {
-        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_KICK");
+        KickChannelUser(kicker, channel, chanserv, reason);
     }
 
     if((uData = GetTrueChannelAccess(channel->channel_info, victim->handle_info)))
@@ -6616,7 +8837,7 @@ handle_kick(struct userNode *kicker, struct userNode *victim, struct chanNode *c
 }
 
 static int
-handle_topic(struct userNode *user, struct chanNode *channel, const char *old_topic)
+handle_topic(struct userNode *user, struct chanNode *channel, const char *old_topic, UNUSED_ARG(void *extra))
 {
     struct chanData *cData;
 
@@ -6627,7 +8848,7 @@ handle_topic(struct userNode *user, struct chanNode *channel, const char *old_to
     if(bad_topic(channel, user, channel->topic))
     {   /* User doesnt have privs to set topics. Undo it */
         send_message(user, chanserv, "CSMSG_TOPIC_LOCKED", channel->name);
-        SetChannelTopic(channel, chanserv, old_topic, 1);
+        SetChannelTopic(channel, chanserv, chanserv, old_topic, 1);
         return 1;
     }
     /* If there is a topic mask set, and the new topic doesnt match,
@@ -6638,12 +8859,12 @@ handle_topic(struct userNode *user, struct chanNode *channel, const char *old_to
         conform_topic(cData->topic_mask, channel->topic, new_topic);
         if(*new_topic)
         {
-           SetChannelTopic(channel, chanserv, new_topic, 1);
+           SetChannelTopic(channel, chanserv, user, new_topic, 1);
            /* and fall through to topicsnarf code below.. */
         }
         else /* Topic couldnt fit into mask, was too long */
         {
-            SetChannelTopic(channel, chanserv, old_topic, 1);
+            SetChannelTopic(channel, chanserv, user, old_topic, 1);
             send_message(user, chanserv, "CSMSG_TOPICMASK_CONFLICT1", channel->name, cData->topic_mask);
             send_message(user, chanserv, "CSMSG_TOPICMASK_CONFLICT2", TOPICLEN);
             return 1;
@@ -6659,7 +8880,7 @@ handle_topic(struct userNode *user, struct chanNode *channel, const char *old_to
 }
 
 static void
-handle_mode(struct chanNode *channel, struct userNode *user, const struct mod_chanmode *change)
+handle_mode(struct chanNode *channel, struct userNode *user, const struct mod_chanmode *change, UNUSED_ARG(void *extra))
 {
     struct mod_chanmode *bounce = NULL;
     unsigned int bnc, ii;
@@ -6681,7 +8902,7 @@ handle_mode(struct chanNode *channel, struct userNode *user, const struct mod_ch
         if((change->args[ii].mode & (MODE_REMOVE|MODE_CHANOP)) == (MODE_REMOVE|MODE_CHANOP))
         {
             const struct userNode *victim = change->args[ii].u.member->user;
-            if(!protect_user(victim, user, channel->channel_info))
+            if(!protect_user(victim, user, channel->channel_info, false))
                 continue;
             if(!bounce)
                 bounce = mod_chanmode_alloc(change->argc + 1 - ii);
@@ -6701,7 +8922,7 @@ handle_mode(struct chanNode *channel, struct userNode *user, const struct mod_ch
         else if(change->args[ii].mode & MODE_CHANOP)
         {
             const struct userNode *victim = change->args[ii].u.member->user;
-            if(IsService(victim) || validate_op(user, channel, (struct userNode*)victim))
+            if(IsService(victim) || validate_op(NULL, user, channel, (struct userNode*)victim))
                 continue;
             if(!bounce)
                 bounce = mod_chanmode_alloc(change->argc + 1 - ii);
@@ -6712,7 +8933,7 @@ handle_mode(struct chanNode *channel, struct userNode *user, const struct mod_ch
         else if((change->args[ii].mode & (MODE_REMOVE | MODE_BAN)) == MODE_BAN)
         {
             const char *ban = change->args[ii].u.hostmask;
-            if(!bad_channel_ban(channel, user, ban, NULL, NULL))
+            if(bad_channel_ban(channel, user, ban, NULL, NULL) != 1)
                 continue;
             if(!bounce)
                 bounce = mod_chanmode_alloc(change->argc + 1 - ii);
@@ -6734,7 +8955,7 @@ handle_mode(struct chanNode *channel, struct userNode *user, const struct mod_ch
 }
 
 static void
-handle_nick_change(struct userNode *user, UNUSED_ARG(const char *old_nick))
+handle_nick_change(struct userNode *user, UNUSED_ARG(const char *old_nick), UNUSED_ARG(void *extra))
 {
     struct chanNode *channel;
     struct banData *bData;
@@ -6749,22 +8970,27 @@ handle_nick_change(struct userNode *user, UNUSED_ARG(const char *old_nick))
     {
         channel = user->channels.list[ii]->channel;
         /* Need not check for bans if they're opped or voiced. */
-        if(user->channels.list[ii]->modes & (MODE_CHANOP|MODE_HALFOP|MODE_VOICE))
+        /* TODO: does this make sense in automode v, h, and o? *
+         * lets still enforce on voice people anyway, and see how that goes -Rubin */
+        if(user->channels.list[ii]->modes & (MODE_CHANOP|MODE_HALFOP|MODE_VOICE ))
             continue;
         /* Need not check for bans unless channel registration is active. */
         if(!channel->channel_info || IsSuspended(channel->channel_info))
             continue;
         /* Look for a matching ban already on the channel. */
         for(jj = 0; jj < channel->banlist.used; ++jj)
-            if(user_matches_glob(user, channel->banlist.list[jj]->ban, 1))
+            if(user_matches_glob(user, channel->banlist.list[jj]->ban, MATCH_USENICK, 0))
                 break;
         /* Need not act if we found one. */
         if(jj < channel->banlist.used)
             continue;
+        /* don't kick someone on the userlist */
+        if(protect_user(user, chanserv, channel->channel_info, true))
+            continue;
         /* Look for a matching ban in this channel. */
         for(bData = channel->channel_info->bans; bData; bData = bData->next)
         {
-            if(!user_matches_glob(user, bData->mask, 1))
+            if(!user_matches_glob(user, bData->mask, MATCH_USENICK | MATCH_VISIBLE, 0))
                 continue;
             change.args[0].u.hostmask = bData->mask;
             mod_chanmode_announce(chanserv, channel, &change);
@@ -6776,7 +9002,7 @@ handle_nick_change(struct userNode *user, UNUSED_ARG(const char *old_nick))
     }
 }
 
-static void handle_rename(struct handle_info *handle, const char *old_handle)
+static void handle_rename(struct handle_info *handle, const char *old_handle, UNUSED_ARG(void *extra))
 {
     struct do_not_register *dnr = dict_find(handle_dnrs, old_handle, NULL);
 
@@ -6789,7 +9015,7 @@ static void handle_rename(struct handle_info *handle, const char *old_handle)
 }
 
 static void
-handle_unreg(UNUSED_ARG(struct userNode *user), struct handle_info *handle)
+handle_unreg(UNUSED_ARG(struct userNode *user), struct handle_info *handle, UNUSED_ARG(void *extra))
 {
     struct userNode *h_user;
 
@@ -6803,8 +9029,8 @@ handle_unreg(UNUSED_ARG(struct userNode *user), struct handle_info *handle)
     }
 }
 
-static void
-handle_server_link(UNUSED_ARG(struct server *server))
+static int 
+handle_server_link(UNUSED_ARG(struct server *server), UNUSED_ARG(void *extra))
 {
     struct chanData *cData;
 
@@ -6821,6 +9047,7 @@ handle_server_link(UNUSED_ARG(struct server *server))
             timeq_add(now + chanserv_conf.adjust_delay, chanserv_adjust_limit, cData);
         }
     }
+    return 0;
 }
 
 static void
@@ -6836,8 +9063,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]);
@@ -6876,8 +9103,12 @@ chanserv_conf_read(void)
     chanserv_conf.adjust_delay = str ? ParseInterval(str) : 30;
     str = database_get_data(conf_node, KEY_CHAN_EXPIRE_FREQ, RECDB_QSTRING);
     chanserv_conf.channel_expire_frequency = str ? ParseInterval(str) : 86400;
+    str = database_get_data(conf_node, KEY_BAN_TIMEOUT_FREQ, RECDB_QSTRING);
+    chanserv_conf.ban_timeout_frequency = str ? ParseInterval(str) : 600;
     str = database_get_data(conf_node, KEY_CHAN_EXPIRE_DELAY, RECDB_QSTRING);
     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_NODELETE_LEVEL, RECDB_QSTRING);
     chanserv_conf.nodelete_level = str ? atoi(str) : 1;
     str = database_get_data(conf_node, KEY_MAX_CHAN_USERS, RECDB_QSTRING);
@@ -6905,16 +9136,48 @@ 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_GOD_TIMEOUT, RECDB_QSTRING);
+    god_timeout = str ? ParseInterval(str) : 60*15;
     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)) && (change->argc < 2))
+    if((change = mod_chanmode_parse(NULL, modes, ii, MCP_KEY_FREE, 0))
+       && (change->argc < 2))
     {
         chanserv_conf.default_modes = *change;
         mod_chanmode_free(change);
     }
+    str = database_get_data(conf_node, KEY_VALID_CHANNEL_REGEX, RECDB_QSTRING);
+    if (chanserv_conf.valid_channel_regex_set)
+        regfree(&chanserv_conf.valid_channel_regex);
+    if (str) {
+        int err = regcomp(&chanserv_conf.valid_channel_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
+        chanserv_conf.valid_channel_regex_set = !err;
+        if (err) log_module(CS_LOG, LOG_ERROR, "Bad valid_channel_regex (error %d)", err);
+    } 
+    else {
+        chanserv_conf.valid_channel_regex_set = 0;
+    }
+    free_string_list(chanserv_conf.wheel);
+    strlist = database_get_data(conf_node, "wheel", RECDB_STRING_LIST);
+    if(strlist)
+        strlist = string_list_copy(strlist);
+    else
+    {
+       static const char *list[] = {
+          "peer", "partall", "gline",  /* "shun", */
+          "nothing", "randjoin", "abusewhois", "kickall", 
+          "nickchange", "kill", "svsignore", "kickbanall", 
+          NULL};
+       unsigned int ii;
+       strlist = alloc_string_list(ArrayLength(list)-1);
+       for(ii=0; list[ii]; ii++)
+          string_list_append(strlist, strdup(list[ii]));
+    }
+    chanserv_conf.wheel = strlist;
+
     free_string_list(chanserv_conf.set_shows);
     strlist = database_get_data(conf_node, "set_shows", RECDB_STRING_LIST);
     if(strlist)
@@ -6925,16 +9188,15 @@ chanserv_conf_read(void)
             /* free form text */
             "DefaultTopic", "TopicMask", "Greeting", "UserGreeting", "Modes",
             /* options based on user level */
-            "PubCmd", "InviteMe", "UserInfo",/* "GiveVoice", "GiveHalfOps", "GiveOps", */ "EnfOps",
-            "EnfHalfOps", "EnfModes", "EnfTopic", "TopicSnarf", "Setters", "CtcpUsers",
+            "PubCmd", "InviteMe", "UserInfo","EnfOps",
+            "EnfHalfOps", "EnfModes", "EnfTopic", "TopicSnarf", "Setters", 
             /* multiple choice options */
-            "CtcpReaction", "Protect", "Toys", "TopicRefresh",
+            "AutoMode", "CtcpReaction", "Protect", "Toys", "TopicRefresh", "Resync",
             /* binary options */
-            "DynLimit", "NoDelete",
+            "DynLimit", "NoDelete", "BanTimeout",
             /* 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]));
@@ -6945,6 +9207,7 @@ chanserv_conf_read(void)
      * function list as invalid, so it will be initialized.
      */
     set_shows_list.used = 0;
+
     free_string_list(chanserv_conf.eightball);
     strlist = database_get_data(conf_node, KEY_8BALL_RESPONSES, RECDB_STRING_LIST);
     if(strlist)
@@ -6959,6 +9222,7 @@ chanserv_conf_read(void)
         string_list_append(strlist, strdup("Maybe so."));
     }
     chanserv_conf.eightball = strlist;
+
     free_string_list(chanserv_conf.old_ban_names);
     strlist = database_get_data(conf_node, KEY_OLD_BAN_NAMES, RECDB_STRING_LIST);
     if(strlist)
@@ -7031,27 +9295,33 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
 {
     struct handle_info *handle;
     struct userData *uData;
-    char *seen, *inf, *flags;
+    char *seen, *inf, *flags, *expires, *accessexpiry, *clvlexpiry, *lstacc;
     time_t last_seen;
-    unsigned short access;
+    unsigned short access_level, lastaccess = 0;
 
     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;
     flags = database_get_data(rd->d.object, KEY_FLAGS, RECDB_QSTRING);
+    expires = database_get_data(rd->d.object, KEY_EXPIRES, RECDB_QSTRING);
+    accessexpiry = database_get_data(rd->d.object, KEY_ACCESSEXPIRY, RECDB_QSTRING);
+    clvlexpiry = database_get_data(rd->d.object, KEY_CLVLEXPIRY, RECDB_QSTRING);
+    lstacc = database_get_data(rd->d.object, KEY_LASTLEVEL, RECDB_QSTRING);
+    lastaccess  = lstacc ? atoi(lstacc) : 0;
+
     handle = get_handle_info(key);
     if(!handle)
     {
@@ -7059,8 +9329,27 @@ 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, 0);
     uData->flags = flags ? strtoul(flags, NULL, 0) : 0;
+    uData->expires = expires ? strtoul(expires, NULL, 0) : 0;
+
+    uData->accessexpiry = accessexpiry ? strtoul(accessexpiry, NULL, 0) : 0;
+    if (uData->accessexpiry > 0)
+        timeq_add(uData->accessexpiry, chanserv_expire_tempuser, uData);
+
+    uData->clvlexpiry = clvlexpiry ? strtoul(clvlexpiry, NULL, 0) : 0;
+    if (uData->clvlexpiry > 0)
+        timeq_add(uData->clvlexpiry, chanserv_expire_tempclvl, uData);
+
+    uData->lastaccess = lastaccess;
+
+    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;
+    }
 
     /* Upgrade: set autoop to the inverse of noautoop */
     if(chanserv_read_version < 2)
@@ -7078,14 +9367,14 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
 static void
 ban_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
 {
-    struct banData *bData;
+    //struct banData *bData;
     char *set, *triggered, *s_duration, *s_expires, *reason, *owner;
     time_t 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);
@@ -7109,7 +9398,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 *
@@ -7132,10 +9421,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;
@@ -7159,7 +9474,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)))
@@ -7197,7 +9512,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)
         {
@@ -7211,14 +9526,14 @@ chanserv_channel_read(const char *key, struct record_data *hir)
             }
             else switch(((count <= levelOptions[lvlOpt].old_idx) ? str : CHANNEL_DEFAULT_OPTIONS)[levelOptions[lvlOpt].old_idx])
             {
-            case 'c': lvl = UL_COOWNER; break;
-            case 'm': lvl = UL_MANAGER; break;
-            case 'n': lvl = UL_OWNER+1; break;
-            case 'o': lvl = UL_OP; break;
-            case 'p': lvl = UL_PEON; break;
-            case 'h': lvl = UL_HALFOP; break;
-            case 'w': lvl = UL_OWNER; break;
-            default: lvl = 0; break;
+                case 'c': lvl = UL_COOWNER; break;
+                case 'm': lvl = UL_MANAGER; break;
+                case 'n': lvl = UL_OWNER+1; break;
+                case 'o': lvl = UL_OP; break;
+                case 'p': lvl = UL_PEON; break;
+                case 'h': lvl = UL_HALFOP; break;
+                case 'w': lvl = UL_OWNER; break;
+                default: lvl = 0; break;
             }
             cData->lvlOpts[lvlOpt] = lvl;
         }
@@ -7261,6 +9576,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);
@@ -7287,12 +9608,16 @@ 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_MAXSETINFO, RECDB_QSTRING);
+    cData->maxsetinfo = str ? strtoul(str, NULL, 0) : chanserv_conf.max_userinfo_length;
+
     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))) {
+       && (modes = mod_chanmode_parse(cNode, argv, argc, MCP_KEY_FREE, 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;
@@ -7302,12 +9627,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;
     }
 
@@ -7350,6 +9675,7 @@ chanserv_dnr_read(const char *key, struct record_data *hir)
 {
     const char *setter, *reason, *str;
     struct do_not_register *dnr;
+    time_t expiry;
 
     setter = database_get_data(hir->d.object, KEY_DNR_SETTER, RECDB_QSTRING);
     if(!setter)
@@ -7363,7 +9689,11 @@ chanserv_dnr_read(const char *key, struct record_data *hir)
         log_module(CS_LOG, LOG_ERROR, "Missing reason for DNR %s.", key);
         return;
     }
-    dnr = chanserv_add_dnr(key, setter, reason);
+    str = database_get_data(hir->d.object, KEY_EXPIRES, RECDB_QSTRING);
+    expiry = str ? (time_t)strtoul(str, NULL, 0) : 0;
+    if(expiry && expiry <= now)
+        return;
+    dnr = chanserv_add_dnr(key, setter, expiry, reason);
     if(!dnr)
         return;
     str = database_get_data(hir->d.object, KEY_DNR_SET, RECDB_QSTRING);
@@ -7398,8 +9728,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))
@@ -7420,9 +9750,14 @@ chanserv_write_users(struct saxdb_context *ctx, struct userData *uData)
         saxdb_start_record(ctx, uData->handle->handle, 0);
         saxdb_write_int(ctx, KEY_LEVEL, uData->access);
         saxdb_write_int(ctx, KEY_SEEN, uData->seen);
+        saxdb_write_int(ctx, KEY_ACCESSEXPIRY, uData->accessexpiry);
+        saxdb_write_int(ctx, KEY_CLVLEXPIRY, uData->clvlexpiry);
+        saxdb_write_int(ctx, KEY_LASTLEVEL, uData->lastaccess);
         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->info)
             saxdb_write_string(ctx, KEY_INFO, uData->info);
         saxdb_end_record(ctx);
     }
@@ -7470,6 +9805,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)
 {
@@ -7494,6 +9850,8 @@ 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);
 
     saxdb_start_record(ctx, KEY_OPTIONS, 0);
     saxdb_write_int(ctx, KEY_FLAGS, channel->flags);
@@ -7507,9 +9865,12 @@ chanserv_write_channel(struct saxdb_context *ctx, struct chanData *channel)
     }
     saxdb_end_record(ctx);
 
+    if (channel->maxsetinfo)
+        saxdb_write_int(ctx, KEY_MAXSETINFO, channel->maxsetinfo);
+
     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);
     }
 
@@ -7523,7 +9884,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);
@@ -7546,21 +9907,22 @@ chanserv_write_note_type(struct saxdb_context *ctx, struct note_type *ntype)
     saxdb_start_record(ctx, ntype->name, 0);
     switch(ntype->set_access_type)
     {
-    case NOTE_SET_CHANNEL_ACCESS:
-        saxdb_write_int(ctx, KEY_NOTE_CHANNEL_ACCESS, ntype->set_access.min_ulevel);
-        break;
-    case NOTE_SET_CHANNEL_SETTER:
-        saxdb_write_int(ctx, KEY_NOTE_SETTER_ACCESS, 1);
-        break;
-    case NOTE_SET_PRIVILEGED: default:
-        saxdb_write_int(ctx, KEY_NOTE_OPSERV_ACCESS, ntype->set_access.min_opserv);
-        break;
+        case NOTE_SET_CHANNEL_ACCESS:
+            saxdb_write_int(ctx, KEY_NOTE_CHANNEL_ACCESS, ntype->set_access.min_ulevel);
+            break;
+        case NOTE_SET_CHANNEL_SETTER:
+            saxdb_write_int(ctx, KEY_NOTE_SETTER_ACCESS, 1);
+            break;
+        case NOTE_SET_PRIVILEGED: default:
+            saxdb_write_int(ctx, KEY_NOTE_OPSERV_ACCESS, ntype->set_access.min_opserv);
+            break;
     }
     switch(ntype->visible_type)
     {
-    case NOTE_VIS_ALL: str = KEY_NOTE_VIS_ALL; break;
-    case NOTE_VIS_CHANNEL_USERS: str = KEY_NOTE_VIS_CHANNEL_USERS; break;
-    case NOTE_VIS_PRIVILEGED: default: str = KEY_NOTE_VIS_PRIVILEGED; break;
+        case NOTE_VIS_ALL: str = KEY_NOTE_VIS_ALL; break;
+        case NOTE_VIS_CHANNEL_USERS: str = KEY_NOTE_VIS_CHANNEL_USERS; break;
+        case NOTE_VIS_PRIVILEGED: 
+        default: str = KEY_NOTE_VIS_PRIVILEGED; break;
     }
     saxdb_write_string(ctx, KEY_NOTE_VISIBILITY, str);
     saxdb_write_int(ctx, KEY_NOTE_MAX_LENGTH, ntype->max_length);
@@ -7571,14 +9933,22 @@ static void
 write_dnrs_helper(struct saxdb_context *ctx, struct dict *dnrs)
 {
     struct do_not_register *dnr;
-    dict_iterator_t it;
+    dict_iterator_t it, next;
 
-    for(it = dict_first(dnrs); it; it = iter_next(it))
+    for(it = dict_first(dnrs); it; it = next)
     {
+        next = iter_next(it);
         dnr = iter_data(it);
+        if(dnr->expires && dnr->expires <= now)
+            continue;
         saxdb_start_record(ctx, dnr->chan_name, 0);
         if(dnr->set)
             saxdb_write_int(ctx, KEY_DNR_SET, dnr->set);
+        if(dnr->expires)
+        {
+            dict_remove(dnrs, iter_key(it));
+            saxdb_write_int(ctx, KEY_EXPIRES, dnr->expires);
+        }
         saxdb_write_string(ctx, KEY_DNR_SETTER, dnr->setter);
         saxdb_write_string(ctx, KEY_DNR_REASON, dnr->reason);
         saxdb_end_record(ctx);
@@ -7593,7 +9963,7 @@ chanserv_saxdb_write(struct saxdb_context *ctx)
 
     /* Version Control*/
     saxdb_start_record(ctx, KEY_VERSION_CONTROL, 1);
-      saxdb_write_int(ctx, KEY_VERSION_NUMBER, CHANSERV_DB_VERSION);
+    saxdb_write_int(ctx, KEY_VERSION_NUMBER, CHANSERV_DB_VERSION);
     saxdb_end_record(ctx);
 
     /* Notes */
@@ -7619,9 +9989,9 @@ chanserv_saxdb_write(struct saxdb_context *ctx)
 }
 
 static void
-chanserv_db_cleanup(void) {
+chanserv_db_cleanup(UNUSED_ARG(void *extra)) {
     unsigned int ii;
-    unreg_part_func(handle_part);
+    unreg_part_func(handle_part, NULL);
     while(channelList)
         unregister_channel(channelList, "terminating.");
     for(ii = 0; ii < chanserv_conf.support_channels.used; ++ii)
@@ -7633,6 +10003,7 @@ chanserv_db_cleanup(void) {
     dict_delete(note_types);
     free_string_list(chanserv_conf.eightball);
     free_string_list(chanserv_conf.old_ban_names);
+    free_string_list(chanserv_conf.wheel);
     free_string_list(chanserv_conf.set_shows);
     free(set_shows_list.list);
     free(uset_shows_list.list);
@@ -7644,29 +10015,37 @@ 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)
 
 void
 init_chanserv(const char *nick)
 {
+    struct chanNode *chan;
+    unsigned int i;
+
     CS_LOG = log_register_type("ChanServ", "file:chanserv.log");
     conf_register_reload(chanserv_conf_read);
 
-    reg_server_link_func(handle_server_link);
-
-    reg_new_channel_func(handle_new_channel);
-    reg_join_func(handle_join);
-    reg_part_func(handle_part);
-    reg_kick_func(handle_kick);
-    reg_topic_func(handle_topic);
-    reg_mode_change_func(handle_mode);
-    reg_nick_change_func(handle_nick_change);
+    if (nick) {
+        reg_server_link_func(handle_server_link, NULL);
+        reg_new_channel_func(handle_new_channel, NULL);
+        reg_join_func(handle_join, NULL);
+        reg_part_func(handle_part, NULL);
+        reg_kick_func(handle_kick, NULL);
+        reg_topic_func(handle_topic, NULL);
+        reg_mode_change_func(handle_mode, NULL);
+        reg_nick_change_func(handle_nick_change, NULL);
+        reg_auth_func(handle_auth, NULL);
+    }
 
-    reg_auth_func(handle_auth);
-    reg_handle_rename_func(handle_rename);
-    reg_unreg_func(handle_unreg);
+    reg_handle_rename_func(handle_rename, NULL);
+    reg_unreg_func(handle_unreg, NULL);
 
     handle_dnrs = dict_new();
     dict_set_free_data(handle_dnrs, free);
@@ -7675,14 +10054,18 @@ init_chanserv(const char *nick)
     mask_dnrs = dict_new();
     dict_set_free_data(mask_dnrs, free);
 
-    reg_svccmd_unbind_func(handle_svccmd_unbind);
+    reg_svccmd_unbind_func(handle_svccmd_unbind, NULL);
     chanserv_module = module_register("ChanServ", CS_LOG, "chanserv.help", chanserv_expand_variable);
-    DEFINE_COMMAND(register, 1, MODCMD_REQUIRE_AUTHED, "flags", "+acceptchan,+helping", NULL);
+    DEFINE_COMMAND(register, 1, MODCMD_REQUIRE_AUTHED, "flags", "+acceptchan,+channel", NULL);
     DEFINE_COMMAND(noregister, 1, MODCMD_REQUIRE_AUTHED, "flags", "+helping", NULL);
     DEFINE_COMMAND(allowregister, 2, 0, "template", "noregister", NULL);
+    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);
+    DEFINE_COMMAND(csuspend, 2, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_REGCHAN|MODCMD_IGNORE_CSUSPEND, "flags", "+helping", NULL);
+    DEFINE_COMMAND(cunsuspend, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_REGCHAN|MODCMD_IGNORE_CSUSPEND, "flags", "+helping", NULL);
     DEFINE_COMMAND(createnote, 5, 0, "level", "800", NULL);
     DEFINE_COMMAND(removenote, 2, 0, "level", "800", NULL);
 
@@ -7702,8 +10085,11 @@ init_chanserv(const char *nick)
     DEFINE_COMMAND(mdelmanager, 2, MODCMD_REQUIRE_CHANUSER, "access", "coowner", NULL);
     DEFINE_COMMAND(mdelop, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
     DEFINE_COMMAND(mdelpeon, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
+    DEFINE_COMMAND(mdelpal, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
     DEFINE_COMMAND(mdelhalfop, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
 
+    DEFINE_COMMAND(levels, 1, 0, NULL);
+
     DEFINE_COMMAND(trim, 3, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
     DEFINE_COMMAND(opchan, 1, MODCMD_REQUIRE_REGCHAN|MODCMD_NEVER_CSUSPEND, "access", "1", NULL);
     DEFINE_COMMAND(clvl, 3, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
@@ -7736,6 +10122,7 @@ init_chanserv(const char *nick)
     DEFINE_COMMAND(resync, 1, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
 
     DEFINE_COMMAND(events, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog", "access", "manager", NULL);
+    DEFINE_COMMAND(last, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog", "access", "manager", NULL);
     DEFINE_COMMAND(addlamer, 2, MODCMD_REQUIRE_REGCHAN, "access", "manager", NULL);
     DEFINE_COMMAND(addtimedlamer, 3, MODCMD_REQUIRE_REGCHAN, "access", "manager", NULL);
 
@@ -7782,6 +10169,10 @@ init_chanserv(const char *nick)
     DEFINE_COMMAND(d, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
     DEFINE_COMMAND(huggle, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
     DEFINE_COMMAND(calc, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
+    DEFINE_COMMAND(reply, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
+    DEFINE_COMMAND(roulette, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
+    DEFINE_COMMAND(shoot, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
+    DEFINE_COMMAND(spin, 1, MODCMD_REQUIRE_AUTHED, "spin", "+nolog,+toy,+acceptchan", NULL);
 
     /* Channel options */
     DEFINE_CHANNEL_OPTION(defaulttopic);
@@ -7791,16 +10182,11 @@ init_chanserv(const char *nick)
     DEFINE_CHANNEL_OPTION(modes);
     DEFINE_CHANNEL_OPTION(enfops);
     DEFINE_CHANNEL_OPTION(enfhalfops);
-    /*DEFINE_CHANNEL_OPTION(giveops);
-    DEFINE_CHANNEL_OPTION(givehalfops);
-    */
-    DEFINE_CHANNEL_OPTION(voice);
+    DEFINE_CHANNEL_OPTION(automode);
     DEFINE_CHANNEL_OPTION(protect);
     DEFINE_CHANNEL_OPTION(enfmodes);
     DEFINE_CHANNEL_OPTION(enftopic);
     DEFINE_CHANNEL_OPTION(pubcmd);
-    /*DEFINE_CHANNEL_OPTION(givevoice);
-     */
     DEFINE_CHANNEL_OPTION(userinfo);
     DEFINE_CHANNEL_OPTION(dynlimit);
     DEFINE_CHANNEL_OPTION(topicsnarf);
@@ -7808,9 +10194,14 @@ init_chanserv(const char *nick)
     DEFINE_CHANNEL_OPTION(toys);
     DEFINE_CHANNEL_OPTION(setters);
     DEFINE_CHANNEL_OPTION(topicrefresh);
-    DEFINE_CHANNEL_OPTION(ctcpusers);
+    DEFINE_CHANNEL_OPTION(resync);
     DEFINE_CHANNEL_OPTION(ctcpreaction);
+    DEFINE_CHANNEL_OPTION(bantimeout);
     DEFINE_CHANNEL_OPTION(inviteme);
+    DEFINE_CHANNEL_OPTION(unreviewed);
+    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);
+    DEFINE_CHANNEL_OPTION(maxsetinfo);
     if(off_channel > 1)
         DEFINE_CHANNEL_OPTION(offchannel);
     modcmd_register(chanserv_module, "set defaults", chan_opt_defaults, 1, 0, "access", "owner", NULL);
@@ -7820,6 +10211,7 @@ init_chanserv(const char *nick)
 
     /* User options */
     DEFINE_USER_OPTION(autoinvite);
+    DEFINE_USER_OPTION(autojoin);
     DEFINE_USER_OPTION(info);
     DEFINE_USER_OPTION(autoop);
 
@@ -7831,23 +10223,38 @@ init_chanserv(const char *nick)
     if(nick)
     {
         const char *modes = conf_get_data("services/chanserv/modes", RECDB_QSTRING);
-        chanserv = AddService(nick, modes ? modes : NULL, "Channel Services", NULL);
+        chanserv = AddLocalUser(nick, nick, NULL, "Channel Services", modes);
         service_register(chanserv)->trigger = '!';
-        reg_chanmsg_func('\001', chanserv, chanserv_ctcp_check);
+        reg_chanmsg_func('\001', chanserv, chanserv_ctcp_check, NULL);
     }
 
     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.ban_timeout_frequency)
+        timeq_add(now + chanserv_conf.ban_timeout_frequency, expire_bans, NULL);
 
     if(chanserv_conf.refresh_period)
     {
         time_t 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);
+        timeq_add(next_refresh, chanserv_auto_resync, NULL);
+    }
+
+    if (autojoin_channels && chanserv) {
+        for (i = 0; i < autojoin_channels->used; i++) {
+            chan = AddChannel(autojoin_channels->list[i], now, "+nt", NULL, NULL);
+            AddChannelUser(chanserv, chan)->modes |= MODE_CHANOP;
+        }    
     }
 
-    reg_exit_func(chanserv_db_cleanup);
+    reg_exit_func(chanserv_db_cleanup, NULL);
     message_register_table(msgtab);
 }
+