Convert time-related variables to consistently use "unsigned long".
[srvx.git] / src / chanserv.c
index e8fbe4b4a917831110539d3aadfc6c4f92d6ce1c..9ad4cd40ca496bd48bcdbd45e9683293a1eeb78d 100644 (file)
@@ -1,5 +1,5 @@
 /* chanserv.c - Channel service bot
- * Copyright 2000-2004 srvx Development Team
+ * Copyright 2000-2006 srvx Development Team
  *
  * This file is part of srvx.
  *
 #include "saxdb.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_MAX_SWITCH_LOAD    "max_switch_load"
-#define KEY_SWITCH_TIMEOUT     "switch_timeout"
-#define KEY_8BALL_RESPONSES     "8ball"
-#define KEY_OLD_BAN_NAMES       "old_ban_names"
-#define KEY_REFRESH_PERIOD      "refresh_period"
+#define KEY_SUPPORT_CHANNEL         "support_channel"
+#define KEY_SUPPORT_CHANNEL_MODES   "support_channel_modes"
+#define KEY_DB_BACKUP_FREQ          "db_backup_freq"
+#define KEY_INFO_DELAY              "info_delay"
+#define KEY_MAX_GREETLEN            "max_greetlen"
+#define KEY_ADJUST_THRESHOLD        "adjust_threshold"
+#define KEY_ADJUST_DELAY            "adjust_delay"
+#define KEY_CHAN_EXPIRE_FREQ        "chan_expire_freq"
+#define KEY_CHAN_EXPIRE_DELAY       "chan_expire_delay"
+#define KEY_DNR_EXPIRE_FREQ         "dnr_expire_freq"
+#define KEY_MAX_CHAN_USERS          "max_chan_users"
+#define KEY_MAX_CHAN_BANS           "max_chan_bans"
+#define KEY_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_NODELETE_LEVEL          "nodelete_level"
+#define KEY_MAX_USERINFO_LENGTH     "max_userinfo_length"
+#define KEY_GIVEOWNERSHIP_PERIOD    "giveownership_timeout"
 
 /* ChanServ database */
-#define KEY_CHANNELS           "channels"
-#define KEY_NOTE_TYPES          "note_types"
+#define KEY_CHANNELS                "channels"
+#define KEY_NOTE_TYPES              "note_types"
 
 /* Note type parameters */
-#define KEY_NOTE_OPSERV_ACCESS  "opserv_access"
-#define KEY_NOTE_CHANNEL_ACCESS "channel_access"
-#define KEY_NOTE_SETTER_ACCESS  "setter_access"
-#define KEY_NOTE_VISIBILITY     "visibility"
-#define KEY_NOTE_VIS_PRIVILEGED "privileged"
-#define KEY_NOTE_VIS_CHANNEL_USERS "channel_users"
-#define KEY_NOTE_VIS_ALL        "all"
-#define KEY_NOTE_MAX_LENGTH     "max_length"
-#define KEY_NOTE_SETTER         "setter"
-#define KEY_NOTE_NOTE           "note"
+#define KEY_NOTE_OPSERV_ACCESS      "opserv_access"
+#define KEY_NOTE_CHANNEL_ACCESS     "channel_access"
+#define KEY_NOTE_SETTER_ACCESS      "setter_access"
+#define KEY_NOTE_VISIBILITY         "visibility"
+#define KEY_NOTE_VIS_PRIVILEGED     "privileged"
+#define KEY_NOTE_VIS_CHANNEL_USERS  "channel_users"
+#define KEY_NOTE_VIS_ALL            "all"
+#define KEY_NOTE_MAX_LENGTH         "max_length"
+#define KEY_NOTE_SETTER             "setter"
+#define KEY_NOTE_NOTE               "note"
 
 /* Do-not-register channels */
-#define KEY_DNR                 "dnr"
-#define KEY_DNR_SET             "set"
-#define KEY_DNR_SETTER          "setter"
-#define KEY_DNR_REASON          "reason"
+#define KEY_DNR             "dnr"
+#define KEY_DNR_SET         "set"
+#define KEY_DNR_SETTER      "setter"
+#define KEY_DNR_REASON      "reason"
 
 /* Channel data */
-#define KEY_REGISTERED         "registered"
-#define KEY_REGISTRAR          "registrar"
-#define KEY_SUSPENDED           "suspended"
-#define KEY_PREVIOUS            "previous"
-#define KEY_SUSPENDER          "suspender"
-#define KEY_ISSUED              "issued"
-#define KEY_REVOKED             "revoked"
-#define KEY_SUSPEND_EXPIRES     "suspend_expires"
-#define KEY_SUSPEND_REASON      "suspend_reason"
-#define KEY_VISITED            "visited"
-#define KEY_TOPIC              "topic"
-#define KEY_GREETING           "greeting"
-#define KEY_USER_GREETING      "user_greeting"
-#define KEY_MODES              "modes"
-#define KEY_FLAGS              "flags"
-#define KEY_OPTIONS             "options"
-#define KEY_USERS              "users"
-#define KEY_BANS               "bans"
-#define KEY_MAX                        "max"
-#define KEY_NOTES               "notes"
-#define KEY_TOPIC_MASK          "topic_mask"
+#define KEY_REGISTERED      "registered"
+#define KEY_REGISTRAR       "registrar"
+#define KEY_SUSPENDED       "suspended"
+#define KEY_PREVIOUS        "previous"
+#define KEY_SUSPENDER       "suspender"
+#define KEY_ISSUED          "issued"
+#define KEY_REVOKED         "revoked"
+#define KEY_SUSPEND_EXPIRES "suspend_expires"
+#define KEY_SUSPEND_REASON  "suspend_reason"
+#define KEY_VISITED         "visited"
+#define KEY_TOPIC           "topic"
+#define KEY_GREETING        "greeting"
+#define KEY_USER_GREETING   "user_greeting"
+#define KEY_MODES           "modes"
+#define KEY_FLAGS           "flags"
+#define KEY_OPTIONS         "options"
+#define KEY_USERS           "users"
+#define KEY_BANS            "bans"
+#define KEY_MAX             "max"
+#define KEY_NOTES           "notes"
+#define KEY_TOPIC_MASK      "topic_mask"
+#define KEY_OWNER_TRANSFER  "owner_transfer"
 
 /* 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"
 
 /* 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   (0)
+#define KEY_OWNER       "owner"
+#define KEY_REASON      "reason"
+#define KEY_SET         "set"
+#define KEY_DURATION    "duration"
+#define KEY_EXPIRES     "expires"
+#define KEY_TRIGGERED   "triggered"
+
+#define CHANNEL_DEFAULT_FLAGS   (CHANNEL_OFFCHANNEL | CHANNEL_UNREVIEWED)
+#define CHANNEL_PRESERVED_FLAGS (CHANNEL_UNREVIEWED)
 #define CHANNEL_DEFAULT_OPTIONS "lmoooanpcnat"
 
 /* Administrative messages */
@@ -133,6 +136,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_DNR_SEARCH_RESULTS", "The following do-not-registers were found:" },
     { "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." },
@@ -140,6 +144,8 @@ 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." },
@@ -194,7 +200,9 @@ 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_MULTIPLE_OWNERS", "There is more than one owner in %s; please use $bCLVL$b, $bDELOWNER$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 */
@@ -225,7 +233,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_TOPIC_SET", "Topic is now '%s'." },
     { "CSMSG_NO_TOPIC", "$b%s$b does not have a default topic." },
     { "CSMSG_TOPICMASK_CONFLICT1", "I do not know how to make that topic work with the current topic mask in $b%s$b, which is: %s" },
-    { "CSMSG_TOPICMASK_CONFLICT2", "Please make sure your topic at most %d characters and matches the topic mask pattern." },
+    { "CSMSG_TOPICMASK_CONFLICT2", "Please make sure your topic is at most %d characters and matches the topic mask pattern." },
     { "CSMSG_TOPIC_LOCKED", "The %s topic is locked." },
     { "CSMSG_MASK_BUT_NO_TOPIC", "Warning: $b%s$b does not have a default topic, but you just set the topic mask." },
     { "CSMSG_TOPIC_MISMATCH", "Warning: The default topic for $b%s$b does not match the topic mask; changing it anyway." },
@@ -251,6 +259,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_SET_MODES",         "$bModes       $b %s" },
     { "CSMSG_SET_NODELETE",      "$bNoDelete    $b %s" },
     { "CSMSG_SET_DYNLIMIT",      "$bDynLimit    $b %s" },
+    { "CSMSG_SET_OFFCHANNEL",    "$bOffChannel  $b %s" },
     { "CSMSG_SET_USERINFO",      "$bUserInfo    $b %d" },
     { "CSMSG_SET_GIVE_VOICE",    "$bGiveVoice   $b %d" },
     { "CSMSG_SET_TOPICSNARF",    "$bTopicSnarf  $b %d" },
@@ -266,6 +275,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_SET_TOYS",          "$bToys        $b %d - %s" },
     { "CSMSG_SET_CTCPREACTION",  "$bCTCPReaction$b %d - %s" },
     { "CSMSG_SET_TOPICREFRESH",  "$bTopicRefresh$b %d - %s" },
+    { "CSMSG_SET_UNREVIEWED",    "$bUnreviewed  $b %s" },
     { "CSMSG_USET_NOAUTOOP",     "$bNoAutoOp    $b %s" },
     { "CSMSG_USET_NOAUTOVOICE",  "$bNoAutoVoice $b %s" },
     { "CSMSG_USET_AUTOINVITE",   "$bAutoInvite  $b %s" },
@@ -301,6 +311,8 @@ static const struct message_entry msgtab[] = {
     { "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_INFOLINE_TOO_LONG", "Your infoline may not exceed %u characters." },
+    { "CSMSG_BAD_INFOLINE", "You may not use the character \\%03o in your infoline." },
 
     { "CSMSG_KICK_DONE", "Kicked $b%s$b from %s." },
     { "CSMSG_NO_BANS", "No channel bans found on $b%s$b." },
@@ -349,6 +361,9 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_HELPER_HAS_ACCESS", "%s has access $b%d$b 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_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 */
@@ -415,6 +430,7 @@ 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", "Channel Options:" },
     { "CSMSG_GREETING_TOO_LONG", "Your greeting ($b%d$b characters) must be shorter than $b%d$b characters." },
 
@@ -428,9 +444,9 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_WUT_RESPONSE", "wut" },
     { "CSMSG_BAD_NUMBER", "$b%s$b is an invalid number.  Please use a number greater than 1 with this command." },
     { "CSMSG_BAD_DIE_FORMAT", "I do not understand $b%s$b.  Please use either a single number or standard 4d6+3 format." },
-    { "CSMSG_BAD_DICE_COUNT", "%d is too many dice.  Please use at most %d." },
-    { "CSMSG_DICE_ROLL", "The total is $b%d$b from rolling %dd%d+%d." },
-    { "CSMSG_DIE_ROLL", "A $b%d$b shows on the %d-sided die." },
+    { "CSMSG_BAD_DICE_COUNT", "%lu is too many dice.  Please use at most %lu." },
+    { "CSMSG_DICE_ROLL", "The total is $b%lu$b from rolling %lud%lu+%lu." },
+    { "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" },
 
@@ -440,26 +456,26 @@ static const struct message_entry msgtab[] = {
 };
 
 /* eject_user and unban_user flags */
-#define ACTION_KICK            0x0001
-#define ACTION_BAN             0x0002
-#define ACTION_ADD_BAN         0x0004
-#define ACTION_ADD_TIMED_BAN           0x0008
-#define ACTION_UNBAN           0x0010
-#define ACTION_DEL_BAN         0x0020
+#define ACTION_KICK             0x0001
+#define ACTION_BAN              0x0002
+#define ACTION_ADD_BAN          0x0004
+#define ACTION_ADD_TIMED_BAN    0x0008
+#define ACTION_UNBAN            0x0010
+#define ACTION_DEL_BAN          0x0020
 
 /* The 40 allows for [+-ntlksimprD] and lots of fudge factor. */
-#define MODELEN                        40 + KEYLEN
-#define PADLEN                 21
-#define ACCESSLEN              10
+#define MODELEN     40 + KEYLEN
+#define PADLEN      21
+#define ACCESSLEN   10
 
-#define CSFUNC_ARGS            user, channel, argc, argv, cmd
+#define CSFUNC_ARGS user, channel, argc, argv, cmd
 
 #define CHANSERV_FUNC(NAME) MODCMD_FUNC(NAME)
-#define CHANSERV_SYNTAX()      svccmd_send_help(user, chanserv, cmd)
-#define REQUIRE_PARAMS(N)      if(argc < (N)) {            \
-       reply("MSG_MISSING_PARAMS", argv[0]); \
-       CHANSERV_SYNTAX(); \
-       return 0; }
+#define CHANSERV_SYNTAX() svccmd_send_help(user, chanserv, cmd)
+#define REQUIRE_PARAMS(N)   if(argc < (N)) {            \
+        reply("MSG_MISSING_PARAMS", argv[0]); \
+        CHANSERV_SYNTAX(); \
+        return 0; }
 
 DECLARE_LIST(dnrList, struct do_not_register *);
 DEFINE_LIST(dnrList, struct do_not_register *);
@@ -468,6 +484,7 @@ static int eject_user(struct userNode *user, struct chanNode *channel, unsigned
 
 struct userNode *chanserv;
 dict_t note_types;
+int off_channel;
 static dict_t plain_dnrs, mask_dnrs, handle_dnrs;
 static struct log_type *CS_LOG;
 
@@ -476,23 +493,26 @@ 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   dnr_expire_frequency;
 
-    long               info_delay;
-    unsigned int       adjust_delay;
-    long               channel_expire_delay;
-    unsigned int        nodelete_level;
+    unsigned long   info_delay;
+    unsigned long   adjust_delay;
+    unsigned long   channel_expire_delay;
+    unsigned int    nodelete_level;
 
-    unsigned int       adjust_threshold;
-    int                        join_flood_threshold;
+    unsigned int    adjust_threshold;
+    int             join_flood_threshold;
 
-    unsigned int       greeting_length;
-    unsigned int        refresh_period;
+    unsigned int    greeting_length;
+    unsigned int    refresh_period;
+    unsigned int    giveownership_period;
 
-    unsigned int        max_owned;
-    unsigned int       max_chan_users;
-    unsigned int       max_chan_bans;
+    unsigned int    max_owned;
+    unsigned int    max_chan_users;
+    unsigned int    max_chan_bans;
+    unsigned int    max_userinfo_length;
 
     struct string_list  *set_shows;
     struct string_list  *eightball;
@@ -643,7 +663,7 @@ user_level_from_name(const char *name, unsigned short clamp_level)
 {
     unsigned int level = 0, ii;
     if(isdigit(name[0]))
-        level = atoi(name);
+        level = strtoul(name, NULL, 10);
     else for(ii = 0; (ii < ArrayLength(accessLevels)) && !level; ++ii)
         if(!irccasecmp(name, accessLevels[ii].name))
             level = accessLevels[ii].level;
@@ -682,52 +702,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;
@@ -778,7 +798,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))
 {
     unsigned int eflags, argc;
     char *argv[4];
@@ -795,7 +815,7 @@ chanserv_ctcp_check(struct userNode *user, struct chanNode *channel, char *text,
         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))
@@ -834,7 +854,7 @@ chanserv_deref_note_type(void *data)
     struct note_type *ntype = data;
 
     if(--ntype->refs > 0)
-       return;
+        return;
     free(ntype);
 }
 
@@ -852,12 +872,12 @@ chanserv_truncate_notes(struct note_type *ntype)
     struct chanData *cData;
     struct note *note;
     unsigned int size = sizeof(*note) + ntype->max_length;
-    
+
     for(cData = channelList; cData; cData = cData->next) {
         note = dict_find(cData->notes, ntype->name, NULL);
         if(!note)
             continue;
-       if(strlen(note->note) <= ntype->max_length)
+        if(strlen(note->note) <= ntype->max_length)
             continue;
         dict_remove2(cData->notes, ntype->name, 1);
         note = realloc(note, size);
@@ -902,7 +922,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++;
@@ -915,7 +935,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;
@@ -927,7 +947,7 @@ static MODCMD_FUNC(cmd_createnote) {
     else
     {
         reply("CSMSG_BAD_NOTE_ACCESS", argv[arg]);
-       goto fail;
+        goto fail;
     }
 
     if(!irccasecmp(argv[++arg], "privileged"))
@@ -938,35 +958,35 @@ static MODCMD_FUNC(cmd_createnote) {
         ntype->visible_type = NOTE_VIS_ALL;
     else {
         reply("CSMSG_BAD_NOTE_ACCESS", argv[arg]);
-       goto fail;
+        goto fail;
     }
 
     if((arg+1) >= argc) {
         reply("MSG_MISSING_PARAMS", argv[0]);
-       goto fail;
+        goto fail;
     }
     max_length = strtoul(argv[++arg], NULL, 0);
     if(max_length < 20 || max_length > 450)
     {
         reply("CSMSG_BAD_MAX_LENGTH", argv[arg]);
-       goto fail;
+        goto fail;
     }
     if(existed && (max_length < ntype->max_length))
     {
-       ntype->max_length = max_length;
-       chanserv_truncate_notes(ntype);
+        ntype->max_length = max_length;
+        chanserv_truncate_notes(ntype);
     }
     ntype->max_length = max_length;
 
     if(existed)
-        reply("CSMSG_NOTE_MODIFIED", ntype->name);    
+        reply("CSMSG_NOTE_MODIFIED", ntype->name);
     else
         reply("CSMSG_NOTE_CREATED", ntype->name);
     return 1;
 
 fail:
     if(!existed)
-       dict_remove(note_types, ntype->name);
+        dict_remove(note_types, ntype->name);
     return 0;
 }
 
@@ -1004,10 +1024,10 @@ mode_lock_violated(const struct mod_chanmode *orig, const struct mod_chanmode *c
         return 1;
     if(orig->modes_clear & change->modes_set)
         return 1;
-    if((orig->modes_set & MODE_KEY)
+    if((orig->modes_set & MODE_KEY) && (change->modes_set & MODE_KEY)
        && strcmp(orig->new_key, change->new_key))
         return 1;
-    if((orig->modes_set & MODE_LIMIT)
+    if((orig->modes_set & MODE_LIMIT) && (change->modes_set & MODE_LIMIT)
        && (orig->new_limit != change->new_limit))
         return 1;
     return 0;
@@ -1072,6 +1092,7 @@ register_channel(struct chanNode *cNode, char *registrar)
     channel->registered = now;
     channel->visited = now;
     channel->limitAdjusted = now;
+    channel->ownerTransfer = now;
     channel->flags = CHANNEL_DEFAULT_FLAGS;
     for(lvlOpt = 0; lvlOpt < NUM_LEVEL_OPTIONS; ++lvlOpt)
         channel->lvlOpts[lvlOpt] = levelOptions[lvlOpt].default_value;
@@ -1082,7 +1103,7 @@ register_channel(struct chanNode *cNode, char *registrar)
     channel->next = channelList;
 
     if(channelList)
-       channelList->prev = channel;
+        channelList->prev = channel;
     channelList = channel;
     registered_channels++;
 
@@ -1094,12 +1115,12 @@ 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, unsigned long seen, const char *info)
 {
     struct userData *ud;
 
     if(access > UL_OWNER)
-       return NULL;
+        return NULL;
 
     ud = calloc(1, sizeof(*ud));
     ud->channel = channel;
@@ -1111,7 +1132,7 @@ add_channel_user(struct chanData *channel, struct handle_info *handle, unsigned
     ud->prev = NULL;
     ud->next = channel->users;
     if(channel->users)
-       channel->users->prev = ud;
+        channel->users->prev = ud;
     channel->users = ud;
 
     channel->userCount++;
@@ -1159,7 +1180,7 @@ del_channel_user(struct userData *user, int do_gc)
 static void expire_ban(void *data);
 
 static struct banData*
-add_channel_ban(struct chanData *channel, const char *mask, char *owner, time_t set, time_t triggered, time_t expires, char *reason)
+add_channel_ban(struct chanData *channel, const char *mask, char *owner, unsigned long set, unsigned long triggered, unsigned long expires, char *reason)
 {
     struct banData *bd;
     unsigned int ii, l1, l2;
@@ -1187,21 +1208,21 @@ add_channel_ban(struct chanData *channel, const char *mask, char *owner, time_t
         if(irccasecmp(mask + l1 - l2, old_name))
             continue;
         new_mask = alloca(MAXLEN);
-        sprintf(new_mask, "%.*s%s", l1-l2, mask, hidden_host_suffix);
+        sprintf(new_mask, "%.*s%s", (int)(l1-l2), mask, hidden_host_suffix);
         mask = new_mask;
     }
     safestrncpy(bd->mask, mask, sizeof(bd->mask));
     if(owner)
         safestrncpy(bd->owner, owner, sizeof(bd->owner));
-    bd->reason = reason ? strdup(reason) : NULL;
+    bd->reason = strdup(reason);
 
     if(expires)
-       timeq_add(expires, expire_ban, bd);
+        timeq_add(expires, expire_ban, bd);
 
     bd->prev = NULL;
     bd->next = channel->bans;
     if(channel->bans)
-       channel->bans->prev = bd;
+        channel->bans->prev = bd;
     channel->bans = bd;
     channel->banCount++;
     banCount++;
@@ -1224,7 +1245,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);
@@ -1246,10 +1267,10 @@ expire_ban(void *data)
         for(ii=0; ii<bans.used; ii++)
         {
             if(!strcmp(bans.list[ii]->ban, bd->mask))
-           {
+            {
                 change.argc = 1;
                 change.args[0].mode = MODE_REMOVE|MODE_BAN;
-                change.args[0].hostmask = bd->mask;
+                change.args[0].u.hostmask = bd->mask;
                 mod_chanmode_announce(chanserv, bd->channel->channel, &change);
                 break;
             }
@@ -1264,6 +1285,7 @@ static void chanserv_expire_suspension(void *data);
 static void
 unregister_channel(struct chanData *channel, const char *reason)
 {
+    struct mod_chanmode change;
     char msgbuf[MAXLEN];
 
     /* After channel unregistration, the following must be cleaned
@@ -1276,31 +1298,41 @@ unregister_channel(struct chanData *channel, const char *reason)
     */
 
     if(!channel)
-       return;
+        return;
 
     timeq_del(0, NULL, channel, TIMEQ_IGNORE_FUNC | TIMEQ_IGNORE_WHEN);
 
+    if(off_channel > 0)
+    {
+      mod_chanmode_init(&change);
+      change.modes_clear |= MODE_REGISTERED;
+      mod_chanmode_announce(chanserv, channel->channel, &change);
+    }
+
     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);
 
-    if(channel->topic) free(channel->topic);
-    if(channel->registrar) free(channel->registrar);
-    if(channel->greeting) free(channel->greeting);
-    if(channel->user_greeting) free(channel->user_greeting);
-    if(channel->topic_mask) free(channel->topic_mask);
+    free(channel->topic);
+    free(channel->registrar);
+    free(channel->greeting);
+    free(channel->user_greeting);
+    free(channel->topic_mask);
 
-    if(channel->prev) channel->prev->next = channel->next;
-    else channelList = channel->next;
+    if(channel->prev)
+        channel->prev->next = channel->next;
+    else
+        channelList = channel->next;
 
-    if(channel->next) channel->next->prev = channel->prev;
+    if(channel->next)
+        channel->next->prev = channel->prev;
 
     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)
         {
@@ -1312,13 +1344,12 @@ 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;
 
-    if(channel->notes)
-        dict_delete(channel->notes);
+    dict_delete(channel->notes);
     sprintf(msgbuf, "%s %s", channel->channel->name, reason);
     if(!IsSuspended(channel))
         DelChannelUser(chanserv, channel->channel, msgbuf, 0);
@@ -1340,7 +1371,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)
@@ -1360,7 +1391,39 @@ expire_channels(UNUSED_ARG(void *data))
     }
 
     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
@@ -1372,7 +1435,7 @@ protect_user(const struct userNode *victim, const struct userNode *aggressor, st
     /* Don't protect if no one is to be protected, someone is attacking
        himself, or if the aggressor is an IRC Operator. */
     if(protect == 'n' || victim == aggressor || IsOper(aggressor))
-       return 0;
+        return 0;
 
     /* Don't protect if the victim isn't authenticated (because they
        can't be a channel user), unless we are to protect non-users
@@ -1394,14 +1457,14 @@ protect_user(const struct userNode *victim, const struct userNode *aggressor, st
     switch(protect)
     {
     case 'l':
-       if(cs_victim->access > cs_aggressor->access)
+        if(cs_victim->access > cs_aggressor->access)
             return 1;
-       break;
+        break;
     case 'a':
     case 'e':
-       if(cs_victim->access >= cs_aggressor->access)
+        if(cs_victim->access >= cs_aggressor->access)
             return 1;
-       break;
+        break;
     }
 
     return 0;
@@ -1417,8 +1480,8 @@ validate_op(struct userNode *user, struct chanNode *channel, struct userNode *vi
         || (cs_victim->access < cData->lvlOpts[lvlGiveOps]))
        && !check_user_level(channel, user, lvlEnfOps, 0, 0))
     {
-       send_message(user, chanserv, "CSMSG_OPBY_LOCKED");
-       return 0;
+        send_message(user, chanserv, "CSMSG_OPBY_LOCKED");
+        return 0;
     }
 
     return 1;
@@ -1429,27 +1492,28 @@ validate_deop(struct userNode *user, struct chanNode *channel, struct userNode *
 {
     if(IsService(victim))
     {
-       send_message(user, chanserv, "MSG_SERVICE_IMMUNE", victim->nick);
-       return 0;
+        send_message(user, chanserv, "MSG_SERVICE_IMMUNE", victim->nick);
+        return 0;
     }
 
     if(protect_user(victim, user, channel->channel_info))
     {
-       send_message(user, chanserv, "CSMSG_USER_PROTECTED", victim->nick);
-       return 0;
+        send_message(user, chanserv, "CSMSG_USER_PROTECTED", victim->nick);
+        return 0;
     }
 
     return 1;
 }
 
 static struct do_not_register *
-chanserv_add_dnr(const char *chan_name, const char *setter, const char *reason)
+chanserv_add_dnr(const char *chan_name, const char *setter, unsigned long expires, const char *reason)
 {
     struct do_not_register *dnr = calloc(1, sizeof(*dnr)+strlen(reason));
     safestrncpy(dnr->chan_name, chan_name, sizeof(dnr->chan_name));
     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, "*?"))
@@ -1460,44 +1524,85 @@ chanserv_add_dnr(const char *chan_name, const char *setter, const char *reason)
 }
 
 static struct dnrList
-chanserv_find_dnrs(const char *chan_name, struct handle_info *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->handle, NULL)))
-        dnrList_append(&list, dnr);
+
+    if(handle && (dnr = dict_find(handle_dnrs, handle, NULL)))
+    {
+        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];
+    time_t feh;
+
+    user = extra;
+    if(dnr->set)
+    {
+        feh = dnr->set;
+        strftime(buf1, sizeof(buf1), "%d %b %Y", localtime(&feh));
+    }
+    if(dnr->expires)
+    {
+        feh = dnr->expires;
+        strftime(buf2, sizeof(buf2), "%d %b %Y", localtime(&feh));
+        send_message(user, chanserv, "CSMSG_DNR_INFO_SET_EXPIRES", dnr->chan_name, buf1, dnr->setter, buf2, dnr->reason);
+    }
+    else if(dnr->set)
+    {
+        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, struct handle_info *handle)
+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);
@@ -1507,63 +1612,49 @@ 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];
+    unsigned long expiry, duration;
     unsigned int matches;
 
     if(argc < 2)
     {
-        dict_iterator_t it;
-
         reply("CSMSG_DNR_SEARCH_RESULTS");
-        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
@@ -1581,20 +1672,36 @@ 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;
+        }
+
+        const char *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");
     if(*target == '*')
-        matches = chanserv_show_dnrs(user, cmd, NULL, get_handle_info(target + 1));
+        matches = chanserv_show_dnrs(user, cmd, NULL, target + 1);
     else
         matches = chanserv_show_dnrs(user, cmd, target, NULL);
     if(!matches)
@@ -1606,26 +1713,264 @@ static CHANSERV_FUNC(cmd_allowregister)
 {
     const char *chan_name = argv[1];
 
-    if((chan_name[0] == '*') && dict_find(handle_dnrs, chan_name+1, 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(handle_dnrs, chan_name+1);
         reply("CSMSG_DNR_REMOVED", chan_name);
+        return 1;
     }
-    else if(dict_find(plain_dnrs, chan_name, NULL))
+    reply("CSMSG_NO_SUCH_DNR", chan_name);
+    return 0;
+}
+
+struct dnr_search {
+    struct userNode *source;
+    char *chan_mask;
+    char *setter_mask;
+    char *reason_mask;
+    unsigned long min_set, max_set;
+    unsigned long min_expires, max_expires;
+    unsigned int limit;
+};
+
+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;
+
+    for(ii=0; ii<argc; ++ii)
     {
-        dict_remove(plain_dnrs, chan_name);
-        reply("CSMSG_DNR_REMOVED", chan_name);
+        if(ii == argc - 1)
+        {
+            reply("MSG_MISSING_PARAMS", argv[ii]);
+            goto fail;
+        }
+        else if(0 == irccasecmp(argv[ii], "channel"))
+        {
+            discrim->chan_mask = argv[++ii];
+        }
+        else if(0 == irccasecmp(argv[ii], "setter"))
+        {
+            discrim->setter_mask = argv[++ii];
+        }
+        else if(0 == irccasecmp(argv[ii], "reason"))
+        {
+            discrim->reason_mask = argv[++ii];
+        }
+        else if(0 == irccasecmp(argv[ii], "limit"))
+        {
+            discrim->limit = strtoul(argv[++ii], NULL, 0);
+        }
+        else if(0 == irccasecmp(argv[ii], "set"))
+        {
+            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;
+        }
     }
-    else if(dict_find(mask_dnrs, chan_name, NULL))
+    return discrim;
+
+  fail:
+    free(discrim);
+    return NULL;
+}
+
+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)
     {
-        dict_remove(mask_dnrs, chan_name);
-        reply("CSMSG_DNR_REMOVED", chan_name);
+        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_NO_SUCH_DNR", chan_name);
+        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;
 }
 
@@ -1643,7 +1988,6 @@ chanserv_get_owned_count(struct handle_info *hi)
 
 static CHANSERV_FUNC(cmd_register)
 {
-    struct mod_chanmode *change;
     struct handle_info *handle;
     struct chanData *cData;
     struct modeNode *mn;
@@ -1666,7 +2010,8 @@ static CHANSERV_FUNC(cmd_register)
             return 0;
         }
 
-        if(!IsHelping(user) && (!(mn = GetUserMode(channel, user)) || !(mn->modes & MODE_CHANOP)))
+        if(!IsHelping(user)
+           && (!(mn = GetUserMode(channel, user)) || !(mn->modes & MODE_CHANOP)))
         {
             reply("CSMSG_MUST_BE_OPPED", channel->name);
             return 0;
@@ -1695,20 +2040,20 @@ static CHANSERV_FUNC(cmd_register)
 
     if(argc >= (new_channel+2))
     {
-       if(!IsHelping(user))
-       {
-           reply("CSMSG_PROXY_FORBIDDEN");
-           return 0;
-       }
+        if(!IsHelping(user))
+        {
+            reply("CSMSG_PROXY_FORBIDDEN");
+            return 0;
+        }
 
-       if(!(handle = modcmd_get_handle_info(user, argv[new_channel+1])))
+        if(!(handle = modcmd_get_handle_info(user, argv[new_channel+1])))
             return 0;
         force = (argc > (new_channel+2)) && !irccasecmp(argv[new_channel+2], "force");
         dnr = chanserv_is_dnr(chan_name, handle);
     }
     else
     {
-       handle = user->handle_info;
+        handle = user->handle_info;
         dnr = chanserv_is_dnr(chan_name, handle);
     }
     if(dnr && !force)
@@ -1716,7 +2061,7 @@ static CHANSERV_FUNC(cmd_register)
         if(!IsHelping(user))
             reply("CSMSG_DNR_CHANNEL", chan_name);
         else
-            chanserv_show_dnrs(user, cmd, chan_name, handle);
+            chanserv_show_dnrs(user, cmd, chan_name, handle->handle);
         return 0;
     }
 
@@ -1732,12 +2077,21 @@ static CHANSERV_FUNC(cmd_register)
     cData = register_channel(channel, user->handle_info->handle);
     scan_user_presence(add_channel_user(cData, handle, UL_OWNER, 0, NULL), NULL);
     cData->modes = chanserv_conf.default_modes;
-    change = mod_chanmode_dup(&cData->modes, 1);
-    change->args[change->argc].mode = MODE_CHANOP;
-    change->args[change->argc].member = AddChannelUser(chanserv, channel);
-    change->argc++;
-    mod_chanmode_announce(chanserv, channel, change);
-    mod_chanmode_free(change);
+    if(off_channel > 0)
+        cData->modes.modes_set |= MODE_REGISTERED;
+    if (IsOffChannel(cData))
+    {
+        mod_chanmode_announce(chanserv, channel, &cData->modes);
+    }
+    else
+    {
+        struct mod_chanmode *change = mod_chanmode_dup(&cData->modes, 1);
+        change->args[change->argc].mode = MODE_CHANOP;
+        change->args[change->argc].u.member = AddChannelUser(chanserv, channel);
+        change->argc++;
+        mod_chanmode_announce(chanserv, channel, change);
+        mod_chanmode_free(change);
+    }
 
     /* Initialize the channel's max user record. */
     cData->max = channel->members.used;
@@ -1805,11 +2159,11 @@ static CHANSERV_FUNC(cmd_unregister)
             return 0;
         }
         confirm_string = make_confirmation_string(uData);
-       if((argc < 2) || strcmp(argv[1], confirm_string))
-       {
-           reply("CSMSG_CONFIRM_UNREG", confirm_string);
-           return 0;
-       }
+        if((argc < 2) || strcmp(argv[1], confirm_string))
+        {
+            reply("CSMSG_CONFIRM_UNREG", confirm_string);
+            return 0;
+        }
     }
 
     sprintf(reason, "unregistered by %s.", user->handle_info->handle);
@@ -1822,6 +2176,7 @@ static CHANSERV_FUNC(cmd_unregister)
 
 static CHANSERV_FUNC(cmd_move)
 {
+    struct mod_chanmode change;
     struct chanNode *target;
     struct modeNode *mn;
     struct userData *uData;
@@ -1832,8 +2187,8 @@ static CHANSERV_FUNC(cmd_move)
 
     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]))
@@ -1857,12 +2212,13 @@ static CHANSERV_FUNC(cmd_move)
                 if(!IsHelping(user))
                     reply("CSMSG_DNR_CHANNEL_MOVE", argv[1]);
                 else
-                    chanserv_show_dnrs(user, cmd, argv[1], uData->handle);
+                    chanserv_show_dnrs(user, cmd, argv[1], uData->handle->handle);
                 return 0;
             }
         }
     }
 
+    mod_chanmode_init(&change);
     if(!(target = GetChannel(argv[1])))
     {
         target = AddChannel(argv[1], now, NULL, NULL);
@@ -1871,8 +2227,8 @@ static CHANSERV_FUNC(cmd_move)
     }
     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))
@@ -1882,11 +2238,20 @@ static CHANSERV_FUNC(cmd_move)
     }
     else if(!IsSuspended(channel->channel_info))
     {
-        struct mod_chanmode change;
-        mod_chanmode_init(&change);
         change.argc = 1;
         change.args[0].mode = MODE_CHANOP;
-        change.args[0].member = AddChannelUser(chanserv, target);
+        change.args[0].u.member = AddChannelUser(chanserv, target);
+        mod_chanmode_announce(chanserv, target, &change);
+    }
+
+    if(off_channel > 0)
+    {
+        /* Clear MODE_REGISTERED from old channel, add it to new. */
+        change.argc = 0;
+        change.modes_clear = MODE_REGISTERED;
+        mod_chanmode_announce(chanserv, channel, &change);
+        change.modes_clear = 0;
+        change.modes_set = MODE_REGISTERED;
         mod_chanmode_announce(chanserv, target, &change);
     }
 
@@ -1903,8 +2268,8 @@ static CHANSERV_FUNC(cmd_move)
     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);
@@ -1923,7 +2288,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
@@ -1931,50 +2296,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. */
+        /* Pick the data we want to keep. */
         /* If the access is the same, use the later seen time. */
-       if(suData->access == tuData->access)
-           choice = (suData->seen > tuData->seen) ? suData : tuData;
-       else /* Otherwise, keep the higher access level. */
-           choice = (suData->access > tuData->access) ? suData : tuData;
+        if(suData->access == tuData->access)
+            choice = (suData->seen > tuData->seen) ? suData : tuData;
+        else /* Otherwise, keep the higher access level. */
+            choice = (suData->access > tuData->access) ? suData : tuData;
 
-       /* 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. */
@@ -1994,72 +2359,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. */
@@ -2069,8 +2434,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
@@ -2148,7 +2525,7 @@ static CHANSERV_FUNC(cmd_opchan)
     mod_chanmode_init(&change);
     change.argc = 1;
     change.args[0].mode = MODE_CHANOP;
-    change.args[0].member = GetUserMode(channel, chanserv);
+    change.args[0].u.member = GetUserMode(channel, chanserv);
     mod_chanmode_announce(chanserv, channel, &change);
     reply("CSMSG_OPCHAN_DONE", channel->name);
     return 1;
@@ -2157,118 +2534,136 @@ static CHANSERV_FUNC(cmd_opchan)
 static CHANSERV_FUNC(cmd_adduser)
 {
     struct userData *actee;
-    struct userData *actor;
+    struct userData *actor, *real_actor;
     struct handle_info *handle;
-    unsigned short access;
+    unsigned short access, 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)
     {
-       reply("CSMSG_INVALID_ACCESS", argv[2]);
-       return 0;
+        reply("CSMSG_INVALID_ACCESS", argv[2]);
+        return 0;
     }
 
     actor = GetChannelUser(channel->channel_info, user->handle_info);
+    real_actor = GetChannelAccess(channel->channel_info, user->handle_info);
+
     if(actor->access <= access)
     {
-       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)
+        override = CMD_LOG_OVERRIDE;
+
     if(!(handle = modcmd_get_handle_info(user, argv[1])))
         return 0;
 
     if((actee = GetTrueChannelAccess(channel->channel_info, handle)))
     {
-       reply("CSMSG_USER_EXISTS", handle->handle, channel->name, actee->access);
-       return 0;
+        reply("CSMSG_USER_EXISTS", handle->handle, channel->name, actee->access);
+        return 0;
     }
 
     actee = add_channel_user(channel->channel_info, handle, access, 0, NULL);
     scan_user_presence(actee, NULL);
     reply("CSMSG_ADDED_USER", handle->handle, channel->name, access);
-    return 1;
+    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;
     }
 
+    /* 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, 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, override = 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)
@@ -2279,11 +2674,11 @@ static CHANSERV_FUNC(cmd_deluser)
             reply("CSMSG_INVALID_ACCESS", argv[1]);
             return 0;
         }
-       if(access != victim->access)
-       {
-           reply("CSMSG_INCORRECT_ACCESS", handle->handle, victim->access, argv[1]);
-           return 0;
-       }
+        if(access != victim->access)
+        {
+            reply("CSMSG_INCORRECT_ACCESS", handle->handle, victim->access, argv[1]);
+            return 0;
+        }
     }
     else
     {
@@ -2292,23 +2687,31 @@ static CHANSERV_FUNC(cmd_deluser)
 
     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);
     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)
     {
@@ -2318,22 +2721,25 @@ 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)
+        if((uData->access >= min_access)
            && (uData->access <= max_access)
            && match_ircglob(uData->handle->handle, mask))
-           del_channel_user(uData, 1);
+            del_channel_user(uData, 1);
     }
 
     reply("CSMSG_DELETED_USERS", mask, min_access, max_access, channel->name);
-    return 1;
+    return 1 | override;
 }
 
 static CHANSERV_FUNC(cmd_mdelowner)
@@ -2367,13 +2773,13 @@ cmd_trim_bans(struct userNode *user, struct chanNode *channel, unsigned long dur
     struct banData *bData, *next;
     char interval[INTERVALLEN];
     unsigned int count;
-    time_t limit;
+    unsigned long limit;
 
     count = 0;
     limit = now - duration;
     for(bData = channel->channel_info->bans; bData; bData = next)
     {
-       next = bData->next;
+        next = bData->next;
 
         if((bData->triggered && bData->triggered >= limit) || (bData->set && bData->set >= limit))
             continue;
@@ -2388,47 +2794,49 @@ cmd_trim_bans(struct userNode *user, struct chanNode *channel, unsigned long dur
 }
 
 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 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;
+    unsigned long 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);
         return 0;
     }
 
-    if((actor->access <= max_access) && !IsHelping(user))
+    if(!actor || actor->access <= max_access)
     {
-       send_message(user, chanserv, "CSMSG_NO_ACCESS");
-       return 0;
+        send_message(user, chanserv, "CSMSG_NO_ACCESS");
+        return 0;
     }
 
     count = 0;
     limit = now - duration;
     for(uData = channel->channel_info->users; uData; uData = next)
     {
-       next = uData->next;
+        next = uData->next;
 
-       if((uData->seen > limit) || 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))
+        if(((uData->access >= min_access) && (uData->access <= max_access))
            || (!max_access && (uData->access < actor->access)))
-       {
-           del_channel_user(uData, 1);
-           count++;
-       }
+        {
+            del_channel_user(uData, 1);
+            count++;
+        }
     }
 
     if(!max_access)
     {
         min_access = 1;
-        max_access = UL_OWNER;
+        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));
     return 1;
@@ -2438,35 +2846,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], "bans"))
     {
-       cmd_trim_bans(user, channel, duration);
-       return 1;
+        cmd_trim_bans(user, channel, duration);
+        return 1;
     }
     else if(!irccasecmp(argv[1], "users"))
     {
-       cmd_trim_users(user, channel, 0, 0, duration);
-       return 1;
+        cmd_trim_users(user, channel, 0, 0, duration, vacation);
+        return 1;
     }
     else if(parse_level_range(&min_level, &max_level, argv[1]))
     {
-       cmd_trim_users(user, channel, min_level, max_level, duration);
-       return 1;
+        cmd_trim_users(user, channel, min_level, max_level, duration, vacation);
+        return 1;
     }
     else if((min_level = user_level_from_name(argv[1], UL_OWNER)))
     {
-       cmd_trim_users(user, channel, min_level, min_level, duration);
-       return 1;
+        cmd_trim_users(user, channel, min_level, min_level, duration, vacation);
+        return 1;
     }
     else
     {
@@ -2485,12 +2895,12 @@ static CHANSERV_FUNC(cmd_up)
 
     mod_chanmode_init(&change);
     change.argc = 1;
-    change.args[0].member = GetUserMode(channel, user);
-    if(!change.args[0].member)
+    change.args[0].u.member = GetUserMode(channel, user);
+    if(!change.args[0].u.member)
     {
         if(argc)
             reply("MSG_CHANNEL_ABSENT", channel->name);
-       return 0;
+        return 0;
     }
 
     uData = GetChannelAccess(channel->channel_info, user->handle_info);
@@ -2505,12 +2915,18 @@ static CHANSERV_FUNC(cmd_up)
         change.args[0].mode = MODE_CHANOP;
         errmsg = "CSMSG_ALREADY_OPPED";
     }
-    else
+    else if(uData->access >= channel->channel_info->lvlOpts[lvlGiveVoice])
     {
         change.args[0].mode = MODE_VOICE;
         errmsg = "CSMSG_ALREADY_VOICED";
     }
-    change.args[0].mode &= ~change.args[0].member->modes;
+    else
+    {
+        if(argc)
+            reply("CSMSG_NO_ACCESS");
+        return 0;
+    }
+    change.args[0].mode &= ~change.args[0].u.member->modes;
     if(!change.args[0].mode)
     {
         if(argc)
@@ -2527,22 +2943,22 @@ static CHANSERV_FUNC(cmd_down)
 
     mod_chanmode_init(&change);
     change.argc = 1;
-    change.args[0].member = GetUserMode(channel, user);
-    if(!change.args[0].member)
+    change.args[0].u.member = GetUserMode(channel, user);
+    if(!change.args[0].u.member)
     {
         if(argc)
             reply("MSG_CHANNEL_ABSENT", channel->name);
-       return 0;
+        return 0;
     }
 
-    if(!change.args[0].member->modes)
+    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].member->modes;
+    change.args[0].mode = MODE_REMOVE | change.args[0].u.member->modes;
     modcmd_chanmode_announce(&change);
     return 1;
 }
@@ -2553,12 +2969,12 @@ static int cmd_all(struct userNode *user, UNUSED_ARG(struct chanNode *channel),
 
     for(cList = user->handle_info->channels; cList; cList = cList->u_next)
     {
-       if(IsSuspended(cList->channel)
+        if(IsSuspended(cList->channel)
            || IsUserSuspended(cList)
            || !GetUserMode(cList->channel->channel, user))
-           continue;
+            continue;
 
-       mcmd(user, cList->channel->channel, 0, NULL, cmd);
+        mcmd(user, cList->channel->channel, 0, NULL, cmd);
     }
 
     return 1;
@@ -2588,20 +3004,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].member = GetUserMode(channel, victim);
-        if(!change->args[valid].member)
+        change->args[valid].u.member = GetUserMode(channel, victim);
+        if(!change->args[valid].u.member)
             continue;
         if(validate && !validate(user, channel, victim))
-           continue;
+            continue;
         valid++;
     }
 
     change->argc = valid;
     if(valid < (argc-1))
-       reply("CSMSG_PROCESS_FAILED");
+        reply("CSMSG_PROCESS_FAILED");
     if(valid)
     {
         modcmd_chanmode_announce(change);
@@ -2632,7 +3048,7 @@ 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;
 
@@ -2645,7 +3061,7 @@ 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))
+        if(!user_matches_glob(mn->user, ban, MATCH_USENICK | MATCH_VISIBLE))
             continue;
 
         if(protect_user(mn->user, user, channel->channel_info))
@@ -2670,7 +3086,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
@@ -2689,13 +3105,13 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
          * want to allow those (e.g.  unbans) in that case.  If we add
          * some other ejection action for in-channel users, change
          * this too. */
-       victimCount = victims[0] ? 1 : 0;
+        victimCount = victims[0] ? 1 : 0;
 
-       if(IsService(victim))
-       {
-           reply("MSG_SERVICE_IMMUNE", victim->nick);
-           return 0;
-       }
+        if(IsService(victim))
+        {
+            reply("MSG_SERVICE_IMMUNE", victim->nick);
+            return 0;
+        }
 
         if((action == ACTION_KICK) && !victimCount)
         {
@@ -2703,30 +3119,59 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
             return 0;
         }
 
-       if(protect_user(victim, user, channel->channel_info))
-       {
-           reply("CSMSG_USER_PROTECTED", victim->nick);
-           return 0;
-       }
+        if(protect_user(victim, user, channel->channel_info))
+        {
+            reply("CSMSG_USER_PROTECTED", victim->nick);
+            return 0;
+        }
+
+        ban = generate_hostmask(victim, GENMASK_STRICT_HOST|GENMASK_ANY_IDENT);
+        name = victim->nick;
+    }
+    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;
+        }
+
+        snprintf(banmask, sizeof(banmask), "*!*@%s.*", hi->handle);
+        victims = alloca(sizeof(victims[0]) * channel->members.used);
+
+        if(bad_channel_ban(channel, user, banmask, &victimCount, victims))
+        {
+            reply("CSMSG_MASK_PROTECTED", banmask);
+            return 0;
+        }
+
+        if((action == ACTION_KICK) && (victimCount == 0))
+        {
+            reply("CSMSG_NO_MATCHING_USERS", channel->name, banmask);
+            return 0;
+        }
 
-       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]))
+        {
+            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))
         {
-           reply("CSMSG_MASK_PROTECTED", argv[1]);
-           return 0;
-       }
+            reply("CSMSG_MASK_PROTECTED", argv[1]);
+            return 0;
+        }
 
         if((victimCount > 4) && ((victimCount * 3) > channel->members.used) && !IsOper(user))
         {
@@ -2740,7 +3185,7 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
             return 0;
         }
 
-       name = ban = strdup(argv[1]);
+        name = ban = strdup(argv[1]);
     }
 
     /* Truncate the ban in place if necessary; we must ensure
@@ -2749,108 +3194,108 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
 
     if(action & ACTION_ADD_BAN)
     {
-       struct banData *bData, *next;
-
-       if(channel->channel_info->banCount >= chanserv_conf.max_chan_bans)
-       {
-           reply("CSMSG_MAXIMUM_BANS", chanserv_conf.max_chan_bans);
-           free(ban);
-           return 0;
-       }
-
-       if(action & ACTION_ADD_TIMED_BAN)
-       {
-           duration = ParseInterval(argv[2]);
-
-           if(duration < 15)
-           {
-               reply("CSMSG_DURATION_TOO_LOW");
-               free(ban);
-               return 0;
-           }
-           else if(duration > (86400 * 365 * 2))
-           {
-               reply("CSMSG_DURATION_TOO_HIGH");
-               free(ban);
-               return 0;
-           }
-       }
-
-       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)
+        struct banData *bData, *next;
+
+        if(channel->channel_info->banCount >= chanserv_conf.max_chan_bans)
+        {
+            reply("CSMSG_MAXIMUM_BANS", chanserv_conf.max_chan_bans);
+            free(ban);
+            return 0;
+        }
+
+        if(action & ACTION_ADD_TIMED_BAN)
+        {
+            duration = ParseInterval(argv[2]);
+
+            if(duration < 15)
+            {
+                reply("CSMSG_DURATION_TOO_LOW");
+                free(ban);
+                return 0;
+            }
+            else if(duration > (86400 * 365 * 2))
+            {
+                reply("CSMSG_DURATION_TOO_HIGH");
+                free(ban);
+                return 0;
+            }
+        }
+
+        for(bData = channel->channel_info->bans; bData; bData = next)
+        {
+            if(match_ircglobs(bData->mask, ban))
+            {
+                int exact = !irccasecmp(bData->mask, ban);
+
+                /* The ban is redundant; there is already a ban
+                   with the same effect in place. */
+                if(exact)
+                {
+                    if(bData->reason)
                         free(bData->reason);
-                   bData->reason = strdup(reason);
+                    bData->reason = strdup(reason);
                     safestrncpy(bData->owner, (user->handle_info ? user->handle_info->handle : user->nick), sizeof(bData->owner));
                     if(cmd)
                         reply("CSMSG_REASON_CHANGE", ban);
-                   if(!bData->expires)
+                    if(!bData->expires)
                         goto post_add_ban;
-               }
-               if(exact && bData->expires)
-               {
-                   int reset = 0;
-
-                   /* If the ban matches an existing one exactly,
-                      extend the expiration time if the provided
-                      duration is longer. */
-                   if(duration && ((time_t)(now + duration) > bData->expires))
-                   {
-                       bData->expires = now + duration;
-                       reset = 1;
-                   }
-                   else if(!duration)
-                   {
-                       bData->expires = 0;
-                       reset = 1;
-                   }
-
-                   if(reset)
-                   {
-                       /* Delete the expiration timeq entry and
-                          requeue if necessary. */
-                       timeq_del(0, expire_ban, bData, TIMEQ_IGNORE_WHEN);
-
-                       if(bData->expires)
-                           timeq_add(bData->expires, expire_ban, bData);
+                }
+                if(exact && bData->expires)
+                {
+                    int reset = 0;
+
+                    /* If the ban matches an existing one exactly,
+                       extend the expiration time if the provided
+                       duration is longer. */
+                    if(duration && (now + duration > bData->expires))
+                    {
+                        bData->expires = now + duration;
+                        reset = 1;
+                    }
+                    else if(!duration)
+                    {
+                        bData->expires = 0;
+                        reset = 1;
+                    }
+
+                    if(reset)
+                    {
+                        /* Delete the expiration timeq entry and
+                           requeue if necessary. */
+                        timeq_del(0, expire_ban, bData, TIMEQ_IGNORE_WHEN);
+
+                        if(bData->expires)
+                            timeq_add(bData->expires, expire_ban, bData);
 
                         if(!cmd)
                         {
                             /* automated kickban */
                         }
-                       else if(duration)
-                           reply("CSMSG_BAN_EXTENDED", ban, intervalString(interval, duration, user->handle_info));
-                       else
-                           reply("CSMSG_BAN_ADDED", name, channel->name);
-
-                       goto post_add_ban;
-                   }
-               }
+                        else if(duration)
+                            reply("CSMSG_BAN_EXTENDED", ban, intervalString(interval, duration, user->handle_info));
+                        else
+                            reply("CSMSG_BAN_ADDED", name, channel->name);
+
+                        goto post_add_ban;
+                    }
+                }
                 if(cmd)
                     reply("CSMSG_REDUNDANT_BAN", 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);
     }
@@ -2870,37 +3315,37 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
             if(irccasecmp(ban + l1 - l2, old_name))
                 continue;
             new_mask = malloc(MAXLEN);
-            sprintf(new_mask, "%.*s%s", l1-l2, ban, hidden_host_suffix);
+            sprintf(new_mask, "%.*s%s", (int)(l1-l2), ban, hidden_host_suffix);
             free(ban);
             name = ban = new_mask;
         }
     }
 
-  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);
         for(n = 0; n < victimCount; ++n)
         {
             change->args[n].mode = MODE_REMOVE|MODE_CHANOP|MODE_VOICE;
-            change->args[n].member = victims[n];
+            change->args[n].u.member = victims[n];
         }
         if(!exists)
         {
             change->args[n].mode = MODE_BAN;
-            change->args[n++].hostmask = ban;
+            change->args[n++].u.hostmask = ban;
         }
         change->argc = n;
         if(cmd)
@@ -2910,7 +3355,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);
@@ -2921,10 +3366,10 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
     if(action & ACTION_KICK)
     {
         char kick_reason[MAXLEN];
-       sprintf(kick_reason, "%s (%s)", reason, user->nick);
+        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)
@@ -2933,17 +3378,17 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
     }
     else if(action & ACTION_ADD_BAN)
     {
-       if(duration)
-           reply("CSMSG_TIMED_BAN_ADDED", name, channel->name, intervalString(interval, duration, user->handle_info));
-       else
-           reply("CSMSG_BAN_ADDED", name, channel->name);
+        if(duration)
+            reply("CSMSG_TIMED_BAN_ADDED", name, channel->name, intervalString(interval, duration, user->handle_info));
+        else
+            reply("CSMSG_BAN_ADDED", name, channel->name);
     }
     else if((action & (ACTION_BAN | ACTION_KICK)) == (ACTION_BAN | ACTION_KICK))
-       reply("CSMSG_KICK_BAN_DONE", name, channel->name);
+        reply("CSMSG_KICK_BAN_DONE", name, channel->name);
     else if(action & ACTION_BAN)
-       reply("CSMSG_BAN_DONE", name, channel->name);
+        reply("CSMSG_BAN_DONE", name, channel->name);
     else if(action & ACTION_KICK && victimCount)
-       reply("CSMSG_KICK_DONE", name, channel->name);
+        reply("CSMSG_KICK_DONE", name, channel->name);
 
     free(ban);
     return 1;
@@ -2986,7 +3431,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);
             if(match[ii])
                 count++;
         }
@@ -3008,8 +3454,9 @@ find_matching_bans(struct banList *bans, struct userNode *actee, const char *mas
         if(!match[ii])
             continue;
         change->args[count].mode = MODE_REMOVE | MODE_BAN;
-        change->args[count++].hostmask = bans->list[ii]->ban;
+        change->args[count++].u.hostmask = strdup(bans->list[ii]->ban);
     }
+    assert(count == change->argc);
     return change;
 }
 
@@ -3025,13 +3472,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
@@ -3042,7 +3499,11 @@ unban_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
         change = find_matching_bans(&channel->banlist, actee, mask);
         if(change)
         {
+            unsigned int ii;
+
             modcmd_chanmode_announce(change);
+            for(ii = 0; ii < change->argc; ++ii)
+                free((char*)change->args[ii].u.hostmask);
             mod_chanmode_free(change);
             acted = 1;
         }
@@ -3050,30 +3511,31 @@ unban_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
 
     if(action & ACTION_DEL_BAN)
     {
-       struct banData *ban, *next;
-
-       ban = channel->channel_info->bans;
-       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)
+        struct banData *ban, *next;
+
+        ban = channel->channel_info->bans;
+        while(ban)
+        {
+            if(actee)
+                for( ; ban && !user_matches_glob(actee, ban->mask,
+                                                 MATCH_USENICK | MATCH_VISIBLE);
+                     ban = ban->next);
+            else
+                for( ; ban && !match_ircglobs(mask, ban->mask);
+                     ban = ban->next);
+            if(!ban)
                 break;
-           next = ban->next;
-           del_channel_ban(ban);
-           ban = next;
-           acted = 1;
-       }
+            next = ban->next;
+            del_channel_ban(ban);
+            ban = next;
+            acted = 1;
+        }
     }
 
     if(!acted)
-       reply("CSMSG_BAN_NOT_FOUND", actee ? actee->nick : mask);
+        reply("CSMSG_BAN_NOT_FOUND", actee ? actee->nick : mask);
     else
-       reply("CSMSG_BAN_REMOVED", actee ? actee->nick : mask);
+        reply("CSMSG_BAN_REMOVED", actee ? actee->nick : mask);
     if(mask)
         free(mask);
     return 1;
@@ -3098,7 +3560,7 @@ static CHANSERV_FUNC(cmd_unbanme)
 
     /* remove permanent bans if the user has the proper access. */
     if(uData->access >= UL_MASTER)
-       flags |= ACTION_DEL_BAN;
+        flags |= ACTION_DEL_BAN;
 
     argv[1] = user->nick;
     return unban_user(user, channel, 2, argv, cmd, flags);
@@ -3111,17 +3573,19 @@ static CHANSERV_FUNC(cmd_unbanall)
 
     if(!channel->banlist.used)
     {
-       reply("CSMSG_NO_BANS", channel->name);
-       return 0;
+        reply("CSMSG_NO_BANS", channel->name);
+        return 0;
     }
 
     change = mod_chanmode_alloc(channel->banlist.used);
     for(ii=0; ii<channel->banlist.used; ii++)
     {
         change->args[ii].mode = MODE_REMOVE | MODE_BAN;
-        change->args[ii].hostmask = channel->banlist.list[ii]->ban;
+        change->args[ii].u.hostmask = strdup(channel->banlist.list[ii]->ban);
     }
     modcmd_chanmode_announce(change);
+    for(ii = 0; ii < change->argc; ++ii)
+        free((char*)change->args[ii].u.hostmask);
     mod_chanmode_free(change);
     reply("CSMSG_BANS_REMOVED", channel->name);
     return 1;
@@ -3130,6 +3594,7 @@ static CHANSERV_FUNC(cmd_unbanall)
 static CHANSERV_FUNC(cmd_open)
 {
     struct mod_chanmode *change;
+    unsigned int ii;
 
     change = find_matching_bans(&channel->banlist, user, NULL);
     if(!change)
@@ -3140,15 +3605,17 @@ static CHANSERV_FUNC(cmd_open)
         change->modes_clear &= ~channel->channel_info->modes.modes_set;
     modcmd_chanmode_announce(change);
     reply("CSMSG_CHANNEL_OPENED", channel->name);
+    for(ii = 0; ii < change->argc; ++ii)
+        free((char*)change->args[ii].u.hostmask);
     mod_chanmode_free(change);
     return 1;
 }
 
 static CHANSERV_FUNC(cmd_myaccess)
 {
+    static struct string_buffer sbuf;
     struct handle_info *target_handle;
     struct userData *uData;
-    const char *chanName;
 
     if(argc < 2)
         target_handle = user->handle_info;
@@ -3177,11 +3644,27 @@ static CHANSERV_FUNC(cmd_myaccess)
            && (target_handle != user->handle_info)
            && !GetTrueChannelAccess(cData, user->handle_info))
             continue;
-        chanName = cData->channel->name;
+        sbuf.used = 0;
+        string_buffer_append_printf(&sbuf, "[%s (%d", cData->channel->name, uData->access);
+        if(uData->flags != USER_AUTO_OP)
+            string_buffer_append(&sbuf, ',');
+        if(IsUserSuspended(uData))
+            string_buffer_append(&sbuf, 's');
+        if(IsUserAutoOp(uData))
+        {
+            if(uData->access >= cData->lvlOpts[lvlGiveOps])
+                string_buffer_append(&sbuf, 'o');
+            else if(uData->access >= cData->lvlOpts[lvlGiveVoice])
+                string_buffer_append(&sbuf, 'v');
+        }
+        if(IsUserAutoInvite(uData) && (uData->access >= cData->lvlOpts[lvlInviteMe]))
+            string_buffer_append(&sbuf, 'i');
         if(uData->info)
-            send_message_type(4, user, cmd->parent->bot, "[%s (%d)] %s", chanName, uData->access, uData->info);
+            string_buffer_append_printf(&sbuf, ")] %s", uData->info);
         else
-            send_message_type(4, user, cmd->parent->bot, "[%s (%d)]", chanName, uData->access);
+            string_buffer_append_string(&sbuf, ")]");
+        string_buffer_append(&sbuf, '\0');
+        send_message_type(4, user, cmd->parent->bot, "%s", sbuf.list);
     }
 
     return 1;
@@ -3197,7 +3680,7 @@ static CHANSERV_FUNC(cmd_access)
 
     if(argc < 2)
     {
-       target = user;
+        target = user;
         target_handle = target->handle_info;
     }
     else if((target = GetUserH(argv[1])))
@@ -3222,8 +3705,8 @@ static CHANSERV_FUNC(cmd_access)
 
     if(target == chanserv)
     {
-       reply("CSMSG_IS_CHANSERV");
-       return 1;
+        reply("CSMSG_IS_CHANSERV");
+        return 1;
     }
 
     if(!target_handle)
@@ -3233,11 +3716,11 @@ static CHANSERV_FUNC(cmd_access)
             reply("CSMSG_LAZY_SMURF_TARGET", target->nick, chanserv_conf.irc_operator_epithet);
             return 0;
         }
-       if(target != user)
-       {
-           reply("MSG_USER_AUTHENTICATE", target->nick);
-           return 0;
-       }
+        if(target != user)
+        {
+            reply("MSG_USER_AUTHENTICATE", target->nick);
+            return 0;
+        }
         reply("MSG_AUTHENTICATE");
         return 0;
     }
@@ -3248,17 +3731,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)
         {
@@ -3403,26 +3886,27 @@ 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 = zoot_list;
+    send_list = def_list;
+    (void)zoot_list; /* since it doesn't show user levels */
 
     if(user->handle_info)
     {
-       switch(user->handle_info->userlist_style)
-       {
-       case HI_STYLE_DEF: send_list = def_list; break;
-        case HI_STYLE_ZOOT: send_list = zoot_list; break;
-       }
+        switch(user->handle_info->userlist_style)
+        {
+        case HI_STYLE_DEF: send_list = def_list; break;
+        case HI_STYLE_ZOOT: send_list = def_list; break;
+        }
     }
 
     lData.users = alloca(channel->channel_info->userCount * sizeof(struct userData *));
     matches = 0;
     for(uData = channel->channel_info->users; uData; uData = uData->next)
     {
-       if((uData->access < lowest)
+        if((uData->access < lowest)
            || (uData->access > highest)
            || (lData.search && !match_ircglob(uData->handle->handle, lData.search)))
-           continue;
-       lData.users[matches++] = uData;
+            continue;
+        lData.users[matches++] = uData;
     }
     qsort(lData.users, matches, sizeof(lData.users[0]), userData_access_comp);
 
@@ -3502,25 +3986,39 @@ static CHANSERV_FUNC(cmd_plist)
 
 static CHANSERV_FUNC(cmd_bans)
 {
+    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;
 
-    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);
 
     bans = alloca(channel->channel_info->banCount * sizeof(struct banData *));
 
     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))
+                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;
     }
 
@@ -3543,28 +4041,28 @@ static CHANSERV_FUNC(cmd_bans)
     if(!matches)
     {
         table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
-       reply("MSG_NONE");
+        reply("MSG_NONE");
         free(tbl.contents[0]);
         free(tbl.contents);
-       return 0;
+        return 0;
     }
 
     msg_never = user_find_message(user, "MSG_NEVER");
     for(ii = 0; ii < matches; )
     {
-       ban = bans[ii];
+        ban = bans[ii];
 
-       if(!timed)
-           expires = "";
-       else if(ban->expires)
-           expires = intervalString(e_buffer, ban->expires - now, user->handle_info);
-       else
-           expires = msg_never;
+        if(!timed)
+            expires = "";
+        else if(ban->expires)
+            expires = intervalString(e_buffer, ban->expires - now, user->handle_info);
+        else
+            expires = msg_never;
 
-       if(ban->triggered)
-           triggered = intervalString(t_buffer, now - ban->triggered, user->handle_info);
-       else
-           triggered = msg_never;
+        if(ban->triggered)
+            triggered = intervalString(t_buffer, now - ban->triggered, user->handle_info);
+        else
+            triggered = msg_never;
 
         tbl.contents[++ii] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
         tbl.contents[ii][0] = ban->mask;
@@ -3614,12 +4112,12 @@ static CHANSERV_FUNC(cmd_topic)
     cData = channel->channel_info;
     if(argc < 2)
     {
-       if(cData->topic)
-       {
-           SetChannelTopic(channel, chanserv, cData->topic, 1);
-           reply("CSMSG_TOPIC_SET", cData->topic);
+        if(cData->topic)
+        {
+            SetChannelTopic(channel, chanserv, cData->topic, 1);
+            reply("CSMSG_TOPIC_SET", cData->topic);
             return 1;
-       }
+        }
 
         reply("CSMSG_NO_TOPIC", channel->name);
         return 0;
@@ -3684,24 +4182,33 @@ static CHANSERV_FUNC(cmd_topic)
 
 static CHANSERV_FUNC(cmd_mode)
 {
+    struct userData *uData;
     struct mod_chanmode *change;
-    
+    short base_oplevel;
+
     if(argc < 2)
     {
         change = &channel->channel_info->modes;
-       if(change->modes_set || change->modes_clear) {
+        if(change->modes_set || change->modes_clear) {
             modcmd_chanmode_announce(change);
             reply("CSMSG_DEFAULTED_MODES", channel->name);
-       } else
-           reply("CSMSG_NO_MODES", channel->name);
-       return 1;
+        } else
+            reply("CSMSG_NO_MODES", channel->name);
+        return 1;
     }
 
-    change = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE);
+    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)
@@ -3729,7 +4236,7 @@ static CHANSERV_FUNC(cmd_invite)
     if(argc > 1)
     {
         if(!(invite = GetUserH(argv[1])))
-       {
+        {
             reply("MSG_NICK_UNKNOWN", argv[1]);
             return 0;
         }
@@ -3739,8 +4246,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)
@@ -3755,7 +4262,7 @@ static CHANSERV_FUNC(cmd_invite)
     }
     irc_invite(chanserv, invite, channel);
     if(argc > 1)
-       reply("CSMSG_INVITED_USER", argv[1], channel->name);
+        reply("CSMSG_INVITED_USER", argv[1], channel->name);
 
     return 1;
 }
@@ -3764,8 +4271,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))
@@ -3849,8 +4356,8 @@ static CHANSERV_FUNC(cmd_info)
     if(uData && (uData->access >= cData->lvlOpts[lvlGiveOps]))
     {
         mod_chanmode_format(&cData->modes, modes);
-       reply("CSMSG_CHANNEL_TOPIC", cData->topic);
-       reply("CSMSG_CHANNEL_MODES", modes[0] ? modes : user_find_message(user, "MSG_NONE"));
+        reply("CSMSG_CHANNEL_TOPIC", cData->topic);
+        reply("CSMSG_CHANNEL_MODES", modes[0] ? modes : user_find_message(user, "MSG_NONE"));
     }
 
     for(it = dict_first(cData->notes); it; it = iter_next(it))
@@ -3872,9 +4379,10 @@ static CHANSERV_FUNC(cmd_info)
     reply("CSMSG_CHANNEL_USERS", cData->userCount);
     reply("CSMSG_CHANNEL_BANS", 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(privileged)
+        reply("CSMSG_CHANNEL_REGISTERED", intervalString(buffer, now - cData->registered, user->handle_info));
     if(((uData && uData->access >= UL_COOWNER) || privileged) && cData->registrar)
         reply("CSMSG_CHANNEL_REGISTRAR", cData->registrar);
 
@@ -3898,7 +4406,7 @@ static CHANSERV_FUNC(cmd_info)
 
 static CHANSERV_FUNC(cmd_netinfo)
 {
-    extern time_t boot_time;
+    extern unsigned long boot_time;
     extern unsigned long burst_length;
     char interval[INTERVALLEN];
 
@@ -3989,8 +4497,8 @@ static CHANSERV_FUNC(cmd_peek)
     table.contents = alloca(channel->members.used*sizeof(*table.contents));
     for(n = 0; n < channel->members.used; n++)
     {
-       mn = channel->members.list[n];
-       if(!(mn->modes & MODE_CHANOP) || IsLocal(mn->user))
+        mn = channel->members.list[n];
+        if(!(mn->modes & MODE_CHANOP) || IsLocal(mn->user))
             continue;
         table.contents[table.length] = alloca(sizeof(**table.contents));
         table.contents[table.length][0] = mn->user->nick;
@@ -4009,10 +4517,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)))
@@ -4025,11 +4535,13 @@ 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)
@@ -4054,7 +4566,7 @@ static CHANSERV_FUNC(cmd_resync)
             if(!(mn->modes & MODE_CHANOP))
             {
                 changes->args[used].mode = MODE_CHANOP;
-                changes->args[used++].member = mn;
+                changes->args[used++].u.member = mn;
             }
         }
         else if(!cData->lvlOpts[lvlGiveVoice]
@@ -4063,12 +4575,12 @@ static CHANSERV_FUNC(cmd_resync)
             if(mn->modes & MODE_CHANOP)
             {
                 changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_VOICE);
-                changes->args[used++].member = mn;
+                changes->args[used++].u.member = mn;
             }
             if(!(mn->modes & MODE_VOICE))
             {
                 changes->args[used].mode = MODE_VOICE;
-                changes->args[used++].member = mn;
+                changes->args[used++].u.member = mn;
             }
         }
         else
@@ -4076,7 +4588,7 @@ static CHANSERV_FUNC(cmd_resync)
             if(mn->modes)
             {
                 changes->args[used].mode = MODE_REMOVE | mn->modes;
-                changes->args[used++].member = mn;
+                changes->args[used++].u.member = mn;
             }
         }
     }
@@ -4097,24 +4609,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
@@ -4139,7 +4651,7 @@ static MODCMD_FUNC(cmd_names)
         targData = GetTrueChannelAccess(channel->channel_info, targ->handle_info);
         if(!targData)
             continue;
-        if(pos + strlen(targ->nick) + strlen(targ->handle_info->handle) + 6 > sizeof(buf))
+        if(pos + strlen(targ->nick) + strlen(targ->handle_info->handle) + 8 > sizeof(buf))
         {
             buf[pos] = 0;
             reply("CSMSG_CHANNEL_NAMES", channel->name, buf);
@@ -4298,12 +4810,14 @@ static CHANSERV_FUNC(cmd_events)
     unsigned int matches, limit;
 
     limit = (argc > 1) ? atoi(argv[1]) : 10;
-    if(limit < 1 || limit > 200) limit = 10;
+    if(limit < 1 || limit > 200)
+        limit = 10;
 
     memset(&discrim, 0, sizeof(discrim));
     discrim.masks.bot = chanserv;
     discrim.masks.channel_name = channel->name;
-    if(argc > 2) discrim.masks.command = argv[2];
+    if(argc > 2)
+        discrim.masks.command = argv[2];
     discrim.limit = limit;
     discrim.max_time = INT_MAX;
     discrim.severities = 1 << LOG_COMMAND;
@@ -4312,9 +4826,9 @@ static CHANSERV_FUNC(cmd_events)
     reply("CSMSG_EVENT_SEARCH_RESULTS");
     matches = log_entry_search(&discrim, log_report_entry, &report);
     if(matches)
-       reply("MSG_MATCH_COUNT", matches);
+        reply("MSG_MATCH_COUNT", matches);
     else
-       reply("MSG_NO_MATCHES");
+        reply("MSG_NO_MATCHES");
     return 1;
 }
 
@@ -4383,25 +4897,28 @@ chanserv_expire_suspension(void *data)
 {
     struct suspended *suspended = data;
     struct chanNode *channel;
-    struct mod_chanmode change;
 
     if(!suspended->expires || (now < suspended->expires))
         suspended->revoked = now;
     channel = suspended->cData->channel;
     suspended->cData->channel = channel;
     suspended->cData->flags &= ~CHANNEL_SUSPENDED;
-    mod_chanmode_init(&change);
-    change.argc = 1;
-    change.args[0].mode = MODE_CHANOP;
-    change.args[0].member = AddChannelUser(chanserv, channel);
-    mod_chanmode_announce(chanserv, channel, &change);
+    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);
+    }
 }
 
 static CHANSERV_FUNC(cmd_csuspend)
 {
     struct suspended *suspended;
     char reason[MAXLEN];
-    time_t expiry, duration;
+    unsigned long expiry, duration;
     struct userData *uData;
 
     REQUIRE_PARAMS(3);
@@ -4416,9 +4933,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"))
@@ -4503,8 +5020,8 @@ typedef struct chanservSearch
     char *name;
     char *registrar;
 
-    time_t unvisited;
-    time_t registered;
+    unsigned long unvisited;
+    unsigned long registered;
 
     unsigned long flags;
     unsigned int limit;
@@ -4524,47 +5041,49 @@ chanserv_search_create(struct userNode *user, unsigned int argc, char *argv[])
 
     for(i = 0; i < argc; i++)
     {
-       /* Assume all criteria require arguments. */
-       if(i == (argc - 1))
-       {
-           send_message(user, chanserv, "MSG_MISSING_PARAMS", argv[i]);
+        /* Assume all criteria require arguments. */
+        if(i == (argc - 1))
+        {
+            send_message(user, chanserv, "MSG_MISSING_PARAMS", argv[i]);
+            goto fail;
+        }
+
+        if(!irccasecmp(argv[i], "name"))
+            search->name = argv[++i];
+        else if(!irccasecmp(argv[i], "registrar"))
+            search->registrar = argv[++i];
+        else if(!irccasecmp(argv[i], "unvisited"))
+            search->unvisited = ParseInterval(argv[++i]);
+        else if(!irccasecmp(argv[i], "registered"))
+            search->registered = ParseInterval(argv[++i]);
+        else if(!irccasecmp(argv[i], "flags"))
+        {
+            i++;
+            if(!irccasecmp(argv[i], "nodelete"))
+                search->flags |= CHANNEL_NODELETE;
+            else if(!irccasecmp(argv[i], "suspended"))
+                search->flags |= CHANNEL_SUSPENDED;
+            else if(!irccasecmp(argv[i], "unreviewed"))
+                search->flags |= CHANNEL_UNREVIEWED;
+            else
+            {
+                send_message(user, chanserv, "CSMSG_INVALID_CFLAG", argv[i]);
+                goto fail;
+            }
+        }
+        else if(!irccasecmp(argv[i], "limit"))
+            search->limit = strtoul(argv[++i], NULL, 10);
+        else
+        {
+            send_message(user, chanserv, "MSG_INVALID_CRITERIA", argv[i]);
             goto fail;
-       }
-
-       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:
@@ -4582,7 +5101,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;
 }
@@ -4595,10 +5114,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;
@@ -4624,13 +5143,13 @@ static CHANSERV_FUNC(cmd_search)
     REQUIRE_PARAMS(3);
 
     if(!irccasecmp(argv[1], "count"))
-       action = search_count;
+        action = search_count;
     else if(!irccasecmp(argv[1], "print"))
-       action = search_print;
+        action = search_print;
     else
     {
-       reply("CSMSG_ACTION_INVALID", argv[1]);
-       return 0;
+        reply("CSMSG_ACTION_INVALID", argv[1]);
+        return 0;
     }
 
     search = chanserv_search_create(user, argc - 2, argv + 2);
@@ -4638,17 +5157,17 @@ static CHANSERV_FUNC(cmd_search)
         return 0;
 
     if(action == search_count)
-       search->limit = INT_MAX;
+        search->limit = INT_MAX;
 
     if(action == search_print)
-       reply("CSMSG_CHANNEL_SEARCH_RESULTS");
+        reply("CSMSG_CHANNEL_SEARCH_RESULTS");
 
     matches = chanserv_channel_search(search, action, user);
 
     if(matches)
-       reply("MSG_MATCH_COUNT", matches);
+        reply("MSG_MATCH_COUNT", matches);
     else
-       reply("MSG_NO_MATCHES");
+        reply("MSG_NO_MATCHES");
 
     free(search);
     return 1;
@@ -4657,14 +5176,14 @@ static CHANSERV_FUNC(cmd_search)
 static CHANSERV_FUNC(cmd_unvisited)
 {
     struct chanData *cData;
-    time_t interval = chanserv_conf.channel_expire_delay;
+    unsigned long interval = chanserv_conf.channel_expire_delay;
     char buffer[INTERVALLEN];
     unsigned int limit = 25, matches = 0;
 
     if(argc > 1)
     {
-       interval = ParseInterval(argv[1]);
-       if(argc > 2)
+        interval = ParseInterval(argv[1]);
+        if(argc > 2)
             limit = atoi(argv[2]);
     }
 
@@ -4673,12 +5192,12 @@ static CHANSERV_FUNC(cmd_unvisited)
 
     for(cData = channelList; cData && matches < limit; cData = cData->next)
     {
-       if((now - cData->visited) < interval)
+        if((now - cData->visited) < interval)
             continue;
 
-       intervalString(buffer, now - cData->visited, user->handle_info);
-       reply("CSMSG_UNVISITED_DATA", cData->channel->name, buffer);
-       matches++;
+        intervalString(buffer, now - cData->visited, user->handle_info);
+        reply("CSMSG_UNVISITED_DATA", cData->channel->name, buffer);
+        matches++;
     }
 
     return 1;
@@ -4696,20 +5215,20 @@ static MODCMD_FUNC(chan_opt_defaulttopic)
             return 0;
         }
 
-       topic = unsplit_string(argv+1, argc-1, NULL);
+        topic = unsplit_string(argv+1, argc-1, NULL);
 
         free(channel->channel_info->topic);
-       if(topic[0] == '*' && topic[1] == 0)
-       {
+        if(topic[0] == '*' && topic[1] == 0)
+        {
             topic = channel->channel_info->topic = NULL;
-       }
-       else
-       {
-           topic = channel->channel_info->topic = strdup(topic);
+        }
+        else
+        {
+            topic = channel->channel_info->topic = strdup(topic);
             if(channel->channel_info->topic_mask
                && !match_ircglob(channel->channel_info->topic, channel->channel_info->topic_mask))
                 reply("CSMSG_TOPIC_MISMATCH", channel->name);
-       }
+        }
         SetChannelTopic(channel, chanserv, topic ? topic : "", 1);
     }
 
@@ -4733,22 +5252,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)
@@ -4766,18 +5285,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);
     }
@@ -4811,12 +5330,12 @@ static MODCMD_FUNC(chan_opt_modes)
             reply("CSMSG_NO_ACCESS");
             return 0;
         }
-       if(argv[1][0] == '*' && argv[1][1] == 0)
-       {
+        if(argv[1][0] == '*' && argv[1][1] == 0)
+        {
             memset(&channel->channel_info->modes, 0, sizeof(channel->channel_info->modes));
-       }
-       else if(!(new_modes = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE)))
-       {
+        }
+        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;
         }
@@ -4851,27 +5370,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)
@@ -4885,8 +5404,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);
@@ -4897,6 +5416,108 @@ static MODCMD_FUNC(chan_opt_dynlimit)
     CHANNEL_BINARY_OPTION("CSMSG_SET_DYNLIMIT", CHANNEL_DYNAMIC_LIMIT);
 }
 
+static MODCMD_FUNC(chan_opt_offchannel)
+{
+    struct chanData *cData = channel->channel_info;
+    int value;
+
+    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;
+        }
+    }
+    else
+    {
+        /* Find current option value. */
+        value = (cData->flags & CHANNEL_OFFCHANNEL) ? 1 : 0;
+    }
+
+    if(value)
+        reply("CSMSG_SET_OFFCHANNEL", user_find_message(user, "MSG_ON"));
+    else
+        reply("CSMSG_SET_OFFCHANNEL", user_find_message(user, "MSG_OFF"));
+    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;
+}
+
 static MODCMD_FUNC(chan_opt_defaults)
 {
     struct userData *uData;
@@ -4918,7 +5539,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;
@@ -4944,8 +5566,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);
@@ -5055,22 +5677,22 @@ channel_multiple_option(enum charOption option, struct userNode *user, struct ch
     {
         index = atoi(argv[1]);
 
-       if(!isdigit(argv[1][0]) || (index < 0) || (index >= count))
-       {
-           reply("CSMSG_INVALID_NUMERIC", index);
+        if(!isdigit(argv[1][0]) || (index < 0) || (index >= count))
+        {
+            reply("CSMSG_INVALID_NUMERIC", index);
             /* 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;
-       }
+            return 0;
+        }
 
-       cData->chOpts[option] = charOptions[option].values[index].value;
+        cData->chOpts[option] = charOptions[option].values[index].value;
     }
     else
     {
-       /* Find current option value. */
+        /* Find current option value. */
       find_value:
-       for(index = 0;
+        for(index = 0;
             (index < count) && (cData->chOpts[option] != charOptions[option].values[index].value);
             index++);
         if(index == count)
@@ -5145,13 +5767,13 @@ static CHANSERV_FUNC(cmd_set)
 
     if(argc < 2)
     {
-       reply("CSMSG_CHANNEL_OPTIONS");
+        reply("CSMSG_CHANNEL_OPTIONS");
         for(ii = 0; ii < set_shows_list.used; ii++)
         {
             subcmd = set_shows_list.list[ii];
             subcmd->command->func(user, channel, 1, argv+1, subcmd);
         }
-       return 1;
+        return 1;
     }
 
     sprintf(buf, "%s %s", argv[0], argv[1]);
@@ -5167,6 +5789,8 @@ static CHANSERV_FUNC(cmd_set)
         return 0;
     }
 
+    argv[0] = "";
+    argv[1] = buf;
     return subcmd->command->func(user, channel, argc - 1, argv + 1, subcmd);
 }
 
@@ -5184,7 +5808,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]))
     {
@@ -5234,16 +5858,28 @@ 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)
+        {
+            reply("CSMSG_INFOLINE_TOO_LONG", chanserv_conf.max_userinfo_length);
+            return 0;
+        }
+        bp = strcspn(infoline, "\001");
+        if(infoline[bp])
+        {
+            reply("CSMSG_BAD_INFOLINE", infoline[bp]);
+            return 0;
+        }
         if(uData->info)
             free(uData->info);
         if(infoline[0] == '*' && infoline[1] == 0)
@@ -5295,11 +5931,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]);
@@ -5316,9 +5952,12 @@ 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;
+    const char *confirm;
     unsigned int force;
     unsigned short co_access;
     char reason[MAXLEN];
@@ -5344,6 +5983,13 @@ static CHANSERV_FUNC(cmd_giveownership)
         }
         curr_user = owner;
     }
+    else if(!force && (now < cData->ownerTransfer + chanserv_conf.giveownership_period))
+    {
+        char delay[INTERVALLEN];
+        intervalString(delay, cData->ownerTransfer + chanserv_conf.giveownership_period - now, user->handle_info);
+        reply("CSMSG_TRANSFER_WAIT", delay, channel->name);
+        return 0;
+    }
     if(!(new_owner_hi = modcmd_get_handle_info(user, argv[1])))
         return 0;
     if(new_owner_hi == user->handle_info)
@@ -5354,8 +6000,15 @@ static CHANSERV_FUNC(cmd_giveownership)
     new_owner = GetChannelAccess(cData, new_owner_hi);
     if(!new_owner)
     {
-        reply("CSMSG_NO_CHAN_USER", new_owner_hi->handle, channel->name);
-        return 0;
+        if(force)
+        {
+            new_owner = add_channel_user(cData, new_owner_hi, UL_OWNER - 1, 0, NULL);
+        }
+        else
+        {
+            reply("CSMSG_NO_CHAN_USER", new_owner_hi->handle, channel->name);
+            return 0;
+        }
     }
     if((chanserv_get_owned_count(new_owner_hi) >= chanserv_conf.max_owned) && !force)
     {
@@ -5366,9 +6019,19 @@ static CHANSERV_FUNC(cmd_giveownership)
         if(!IsHelping(user))
             reply("CSMSG_DNR_ACCOUNT", new_owner_hi->handle);
         else
-            chanserv_show_dnrs(user, cmd, NULL, new_owner_hi);
+            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;
+        }
+    }
     if(new_owner->access >= UL_COOWNER)
         co_access = new_owner->access;
     else
@@ -5376,6 +6039,7 @@ static CHANSERV_FUNC(cmd_giveownership)
     new_owner->access = UL_OWNER;
     if(curr_user)
         curr_user->access = co_access;
+    cData->ownerTransfer = now;
     reply("CSMSG_OWNERSHIP_GIVEN", channel->name, new_owner_hi->handle);
     sprintf(reason, "%s ownership transferred to %s by %s.", channel->name, new_owner_hi->handle, user->handle_info->handle);
     global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
@@ -5385,11 +6049,13 @@ static CHANSERV_FUNC(cmd_giveownership)
 static CHANSERV_FUNC(cmd_suspend)
 {
     struct handle_info *hi;
-    struct userData *self, *target;
+    struct userData *self, *real_self, *target;
+    unsigned int override = 0;
 
     REQUIRE_PARAMS(2);
     if(!(hi = modcmd_get_handle_info(user, argv[1]))) return 0;
     self = GetChannelUser(channel->channel_info, user->handle_info);
+    real_self = GetChannelAccess(channel->channel_info, user->handle_info);
     if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
     {
         reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
@@ -5410,19 +6076,23 @@ static CHANSERV_FUNC(cmd_suspend)
         target->present = 0;
         target->seen = now;
     }
+    if(!real_self || target->access >= real_self->access)
+        override = CMD_LOG_OVERRIDE;
     target->flags |= USER_SUSPENDED;
     reply("CSMSG_USER_SUSPENDED", hi->handle, channel->name);
-    return 1;
+    return 1 | override;
 }
 
 static CHANSERV_FUNC(cmd_unsuspend)
 {
     struct handle_info *hi;
-    struct userData *self, *target;
+    struct userData *self, *real_self, *target;
+    unsigned int override = 0;
 
     REQUIRE_PARAMS(2);
     if(!(hi = modcmd_get_handle_info(user, argv[1]))) return 0;
     self = GetChannelUser(channel->channel_info, user->handle_info);
+    real_self = GetChannelAccess(channel->channel_info, user->handle_info);
     if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
     {
         reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
@@ -5438,9 +6108,12 @@ static CHANSERV_FUNC(cmd_unsuspend)
         reply("CSMSG_NOT_SUSPENDED", hi->handle);
         return 0;
     }
+    if(!real_self || target->access >= real_self->access)
+        override = CMD_LOG_OVERRIDE;
     target->flags &= ~USER_SUSPENDED;
+    scan_user_presence(target, NULL);
     reply("CSMSG_USER_UNSUSPENDED", hi->handle, channel->name);
-    return 1;
+    return 1 | override;
 }
 
 static MODCMD_FUNC(cmd_deleteme)
@@ -5713,7 +6386,7 @@ handle_join(struct modeNode *mNode)
         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))
             {
                 /* Riding a netburst.  Naughty. */
                 KickChannelUser(user, channel, chanserv, "User from far side of netsplit should have been banned - bye.");
@@ -5728,13 +6401,13 @@ handle_join(struct modeNode *mNode)
     {
         /* Not joining through a ban. */
         for(bData = cData->bans;
-                bData && !user_matches_glob(user, bData->mask, 1);
-                bData = bData->next);
+            bData && !user_matches_glob(user, bData->mask, MATCH_USENICK);
+            bData = bData->next);
 
         if(bData)
         {
             char kick_reason[MAXLEN];
-            sprintf(kick_reason, "%s (%s)", bData->reason, bData->owner);
+            sprintf(kick_reason, "(%s) %s", bData->owner, bData->reason);
 
             bData->triggered = now;
             if(bData != cData->bans)
@@ -5754,7 +6427,7 @@ handle_join(struct modeNode *mNode)
             }
 
             change.args[0].mode = MODE_BAN;
-            change.args[0].hostmask = bData->mask;
+            change.args[0].u.hostmask = bData->mask;
             mod_chanmode_announce(chanserv, channel, &change);
             KickChannelUser(user, channel, chanserv, kick_reason);
             return 1;
@@ -5829,6 +6502,10 @@ 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)
@@ -5836,10 +6513,10 @@ handle_join(struct modeNode *mNode)
             if(modes & MODE_CHANOP)
                 modes &= ~MODE_VOICE;
             change.args[0].mode = modes;
-            change.args[0].member = mNode;
+            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);
@@ -5855,7 +6532,7 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
     unsigned int ii, jj;
 
     if(!user->handle_info)
-       return;
+        return;
 
     mod_chanmode_init(&change);
     change.argc = 1;
@@ -5874,14 +6551,14 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
             if(!IsUserSuspended(channel)
                && IsUserAutoInvite(channel)
                && (channel->access >= channel->channel->lvlOpts[lvlInviteMe])
-               && (cn->modes & (MODE_KEY | MODE_INVITEONLY))
-               && !self->burst)
+               && !self->burst
+               && !user->uplink->burst)
                 irc_invite(chanserv, user, cn);
             continue;
         }
 
-       if(channel->access >= UL_PRESENT)
-           channel->channel->visited = now;
+        if(channel->access >= UL_PRESENT)
+            channel->channel->visited = now;
 
         if(IsUserAutoOp(channel))
         {
@@ -5891,13 +6568,13 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
                 change.args[0].mode = MODE_VOICE;
             else
                 change.args[0].mode = 0;
-            change.args[0].member = mn;
+            change.args[0].u.member = mn;
             if(change.args[0].mode)
                 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)
@@ -5906,20 +6583,21 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
         struct banData *ban;
 
         if((user->channels.list[ii]->modes & (MODE_CHANOP|MODE_VOICE))
-           || !channel->channel_info)
+           || !channel->channel_info
+           || IsSuspended(channel->channel_info))
             continue;
         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))
                 break;
         if(jj < channel->banlist.used)
             continue;
         for(ban = channel->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))
                 continue;
             change.args[0].mode = MODE_BAN;
-            change.args[0].hostmask = ban->mask;
+            change.args[0].u.hostmask = ban->mask;
             mod_chanmode_announce(chanserv, channel, &change);
             sprintf(kick_reason, "(%s) %s", ban->owner, ban->reason);
             KickChannelUser(user, channel, chanserv, kick_reason);
@@ -5942,45 +6620,47 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
 }
 
 static void
-handle_part(struct userNode *user, struct chanNode *channel, UNUSED_ARG(const char *reason))
+handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason))
 {
     struct chanData *cData;
     struct userData *uData;
 
-    cData = channel->channel_info;
-    if(!cData || IsSuspended(cData) || IsLocal(user))
+    cData = mn->channel->channel_info;
+    if(!cData || IsSuspended(cData) || IsLocal(mn->user))
         return;
 
-    if((cData->flags & CHANNEL_DYNAMIC_LIMIT) && !channel->join_flooded)
+    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((channel->limit - 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, user->handle_info)))
+    if((uData = GetTrueChannelAccess(cData, mn->user->handle_info)))
     {
-       scan_user_presence(uData, user);
+        scan_user_presence(uData, mn->user);
         uData->seen = now;
+        if (uData->access >= UL_PRESENT)
+            cData->visited = now;
     }
 
-    if(IsHelping(user) && IsSupportHelper(user))
+    if(IsHelping(mn->user) && IsSupportHelper(mn->user))
     {
         unsigned int ii, jj;
         for(ii = 0; ii < chanserv_conf.support_channels.used; ++ii)
         {
-            for(jj = 0; jj < user->channels.used; ++jj)
-                if(user->channels.list[jj]->channel == chanserv_conf.support_channels.list[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 < user->channels.used)
+            if(jj < mn->user->channels.used)
                 break;
         }
         if(ii == chanserv_conf.support_channels.used)
-            HANDLE_CLEAR_FLAG(user->handle_info, HELPING);
+            HANDLE_CLEAR_FLAG(mn->user->handle_info, HELPING);
     }
 }
 
@@ -5997,7 +6677,7 @@ handle_kick(struct userNode *kicker, struct userNode *victim, struct chanNode *c
     if(protect_user(victim, kicker, channel->channel_info))
     {
         const char *reason = user_find_message(kicker, "CSMSG_USER_PROTECTED");
-       KickChannelUser(kicker, channel, chanserv, reason);
+        KickChannelUser(kicker, channel, chanserv, reason);
     }
 
     if((uData = GetTrueChannelAccess(channel->channel_info, victim->handle_info)))
@@ -6053,7 +6733,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].member->user;
+            const struct userNode *victim = change->args[ii].u.member->user;
             if(!protect_user(victim, user, channel->channel_info))
                 continue;
             if(!bounce)
@@ -6061,36 +6741,36 @@ handle_mode(struct chanNode *channel, struct userNode *user, const struct mod_ch
             if(!deopped)
             {
                 bounce->args[bnc].mode = MODE_REMOVE | MODE_CHANOP;
-                bounce->args[bnc].member = GetUserMode(channel, user);
-                if(bounce->args[bnc].member)
+                bounce->args[bnc].u.member = GetUserMode(channel, user);
+                if(bounce->args[bnc].u.member)
                     bnc++;
                 deopped = 1;
             }
             bounce->args[bnc].mode = MODE_CHANOP;
-            bounce->args[bnc].member = change->args[ii].member;
+            bounce->args[bnc].u.member = change->args[ii].u.member;
             bnc++;
             send_message(user, chanserv, "CSMSG_USER_PROTECTED", victim->nick);
         }
         else if(change->args[ii].mode & MODE_CHANOP)
         {
-            const struct userNode *victim = change->args[ii].member->user;
+            const struct userNode *victim = change->args[ii].u.member->user;
             if(IsService(victim) || validate_op(user, channel, (struct userNode*)victim))
                 continue;
             if(!bounce)
                 bounce = mod_chanmode_alloc(change->argc + 1 - ii);
             bounce->args[bnc].mode = MODE_REMOVE | MODE_CHANOP;
-            bounce->args[bnc].member = change->args[ii].member;
+            bounce->args[bnc].u.member = change->args[ii].u.member;
             bnc++;
         }
         else if((change->args[ii].mode & (MODE_REMOVE | MODE_BAN)) == MODE_BAN)
         {
-            const char *ban = change->args[ii].hostmask;
+            const char *ban = change->args[ii].u.hostmask;
             if(!bad_channel_ban(channel, user, ban, NULL, NULL))
                 continue;
             if(!bounce)
                 bounce = mod_chanmode_alloc(change->argc + 1 - ii);
             bounce->args[bnc].mode = MODE_REMOVE | MODE_BAN;
-            bounce->args[bnc].hostmask = ban;
+            bounce->args[bnc].u.hostmask = strdup(ban);
             bnc++;
             send_message(user, chanserv, "CSMSG_MASK_PROTECTED", ban);
         }
@@ -6099,6 +6779,9 @@ handle_mode(struct chanNode *channel, struct userNode *user, const struct mod_ch
     {
         if((bounce->argc = bnc) || bounce->modes_set || bounce->modes_clear)
             mod_chanmode_announce(chanserv, channel, bounce);
+        for(ii = 0; ii < change->argc; ++ii)
+            if(bounce->args[ii].mode == (MODE_REMOVE | MODE_BAN))
+                free((char*)bounce->args[ii].u.hostmask);
         mod_chanmode_free(bounce);
     }
 }
@@ -6126,7 +6809,7 @@ handle_nick_change(struct userNode *user, UNUSED_ARG(const char *old_nick))
             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))
                 break;
         /* Need not act if we found one. */
         if(jj < channel->banlist.used)
@@ -6134,9 +6817,9 @@ handle_nick_change(struct userNode *user, UNUSED_ARG(const char *old_nick))
         /* 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))
                 continue;
-            change.args[0].hostmask = bData->mask;
+            change.args[0].u.hostmask = bData->mask;
             mod_chanmode_announce(chanserv, channel, &change);
             sprintf(kick_reason, "(%s) %s", bData->owner, bData->reason);
             KickChannelUser(user, channel, chanserv, kick_reason);
@@ -6206,8 +6889,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]);
@@ -6239,7 +6922,7 @@ chanserv_conf_read(void)
     str = database_get_data(conf_node, KEY_INFO_DELAY, RECDB_QSTRING);
     chanserv_conf.info_delay = str ? ParseInterval(str) : 180;
     str = database_get_data(conf_node, KEY_MAX_GREETLEN, RECDB_QSTRING);
-    chanserv_conf.greeting_length = str ? atoi(str) : 120;
+    chanserv_conf.greeting_length = str ? atoi(str) : 200;
     str = database_get_data(conf_node, KEY_ADJUST_THRESHOLD, RECDB_QSTRING);
     chanserv_conf.adjust_threshold = str ? atoi(str) : 15;
     str = database_get_data(conf_node, KEY_ADJUST_DELAY, RECDB_QSTRING);
@@ -6248,17 +6931,23 @@ chanserv_conf_read(void)
     chanserv_conf.channel_expire_frequency = str ? ParseInterval(str) : 86400;
     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);
     chanserv_conf.max_chan_users = str ? atoi(str) : 512;
     str = database_get_data(conf_node, KEY_MAX_CHAN_BANS, RECDB_QSTRING);
     chanserv_conf.max_chan_bans = str ? atoi(str) : 512;
+    str = database_get_data(conf_node, KEY_MAX_USERINFO_LENGTH, RECDB_QSTRING);
+    chanserv_conf.max_userinfo_length = str ? atoi(str) : 400;
     str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
     if(chanserv && str)
         NickChange(chanserv, str, 0);
     str = database_get_data(conf_node, KEY_REFRESH_PERIOD, RECDB_QSTRING);
     chanserv_conf.refresh_period = str ? ParseInterval(str) : 3*60*60;
+    str = database_get_data(conf_node, KEY_GIVEOWNERSHIP_PERIOD, RECDB_QSTRING);
+    chanserv_conf.giveownership_period = str ? ParseInterval(str) : 0;
     str = database_get_data(conf_node, KEY_CTCP_SHORT_BAN_DURATION, RECDB_QSTRING);
     chanserv_conf.ctcp_short_ban_duration = str ? str : "3m";
     str = database_get_data(conf_node, KEY_CTCP_LONG_BAN_DURATION, RECDB_QSTRING);
@@ -6276,7 +6965,8 @@ chanserv_conf_read(void)
         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);
@@ -6332,6 +7022,8 @@ chanserv_conf_read(void)
     else
         strlist = alloc_string_list(2);
     chanserv_conf.old_ban_names = strlist;
+    str = database_get_data(conf_node, "off_channel", RECDB_QSTRING);
+    off_channel = str ? atoi(str) : 0;
 }
 
 static void
@@ -6396,25 +7088,25 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
     struct handle_info *handle;
     struct userData *uData;
     char *seen, *inf, *flags;
-    time_t last_seen;
+    unsigned long last_seen;
     unsigned short access;
 
     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)
     {
-       log_module(CS_LOG, LOG_ERROR, "Invalid access level for %s in %s.", key, chan->channel->name);
-       return;
+        log_module(CS_LOG, LOG_ERROR, "Invalid access level for %s in %s.", key, chan->channel->name);
+        return;
     }
 
     inf = database_get_data(rd->d.object, KEY_INFO, RECDB_QSTRING);
     seen = database_get_data(rd->d.object, KEY_SEEN, RECDB_QSTRING);
-    last_seen = seen ? (signed)strtoul(seen, NULL, 0) : now;
+    last_seen = seen ? strtoul(seen, NULL, 0) : now;
     flags = database_get_data(rd->d.object, KEY_FLAGS, RECDB_QSTRING);
     handle = get_handle_info(key);
     if(!handle)
@@ -6432,12 +7124,12 @@ ban_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
 {
     struct banData *bData;
     char *set, *triggered, *s_duration, *s_expires, *reason, *owner;
-    time_t set_time, triggered_time, expires_time;
+    unsigned long set_time, triggered_time, expires_time;
 
     if(rd->type != RECDB_OBJECT || !dict_size(rd->d.object))
     {
-       log_module(CS_LOG, LOG_ERROR, "Invalid ban in %s.", chan->channel->name);
-       return;
+        log_module(CS_LOG, LOG_ERROR, "Invalid ban in %s.", chan->channel->name);
+        return;
     }
 
     set = database_get_data(rd->d.object, KEY_SET, RECDB_QSTRING);
@@ -6446,17 +7138,19 @@ ban_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
     s_expires = database_get_data(rd->d.object, KEY_EXPIRES, RECDB_QSTRING);
     owner = database_get_data(rd->d.object, KEY_OWNER, RECDB_QSTRING);
     reason = database_get_data(rd->d.object, KEY_REASON, RECDB_QSTRING);
+    if (!reason || !owner)
+        return;
 
-    set_time = set ? (time_t)strtoul(set, NULL, 0) : now;
-    triggered_time = triggered ? (time_t)strtoul(triggered, NULL, 0) : 0;
+    set_time = set ? strtoul(set, NULL, 0) : now;
+    triggered_time = triggered ? strtoul(triggered, NULL, 0) : 0;
     if(s_expires)
-        expires_time = (time_t)strtoul(s_expires, NULL, 0);
+        expires_time = strtoul(s_expires, NULL, 0);
     else if(s_duration)
         expires_time = set_time + atoi(s_duration);
     else
         expires_time = 0;
 
-    if(expires_time && (expires_time < now))
+    if(!reason || (expires_time && (expires_time < now)))
         return;
 
     bData = add_channel_ban(chan, key, owner, set_time, triggered_time, expires_time, reason);
@@ -6470,11 +7164,11 @@ chanserv_read_suspended(dict_t obj)
     dict_t previous;
 
     str = database_get_data(obj, KEY_EXPIRES, RECDB_QSTRING);
-    suspended->expires = str ? (time_t)strtoul(str, NULL, 0) : 0;
+    suspended->expires = str ? strtoul(str, NULL, 0) : 0;
     str = database_get_data(obj, KEY_REVOKED, RECDB_QSTRING);
-    suspended->revoked = str ? (time_t)strtoul(str, NULL, 0) : 0;
+    suspended->revoked = str ? strtoul(str, NULL, 0) : 0;
     str = database_get_data(obj, KEY_ISSUED, RECDB_QSTRING);
-    suspended->issued = str ? (time_t)strtoul(str, NULL, 0) : 0;
+    suspended->issued = str ? strtoul(str, NULL, 0) : 0;
     suspended->suspender = strdup(database_get_data(obj, KEY_SUSPENDER, RECDB_QSTRING));
     suspended->reason = strdup(database_get_data(obj, KEY_REASON, RECDB_QSTRING));
     previous = database_get_data(obj, KEY_PREVIOUS, RECDB_OBJECT);
@@ -6509,7 +7203,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)))
@@ -6547,7 +7241,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)
         {
@@ -6574,7 +7268,7 @@ chanserv_channel_read(const char *key, struct record_data *hir)
         for(chOpt = 0; chOpt < NUM_CHAR_OPTIONS; ++chOpt)
             cData->chOpts[chOpt] = ((count <= charOptions[chOpt].old_idx) ? str : CHANNEL_DEFAULT_OPTIONS)[charOptions[chOpt].old_idx];
     }
-   
+
     if((obj = database_get_data(hir->d.object, KEY_SUSPENDED, RECDB_OBJECT)))
     {
         suspended = chanserv_read_suspended(obj);
@@ -6583,14 +7277,14 @@ chanserv_channel_read(const char *key, struct record_data *hir)
         /* We could use suspended->expires and suspended->revoked to
          * set the CHANNEL_SUSPENDED flag, but we don't. */
     }
-    else if(cData->flags & CHANNEL_SUSPENDED)
+    else if(IsSuspended(cData) && (str = database_get_data(hir->d.object, KEY_SUSPENDER, RECDB_QSTRING)))
     {
         suspended = calloc(1, sizeof(*suspended));
         suspended->issued = 0;
         suspended->revoked = 0;
+        suspended->suspender = strdup(str);
         str = database_get_data(hir->d.object, KEY_SUSPEND_EXPIRES, RECDB_QSTRING);
         suspended->expires = str ? atoi(str) : 0;
-        suspended->suspender = strdup(database_get_data(hir->d.object, KEY_SUSPENDER, RECDB_QSTRING));
         str = database_get_data(hir->d.object, KEY_SUSPEND_REASON, RECDB_QSTRING);
         suspended->reason = strdup(str ? str : "No reason");
         suspended->previous = NULL;
@@ -6598,33 +7292,33 @@ chanserv_channel_read(const char *key, struct record_data *hir)
         suspended->cData = cData;
     }
     else
-        suspended = NULL;
-
-    if((cData->flags & CHANNEL_SUSPENDED)
-       && suspended->expires
-       && (suspended->expires <= now))
     {
         cData->flags &= ~CHANNEL_SUSPENDED;
+        suspended = NULL; /* to squelch a warning */
     }
 
-    if(!(cData->flags & CHANNEL_SUSPENDED))
-    {
+    if(IsSuspended(cData)) {
+        if(suspended->expires > now)
+            timeq_add(suspended->expires, chanserv_expire_suspension, suspended);
+        else if(suspended->expires)
+            cData->flags &= ~CHANNEL_SUSPENDED;
+    }
+
+    if((!off_channel || !IsOffChannel(cData)) && !IsSuspended(cData)) {
         struct mod_chanmode change;
         mod_chanmode_init(&change);
         change.argc = 1;
         change.args[0].mode = MODE_CHANOP;
-        change.args[0].member = AddChannelUser(chanserv, cNode);
+        change.args[0].u.member = AddChannelUser(chanserv, cNode);
         mod_chanmode_announce(chanserv, cNode, &change);
     }
-    else if(suspended->expires > now)
-    {
-        timeq_add(suspended->expires, chanserv_expire_suspension, suspended);
-    }
 
     str = database_get_data(channel, KEY_REGISTERED, RECDB_QSTRING);
-    cData->registered = str ? (time_t)strtoul(str, NULL, 0) : now;
+    cData->registered = str ? strtoul(str, NULL, 0) : now;
     str = database_get_data(channel, KEY_VISITED, RECDB_QSTRING);
-    cData->visited = str ? (time_t)strtoul(str, NULL, 0) : now;
+    cData->visited = str ? strtoul(str, NULL, 0) : now;
+    str = database_get_data(channel, KEY_OWNER_TRANSFER, RECDB_QSTRING);
+    cData->ownerTransfer = str ? strtoul(str, NULL, 0) : 0;
     str = database_get_data(channel, KEY_MAX, RECDB_QSTRING);
     cData->max = str ? atoi(str) : 0;
     str = database_get_data(channel, KEY_GREETING, RECDB_QSTRING);
@@ -6636,25 +7330,27 @@ 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;
 
-    if((str = database_get_data(channel, KEY_MODES, RECDB_QSTRING))
+    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)
+          cData->modes.modes_set |= MODE_REGISTERED;
         if(cData->modes.argc > 1)
             cData->modes.argc = 1;
-        if(!IsSuspended(cData))
-            mod_chanmode_announce(chanserv, cNode, &cData->modes);
+        mod_chanmode_announce(chanserv, cNode, &cData->modes);
         mod_chanmode_free(modes);
     }
 
     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;
     }
 
@@ -6697,6 +7393,7 @@ chanserv_dnr_read(const char *key, struct record_data *hir)
 {
     const char *setter, *reason, *str;
     struct do_not_register *dnr;
+    unsigned long expiry;
 
     setter = database_get_data(hir->d.object, KEY_DNR_SETTER, RECDB_QSTRING);
     if(!setter)
@@ -6710,7 +7407,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 ? 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);
@@ -6731,8 +7432,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))
@@ -6755,7 +7456,7 @@ chanserv_write_users(struct saxdb_context *ctx, struct userData *uData)
         saxdb_write_int(ctx, KEY_SEEN, uData->seen);
         if(uData->flags)
             saxdb_write_int(ctx, KEY_FLAGS, uData->flags);
-       if(uData->info)
+        if(uData->info)
             saxdb_write_string(ctx, KEY_INFO, uData->info);
         saxdb_end_record(ctx);
     }
@@ -6842,7 +7543,7 @@ chanserv_write_channel(struct saxdb_context *ctx, struct chanData *channel)
 
     if(channel->modes.modes_set || channel->modes.modes_clear)
     {
-       mod_chanmode_format(&channel->modes, buf);
+        mod_chanmode_format(&channel->modes, buf);
         saxdb_write_string(ctx, KEY_MODES, buf);
     }
 
@@ -6856,7 +7557,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);
@@ -6865,6 +7566,8 @@ chanserv_write_channel(struct saxdb_context *ctx, struct chanData *channel)
         saxdb_end_record(ctx);
     }
 
+    if(channel->ownerTransfer)
+        saxdb_write_int(ctx, KEY_OWNER_TRANSFER, channel->ownerTransfer);
     saxdb_write_int(ctx, KEY_VISITED, high_present ? now : channel->visited);
     saxdb_end_record(ctx);
 }
@@ -6902,14 +7605,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)
+        {
+            dict_remove(dnrs, iter_key(it));
+            continue;
+        }
         saxdb_start_record(ctx, dnr->chan_name, 0);
         if(dnr->set)
             saxdb_write_int(ctx, KEY_DNR_SET, dnr->set);
+        if(dnr->expires)
+            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);
@@ -6980,17 +7691,19 @@ init_chanserv(const char *nick)
     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);
+        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);
+        reg_auth_func(handle_auth);
+    }
 
-    reg_auth_func(handle_auth);
     reg_handle_rename_func(handle_rename);
     reg_unreg_func(handle_unreg);
 
@@ -7006,6 +7719,10 @@ init_chanserv(const char *nick)
     DEFINE_COMMAND(register, 1, MODCMD_REQUIRE_AUTHED, "flags", "+acceptchan,+helping", 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);
@@ -7121,6 +7838,11 @@ init_chanserv(const char *nick)
     DEFINE_CHANNEL_OPTION(ctcpusers);
     DEFINE_CHANNEL_OPTION(ctcpreaction);
     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);
+    if(off_channel > 1)
+        DEFINE_CHANNEL_OPTION(offchannel);
     modcmd_register(chanserv_module, "set defaults", chan_opt_defaults, 1, 0, "access", "owner", NULL);
 
     /* Alias set topic to set defaulttopic for compatibility. */
@@ -7138,22 +7860,26 @@ init_chanserv(const char *nick)
     dict_set_free_data(note_types, chanserv_deref_note_type);
     if(nick)
     {
-        chanserv = AddService(nick, "Channel Services");
-        service_register(chanserv, '!');
+        const char *modes = conf_get_data("services/chanserv/modes", RECDB_QSTRING);
+        chanserv = AddLocalUser(nick, nick, NULL, "Channel Services", modes);
+        service_register(chanserv)->trigger = '!';
         reg_chanmsg_func('\001', chanserv, chanserv_ctcp_check);
     }
     saxdb_register("ChanServ", chanserv_saxdb_read, chanserv_saxdb_write);
 
     if(chanserv_conf.channel_expire_frequency)
-       timeq_add(now + chanserv_conf.channel_expire_frequency, expire_channels, NULL);
+        timeq_add(now + chanserv_conf.channel_expire_frequency, expire_channels, NULL);
+
+    if(chanserv_conf.dnr_expire_frequency)
+        timeq_add(now + chanserv_conf.dnr_expire_frequency, expire_dnrs, NULL);
 
     if(chanserv_conf.refresh_period)
     {
-        time_t next_refresh;
+        unsigned long next_refresh;
         next_refresh = (now + chanserv_conf.refresh_period - 1) / chanserv_conf.refresh_period * chanserv_conf.refresh_period;
         timeq_add(next_refresh, chanserv_refresh_topics, NULL);
     }
-    
+
     reg_exit_func(chanserv_db_cleanup);
     message_register_table(msgtab);
 }