basic off-channel support; restructuring how part functions are handled
[srvx.git] / src / chanserv.c
index 9fdec6c577a3d6fe4117ec83601915511a448f61..927522e50554757f6436c2e7341a25aabcb9dddc 100644 (file)
@@ -54,6 +54,7 @@
 #define KEY_NETWORK_HELPER_EPITHET  "network_helper_epithet"
 #define KEY_SUPPORT_HELPER_EPITHET  "support_helper_epithet"
 #define KEY_NODELETE_LEVEL      "nodelete_level"
+#define KEY_MAX_USERINFO_LENGTH "max_userinfo_length"
 
 /* ChanServ database */
 #define KEY_CHANNELS           "channels"
@@ -301,6 +302,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." },
@@ -396,6 +399,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_NETWORK_SERVERS", "$bServers:             $b%i" },
     { "CSMSG_NETWORK_USERS",   "$bTotal Users:         $b%i" },
     { "CSMSG_NETWORK_BANS",    "$bTotal Ban Count:     $b%i" },
+    { "CSMSG_NETWORK_CHANUSERS", "$bTotal User Count:    $b%i" },
     { "CSMSG_NETWORK_OPERS",   "$bIRC Operators:       $b%i" },
     { "CSMSG_NETWORK_CHANNELS","$bRegistered Channels: $b%i" },
     { "CSMSG_SERVICES_UPTIME", "$bServices Uptime:     $b%s" },
@@ -427,9 +431,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" },
 
@@ -492,6 +496,7 @@ static struct
     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;
@@ -642,7 +647,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;
@@ -2504,11 +2509,17 @@ 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";
     }
+    else
+    {
+        if(argc)
+            reply("CSMSG_NO_ACCESS");
+        return 0;
+    }
     change.args[0].mode &= ~change.args[0].member->modes;
     if(!change.args[0].mode)
     {
@@ -3145,9 +3156,9 @@ static CHANSERV_FUNC(cmd_open)
 
 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;
@@ -3176,11 +3187,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, sbuf.list);
     }
 
     return 1;
@@ -3907,7 +3934,7 @@ static CHANSERV_FUNC(cmd_netinfo)
     reply("CSMSG_NETWORK_OPERS", curr_opers.used);
     reply("CSMSG_NETWORK_CHANNELS", registered_channels);
     reply("CSMSG_NETWORK_BANS", banCount);
-    reply("CSMSG_CHANNEL_USERS", userCount);
+    reply("CSMSG_NETWORK_CHANUSERS", userCount);
     reply("CSMSG_SERVICES_UPTIME", intervalString(interval, time(NULL) - boot_time, user->handle_info));
     reply("CSMSG_BURST_LENGTH", intervalString(interval, burst_length, user->handle_info));
     return 1;
@@ -4138,7 +4165,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);
@@ -4297,12 +4324,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;
@@ -5242,7 +5271,19 @@ static MODCMD_FUNC(user_opt_info)
 
     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)
@@ -5873,8 +5914,8 @@ 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;
         }
@@ -5941,45 +5982,45 @@ 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)
+       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(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);
     }
 }
 
@@ -6081,7 +6122,7 @@ handle_mode(struct chanNode *channel, struct userNode *user, const struct mod_ch
             bounce->args[bnc].member = change->args[ii].member;
             bnc++;
         }
-        else if(change->args[ii].mode & MODE_BAN)
+        else if((change->args[ii].mode & (MODE_REMOVE | MODE_BAN)) == MODE_BAN)
         {
             const char *ban = change->args[ii].hostmask;
             if(!bad_channel_ban(channel, user, ban, NULL, NULL))
@@ -6202,6 +6243,7 @@ chanserv_conf_read(void)
     struct string_list *strlist;
     struct chanNode *chan;
     unsigned int ii;
+    extern int off_channel;
 
     if(!(conf_node = conf_get_data(CHANSERV_CONF_NAME, RECDB_OBJECT)))
     {
@@ -6253,6 +6295,8 @@ chanserv_conf_read(void)
     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);
@@ -6331,6 +6375,10 @@ chanserv_conf_read(void)
     else
         strlist = alloc_string_list(2);
     chanserv_conf.old_ban_names = strlist;
+    /* the variable itself is actually declared in proto-common.c; this is equally 
+     * parse issue. */
+    str = database_get_data(conf_node, "off_channel", RECDB_QSTRING);
+    off_channel = (str && enabled_string(str)) ? 1 : 0;
 }
 
 static void
@@ -6492,6 +6540,7 @@ chanserv_channel_read(const char *key, struct record_data *hir)
     char *str, *argv[10];
     dict_iterator_t it;
     unsigned int argc;
+    extern int off_channel;
 
     channel = hir->d.object;
 
@@ -6606,18 +6655,18 @@ chanserv_channel_read(const char *key, struct record_data *hir)
         cData->flags &= ~CHANNEL_SUSPENDED;
     }
 
-    if(!(cData->flags & CHANNEL_SUSPENDED))
-    {
-        struct mod_chanmode change;
-        mod_chanmode_init(&change);
-        change.argc = 1;
-        change.args[0].mode = MODE_CHANOP;
-        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);
+    if (!off_channel) {
+      if (!(cData->flags & CHANNEL_SUSPENDED)) {
+       struct mod_chanmode change;
+       mod_chanmode_init(&change);
+       change.argc = 1;
+       change.args[0].mode = MODE_CHANOP;
+       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);
@@ -6639,6 +6688,8 @@ chanserv_channel_read(const char *key, struct record_data *hir)
        && (argc = split_line(str, 0, ArrayLength(argv), argv))
        && (modes = mod_chanmode_parse(cNode, argv, argc, MCP_KEY_FREE))) {
         cData->modes = *modes;
+       if(off_channel && !(REGISTERED_MODE == 0))
+         cData->modes.modes_set |= REGISTERED_MODE;
         if(cData->modes.argc > 1)
             cData->modes.argc = 1;
         if(!IsSuspended(cData))
@@ -7137,8 +7188,8 @@ 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, '!');
+        chanserv = AddService(nick, "Channel Services", NULL);
+        service_register(chanserv)->trigger = '!';
         reg_chanmsg_func('\001', chanserv, chanserv_ctcp_check);
     }
     saxdb_register("ChanServ", chanserv_saxdb_read, chanserv_saxdb_write);