]> jfr.im git - irc/evilnet/x3.git/blobdiff - src/chanserv.c
fixing giveownership logs access to invalid memory address after free
[irc/evilnet/x3.git] / src / chanserv.c
index 29748f3e2dbc8eb3de280278eaa4f41590f817b1..5fe14557be3b33a1d664132f355e1ebb7b747669 100644 (file)
 #define KEY_LEVEL              "level"
 #define KEY_INFO               "info"
 #define KEY_SEEN               "seen"
+#define KEY_ACCESSEXPIRY       "accessexpiry"
+#define KEY_CLVLEXPIRY         "clvlexpiry"
+#define KEY_LASTLEVEL          "lastlevel"
 
 /* Ban data */
 #define KEY_OWNER              "owner"
@@ -199,6 +202,8 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_DELETED_YOU", "Your $b%d$b access has been deleted from $b%s$b." },
 
 /* User management */
+    { "CSMSG_AUTO_DELETED", "Your %s access has expired in %s." },
+    { "CSMSG_CLVL_EXPIRED", "Your CLVL access has expired in %s." },
     { "CSMSG_ADDED_USER", "Added %s to the %s user list with access %s (%d)." },
     { "CSMSG_DELETED_USER", "Deleted %s (with access %d) from the %s user list." },
     { "CSMSG_BAD_RANGE", "Invalid access range; minimum (%d) must be greater than maximum (%d)." },
@@ -217,7 +222,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_NO_BUMP_EXPIRY", "You cannot give users timed $bCLVL$b's when they already have timed access." },
     { "CSMSG_MULTIPLE_OWNERS", "There is more than one owner in %s; please use $bCLVL$b, $bDELOWNER$b and/or $bADDOWNER$b instead." },
+    { "CSMSG_NO_OWNER", "There is no owner for %s; please use $bCLVL$b and/or $bADDOWNER$b instead." },
     { "CSMSG_TRANSFER_WAIT", "You must wait %s before you can give ownership of $b%s$b to someone else." },
     { "CSMSG_NO_TRANSFER_SELF", "You cannot give ownership to your own account." },
     { "CSMSG_OWNERSHIP_GIVEN", "Ownership of $b%s$b has been transferred to account $b%s$b." },
@@ -229,7 +236,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_BAN_DONE", "Banned $b%s$b from %s." },
     { "CSMSG_REASON_CHANGE", "Reason for LAMER $b%s$b changed." },
     { "CSMSG_LAMER_EXTENDED", "Extended LAMER for $b%s$b, now expires in %s." },
-    { "CSMSG_BAN_REMOVED", "Matching ban(s) and LAMER(s) in $b%s$b were removed." },
+    { "CSMSG_BAN_REMOVED", "Ban(s) and LAMER(s) matching $b%s$b were removed." },
     { "CSMSG_TRIMMED_LAMERS", "Trimmed $b%d LAMERs$b from the %s LAMER list that were inactive for at least %s." },
     { "CSMSG_REDUNDANT_BAN", "$b%s$b is already banned in %s." },
     { "CSMSG_REDUNDANT_LAMER", "$b%s$b is already LAMER'd in %s." },
@@ -296,6 +303,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_USET_AUTOOP",       "$bAutoOp      $b %s" },
     { "CSMSG_USET_AUTOVOICE",    "$bAutoVoice   $b %s" },
     { "CSMSG_USET_AUTOINVITE",   "$bAutoInvite  $b %s" },
+    { "CSMSG_USET_AUTOJOIN",     "$bAutoJoin    $b %s" },
     { "CSMSG_USET_INFO",         "$bInfo        $b %s" },
 
     { "CSMSG_USER_PROTECTED", "Sorry, $b%s$b is protected." },
@@ -316,6 +324,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_AUTOMODE_HOP", "#1 plus give halfops to everyone." },
     { "CSMSG_AUTOMODE_OP", "#1 plus give ops to everyone (not advised)" },
     { "CSMSG_AUTOMODE_MUTE", "Give half-op to halfops, and op to ops only." },
+    { "CSMSG_AUTOMODE_ONLYVOICE", "Just voice everyone with access." },
 
     { "CSMSG_PROTECT_ALL", "Non-users and users will be protected from those of equal or lower access." },
     { "CSMSG_PROTECT_EQUAL", "Users will be protected from those of equal or lower access." },
@@ -513,6 +522,8 @@ static const struct message_entry msgtab[] = {
 /* Other things */
     { "CSMSG_EVENT_SEARCH_RESULTS", "$bChannel Events for %s$b" },
     { "CSMSG_LAST_INVALID", "Invalid argument.  must be 1-200" },
+    { "CSMSG_DEFCON_NO_NEW_CHANNELS", "You cannot register new channels at this time, please try again soon." },
+    { "CSMSG_DEFCON_NO_MODE_CHANGE", "You cannot change the MODE at this time, please try again soon." },
     { NULL, NULL }
 };
 
@@ -684,7 +695,8 @@ struct charOptionValues {
     { 'v', "CSMSG_AUTOMODE_VOICE" },
     { 'h', "CSMSG_AUTOMODE_HOP" },
     { 'o', "CSMSG_AUTOMODE_OP" },
-    { 'm', "CSMSG_AUTOMODE_MUTE" }
+    { 'm', "CSMSG_AUTOMODE_MUTE" },
+    { 'l', "CSMSG_AUTOMODE_ONLYVOICE" }
 }, protectValues[] = {
     { 'a', "CSMSG_PROTECT_ALL" },
     { 'e', "CSMSG_PROTECT_EQUAL" },
@@ -1232,7 +1244,7 @@ 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, time_t seen, const char *info, time_t accessexpiry)
 {
     struct userData *ud;
 
@@ -1245,6 +1257,9 @@ add_channel_user(struct chanData *channel, struct handle_info *handle, unsigned
     ud->seen = seen;
     ud->access = access;
     ud->info = info ? strdup(info) : NULL;
+    ud->accessexpiry = accessexpiry ? accessexpiry : 0;
+    ud->clvlexpiry = 0;
+    ud->lastaccess = 0;
 
     ud->prev = NULL;
     ud->next = channel->users;
@@ -1267,6 +1282,110 @@ add_channel_user(struct chanData *channel, struct handle_info *handle, unsigned
 
 static void unregister_channel(struct chanData *channel, const char *reason);
 
+static void
+chanserv_expire_tempuser(void *data)
+{
+    struct userData *uData = data;
+    char *handle;
+
+    if (data) {
+        handle = strdup(uData->handle->handle);
+        if (uData->accessexpiry > 0) {
+            if (uData->present) {
+                struct userNode *user, *next_un = NULL;
+                struct handle_info *hi;
+
+                hi = get_handle_info(handle);
+                for (user = hi->users; user; user = next_un) {
+                    struct mod_chanmode *change;
+                    struct modeNode *mn;
+                    unsigned int count = 0;
+
+                    send_message(user, chanserv, "CSMSG_AUTO_DELETED", chanserv->nick, uData->channel->channel->name);
+                    if (!(mn = GetUserMode(uData->channel->channel, user)) || !(mn->modes & MODE_CHANOP)) {
+                        next_un = user->next_authed;
+                        continue;
+                    }
+
+                    change = mod_chanmode_alloc(2);
+                    change->args[count].mode = MODE_REMOVE | MODE_CHANOP;
+                    change->args[count++].u.member = mn;
+
+                    if (count) {
+                        change->argc = count;
+                        mod_chanmode_announce(chanserv, uData->channel->channel, change);
+                    }
+                    mod_chanmode_free(change);
+                    next_un = user->next_authed;
+                }
+            }
+            del_channel_user(uData, 1);
+        }
+    }
+}
+
+static void
+chanserv_expire_tempclvl(void *data)
+{
+    struct userData *uData = data;
+    char *handle;
+
+    if (data) {
+        handle = strdup(uData->handle->handle);
+        if (uData->clvlexpiry > 0) {
+            int changemodes = 0;
+            unsigned int mode = 0;
+            if (((uData->lastaccess == UL_PEON) || (uData->lastaccess == UL_HALFOP)) && (uData->access >= UL_OP)) {
+                changemodes = 1;
+                mode = MODE_REMOVE | MODE_CHANOP;
+            } else if ((uData->lastaccess == UL_PEON) && (uData->access == UL_HALFOP)) {
+                changemodes = 1;
+                mode = MODE_REMOVE | MODE_HALFOP;
+            } else
+                changemodes = 0;
+
+            if (uData->present) {
+                struct userNode *user, *next_un = NULL;
+                struct handle_info *hi;
+
+                hi = get_handle_info(handle);
+                for (user = hi->users; user; user = next_un) {
+                    struct mod_chanmode *change;
+                    struct modeNode *mn;
+                    unsigned int count = 0;
+
+                    send_message(user, chanserv, "CSMSG_CLVL_EXPIRED", uData->channel->channel->name);
+                    if (!(mn = GetUserMode(uData->channel->channel, user))) {
+                        next_un = user->next_authed;
+                        continue;
+                    }
+
+                    if (changemodes == 0) {
+                        next_un = user->next_authed;
+                        continue;
+                    }
+                    change = mod_chanmode_alloc(2);
+                    change->args[count].mode = mode;
+                    change->args[count++].u.member = mn;
+
+                    if (count) {
+                        change->argc = count;
+                        mod_chanmode_announce(chanserv, uData->channel->channel, change);
+                    }
+                    mod_chanmode_free(change);
+                    next_un = user->next_authed;
+                }
+            }
+
+            uData->access = uData->lastaccess;
+            uData->lastaccess = 0;
+            uData->clvlexpiry = 0;
+        }
+    }
+}
+
 void
 del_channel_user(struct userData *user, int do_gc)
 {
@@ -1275,6 +1394,9 @@ del_channel_user(struct userData *user, int do_gc)
     channel->userCount--;
     userCount--;
 
+    timeq_del(0, chanserv_expire_tempuser, user, TIMEQ_IGNORE_WHEN);
+    timeq_del(0, chanserv_expire_tempclvl, user, TIMEQ_IGNORE_WHEN);
+
     if(user->prev)
         user->prev->next = user->next;
     else
@@ -1385,7 +1507,7 @@ process_adduser_pending(struct userNode *user)
         }
         else
         {
-            actee = add_channel_user(ap->channel->channel_info, ap->user->handle_info, ap->level, 0, NULL);
+            actee = add_channel_user(ap->channel->channel_info, ap->user->handle_info, ap->level, 0, NULL, 0);
             scan_user_presence(actee, NULL);
         }
         del_adduser_pending(ap);
@@ -1632,25 +1754,43 @@ expire_channels(UNUSED_ARG(void *data))
 }
 
 static int
-protect_user(const struct userNode *victim, const struct userNode *aggressor, struct chanData *channel)
+protect_user(const struct userNode *victim, const struct userNode *aggressor, struct chanData *channel, int protect_invitables)
 {
     char protect = channel->chOpts[chProtect];
     struct userData *cs_victim, *cs_aggressor;
 
-    /* Don't protect if no one is to be protected, someone is attacking
-       himself, or if the aggressor is an IRC Operator. */
-    if(protect == 'n' || victim == aggressor /* Opers dont get special treatment :/  || IsOper(aggressor) */)
-       return 0;
+    /* If victim access level is greater than set invitelevel, don't let
+     * us kick them, but don't consider it punishment if someone else does
+     */
 
+
+    if(victim == aggressor)
+        return 0;
     /* Don't protect if the victim isn't authenticated (because they
        can't be a channel user), unless we are to protect non-users
        also. */
+
     cs_victim = GetChannelAccess(channel, victim->handle_info);
+
+    /* If they have enough access to invite themselvs through a ban,
+     * and its us kicking them, don't. -Rubin */
+    if(protect_invitables==true && cs_victim && (cs_victim->access >= channel->lvlOpts[lvlInviteMe]))
+        return 1;
+
+    if(protect == 'n')
+       return 0;
+
     if(protect != 'a' && !cs_victim)
         return 0;
 
     /* Protect if the aggressor isn't a user because at this point,
        the aggressor can only be less than or equal to the victim. */
+
+    /* Not protected from chanserv except above */
+    /* XXX: need to generic-ize chanserv to "one of x3's services" somehow.. */
+    if(aggressor == chanserv)
+        return 0;
+
     cs_aggressor = GetChannelAccess(channel, aggressor->handle_info);
     if(!cs_aggressor)
         return 1;
@@ -1722,7 +1862,7 @@ validate_deop(struct svccmd *cmd, struct userNode *user, struct chanNode *channe
        return 0;
     }
 
-    if(protect_user(victim, user, channel->channel_info))
+    if(protect_user(victim, user, channel->channel_info, false))
     {
        reply("CSMSG_USER_PROTECTED", victim->nick);
        return 0;
@@ -1740,7 +1880,7 @@ validate_dehop(struct svccmd *cmd, struct userNode *user, struct chanNode *chann
         return 0;
     }
 
-    if(protect_user(victim, user, channel->channel_info))
+    if(protect_user(victim, user, channel->channel_info, false))
     {
         reply("CSMSG_USER_PROTECTED", victim->nick);
         return 0;
@@ -1963,6 +2103,10 @@ static CHANSERV_FUNC(cmd_register)
     struct do_not_register *dnr;
     unsigned int n;
 
+    if (checkDefCon(DEFCON_NO_NEW_CHANNELS) && !IsOper(user)) {
+        reply("CSMSG_DEFCON_NO_NEW_CHANNELS");
+        return 0;
+    }
 
     if(channel)
     {
@@ -2075,7 +2219,7 @@ static CHANSERV_FUNC(cmd_register)
         channel = AddChannel(argv[1], now, NULL, NULL, NULL);
 
     cData = register_channel(channel, user->handle_info->handle);
-    scan_user_presence(add_channel_user(cData, handle, UL_OWNER, 0, NULL), NULL);
+    scan_user_presence(add_channel_user(cData, handle, UL_OWNER, 0, NULL, 0), NULL);
     cData->modes = chanserv_conf.default_modes;
     if(off_channel > 0)
       cData->modes.modes_set |= MODE_REGISTERED;
@@ -2099,10 +2243,10 @@ static CHANSERV_FUNC(cmd_register)
     if(handle != user->handle_info)
         reply("CSMSG_PROXY_SUCCESS", handle->handle, channel->name);
     else
-        reply("CSMSG_REG_SUCCESS", channel->name);
 
     sprintf(reason, "%s registered to %s by %s.", channel->name, handle->handle, user->handle_info->handle);
-    global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+    global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_REGISTERED_TO", channel->name, 
+                        handle->handle, user->handle_info->handle);
     return 1;
 }
 
@@ -2208,7 +2352,6 @@ static CHANSERV_FUNC(cmd_move)
     struct chanNode *target;
     struct modeNode *mn;
     struct userData *uData;
-    char reason[MAXLEN];
     struct do_not_register *dnr;
     int chanserv_join = 0, spamserv_join;
 
@@ -2291,16 +2434,18 @@ static CHANSERV_FUNC(cmd_move)
     if (chanserv_join)
         ss_cs_join_channel(target, spamserv_join);
 
-    sprintf(reason, "%s moved to %s by %s.", channel->name, target->name, user->handle_info->handle);
     if(!IsSuspended(target->channel_info))
     {
         char reason2[MAXLEN];
-       sprintf(reason2, "Channel moved to %s by %s.", target->name, user->handle_info->handle);
-       DelChannelUser(chanserv, channel, reason2, 0);
+        sprintf(reason2, "Channel moved to %s by %s.", target->name, user->handle_info->handle);
+        DelChannelUser(chanserv, channel, reason2, 0);
     }
+
     UnlockChannel(channel);
     LockChannel(target);
-    global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+    global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_CHANNEL_MOVED",
+                        channel->name, target->name, user->handle_info->handle);
+
     reply("CSMSG_MOVE_SUCCESS", target->name);
     return 1;
 }
@@ -2621,8 +2766,19 @@ static CHANSERV_FUNC(cmd_adduser)
        return 0;
     }
 
-    actee = add_channel_user(channel->channel_info, handle, access, 0, NULL);
+    time_t accessexpiry = 0;
+    unsigned int duration = 0;
+    if (argc > 3) {
+        if ((duration = ParseInterval(argv[3])))
+            accessexpiry = now + duration;
+    }
+
+    actee = add_channel_user(channel->channel_info, handle, access, 0, NULL, accessexpiry);
     scan_user_presence(actee, NULL);
+
+    if (duration > 0)
+        timeq_add(accessexpiry, chanserv_expire_tempuser, actee);
+
     reply("CSMSG_ADDED_USER", handle->handle, channel->name, user_level_name_from_level(access), access);
     return 1;
 }
@@ -2674,6 +2830,24 @@ static CHANSERV_FUNC(cmd_clvl)
        return 0;
     }
 
+    time_t clvlexpiry = 0;
+    unsigned int duration = 0;
+    if (argc > 3) {
+        if ((duration = ParseInterval(argv[3])))
+            clvlexpiry = now + duration;
+    }
+
+    if (duration > 0) {
+        if (victim->accessexpiry > 0) {
+            reply("CSMSG_NO_BUMP_EXPIRY");
+            return 0;
+        }
+
+        victim->clvlexpiry = clvlexpiry;
+        victim->lastaccess = victim->access;
+        timeq_add(clvlexpiry, chanserv_expire_tempclvl, victim);
+    }
+
     victim->access = new_access;
     reply("CSMSG_CHANGED_ACCESS", handle->handle, user_level_name_from_level(new_access), new_access, channel->name);
     return 1;
@@ -2703,12 +2877,13 @@ static CHANSERV_FUNC(cmd_deluser)
     if(argc > 2)
     {
         access = user_level_from_name(argv[1], UL_OWNER);
+        char *useraccess = user_level_name_from_level(victim->access);
         if(!access)
         {
             reply("CSMSG_INVALID_ACCESS", argv[1]);
             return 0;
         }
-       if(access != victim->access)
+       if(strcasecmp(argv[1], useraccess))
        {
            reply("CSMSG_INCORRECT_ACCESS", handle->handle, user_level_name_from_level(victim->access), argv[1]);
            return 0;
@@ -2772,30 +2947,96 @@ static CHANSERV_FUNC(cmd_mdelowner)
 
 static CHANSERV_FUNC(cmd_mdelcoowner)
 {
-    return cmd_mdel_user(user, channel, UL_COOWNER, UL_COOWNER, argv[1], cmd);
+    return cmd_mdel_user(user, channel, UL_COOWNER, UL_OWNER-1, argv[1], cmd);
 }
 
 static CHANSERV_FUNC(cmd_mdelmanager)
 {
-    return cmd_mdel_user(user, channel, UL_MANAGER, UL_MANAGER, argv[1], cmd);
+    return cmd_mdel_user(user, channel, UL_MANAGER, UL_COOWNER-1, argv[1], cmd);
 }
 
 static CHANSERV_FUNC(cmd_mdelop)
 {
-    return cmd_mdel_user(user, channel, UL_OP, UL_OP, argv[1], cmd);
+    return cmd_mdel_user(user, channel, UL_OP, UL_MANAGER-1, argv[1], cmd);
 }
 
-static CHANSERV_FUNC(cmd_mdelpeon)
+static CHANSERV_FUNC(cmd_mdelhalfop)
 {
-    return cmd_mdel_user(user, channel, UL_PEON, UL_PEON, argv[1], cmd);
+    return cmd_mdel_user(user, channel, UL_HALFOP, UL_OP-1, argv[1], cmd);
 }
 
-static CHANSERV_FUNC(cmd_mdelhalfop)
+static CHANSERV_FUNC(cmd_mdelpeon)
 {
-    return cmd_mdel_user(user, channel, UL_HALFOP, UL_HALFOP, argv[1], cmd);
+    return cmd_mdel_user(user, channel, UL_PEON, UL_HALFOP-1, argv[1], cmd);
 }
 
 
+static CHANSERV_FUNC(cmd_levels)
+{
+    struct helpfile_table tbl;
+    int ii = 0;
+
+    tbl.length = 6 + 1; // 6 levels
+    tbl.width = 4;
+    tbl.flags = 0;
+    tbl.contents = calloc(tbl.length,sizeof(tbl.contents[0]));
+    tbl.contents[0] = calloc(tbl.width,sizeof(tbl.contents[0][0]));
+    tbl.contents[0][0] = "Level";
+    tbl.contents[0][1] = "From";
+    tbl.contents[0][2] = "-";
+    tbl.contents[0][3] = "To";
+
+    tbl.contents[++ii] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
+    tbl.contents[ii][0] = strdup(user_level_name_from_level(UL_OWNER));
+    tbl.contents[ii][1] = msnprintf(4, "%d", UL_OWNER);
+    tbl.contents[ii][2] = msnprintf(2, " ");
+    tbl.contents[ii][3] = msnprintf(1, "");
+
+    tbl.contents[++ii] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
+    tbl.contents[ii][0] = strdup(user_level_name_from_level(UL_COOWNER));
+    tbl.contents[ii][1] = msnprintf(4, "%d", UL_COOWNER);
+    tbl.contents[ii][2] = msnprintf(2, "-");
+    tbl.contents[ii][3] = msnprintf(4, "%d", UL_OWNER-1);
+
+    tbl.contents[++ii] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
+    tbl.contents[ii][0] = strdup(user_level_name_from_level(UL_MANAGER));
+    tbl.contents[ii][1] = msnprintf(4, "%d", UL_MANAGER);
+    tbl.contents[ii][2] = msnprintf(2, "-");
+    tbl.contents[ii][3] = msnprintf(4, "%d", UL_COOWNER-1);
+
+    tbl.contents[++ii] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
+    tbl.contents[ii][0] = strdup(user_level_name_from_level(UL_OP));
+    tbl.contents[ii][1] = msnprintf(4, "%d", UL_OP);
+    tbl.contents[ii][2] = msnprintf(2, "-");
+    tbl.contents[ii][3] = msnprintf(4, "%d", UL_MANAGER-1);
+
+    tbl.contents[++ii] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
+    tbl.contents[ii][0] = strdup(user_level_name_from_level(UL_HALFOP));
+    tbl.contents[ii][1] = msnprintf(4, "%d", UL_HALFOP);
+    tbl.contents[ii][2] = msnprintf(2, "-");
+    tbl.contents[ii][3] = msnprintf(4, "%d", UL_OP-1);
+
+    tbl.contents[++ii] = calloc(tbl.width, sizeof(tbl.contents[0][0]));
+    tbl.contents[ii][0] = strdup(user_level_name_from_level(UL_PEON));
+    tbl.contents[ii][1] = msnprintf(4, "%d", UL_PEON);
+    tbl.contents[ii][2] = msnprintf(2, "-");
+    tbl.contents[ii][3] = msnprintf(4, "%d", UL_HALFOP-1);
+
+    table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
+    return 0;
+
+/*
+    reply("CSMSG_LEVELS_HEADER");
+    reply("CSMSG_LEVELS",  user_level_name_from_level(UL_OWNER), UL_OWNER, UL_OWNER);
+    reply("CSMSG_LEVELS",  user_level_name_from_level(UL_COOWNER), UL_COOWNER, UL_OWNER-1);
+    reply("CSMSG_LEVELS",  user_level_name_from_level(UL_MANAGER), UL_MANAGER, UL_COOWNER-1);
+    reply("CSMSG_LEVELS",  user_level_name_from_level(UL_OP), UL_OP, UL_MANAGER-1);
+    reply("CSMSG_LEVELS",  user_level_name_from_level(UL_HALFOP), UL_HALFOP, UL_OP-1);
+    reply("CSMSG_LEVELS",  user_level_name_from_level(UL_PEON), UL_PEON, UL_HALFOP-1);
+    reply("CSMSG_BAR");
+ */
+}
+
 /* trim_lamers.. */
 static int
 cmd_trim_bans(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, unsigned long duration)
@@ -3109,7 +3350,7 @@ bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban
         if(!user_matches_glob(mn->user, ban, MATCH_USENICK | MATCH_VISIBLE))
             continue;
 
-        if(protect_user(mn->user, user, channel->channel_info))
+        if(protect_user(mn->user, user, channel->channel_info, false))
             return 1;
 
         if(victims)
@@ -3166,7 +3407,7 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
             return 0;
         }
 
-       if(protect_user(victim, user, channel->channel_info))
+       if(protect_user(victim, user, channel->channel_info, false))
        {
            // This translates to  send_message(user, cmd->parent->bot, ...)
            // if user is x3 (ctcp action) cmd is null and segfault.
@@ -3398,13 +3639,25 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
         }
     }
 
-    if(action & ACTION_KICK)
+    if(action & ACTION_ADD_LAMER)
+    {
+        char kick_reason[MAXLEN];
+       sprintf(kick_reason, "(%s) %s", user->nick, reason);
+
+       for(n = 0; n < victimCount; n++) {
+            if(!protect_user(victims[n]->user, user, channel->channel_info, true)) {
+               KickChannelUser(victims[n]->user, channel, chanserv, kick_reason);
+            }
+        }
+    }
+    else if(action & ACTION_KICK)
     {
         char kick_reason[MAXLEN];
        sprintf(kick_reason, "(%s) %s", user->nick, reason);
 
-       for(n = 0; n < victimCount; n++)
+       for(n = 0; n < victimCount; n++) {
            KickChannelUser(victims[n]->user, channel, chanserv, kick_reason);
+        }
     }
 
     if(!cmd)
@@ -3705,14 +3958,13 @@ static CHANSERV_FUNC(cmd_myaccess)
 
     if(argc < 2)
         target_handle = user->handle_info;
-    else if(!IsHelping(user))
+    else if(!(target_handle = modcmd_get_handle_info(user, argv[1])))
+        return 0;
+    else if(!IsHelping(user) && target_handle != user->handle_info)
     {
         reply("CSMSG_MYACCESS_SELF_ONLY", argv[0]);
         return 0;
     }
-    else if(!(target_handle = modcmd_get_handle_info(user, argv[1])))
-        return 0;
-
     if(!target_handle->channels)
     {
         reply("CSMSG_SQUAT_ACCESS", target_handle->handle);
@@ -3747,6 +3999,8 @@ static CHANSERV_FUNC(cmd_myaccess)
         }
         if(IsUserAutoInvite(uData) && (uData->access >= cData->lvlOpts[lvlInviteMe]))
             string_buffer_append(&sbuf, 'i');
+        if(IsUserAutoJoin(uData) && (uData->access >= cData->lvlOpts[lvlInviteMe]))
+            string_buffer_append(&sbuf, 'j');
         if(uData->info)
             string_buffer_append_printf(&sbuf, ")] %s", uData->info);
         else
@@ -4076,9 +4330,9 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
     lData.table.contents = malloc(lData.table.length*sizeof(*lData.table.contents));
 
     if(user->handle_info && user->handle_info->userlist_style == HI_STYLE_ADVANCED)
-        lData.table.width = 5; /* with level = 5 */
+        lData.table.width = 6; /* with level = 6 */
     else
-        lData.table.width = 4; /* without = 4 */
+        lData.table.width = 5; /* without = 5 */
     ary = malloc(lData.table.width*sizeof(**lData.table.contents));
     lData.table.contents[0] = ary;
     if(user->handle_info) {
@@ -4106,6 +4360,7 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
     ary[i] = "Last Seen";
     seen_index = i++;
     ary[i++] = "Status";
+    ary[i++] = "Expiry";
     for(matches = 1; matches < lData.table.length; ++matches)
     {
         struct userData *uData = lData.users[matches-1];
@@ -4150,7 +4405,23 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
             ary[i++] = "Vacation";
         else
             ary[i++] = "Normal";
+
+        if ((uData->accessexpiry > 0) || (uData->clvlexpiry > 0)) {
+            char delay[INTERVALLEN];
+            time_t diff;
+
+            if (uData->accessexpiry > 0) {
+                diff = uData->accessexpiry - now;
+                intervalString(delay, diff, user->handle_info);
+            } else {
+                diff = uData->clvlexpiry - now;
+                intervalString(delay, diff, user->handle_info);
+            }
+            ary[i++] = delay;
+        } else
+            ary[i++] = "Never";
     }
+
     send_list(&lData);
     for(matches = 1; matches < lData.table.length; ++matches)
     {
@@ -4257,7 +4528,7 @@ static CHANSERV_FUNC(cmd_lamers)
     if(!matches)
     {
         table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
-       reply("MSG_NONE");
+/*     reply("MSG_NONE"); */
         free(tbl.contents[0]);
         free(tbl.contents);
        return 0;
@@ -4447,6 +4718,11 @@ static CHANSERV_FUNC(cmd_mode)
     
     if(argc < 2)
     {
+        if (checkDefCon(DEFCON_NO_MODE_CHANGE) && !IsOper(user)) {
+            reply("CSMSG_DEFCON_NO_MODE_CHANGE");
+            return 0;
+        }
+
         change = &channel->channel_info->modes;
        if(change->modes_set || change->modes_clear) {
             modcmd_chanmode_announce(change);
@@ -5390,8 +5666,9 @@ static CHANSERV_FUNC(cmd_csuspend)
         suspended->previous->revoked = now;
         if(suspended->previous->expires)
             timeq_del(suspended->previous->expires, chanserv_expire_suspension, suspended->previous, 0);
-        sprintf(reason, "%s suspension modified by %s.", channel->name, suspended->suspender);
-        global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+
+        global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_SUSPENSION_MODIFIED",
+                            channel->name, suspended->suspender);
     }
     else
     {
@@ -5410,8 +5687,8 @@ static CHANSERV_FUNC(cmd_csuspend)
         spamserv_cs_suspend(channel, expiry, 1, suspended->reason);
         DelChannelUser(chanserv, channel, suspended->reason, 0);
         reply("CSMSG_SUSPENDED", channel->name);
-        sprintf(reason, "%s suspended by %s.", channel->name, suspended->suspender);
-        global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+        global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_SUSPENDED_BY",
+                            channel->name, suspended->suspender);
     }
     return 1;
 }
@@ -5419,7 +5696,6 @@ static CHANSERV_FUNC(cmd_csuspend)
 static CHANSERV_FUNC(cmd_cunsuspend)
 {
     struct suspended *suspended;
-    char message[MAXLEN];
 
     if(!IsSuspended(channel->channel_info))
     {
@@ -5433,8 +5709,8 @@ static CHANSERV_FUNC(cmd_cunsuspend)
     timeq_del(suspended->expires, chanserv_expire_suspension, suspended, 0);
     chanserv_expire_suspension(suspended);
     reply("CSMSG_UNSUSPENDED", channel->name);
-    sprintf(message, "%s unsuspended by %s.", channel->name, user->handle_info->handle);
-    global_message(MESSAGE_RECIPIENT_OPERS|MESSAGE_RECIPIENT_HELPERS, message);
+    global_message_args(MESSAGE_RECIPIENT_OPERS|MESSAGE_RECIPIENT_HELPERS, "CSMSG_UNSUSPENDED_BY",
+                        channel->name, user->handle_info->handle);
     return 1;
 }
 
@@ -5750,6 +6026,11 @@ static MODCMD_FUNC(chan_opt_modes)
 
     if(argc > 1)
     {
+        if (checkDefCon(DEFCON_NO_MODE_CHANGE) && !IsOper(user)) {
+            reply("CSMSG_DEFCON_NO_MODE_CHANGE");
+            return 0;
+        }
+
         if(!check_user_level(channel, user, lvlEnfModes, 1, 0))
         {
             reply("CSMSG_NO_ACCESS");
@@ -6212,11 +6493,10 @@ static MODCMD_FUNC(user_opt_autoop)
         reply("CSMSG_NOT_USER", channel->name);
         return 0;
     }
-    if(uData->access < UL_OP /*channel->channel_info->lvlOpts[lvlGiveOps]*/)
+    if(uData->access < UL_HALFOP /*channel->channel_info->lvlOpts[lvlGiveOps]*/)
         return user_binary_option("CSMSG_USET_AUTOVOICE", USER_AUTO_OP, CSFUNC_ARGS);
     else
         return user_binary_option("CSMSG_USET_AUTOOP", USER_AUTO_OP, CSFUNC_ARGS);
-    /* TODO: add halfops error message? or is the op one generic enough? */
 }
 
 static MODCMD_FUNC(user_opt_autoinvite)
@@ -6224,6 +6504,11 @@ static MODCMD_FUNC(user_opt_autoinvite)
     return user_binary_option("CSMSG_USET_AUTOINVITE", USER_AUTO_INVITE, CSFUNC_ARGS);
 }
 
+static MODCMD_FUNC(user_opt_autojoin)
+{
+    return user_binary_option("CSMSG_USET_AUTOJOIN", USER_AUTO_JOIN, CSFUNC_ARGS);
+}
+
 static MODCMD_FUNC(user_opt_info)
 {
     struct userData *uData;
@@ -6282,7 +6567,7 @@ static CHANSERV_FUNC(cmd_uset)
     {
         char *options[] =
         {
-            "AutoOp", "AutoInvite", "Info"
+            "AutoOp", "AutoInvite", "AutoJoin", "Info"
         };
 
         if(!uset_shows_list.size)
@@ -6333,7 +6618,7 @@ static CHANSERV_FUNC(cmd_giveownership)
     struct giveownership *giveownership;
     unsigned int force, override;
     unsigned short co_access, new_owner_old_access;
-    char reason[MAXLEN], transfer_reason[MAXLEN];
+    char transfer_reason[MAXLEN];
 
     REQUIRE_PARAMS(2);
     curr_user = GetChannelAccess(cData, user->handle_info);
@@ -6371,6 +6656,10 @@ static CHANSERV_FUNC(cmd_giveownership)
         reply("CSMSG_TRANSFER_WAIT", delay, channel->name);
         return 0;
     }
+    if (!curr_user) {
+        reply("CSMSG_NO_OWNER", channel->name);
+        return 0;
+    }
     if(!(new_owner_hi = modcmd_get_handle_info(user, argv[1])))
         return 0;
     if(new_owner_hi == user->handle_info)
@@ -6383,7 +6672,7 @@ static CHANSERV_FUNC(cmd_giveownership)
     {
         if(force)
         {
-            new_owner = add_channel_user(cData, new_owner_hi, UL_COOWNER, 0, NULL);
+            new_owner = add_channel_user(cData, new_owner_hi, UL_COOWNER, 0, NULL, 0);
         }
         else
         {
@@ -6416,8 +6705,8 @@ static CHANSERV_FUNC(cmd_giveownership)
 
     giveownership = calloc(1, sizeof(*giveownership));
     giveownership->issued = now;
-    giveownership->old_owner = curr_user->handle->handle;
-    giveownership->target = new_owner_hi->handle;
+    giveownership->old_owner = strdup(curr_user->handle->handle);
+    giveownership->target = strdup(new_owner_hi->handle);
     giveownership->target_access = new_owner_old_access;
     if(override)
     {
@@ -6433,8 +6722,8 @@ static CHANSERV_FUNC(cmd_giveownership)
     channel->channel_info->giveownership = giveownership;
 
     reply("CSMSG_OWNERSHIP_GIVEN", channel->name, new_owner_hi->handle);
-    sprintf(reason, "%s ownership transferred to %s by %s.", channel->name, new_owner_hi->handle, user->handle_info->handle);
-    global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+    global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_OWNERSHIP_TRANSFERRED",
+                       channel->name, new_owner_hi->handle, user->handle_info->handle);
     return 1;
 }
 
@@ -6917,6 +7206,19 @@ static CHANSERV_FUNC(cmd_calc)
     return 1;
 }
 
+static CHANSERV_FUNC(cmd_reply)
+{
+
+    REQUIRE_PARAMS(2);
+    unsplit_string(argv + 1, argc - 1, NULL);
+
+    if(channel)
+        send_channel_message(channel, cmd->parent->bot, "$b%s$b: %s", user->nick, unsplit_string(argv + 1, argc - 1, NULL));
+    else
+        send_message_type(4, user, cmd->parent->bot, "%s", unsplit_string(argv + 1, argc - 1, NULL));
+    return 1;
+}
+
 static void
 chanserv_adjust_limit(void *data)
 {
@@ -6959,6 +7261,101 @@ handle_new_channel(struct chanNode *channel)
         SetChannelTopic(channel, chanserv, chanserv, channel->channel_info->topic, 1);
 }
 
+int
+trace_check_bans(struct userNode *user, struct chanNode *chan)
+{
+    struct banData *bData;
+    struct mod_chanmode *change;
+
+    change = find_matching_bans(&chan->banlist, user, NULL);
+    if (change)
+       return 1;
+
+    /* lamer list */
+    if (chan->channel_info) {
+        for(bData = chan->channel_info->bans; bData; bData = bData->next) {
+
+            if(!user_matches_glob(user, bData->mask, MATCH_USENICK))
+                continue;
+
+            if(bData)
+               return 1;
+        }
+    }
+
+    return 0;
+}
+
+int
+check_bans(struct userNode *user, const char *channel)
+{
+    struct chanNode *chan;
+    struct mod_chanmode change;
+    struct chanData *cData;
+    struct banData *bData;
+
+    if (!(chan = GetChannel(channel)))
+        return 0;
+
+    if(!(cData = chan->channel_info))
+        return 0;
+
+    mod_chanmode_init(&change);
+    change.argc = 1;
+
+    if(chan->banlist.used < MAXBANS)
+    {
+        /* Not joining through a ban. */
+        for(bData = cData->bans;
+            bData && !user_matches_glob(user, bData->mask, MATCH_USENICK);
+            bData = bData->next);
+
+        if(bData)
+        {
+            char kick_reason[MAXLEN];
+            sprintf(kick_reason, "(%s) %s", bData->owner, bData->reason);
+
+            bData->triggered = now;
+            if(bData != cData->bans)
+            {
+                /* Shuffle the ban to the head of the list. */
+                if(bData->next)
+                    bData->next->prev = bData->prev;
+                if(bData->prev)
+                    bData->prev->next = bData->next;
+
+                bData->prev = NULL;
+                bData->next = cData->bans;
+
+                if(cData->bans)
+                    cData->bans->prev = bData;
+                
+                cData->bans = bData;
+            }
+
+            change.args[0].mode = MODE_BAN;
+            change.args[0].u.hostmask = bData->mask;
+            mod_chanmode_announce(chanserv, chan, &change);
+            KickChannelUser(user, chan, chanserv, kick_reason);
+            return 1;
+        }
+    }
+    return 0;
+}
+
+int
+channel_user_is_exempt(struct userNode *user, struct chanNode *channel)
+{
+   unsigned int ii;
+   for(ii = 0; ii < channel->exemptlist.used; ii++)
+   {
+       if(user_matches_glob(user, channel->exemptlist.list[ii]->exempt, MATCH_USENICK))
+           return true;
+   }
+   return false;
+}
+
+
 /* Welcome to my worst nightmare. Warning: Read (or modify)
    the code below at your own risk. */
 static int
@@ -6974,7 +7371,7 @@ handle_join(struct modeNode *mNode)
     unsigned int modes = 0, info = 0;
     char *greeting;
 
-    if(IsLocal(user) || !channel->channel_info || IsSuspended(channel->channel_info))
+    if(IsLocal(user) || !channel || !channel->channel_info || IsSuspended(channel->channel_info))
         return 0;
 
     cData = channel->channel_info;
@@ -7007,42 +7404,58 @@ handle_join(struct modeNode *mNode)
     }
 #endif
 
+    if(user->handle_info)
+    {
+        handle = user->handle_info;
+        if(handle)
+        {
+            uData = GetTrueChannelAccess(cData, handle);
+        }
+    }
+
+
     mod_chanmode_init(&change);
     change.argc = 1;
-    if(channel->banlist.used < MAXBANS)
-    {
-        /* Not joining through a ban. */
-        for(bData = cData->bans;
-            bData && !user_matches_glob(user, bData->mask, MATCH_USENICK);
-            bData = bData->next);
 
-        if(bData)
+    /* TODO: maybe only people above inviteme level? -Rubin */
+    /* We don't kick people with access */
+    if(!uData && !channel_user_is_exempt(user, channel))
+    {
+        if(channel->banlist.used < MAXBANS)
         {
-            char kick_reason[MAXLEN];
-            sprintf(kick_reason, "(%s) %s", bData->owner, bData->reason);
+            /* Not joining through a ban. */
+            for(bData = cData->bans;
+                bData && !user_matches_glob(user, bData->mask, MATCH_USENICK);
+                bData = bData->next);
 
-            bData->triggered = now;
-            if(bData != cData->bans)
+            if(bData)
             {
-                /* Shuffle the ban to the head of the list. */
-                if(bData->next)
-                    bData->next->prev = bData->prev;
-                if(bData->prev)
-                    bData->prev->next = bData->next;
+                char kick_reason[MAXLEN];
+                sprintf(kick_reason, "(%s) %s", bData->owner, bData->reason);
 
-                bData->prev = NULL;
-                bData->next = cData->bans;
+                bData->triggered = now;
+                if(bData != cData->bans)
+                {
+                    /* Shuffle the ban to the head of the list. */
+                    if(bData->next)
+                        bData->next->prev = bData->prev;
+                    if(bData->prev)
+                        bData->prev->next = bData->next;
+
+                    bData->prev = NULL;
+                    bData->next = cData->bans;
+
+                    if(cData->bans)
+                        cData->bans->prev = bData;
+                    cData->bans = bData;
+                }
 
-                if(cData->bans)
-                    cData->bans->prev = bData;
-                cData->bans = bData;
+                change.args[0].mode = MODE_BAN;
+                change.args[0].u.hostmask = bData->mask;
+                mod_chanmode_announce(chanserv, channel, &change);
+                KickChannelUser(user, channel, chanserv, kick_reason);
+                return 1;
             }
-
-            change.args[0].mode = MODE_BAN;
-            change.args[0].u.hostmask = bData->mask;
-            mod_chanmode_announce(chanserv, channel, &change);
-            KickChannelUser(user, channel, chanserv, kick_reason);
-            return 1;
         }
     }
 
@@ -7077,7 +7490,7 @@ handle_join(struct modeNode *mNode)
     greeting = cData->greeting;
     if(user->handle_info)
     {
-        handle = user->handle_info;
+/*        handle = user->handle_info; */
 
         if(IsHelper(user) && !IsHelping(user))
         {
@@ -7092,13 +7505,17 @@ handle_join(struct modeNode *mNode)
             }
         }
 
-        uData = GetTrueChannelAccess(cData, handle);
+/*        uData = GetTrueChannelAccess(cData, handle); */
         if(uData && !IsUserSuspended(uData))
         {
             /* non users getting automodes are handled above. */
             if(IsUserAutoOp(uData) && cData->chOpts[chAutomode] != 'n')
             {
-                if(uData->access >= UL_OP )
+                /* just op everyone with access */
+                if(uData->access >= UL_PEON && cData->chOpts[chAutomode] == 'l')
+                    modes |= MODE_VOICE;
+                /* or do their access level */
+                else if(uData->access >= UL_OP )
                     modes |= MODE_CHANOP;
                 else if(uData->access >= UL_HALFOP )
                     modes |= MODE_HALFOP;
@@ -7144,6 +7561,34 @@ handle_join(struct modeNode *mNode)
     return 0;
 }
 
+static void
+chanserv_autojoin_channels(struct userNode *user)
+{
+    struct userData *channel;
+
+    for(channel = user->handle_info->channels; channel; channel = channel->u_next)
+    {
+        struct chanNode *cn;
+        struct modeNode *mn;
+
+        if(IsUserSuspended(channel)
+           || IsSuspended(channel->channel)
+           || !(cn = channel->channel->channel))
+            continue;
+
+        mn = GetUserMode(cn, user);
+        if(!mn)
+        {
+            if(!IsUserSuspended(channel)
+               && IsUserAutoJoin(channel)
+               && (channel->access >= channel->channel->lvlOpts[lvlInviteMe])
+               && !self->burst
+               && !user->uplink->burst)
+                irc_svsjoin(chanserv, user, cn);
+        }
+    }
+}
+
 static void
 handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
 {
@@ -7159,12 +7604,14 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
     for(channel = user->handle_info->channels; channel; channel = channel->u_next)
     {
         struct chanNode *cn;
+        struct chanData *cData;
         struct modeNode *mn;
         if(IsUserSuspended(channel)
            || IsSuspended(channel->channel)
            || !(cn = channel->channel->channel))
             continue;
 
+        cData = cn->channel_info;
         mn = GetUserMode(cn, user);
         if(!mn)
         {
@@ -7180,7 +7627,7 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
        if(channel->access >= UL_PRESENT)
            channel->channel->visited = now;
 
-        if(IsUserAutoOp(channel))
+        if(IsUserAutoOp(channel) && cData->chOpts[chAutomode] != 'n')
         {
             if(channel->access >= UL_OP )
                 change.args[0].mode = MODE_CHANOP;
@@ -7208,6 +7655,8 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
            || !channel->channel_info
            || IsSuspended(channel->channel_info))
             continue;
+        if(protect_user(user, chanserv, channel->channel_info, true))
+            continue;
         for(jj = 0; jj < channel->banlist.used; ++jj)
             if(user_matches_glob(user, channel->banlist.list[jj]->ban, MATCH_USENICK))
                 break;
@@ -7248,6 +7697,11 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
 
     if (user->handle_info->epithet)
       irc_swhois(chanserv, user, user->handle_info->epithet);
+
+   /* process autojoin channels 5 seconds later as this sometimes 
+      happens before autohide */
+//   timeq_add(now + 5, chanserv_autojoin_channels, user);
+    chanserv_autojoin_channels(user);
 }
 
 static void
@@ -7303,7 +7757,7 @@ handle_kick(struct userNode *kicker, struct userNode *victim, struct chanNode *c
        || (kicker->handle_info && kicker->handle_info == victim->handle_info))
         return;
 
-    if(protect_user(victim, kicker, channel->channel_info))
+    if(protect_user(victim, kicker, channel->channel_info, false))
     {
         const char *reason = user_find_message(kicker, "CSMSG_USER_PROTECTED_KICK");
        KickChannelUser(kicker, channel, chanserv, reason);
@@ -7379,7 +7833,7 @@ handle_mode(struct chanNode *channel, struct userNode *user, const struct mod_ch
         if((change->args[ii].mode & (MODE_REMOVE|MODE_CHANOP)) == (MODE_REMOVE|MODE_CHANOP))
         {
             const struct userNode *victim = change->args[ii].u.member->user;
-            if(!protect_user(victim, user, channel->channel_info))
+            if(!protect_user(victim, user, channel->channel_info, false))
                 continue;
             if(!bounce)
                 bounce = mod_chanmode_alloc(change->argc + 1 - ii);
@@ -7449,7 +7903,7 @@ handle_nick_change(struct userNode *user, UNUSED_ARG(const char *old_nick))
         /* Need not check for bans if they're opped or voiced. */
         /* TODO: does this make sense in automode v, h, and o? *
          * lets still enforce on voice people anyway, and see how that goes -Rubin */
-        if(user->channels.list[ii]->modes & (MODE_CHANOP|MODE_HALFOP /*|MODE_VOICE */))
+        if(user->channels.list[ii]->modes & (MODE_CHANOP|MODE_HALFOP|MODE_VOICE ))
             continue;
         /* Need not check for bans unless channel registration is active. */
         if(!channel->channel_info || IsSuspended(channel->channel_info))
@@ -7461,6 +7915,9 @@ handle_nick_change(struct userNode *user, UNUSED_ARG(const char *old_nick))
         /* Need not act if we found one. */
         if(jj < channel->banlist.used)
             continue;
+        /* don't kick someone on the userlist */
+        if(protect_user(user, chanserv, channel->channel_info, true))
+            continue;
         /* Look for a matching ban in this channel. */
         for(bData = channel->channel_info->bans; bData; bData = bData->next)
         {
@@ -7736,9 +8193,9 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
 {
     struct handle_info *handle;
     struct userData *uData;
-    char *seen, *inf, *flags, *expires;
+    char *seen, *inf, *flags, *expires, *accessexpiry, *clvlexpiry, *lstacc;
     time_t last_seen;
-    unsigned short access;
+    unsigned short access, lastaccess = 0;
 
     if(rd->type != RECDB_OBJECT || !dict_size(rd->d.object))
     {
@@ -7758,6 +8215,11 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
     last_seen = seen ? (signed)strtoul(seen, NULL, 0) : now;
     flags = database_get_data(rd->d.object, KEY_FLAGS, RECDB_QSTRING);
     expires = database_get_data(rd->d.object, KEY_EXPIRES, RECDB_QSTRING);
+    accessexpiry = database_get_data(rd->d.object, KEY_ACCESSEXPIRY, RECDB_QSTRING);
+    clvlexpiry = database_get_data(rd->d.object, KEY_CLVLEXPIRY, RECDB_QSTRING);
+    lstacc = database_get_data(rd->d.object, KEY_LASTLEVEL, RECDB_QSTRING);
+    lastaccess  = lstacc ? atoi(lstacc) : 0;
+
     handle = get_handle_info(key);
     if(!handle)
     {
@@ -7765,10 +8227,20 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
         return;
     }
 
-    uData = add_channel_user(chan, handle, access, last_seen, inf);
+    uData = add_channel_user(chan, handle, access, last_seen, inf, 0);
     uData->flags = flags ? strtoul(flags, NULL, 0) : 0;
     uData->expires = expires ? strtoul(expires, NULL, 0) : 0;
 
+    uData->accessexpiry = accessexpiry ? strtoul(accessexpiry, NULL, 0) : 0;
+    if (uData->accessexpiry > 0)
+        timeq_add(uData->accessexpiry, chanserv_expire_tempuser, uData);
+
+    uData->clvlexpiry = clvlexpiry ? strtoul(clvlexpiry, NULL, 0) : 0;
+    if (uData->clvlexpiry > 0)
+        timeq_add(uData->clvlexpiry, chanserv_expire_tempclvl, uData);
+
+    uData->lastaccess = lastaccess;
+
     if((uData->flags & USER_SUSPENDED) && uData->expires)
     {
         if(uData->expires > now)
@@ -8167,6 +8639,9 @@ chanserv_write_users(struct saxdb_context *ctx, struct userData *uData)
         saxdb_start_record(ctx, uData->handle->handle, 0);
         saxdb_write_int(ctx, KEY_LEVEL, uData->access);
         saxdb_write_int(ctx, KEY_SEEN, uData->seen);
+        saxdb_write_int(ctx, KEY_ACCESSEXPIRY, uData->accessexpiry);
+        saxdb_write_int(ctx, KEY_CLVLEXPIRY, uData->clvlexpiry);
+        saxdb_write_int(ctx, KEY_LASTLEVEL, uData->lastaccess);
         if(uData->flags)
             saxdb_write_int(ctx, KEY_FLAGS, uData->flags);
         if(uData->expires)
@@ -8479,6 +8954,8 @@ init_chanserv(const char *nick)
     DEFINE_COMMAND(mdelpeon, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
     DEFINE_COMMAND(mdelhalfop, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
 
+    DEFINE_COMMAND(levels, 1, 0, NULL);
+
     DEFINE_COMMAND(trim, 3, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
     DEFINE_COMMAND(opchan, 1, MODCMD_REQUIRE_REGCHAN|MODCMD_NEVER_CSUSPEND, "access", "1", NULL);
     DEFINE_COMMAND(clvl, 3, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
@@ -8558,6 +9035,7 @@ init_chanserv(const char *nick)
     DEFINE_COMMAND(d, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
     DEFINE_COMMAND(huggle, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
     DEFINE_COMMAND(calc, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
+    DEFINE_COMMAND(reply, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
 
     /* Channel options */
     DEFINE_CHANNEL_OPTION(defaulttopic);
@@ -8592,6 +9070,7 @@ init_chanserv(const char *nick)
 
     /* User options */
     DEFINE_USER_OPTION(autoinvite);
+    DEFINE_USER_OPTION(autojoin);
     DEFINE_USER_OPTION(info);
     DEFINE_USER_OPTION(autoop);
 
@@ -8634,3 +9113,4 @@ init_chanserv(const char *nick)
     reg_exit_func(chanserv_db_cleanup);
     message_register_table(msgtab);
 }
+