Bug fixes and cleanups
[srvx.git] / src / chanserv.c
index f23ff34c66fc14f1dc129b8e742e149cae7d3b04..dcdb6316dfc414c3e767a66123013bba64d75df2 100644 (file)
 #define KEY_EXPIRES             "expires"
 #define KEY_TRIGGERED          "triggered"
 
-#define CHANNEL_DEFAULT_FLAGS   (CHANNEL_INFO_LINES)
+#define CHANNEL_DEFAULT_FLAGS   (0)
 #define CHANNEL_DEFAULT_OPTIONS "lmoooanpcnat"
 
 /* Administrative messages */
@@ -237,6 +237,9 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_CONFIRM_DEFAULTS", "To reset %s's settings to the defaults, you muse use 'set defaults %s'." },
     { "CSMSG_SETTINGS_DEFAULTED", "All settings for %s have been reset to default values." },
     { "CSMSG_BAD_SETLEVEL", "You cannot change any setting to above your level." },
+    { "CSMSG_BAD_GIVEVOICE", "You cannot change GiveVoice to above GiveOps (%d)." },
+    { "CSMSG_BAD_GIVEOPS", "You cannot change GiveOps to below GiveVoice (%d)." },
+    { "CSMSG_BAD_SETTERS", "You cannot change Setters to above your level." },
     { "CSMSG_INVALID_MODE_LOCK", "$b%s$b is an invalid mode lock." },
     { "CSMSG_INVALID_NUMERIC",   "$b%d$b is not a valid choice.  Choose one:" },
     { "CSMSG_SET_DEFAULT_TOPIC", "$bDefaultTopic$b %s" },
@@ -245,11 +248,11 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_SET_USERGREETING",  "$bUserGreeting$b %s" },
     { "CSMSG_SET_MODES",         "$bModes       $b %s" },
     { "CSMSG_SET_NODELETE",      "$bNoDelete    $b %s" },
-    { "CSMSG_SET_USERINFO",      "$bUserInfo    $b %s" },
-    { "CSMSG_SET_VOICE",         "$bVoice       $b %s" },
     { "CSMSG_SET_DYNLIMIT",      "$bDynLimit    $b %s" },
-    { "CSMSG_SET_TOPICSNARF",    "$bTopicSnarf  $b %s" },
-    { "CSMSG_SET_PEONINVITE",    "$bPeonInvite  $b %s" },
+    { "CSMSG_SET_USERINFO",      "$bUserInfo    $b %d" },
+    { "CSMSG_SET_GIVE_VOICE",    "$bGiveVoice   $b %d" },
+    { "CSMSG_SET_TOPICSNARF",    "$bTopicSnarf  $b %d" },
+    { "CSMSG_SET_INVITEME",      "$bInviteMe    $b %d" },
     { "CSMSG_SET_ENFOPS",        "$bEnfOps      $b %d" },
     { "CSMSG_SET_GIVE_OPS",      "$bGiveOps     $b %d" },
     { "CSMSG_SET_ENFMODES",      "$bEnfModes    $b %d" },
@@ -334,8 +337,8 @@ static const struct message_entry msgtab[] = {
 
 /* Access information */
     { "CSMSG_IS_CHANSERV", "$b$C$b is the $bchannel service bot$b." },
-    { "CSMSG_ACCESS_SELF_ONLY", "You may only see the list of infolines for yourself (by using $b%s$b with no arguments)." },
-    { "CSMSG_SQUAT_ACCESS", "You do not have access to any channels." },
+    { "CSMSG_MYACCESS_SELF_ONLY", "You may only see the list of infolines for yourself (by using $b%s$b with no arguments)." },
+    { "CSMSG_SQUAT_ACCESS", "$b%s$b does not have access to any channels." },
     { "CSMSG_INFOLINE_LIST", "Showing all channel entries for account $b%s$b:" },
     { "CSMSG_USER_NO_ACCESS", "%s lacks access to %s." },
     { "CSMSG_USER_HAS_ACCESS", "%s has access $b%d$b in %s." },
@@ -567,14 +570,20 @@ static const struct {
     char *db_name;
     unsigned short default_value;
     unsigned int old_idx;
+    unsigned int old_flag;
+    unsigned short flag_value;
 } levelOptions[] = {
-    { "CSMSG_SET_GIVE_OPS", "giveops", 200, 2 },
-    { "CSMSG_SET_ENFOPS", "enfops", 300, 1 },
-    { "CSMSG_SET_ENFMODES", "enfmodes", 200, 3 },
-    { "CSMSG_SET_ENFTOPIC", "enftopic", 200, 4 },
-    { "CSMSG_SET_PUBCMD", "pubcmd", 0, 5 },
-    { "CSMSG_SET_SETTERS", "setters", 400, 7 },
-    { "CSMSG_SET_CTCPUSERS", "ctcpusers", 0, 9 }
+    { "CSMSG_SET_GIVE_VOICE", "givevoice", 100, ~0, CHANNEL_VOICE_ALL, 0 },
+    { "CSMSG_SET_GIVE_OPS", "giveops", 200, 2, 0, 0 },
+    { "CSMSG_SET_ENFOPS", "enfops", 300, 1, 0, 0 },
+    { "CSMSG_SET_ENFMODES", "enfmodes", 200, 3, 0, 0 },
+    { "CSMSG_SET_ENFTOPIC", "enftopic", 200, 4, 0, 0 },
+    { "CSMSG_SET_PUBCMD", "pubcmd", 0, 5, 0, 0 },
+    { "CSMSG_SET_SETTERS", "setters", 400, 7, 0, 0 },
+    { "CSMSG_SET_CTCPUSERS", "ctcpusers", 0, 9, 0, 0 },
+    { "CSMSG_SET_USERINFO", "userinfo", 1, ~0, CHANNEL_INFO_LINES, 1 },
+    { "CSMSG_SET_INVITEME", "inviteme", 1, ~0, CHANNEL_PEON_INVITE, 200 },
+    { "CSMSG_SET_TOPICSNARF", "topicsnarf", 501, ~0, CHANNEL_TOPIC_SNARF, 1 }
 };
 
 struct charOptionValues {
@@ -747,25 +756,20 @@ int check_user_level(struct chanNode *channel, struct userNode *user, enum level
    user is optional, if not null, it skips checking that userNode
    (for the handle_part function) */
 static void
-scan_handle_presence(struct chanNode *channel, struct handle_info *handle, struct userNode *user)
+scan_user_presence(struct userData *uData, struct userNode *user)
 {
-    struct userData *uData;
-
-    if(!channel->channel_info || IsSuspended(channel->channel_info))
-        return;
+    struct modeNode *mn;
 
-    uData = GetTrueChannelAccess(channel->channel_info, handle);
-    if(uData)
+    if(IsSuspended(uData->channel)
+       || IsUserSuspended(uData)
+       || !(mn = find_handle_in_channel(uData->channel->channel, uData->handle, user)))
     {
-        struct modeNode *mn = find_handle_in_channel(channel, handle, user);
-
-       if(mn)
-       {
-           uData->present = 1;
-           uData->seen = now;
-       }
-       else
-           uData->present = 0;
+        uData->present = 0;
+    }
+    else
+    {
+        uData->present = 1;
+        uData->seen = now;
     }
 }
 
@@ -1723,8 +1727,7 @@ static CHANSERV_FUNC(cmd_register)
         channel = AddChannel(argv[1], now, NULL, NULL);
 
     cData = register_channel(channel, user->handle_info->handle);
-    add_channel_user(cData, handle, UL_OWNER, 0, NULL);
-    scan_handle_presence(channel, handle, NULL);
+    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;
@@ -1860,7 +1863,6 @@ static CHANSERV_FUNC(cmd_move)
     if(!(target = GetChannel(argv[1])))
     {
         target = AddChannel(argv[1], now, NULL, NULL);
-        LockChannel(target);
         if(!IsSuspended(channel->channel_info))
             AddChannelUser(chanserv, target);
     }
@@ -1902,6 +1904,7 @@ static CHANSERV_FUNC(cmd_move)
        DelChannelUser(chanserv, channel, reason2, 0);
     }
     UnlockChannel(channel);
+    LockChannel(target);
     global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
     return 1;
 }
@@ -2163,10 +2166,10 @@ static CHANSERV_FUNC(cmd_adduser)
        return 0;
     }
 
-    access = user_level_from_name(argv[1], UL_OWNER);
+    access = user_level_from_name(argv[2], UL_OWNER);
     if(!access)
     {
-       reply("CSMSG_INVALID_ACCESS", argv[1]);
+       reply("CSMSG_INVALID_ACCESS", argv[2]);
        return 0;
     }
 
@@ -2177,7 +2180,7 @@ static CHANSERV_FUNC(cmd_adduser)
        return 0;
     }
 
-    if(!(handle = modcmd_get_handle_info(user, argv[2])))
+    if(!(handle = modcmd_get_handle_info(user, argv[1])))
         return 0;
 
     if((actee = GetTrueChannelAccess(channel->channel_info, handle)))
@@ -2187,7 +2190,7 @@ static CHANSERV_FUNC(cmd_adduser)
     }
 
     actee = add_channel_user(channel->channel_info, handle, access, 0, NULL);
-    scan_handle_presence(channel, handle, NULL);
+    scan_user_presence(actee, NULL);
     reply("CSMSG_ADDED_USER", handle->handle, channel->name, access);
     return 1;
 }
@@ -2412,13 +2415,18 @@ cmd_trim_users(struct userNode *user, struct chanNode *channel, unsigned short m
            continue;
 
        if(((uData->access >= min_access) && (uData->access <= max_access))
-           || (max_access && (uData->access < actor->access)))
+           || (!max_access && (uData->access < actor->access)))
        {
            del_channel_user(uData, 1);
            count++;
        }
     }
 
+    if(!max_access)
+    {
+        min_access = 1;
+        max_access = UL_OWNER;
+    }
     send_message(user, chanserv, "CSMSG_TRIMMED_USERS", count, min_access, max_access, channel->name, intervalString(interval, duration));
     return 1;
 }
@@ -2779,7 +2787,8 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
                         free(bData->reason);
                    bData->reason = strdup(reason);
                     safestrncpy(bData->owner, (user->handle_info ? user->handle_info->handle : user->nick), sizeof(bData->owner));
-                   reply("CSMSG_REASON_CHANGE", ban);
+                    if(cmd)
+                        reply("CSMSG_REASON_CHANGE", ban);
                    if(!bData->expires)
                         goto post_add_ban;
                }
@@ -2810,7 +2819,11 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
                        if(bData->expires)
                            timeq_add(bData->expires, expire_ban, bData);
 
-                       if(duration)
+                        if(!cmd)
+                        {
+                            /* automated kickban */
+                        }
+                       else if(duration)
                            reply("CSMSG_BAN_EXTENDED", ban, intervalString(interval, duration));
                        else
                            reply("CSMSG_BAN_ADDED", name, channel->name);
@@ -2818,7 +2831,8 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
                        goto post_add_ban;
                    }
                }
-               reply("CSMSG_REDUNDANT_BAN", name, channel->name);
+                if(cmd)
+                    reply("CSMSG_REDUNDANT_BAN", name, channel->name);
 
                free(ban);
                return 0;
@@ -2867,7 +2881,8 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
 
        if(channel->banlist.used >= MAXBANS)
        {
-           reply("CSMSG_BANLIST_FULL", channel->name);
+            if(cmd)
+                reply("CSMSG_BANLIST_FULL", channel->name);
            free(ban);
            return 0;
        }
@@ -2885,7 +2900,7 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
             change->args[n++].hostmask = ban;
         }
         change->argc = n;
-        if (cmd)
+        if(cmd)
             modcmd_chanmode_announce(change);
         else
             mod_chanmode_announce(chanserv, channel, change);
@@ -2893,7 +2908,8 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
 
         if(exists && (action == ACTION_BAN))
        {
-            reply("CSMSG_REDUNDANT_BAN", name, channel->name);
+            if(cmd)
+                reply("CSMSG_REDUNDANT_BAN", name, channel->name);
             free(ban);
             return 0;
         }
@@ -3125,61 +3141,57 @@ static CHANSERV_FUNC(cmd_open)
     return 1;
 }
 
-static CHANSERV_FUNC(cmd_access)
+static CHANSERV_FUNC(cmd_myaccess)
 {
-    struct userNode *target;
     struct handle_info *target_handle;
     struct userData *uData;
-    int helping;
-    char prefix[MAXLEN];
+    const char *chanName;
 
-    if(!channel)
+    if(argc < 2)
+        target_handle = user->handle_info;
+    else if(!IsHelping(user))
     {
-        struct userData *uData;
-        const char *chanName;
-        int hide = 0;
+        reply("CSMSG_MYACCESS_SELF_ONLY", argv[0]);
+        return 0;
+    }
+    else if(!(target_handle = modcmd_get_handle_info(user, argv[1])))
+        return 0;
 
-        target_handle = user->handle_info;
-        if(!target_handle)
-        {
-            reply("MSG_AUTHENTICATE");
-            return 0;
-        }
-        if(argc > 1)
-        {
-            if(!IsHelping(user))
-            {
-                reply("CSMSG_ACCESS_SELF_ONLY", argv[0]);
-                return 0;
-            }
+    if(!target_handle->channels)
+    {
+        reply("CSMSG_SQUAT_ACCESS", target_handle->handle);
+        return 1;
+    }
 
-            if(!(target_handle = modcmd_get_handle_info(user, argv[1])))
-                return 0;
-            hide = 1;
-        }
-        if(!target_handle->channels)
-        {
-            reply("CSMSG_SQUAT_ACCESS");
-            return 1;
-        }
-        reply("CSMSG_INFOLINE_LIST", target_handle->handle);
-        for(uData = target_handle->channels; uData; uData = uData->u_next)
-        {
-            struct chanData *cData = uData->channel;
+    reply("CSMSG_INFOLINE_LIST", target_handle->handle);
+    for(uData = target_handle->channels; uData; uData = uData->u_next)
+    {
+        struct chanData *cData = uData->channel;
 
-            if(uData->access > UL_OWNER)
-                continue;
-            if(IsProtected(cData) && hide && !GetTrueChannelAccess(cData, user->handle_info))
-                continue;
-            chanName = cData->channel->name;
-            if(uData->info)
-                send_message_type(4, user, cmd->parent->bot, "[%s (%d)] %s", chanName, uData->access, uData->info);
-            else
-                send_message_type(4, user, cmd->parent->bot, "[%s (%d)]", chanName, uData->access);
-        }
-        return 1;
+        if(uData->access > UL_OWNER)
+            continue;
+        if(IsProtected(cData)
+           && (target_handle != user->handle_info)
+           && !GetTrueChannelAccess(cData, user->handle_info))
+            continue;
+        chanName = cData->channel->name;
+        if(uData->info)
+            send_message_type(4, user, cmd->parent->bot, "[%s (%d)] %s", chanName, uData->access, uData->info);
+        else
+            send_message_type(4, user, cmd->parent->bot, "[%s (%d)]", chanName, uData->access);
     }
 
+    return 1;
+}
+
+static CHANSERV_FUNC(cmd_access)
+{
+    struct userNode *target;
+    struct handle_info *target_handle;
+    struct userData *uData;
+    int helping;
+    char prefix[MAXLEN];
+
     if(argc < 2)
     {
        target = user;
@@ -3315,9 +3327,9 @@ zoot_list(struct listData *list)
     tmp_table.flags = list->table.flags;
     list->table.contents[0][0] = " ";
     highest = list->highest;
-    if (list->lowest != 0)
+    if(list->lowest != 0)
         lowest = list->lowest;
-    else if (highest < 100)
+    else if(highest < 100)
         lowest = 1;
     else
         lowest = highest - 100;
@@ -3655,7 +3667,7 @@ static CHANSERV_FUNC(cmd_topic)
     else
         SetChannelTopic(channel, chanserv, topic, 1);
 
-    if(cData->flags & CHANNEL_TOPIC_SNARF)
+    if(check_user_level(channel, user, lvlTopicSnarf, 1, 0))
     {
         /* Grab the topic and save it as the default topic. */
         free(cData->topic);
@@ -3740,17 +3752,13 @@ static CHANSERV_FUNC(cmd_invite)
 
 static CHANSERV_FUNC(cmd_inviteme)
 {
-    struct userData *uData;
-
     if(GetUserMode(channel, user))
     {
        reply("CSMSG_YOU_ALREADY_PRESENT", channel->name);
        return 0;
     }
     if(channel->channel_info
-       && !(channel->channel_info->flags & CHANNEL_PEON_INVITE)
-       && (uData = GetChannelUser(channel->channel_info, user->handle_info))
-       && (uData->access < channel->channel_info->lvlOpts[lvlGiveOps]))
+       && !check_user_level(channel, user, lvlInviteMe, 1, 0))
     {
         reply("CSMSG_LOW_CHANNEL_ACCESS", channel->name);
         return 0;
@@ -4162,7 +4170,7 @@ note_type_settable_by_user(struct chanNode *channel, struct note_type *ntype, st
     case NOTE_SET_CHANNEL_SETTER:
         return check_user_level(channel, user, lvlSetters, 1, 0);
     case NOTE_SET_PRIVILEGED: default:
-        return IsHelping(user);
+        return IsHelping(user) && (user->handle_info->opserv_level >= ntype->set_access.min_opserv);
     }
 }
 
@@ -4871,6 +4879,13 @@ static MODCMD_FUNC(chan_opt_nodelete)
     CHANNEL_BINARY_OPTION("CSMSG_SET_NODELETE", CHANNEL_NODELETE);
 }
 
+static MODCMD_FUNC(chan_opt_dynlimit)
+{
+    CHANNEL_BINARY_OPTION("CSMSG_SET_DYNLIMIT", CHANNEL_DYNAMIC_LIMIT);
+}
+
+/* TODO: reimplement
+
 static MODCMD_FUNC(chan_opt_userinfo)
 {
     CHANNEL_BINARY_OPTION("CSMSG_SET_USERINFO", CHANNEL_INFO_LINES);
@@ -4881,11 +4896,6 @@ static MODCMD_FUNC(chan_opt_voice)
     CHANNEL_BINARY_OPTION("CSMSG_SET_VOICE", CHANNEL_VOICE_ALL);
 }
 
-static MODCMD_FUNC(chan_opt_dynlimit)
-{
-    CHANNEL_BINARY_OPTION("CSMSG_SET_DYNLIMIT", CHANNEL_DYNAMIC_LIMIT);
-}
-
 static MODCMD_FUNC(chan_opt_topicsnarf)
 {
     if((argc > 0) && !check_user_level(channel, user, lvlEnfTopic, 1, 0))
@@ -4901,6 +4911,8 @@ static MODCMD_FUNC(chan_opt_peoninvite)
     CHANNEL_BINARY_OPTION("CSMSG_SET_PEONINVITE", CHANNEL_PEON_INVITE);
 }
 
+*/
+
 static MODCMD_FUNC(chan_opt_defaults)
 {
     struct userData *uData;
@@ -4947,17 +4959,47 @@ channel_level_option(enum levelOption option, struct userNode *user, struct chan
             return 0;
         }
         value = user_level_from_name(argv[1], UL_OWNER+1);
-        if(!value && !isdigit(argv[1][0]))
+        if(!value && strcmp(argv[1], "0"))
        {
            reply("CSMSG_INVALID_ACCESS", argv[1]);
             return 0;
         }
         uData = GetChannelUser(cData, user->handle_info);
-        if(!uData || (uData->access < value))
+        if(!uData || ((uData->access < UL_OWNER) && (value > uData->access)))
         {
             reply("CSMSG_BAD_SETLEVEL");
             return 0;
         }
+        switch(option)
+        {
+        case lvlGiveVoice:
+            if(value > cData->lvlOpts[lvlGiveOps])
+            {
+                reply("CSMSG_BAD_GIVEVOICE", cData->lvlOpts[lvlGiveOps]);
+                return 0;
+            }
+            break;
+        case lvlGiveOps:
+            if(value < cData->lvlOpts[lvlGiveVoice])
+            {
+                reply("CSMSG_BAD_GIVEOPS", cData->lvlOpts[lvlGiveVoice]);
+                return 0;
+            }
+            break;
+        case lvlSetters:
+            /* This test only applies to owners, since non-owners
+             * trying to set an option to above their level get caught
+             * by the CSMSG_BAD_SETLEVEL test above.
+             */
+            if(value > uData->access)
+            {
+                reply("CSMSG_BAD_SETTERS");
+                return 0;
+            }
+            break;
+        default:
+            break;
+        }
         cData->lvlOpts[option] = value;
     }
     reply(levelOptions[option].format_name, cData->lvlOpts[option]);
@@ -4999,6 +5041,26 @@ static MODCMD_FUNC(chan_opt_ctcpusers)
     return channel_level_option(lvlCTCPUsers, CSFUNC_ARGS);
 }
 
+static MODCMD_FUNC(chan_opt_userinfo)
+{
+    return channel_level_option(lvlUserInfo, CSFUNC_ARGS);
+}
+
+static MODCMD_FUNC(chan_opt_givevoice)
+{
+    return channel_level_option(lvlGiveVoice, CSFUNC_ARGS);
+}
+
+static MODCMD_FUNC(chan_opt_topicsnarf)
+{
+    return channel_level_option(lvlTopicSnarf, CSFUNC_ARGS);
+}
+
+static MODCMD_FUNC(chan_opt_inviteme)
+{
+    return channel_level_option(lvlInviteMe, CSFUNC_ARGS);
+}
+
 static int
 channel_multiple_option(enum charOption option, struct userNode *user, struct chanNode *channel, int argc, char *argv[], struct svccmd *cmd)
 {
@@ -5296,6 +5358,7 @@ static CHANSERV_FUNC(cmd_giveownership)
             }
             owner = curr_user;
         }
+        curr_user = owner;
     }
     if(!(new_owner_hi = modcmd_get_handle_info(user, argv[1])))
         return 0;
@@ -5523,10 +5586,12 @@ static CHANSERV_FUNC(cmd_d)
     const char *fmt;
 
     REQUIRE_PARAMS(2);
-    if((count = strtoul(argv[1], &sep, 10)) <= 1)
+    if((count = strtoul(argv[1], &sep, 10)) < 1)
         goto no_dice;
     if(sep[0] == 0)
     {
+        if(count == 1)
+            goto no_dice;
         sides = count;
         count = 1;
         modifier = 0;
@@ -5569,7 +5634,7 @@ static CHANSERV_FUNC(cmd_d)
         sprintf(response, fmt, total, sides);
     }
     if(channel)
-        send_target_message(5, channel->name, cmd->parent->bot, "$b%s$b: %s", user->nick, response);
+        send_channel_message(channel, cmd->parent->bot, "$b%s$b: %s", user->nick, response);
     else
         send_message_type(4, user, cmd->parent->bot, "%s", response);
     return 1;
@@ -5737,9 +5802,13 @@ handle_join(struct modeNode *mNode)
         timeq_add(now + chanserv_conf.adjust_delay, chanserv_adjust_limit, cData);
     }
 
-    if(cData->lvlOpts[lvlGiveOps] == 0)
+    if(channel->join_flooded)
+    {
+        /* don't automatically give ops or voice during a join flood */
+    }
+    else if(cData->lvlOpts[lvlGiveOps] == 0)
         modes |= MODE_CHANOP;
-    else if((cData->flags & CHANNEL_VOICE_ALL) && !channel->join_flooded)
+    else if(cData->lvlOpts[lvlGiveVoice] == 0)
         modes |= MODE_VOICE;
 
     greeting = cData->greeting;
@@ -5766,30 +5835,30 @@ handle_join(struct modeNode *mNode)
             /* Ops and above were handled by the above case. */
             if(IsUserAutoOp(uData))
             {
-                if(uData->access < cData->lvlOpts[lvlGiveOps])
-                    modes |= MODE_VOICE;
-                else
+                if(uData->access >= cData->lvlOpts[lvlGiveOps])
                     modes |= MODE_CHANOP;
+                else if(uData->access >= cData->lvlOpts[lvlGiveVoice])
+                    modes |= MODE_VOICE;
             }
             if(uData->access >= UL_PRESENT)
                 cData->visited = now;
-
-            uData->seen = now;
-            uData->present = 1;
-
             if(cData->user_greeting)
                 greeting = cData->user_greeting;
             if(uData->info
-               && (cData->flags & CHANNEL_INFO_LINES)
+               && (uData->access >= cData->lvlOpts[lvlUserInfo])
                && ((now - uData->seen) >= chanserv_conf.info_delay)
                && !uData->present)
                 info = 1;
+            uData->seen = now;
+            uData->present = 1;
         }
     }
     if(!user->uplink->burst)
     {
         if(modes)
         {
+            if(modes & MODE_CHANOP)
+                modes &= ~MODE_VOICE;
             change.args[0].mode = modes;
             change.args[0].member = mNode;
             mod_chanmode_announce(chanserv, channel, &change);
@@ -5797,7 +5866,7 @@ handle_join(struct modeNode *mNode)
         if(greeting && !user->uplink->burst)
             send_message_type(4, user, chanserv, "(%s) %s", channel->name, greeting);
         if(uData && info)
-            send_target_message(4, channel->name, chanserv, "[%s] %s", user->nick, uData->info);
+            send_target_message(5, channel->name, chanserv, "[%s] %s", user->nick, uData->info);
     }
     return 0;
 }
@@ -5818,7 +5887,9 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
     {
         struct chanNode *cn;
         struct modeNode *mn;
-        if(IsSuspended(channel->channel) || !(cn = channel->channel->channel))
+        if(IsUserSuspended(channel)
+           || IsSuspended(channel->channel)
+           || !(cn = channel->channel->channel))
             continue;
 
         mn = GetUserMode(cn, user);
@@ -5826,6 +5897,7 @@ 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)
                 irc_invite(chanserv, user, cn);
@@ -5839,10 +5911,13 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
         {
             if(channel->access >= cn->channel_info->lvlOpts[lvlGiveOps])
                 change.args[0].mode = MODE_CHANOP;
-            else
+            else if(channel->access >= cn->channel_info->lvlOpts[lvlGiveVoice])
                 change.args[0].mode = MODE_VOICE;
+            else
+                change.args[0].mode = 0;
             change.args[0].member = mn;
-            mod_chanmode_announce(chanserv, cn, &change);
+            if(change.args[0].mode)
+                mod_chanmode_announce(chanserv, cn, &change);
         }
 
        channel->seen = now;
@@ -5895,10 +5970,10 @@ handle_part(struct userNode *user, struct chanNode *channel, UNUSED_ARG(const ch
 {
     struct chanData *cData;
     struct userData *uData;
-    struct handle_info *handle;
 
     cData = channel->channel_info;
-    if(!cData || IsSuspended(cData) || IsLocal(user)) return;
+    if(!cData || IsSuspended(cData) || IsLocal(user))
+        return;
 
     if((cData->flags & CHANNEL_DYNAMIC_LIMIT) && !channel->join_flooded)
     {
@@ -5911,11 +5986,8 @@ handle_part(struct userNode *user, struct chanNode *channel, UNUSED_ARG(const ch
        }
     }
 
-    if((handle = user->handle_info) && (uData = GetTrueChannelAccess(cData, handle)))
-    {
-       uData->seen = now;
-       scan_handle_presence(channel, handle, user);
-    }
+    if((uData = GetTrueChannelAccess(cData, user->handle_info)))
+       scan_user_presence(uData, user);
 
     if(IsHelping(user) && IsSupportHelper(user))
     {
@@ -5936,15 +6008,16 @@ handle_part(struct userNode *user, struct chanNode *channel, UNUSED_ARG(const ch
 static void
 handle_kick(struct userNode *kicker, struct userNode *victim, struct chanNode *channel)
 {
-    char *reason = "CSMSG_USER_PROTECTED";
-
     if(!channel->channel_info || !kicker || IsService(kicker)
        || (kicker == victim) || IsSuspended(channel->channel_info)
        || (kicker->handle_info && kicker->handle_info == victim->handle_info))
         return;
 
     if(protect_user(victim, kicker, channel->channel_info))
+    {
+        const char *reason = user_find_message(kicker, "CSMSG_USER_PROTECTED");
        KickChannelUser(kicker, channel, chanserv, reason);
+    }
 }
 
 static int
@@ -5952,7 +6025,8 @@ handle_topic(struct userNode *user, struct chanNode *channel, const char *old_to
 {
     struct chanData *cData;
 
-    if(!channel->channel_info || !user || IsSuspended(channel->channel_info) || IsService(user)) return 0;
+    if(!channel->channel_info || !user || IsSuspended(channel->channel_info) || IsService(user))
+        return 0;
 
     cData = channel->channel_info;
     if(bad_topic(channel, user, channel->topic))
@@ -5965,7 +6039,7 @@ handle_topic(struct userNode *user, struct chanNode *channel, const char *old_to
         return 1;
     }
     /* With topicsnarf, grab the topic and save it as the default topic. */
-    if(cData->flags & CHANNEL_TOPIC_SNARF)
+    if(check_user_level(channel, user, lvlTopicSnarf, 0, 0))
     {
         free(cData->topic);
         cData->topic = strdup(channel->topic);
@@ -6007,6 +6081,7 @@ handle_mode(struct chanNode *channel, struct userNode *user, const struct mod_ch
                 bounce->args[bnc].member = GetUserMode(channel, user);
                 if(bounce->args[bnc].member)
                     bnc++;
+                deopped = 1;
             }
             bounce->args[bnc].mode = MODE_CHANOP;
             bounce->args[bnc].member = change->args[ii].member;
@@ -6034,12 +6109,12 @@ handle_mode(struct chanNode *channel, struct userNode *user, const struct mod_ch
             bounce->args[bnc].mode = MODE_REMOVE | MODE_BAN;
             bounce->args[bnc].hostmask = ban;
             bnc++;
-            send_message(user, chanserv, "CSMSG_MASK_PROTECTED", remove);
+            send_message(user, chanserv, "CSMSG_MASK_PROTECTED", ban);
         }
     }
     if(bounce)
     {
-        if((bounce->argc = bnc))
+        if((bounce->argc = bnc) || bounce->modes_set || bounce->modes_clear)
             mod_chanmode_announce(chanserv, channel, bounce);
         mod_chanmode_free(bounce);
     }
@@ -6197,7 +6272,8 @@ chanserv_conf_read(void)
     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_NICK, RECDB_QSTRING);
-    if(str) NickChange(chanserv, str, 0);
+    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_CTCP_SHORT_BAN_DURATION, RECDB_QSTRING);
@@ -6229,14 +6305,18 @@ chanserv_conf_read(void)
     else
     {
         static const char *list[] = {
-            /* multiple choice options */
+            /* free form text */
             "DefaultTopic", "TopicMask", "Greeting", "UserGreeting", "Modes",
-            "PubCmd", "GiveOps", "EnfOps", "EnfModes", "EnfTopic", "Protect",
-            "Toys", "Setters", "TopicRefresh", "CtcpUsers", "CtcpReaction",
+            /* options based on user level */
+            "PubCmd", "InviteMe", "UserInfo", "GiveVoice", "GiveOps", "EnfOps",
+            "EnfModes", "EnfTopic", "TopicSnarf", "Setters", "CtcpUsers",
+            /* multiple choice options */
+            "CtcpReaction", "Protect", "Toys", "TopicRefresh",
             /* binary options */
-            "Voice", "UserInfo", "DynLimit", "TopicSnarf", "PeonInvite", "NoDelete",
+            "DynLimit", "NoDelete",
             /* delimiter */
-            NULL };
+            NULL
+        };
         unsigned int ii;
         strlist = alloc_string_list(ArrayLength(list)-1);
         for(ii=0; list[ii]; ii++)
@@ -6264,7 +6344,7 @@ chanserv_conf_read(void)
     chanserv_conf.eightball = strlist;
     free_string_list(chanserv_conf.old_ban_names);
     strlist = database_get_data(conf_node, KEY_OLD_BAN_NAMES, RECDB_STRING_LIST);
-    if (strlist)
+    if(strlist)
         strlist = string_list_copy(strlist);
     else
         strlist = alloc_string_list(2);
@@ -6453,20 +6533,30 @@ chanserv_channel_read(const char *key, struct record_data *hir)
     {
         enum levelOption lvlOpt;
         enum charOption chOpt;
+
+        if((str = database_get_data(obj, KEY_FLAGS, RECDB_QSTRING)))
+            cData->flags = atoi(str);
+
         for(lvlOpt = 0; lvlOpt < NUM_LEVEL_OPTIONS; ++lvlOpt)
         {
-            if(!(str = database_get_data(obj, levelOptions[lvlOpt].db_name, RECDB_QSTRING)))
-                continue;
-            cData->lvlOpts[lvlOpt] = user_level_from_name(str, UL_OWNER+1);
+            str = database_get_data(obj, levelOptions[lvlOpt].db_name, RECDB_QSTRING);
+            if(str)
+                cData->lvlOpts[lvlOpt] = user_level_from_name(str, UL_OWNER+1);
+            else if(levelOptions[lvlOpt].old_flag)
+            {
+                if(cData->flags & levelOptions[lvlOpt].old_flag)
+                    cData->lvlOpts[lvlOpt] = levelOptions[lvlOpt].flag_value;
+                else
+                    cData->lvlOpts[lvlOpt] = levelOptions[lvlOpt].default_value;
+            }
         }
+
         for(chOpt = 0; chOpt < NUM_CHAR_OPTIONS; ++chOpt)
         {
             if(!(str = database_get_data(obj, charOptions[chOpt].db_name, RECDB_QSTRING)))
                 continue;
             cData->chOpts[chOpt] = str[0];
         }
-        if((str = database_get_data(channel, KEY_FLAGS, RECDB_QSTRING)))
-            cData->flags = atoi(str);
     }
     else if((str = database_get_data(channel, KEY_FLAGS, RECDB_QSTRING)))
     {
@@ -6479,7 +6569,14 @@ chanserv_channel_read(const char *key, struct record_data *hir)
         for(lvlOpt = 0; lvlOpt < NUM_LEVEL_OPTIONS; ++lvlOpt)
         {
             unsigned short lvl;
-            switch(((count <= levelOptions[lvlOpt].old_idx) ? str : CHANNEL_DEFAULT_OPTIONS)[levelOptions[lvlOpt].old_idx])
+            if(levelOptions[lvlOpt].old_flag)
+            {
+                if(cData->flags & levelOptions[lvlOpt].old_flag)
+                    lvl = levelOptions[lvlOpt].flag_value;
+                else
+                    lvl = levelOptions[lvlOpt].default_value;
+            }
+            else switch(((count <= levelOptions[lvlOpt].old_idx) ? str : CHANNEL_DEFAULT_OPTIONS)[levelOptions[lvlOpt].old_idx])
             {
             case 'c': lvl = UL_COOWNER; break;
             case 'm': lvl = UL_MASTER; break;
@@ -6527,11 +6624,7 @@ chanserv_channel_read(const char *key, struct record_data *hir)
         cData->flags &= ~CHANNEL_SUSPENDED;
     }
 
-    if((cData->flags & CHANNEL_SUSPENDED) && (suspended->expires > now))
-    {
-        timeq_add(suspended->expires, chanserv_expire_suspension, suspended);
-    }
-    else
+    if(!(cData->flags & CHANNEL_SUSPENDED))
     {
         struct mod_chanmode change;
         change.modes_set = change.modes_clear = 0;
@@ -6540,6 +6633,10 @@ chanserv_channel_read(const char *key, struct record_data *hir)
         change.args[0].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;
@@ -6897,7 +6994,6 @@ chanserv_db_cleanup(void) {
 void
 init_chanserv(const char *nick)
 {
-    chanserv = AddService(nick, "Channel Services");
     CS_LOG = log_register_type("ChanServ", "file:chanserv.log");
     conf_register_reload(chanserv_conf_read);
 
@@ -6949,7 +7045,7 @@ init_chanserv(const char *nick)
     DEFINE_COMMAND(mdelpeon, 2, MODCMD_REQUIRE_CHANUSER, "access", "master", NULL);
 
     DEFINE_COMMAND(trim, 3, MODCMD_REQUIRE_CHANUSER, "access", "master", NULL);
-    DEFINE_COMMAND(opchan, 1, MODCMD_REQUIRE_REGCHAN, "access", "peon", NULL);
+    DEFINE_COMMAND(opchan, 1, MODCMD_REQUIRE_REGCHAN, "access", "1", NULL);
     DEFINE_COMMAND(clvl, 3, MODCMD_REQUIRE_CHANUSER, "access", "master", NULL);
     DEFINE_COMMAND(giveownership, 2, MODCMD_REQUIRE_CHANUSER, "access", "owner", "flags", "+loghostmask", NULL);
 
@@ -6971,7 +7067,7 @@ init_chanserv(const char *nick)
     DEFINE_COMMAND(open, 1, MODCMD_REQUIRE_CHANUSER, "template", "op", NULL);
     DEFINE_COMMAND(topic, 1, MODCMD_REQUIRE_REGCHAN, "template", "op", "flags", "+never_csuspend", NULL);
     DEFINE_COMMAND(mode, 1, MODCMD_REQUIRE_REGCHAN, "template", "op", NULL);
-    DEFINE_COMMAND(inviteme, 1, MODCMD_REQUIRE_CHANNEL, "access", "peon", NULL);
+    DEFINE_COMMAND(inviteme, 1, MODCMD_REQUIRE_CHANNEL, "access", "1", NULL);
     DEFINE_COMMAND(invite, 1, MODCMD_REQUIRE_CHANNEL, "access", "master", NULL);
     DEFINE_COMMAND(set, 1, MODCMD_REQUIRE_CHANUSER, "access", "op", NULL);
     DEFINE_COMMAND(wipeinfo, 2, MODCMD_REQUIRE_CHANUSER, "access", "master", NULL);
@@ -6981,12 +7077,13 @@ init_chanserv(const char *nick)
     DEFINE_COMMAND(addban, 2, MODCMD_REQUIRE_REGCHAN, "access", "250", NULL);
     DEFINE_COMMAND(addtimedban, 3, MODCMD_REQUIRE_REGCHAN, "access", "250", NULL);
     DEFINE_COMMAND(delban, 2, MODCMD_REQUIRE_REGCHAN, "access", "250", NULL);
-    DEFINE_COMMAND(uset, 1, MODCMD_REQUIRE_CHANUSER, "access", "peon", NULL);
+    DEFINE_COMMAND(uset, 1, MODCMD_REQUIRE_CHANUSER, "access", "1", NULL);
 
-    DEFINE_COMMAND(bans, 1, MODCMD_REQUIRE_REGCHAN, "access", "peon", "flags", "+nolog", NULL);
+    DEFINE_COMMAND(bans, 1, MODCMD_REQUIRE_REGCHAN, "access", "1", "flags", "+nolog", NULL);
     DEFINE_COMMAND(peek, 1, MODCMD_REQUIRE_REGCHAN, "access", "op", "flags", "+nolog", NULL);
 
-    DEFINE_COMMAND(access, 1, 0, "flags", "+nolog,+acceptchan", NULL);
+    DEFINE_COMMAND(myaccess, 1, MODCMD_REQUIRE_AUTHED, NULL);
+    DEFINE_COMMAND(access, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL);
     DEFINE_COMMAND(users, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL);
     DEFINE_COMMAND(wlist, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL);
     DEFINE_COMMAND(clist, 1, MODCMD_REQUIRE_REGCHAN, "flags", "+nolog,+joinable", NULL);
@@ -7030,7 +7127,7 @@ init_chanserv(const char *nick)
     DEFINE_CHANNEL_OPTION(enfmodes);
     DEFINE_CHANNEL_OPTION(enftopic);
     DEFINE_CHANNEL_OPTION(pubcmd);
-    DEFINE_CHANNEL_OPTION(voice);
+    DEFINE_CHANNEL_OPTION(givevoice);
     DEFINE_CHANNEL_OPTION(userinfo);
     DEFINE_CHANNEL_OPTION(dynlimit);
     DEFINE_CHANNEL_OPTION(topicsnarf);
@@ -7040,7 +7137,7 @@ init_chanserv(const char *nick)
     DEFINE_CHANNEL_OPTION(topicrefresh);
     DEFINE_CHANNEL_OPTION(ctcpusers);
     DEFINE_CHANNEL_OPTION(ctcpreaction);
-    DEFINE_CHANNEL_OPTION(peoninvite);
+    DEFINE_CHANNEL_OPTION(inviteme);
     modcmd_register(chanserv_module, "set defaults", chan_opt_defaults, 1, 0, "access", "owner", NULL);
 
     /* Alias set topic to set defaulttopic for compatibility. */
@@ -7056,8 +7153,13 @@ init_chanserv(const char *nick)
 
     note_types = dict_new();
     dict_set_free_data(note_types, chanserv_deref_note_type);
+    if(nick)
+    {
+        chanserv = AddService(nick, "Channel Services");
+        service_register(chanserv, '!');
+        reg_chanmsg_func('\001', chanserv, chanserv_ctcp_check);
+    }
     saxdb_register("ChanServ", chanserv_saxdb_read, chanserv_saxdb_write);
-    reg_chanmsg_func('\001', chanserv, chanserv_ctcp_check);
 
     if(chanserv_conf.channel_expire_frequency)
        timeq_add(now + chanserv_conf.channel_expire_frequency, expire_channels, NULL);