]> jfr.im git - irc/evilnet/x3.git/blobdiff - src/chanserv.c
nickserv register and set help improvements
[irc/evilnet/x3.git] / src / chanserv.c
index 4518a937ff3c7dfdc8adb40efb1d9e8c264caf2a..e61cb35d76348162fd202a530ecd17d647e3926e 100644 (file)
 #define KEY_GIVEOWNERSHIP_PERIOD "giveownership_timeout"
 
 /* ChanServ database */
+#define KEY_VERSION_CONTROL     "version_control"
 #define KEY_CHANNELS           "channels"
 #define KEY_NOTE_TYPES          "note_types"
 
+/* version control paramiter */
+#define KEY_VERSION_NUMBER      "version_number"
+
 /* Note type parameters */
 #define KEY_NOTE_OPSERV_ACCESS  "opserv_access"
 #define KEY_NOTE_CHANNEL_ACCESS "channel_access"
@@ -191,6 +195,11 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_TRIMMED_USERS", "Trimmed $b%d users$b with access from %d to %d from the %s user list who were inactive for at least %s." },
     { "CSMSG_INCORRECT_ACCESS", "%s has access $b%s$b, not %s." },
     { "CSMSG_USER_EXISTS", "%s is already on the $b%s$b user list (with %s access)." },
+    { "CSMSG_ADDUSER_PENDING", "I have sent him/her a message letting them know, and if they auth or register soon, i will finish adding them automatically." },
+    { "CSMSG_ADDUSER_PENDING_ALREADY", "He or she is already pending addition to %s once he/she auths with $b$N$b." },
+    { "CSMSG_ADDUSER_PENDING_LIST", "Channel %s user %s" }, /* Remove after testing */
+    /*{ "CSMSG_ADDUSER_PENDING_NOTINCHAN", "That user is not in %s, and is not auth'd." }, */
+    { "CSMSG_ADDUSER_PENDING_TARGET", "Channel Services bot here, %s would like to add you to my userlist in channel %s, but you are not auth'd to $b$N$b. Please auth now, and you will be added. If you do not have an accont, type /msg $N help register" },
     { "CSMSG_CANNOT_TRIM", "You must include a minimum inactivity duration of at least 60 seconds to trim." },
 
     { "CSMSG_NO_SELF_CLVL", "You cannot change your own access." },
@@ -275,12 +284,13 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_SET_PUBCMD",        "$bPubCmd      $b %d" },
     { "CSMSG_SET_SETTERS",       "$bSetters     $b %d" },
     { "CSMSG_SET_CTCPUSERS",     "$bCTCPUsers   $b %d" },
+    { "CSMSG_SET_VOICE",         "$bvoice       $b %d - %s" },
     { "CSMSG_SET_PROTECT",       "$bProtect     $b %d - %s" },
     { "CSMSG_SET_TOYS",          "$bToys        $b %d - %s" },
     { "CSMSG_SET_CTCPREACTION",  "$bCTCPReaction$b %d - %s" },
     { "CSMSG_SET_TOPICREFRESH",  "$bTopicRefresh$b %d - %s" },
-    { "CSMSG_USET_NOAUTOOP",     "$bNoAutoOp    $b %s" },
-    { "CSMSG_USET_NOAUTOVOICE",  "$bNoAutoVoice $b %s" },
+    { "CSMSG_USET_AUTOOP",       "$bAutoOp      $b %s" },
+    { "CSMSG_USET_AUTOVOICE",    "$bAutoVoice   $b %s" },
     { "CSMSG_USET_AUTOINVITE",   "$bAutoInvite  $b %s" },
     { "CSMSG_USET_INFO",         "$bInfo        $b %s" },
 
@@ -294,6 +304,9 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_DEHALFOPPED_USERS", "DeHalfopped users in $b%s$b." },
     { "CSMSG_VOICED_USERS", "Voiced users in $b%s$b." },
     { "CSMSG_DEVOICED_USERS", "Devoiced users in $b%s$b." },
+    { "CSMSG_VOICE_NONE", "Noone will be auto-voiced" },
+    { "CSMSG_VOICE_PEON", "PEONs will be auto-voiced" },
+    { "CSMSG_VOICE_ALL", "Everyone will be auto-voiced" },
     { "CSMSG_PROTECT_ALL", "Non-users and users will be protected from those of equal or lower access." },
     { "CSMSG_PROTECT_EQUAL", "Users will be protected from those of equal or lower access." },
     { "CSMSG_PROTECT_LOWER", "Users will be protected from those of lower access." },
@@ -494,6 +507,8 @@ dict_t note_types;
 int off_channel;
 static dict_t plain_dnrs, mask_dnrs, handle_dnrs;
 static struct log_type *CS_LOG;
+struct adduserPending* adduser_pendings = NULL;
+unsigned int adduser_pendings_count = 0;
 
 static struct
 {
@@ -622,7 +637,11 @@ static const struct {
 struct charOptionValues {
     char value;
     char *format_name;
-} protectValues[] = {
+} voiceValues[] = {
+    { 'n', "CSMSG_VOICE_NONE" },
+    { 'p', "CSMSG_VOICE_PEON" },
+    { 'a', "CSMSG_VOICE_ALL" }
+}, protectValues[] = {
     { 'a', "CSMSG_PROTECT_ALL" },
     { 'e', "CSMSG_PROTECT_EQUAL" },
     { 'l', "CSMSG_PROTECT_LOWER" },
@@ -652,8 +671,9 @@ static const struct {
     unsigned char count;
     struct charOptionValues *values;
 } charOptions[] = {
-    { "CSMSG_SET_PROTECT", "protect", 'l', 0, ArrayLength(protectValues), protectValues },
-    { "CSMSG_SET_TOYS", "toys", 'p', 6, ArrayLength(toysValues), toysValues },
+    { "CSMSG_SET_VOICE",        "voice",        'p', 99, ArrayLength(voiceValues), voiceValues },
+    { "CSMSG_SET_PROTECT",      "protect",      'l', 0, ArrayLength(protectValues), protectValues },
+    { "CSMSG_SET_TOYS",         "toys",         'p', 6, ArrayLength(toysValues), toysValues },
     { "CSMSG_SET_TOPICREFRESH", "topicrefresh", 'n', 8, ArrayLength(topicRefreshValues), topicRefreshValues },
     { "CSMSG_SET_CTCPREACTION", "ctcpreaction", 't', 10, ArrayLength(ctcpReactionValues), ctcpReactionValues }
 };
@@ -662,6 +682,9 @@ struct userData *helperList;
 struct chanData *channelList;
 static struct module *chanserv_module;
 static unsigned int userCount;
+unsigned int chanserv_read_version = 0; /* db version control */
+
+#define CHANSERV_DB_VERSION 2
 
 #define GetChannelUser(channel, handle) _GetChannelUser(channel, handle, 1, 0)
 #define GetChannelAccess(channel, handle) _GetChannelUser(channel, handle, 0, 0)
@@ -1169,6 +1192,7 @@ add_channel_user(struct chanData *channel, struct handle_info *handle, unsigned
         ud->u_next->u_prev = ud;
     ud->handle->channels = ud;
 
+    ud->flags = USER_FLAGS_DEFAULT;
     return ud;
 }
 
@@ -1202,6 +1226,110 @@ del_channel_user(struct userData *user, int do_gc)
         unregister_channel(channel, "lost all users.");
 }
 
+static struct adduserPending* 
+add_adduser_pending(struct chanNode *channel, struct userNode *user, int level)
+{
+    struct adduserPending *ap;
+    ap = calloc(1,sizeof(struct adduserPending));
+    ap->channel = channel;
+    ap->user = user;
+    ap->level = level;
+    ap->created = time(NULL);
+
+    /* ap->prev defaults to NULL already..  */
+    ap->next = adduser_pendings;
+    if(adduser_pendings)
+        adduser_pendings->prev = ap;
+    adduser_pendings = ap;
+    adduser_pendings_count++;
+    return(ap);
+}
+
+static void 
+del_adduser_pending(struct adduserPending *ap)
+{
+    if(ap->prev)
+        ap->prev->next = ap->next;
+    else
+        adduser_pendings = ap->next;
+
+    if(ap->next)
+        ap->next->prev = ap->prev;
+    free(ap);
+}
+
+static void expire_adduser_pending();
+
+/* find_adduser_pending(channel, user) will find an arbitrary record
+ * from user, channel, or user and channel.
+ * if user or channel are NULL, they will match any records.
+ */
+static struct adduserPending* 
+find_adduser_pending(struct chanNode *channel, struct userNode *user)
+{
+    struct adduserPending *ap;
+
+    expire_adduser_pending(); /* why not here.. */
+
+    if(!channel && !user) /* 2 nulls matches all */
+        return(adduser_pendings);
+    for(ap = adduser_pendings;ap;ap = ap->next)
+    {
+        if((channel == ap->channel && (user == NULL || user == ap->user)) || (user==ap->user && channel==NULL))
+            return ap;
+    }
+    return NULL;
+}
+
+
+/* Remove all pendings for a user or channel 
+ *
+ * called in nickserv.c DelUser() and proto-* unregister_channel()
+ */
+void
+wipe_adduser_pending(struct chanNode *channel, struct userNode *user)
+{
+    struct adduserPending *ap;
+
+    /* So this is a bit wastefull, i hate dealing with linked lists.
+     * if its a problem we'll rewrite it right */
+    while((ap = find_adduser_pending(channel, user))) {
+        del_adduser_pending(ap);
+    }
+}
+
+/* Called from nickserv.c cmd_auth after someone auths */
+void
+process_adduser_pending(struct userNode *user)
+{
+    struct adduserPending *ap;
+    while((ap = find_adduser_pending(NULL, user)))
+    {
+        struct userData *actee;
+        actee = add_channel_user(ap->channel->channel_info, ap->user->handle_info, ap->level, 0, NULL);
+        scan_user_presence(actee, NULL);
+        del_adduser_pending(ap);
+    }
+}
+
+static void
+expire_adduser_pending()
+{
+    struct adduserPending *ap, *ap_next;
+    ap = adduser_pendings;
+    while(ap)
+    {
+        if((ap->created + ADDUSER_PENDING_EXPIRE) < time(NULL))
+        {  /* expire it */
+            ap_next = ap->next; /* save next */
+            del_adduser_pending(ap); /* free and relink */
+            ap = ap_next; /* advance */
+        }
+        else
+            ap = ap->next;
+    }
+}
+
 static void expire_ban(void *data);
 
 static struct banData*
@@ -1319,6 +1447,7 @@ unregister_channel(struct chanData *channel, const char *reason)
        - Channel users.
        - Channel bans.
        - Channel suspension data.
+       - adduser_pending data.
        - Timeq entries. (Except timed bans, which are handled elsewhere.)
     */
 
@@ -1334,6 +1463,8 @@ unregister_channel(struct chanData *channel, const char *reason)
       mod_chanmode_announce(chanserv, channel->channel, &change);
     }
 
+    wipe_adduser_pending(channel->channel, NULL);
+
     while(channel->users)
        del_channel_user(channel->users, 0);
 
@@ -1427,7 +1558,7 @@ protect_user(const struct userNode *victim, const struct userNode *aggressor, st
 
     /* Don't protect if no one is to be protected, someone is attacking
        himself, or if the aggressor is an IRC Operator. */
-    if(protect == 'n' || victim == aggressor || IsOper(aggressor))
+    if(protect == 'n' || victim == aggressor /* Opers dont get special treatment :/  || IsOper(aggressor) */)
        return 0;
 
     /* Don't protect if the victim isn't authenticated (because they
@@ -2304,7 +2435,30 @@ static CHANSERV_FUNC(cmd_adduser)
     }
 
     if(!(handle = modcmd_get_handle_info(user, argv[1])))
+    {
+        // 'kevin must first authenticate with AuthServ.' is sent to user
+        struct userNode *unode;
+        unode = GetUserH(argv[1]); /* find user struct by nick */
+        if(unode)
+        {
+            if(find_adduser_pending(channel, unode)) {
+                reply("CSMSG_ADDUSER_PENDING_ALREADY", channel->name);
+            }
+            else {
+                if(IsInChannel(channel, unode)) {
+                   reply("CSMSG_ADDUSER_PENDING");
+                   add_adduser_pending(channel, unode, access);
+                   send_message_type(1,unode, chanserv, "CSMSG_ADDUSER_PENDING_TARGET", user->nick, channel->name);
+                }
+                /* this results in user must auth AND not in chan errors. too confusing..
+                else {
+                    reply("CSMSG_ADDUSER_PENDING_NOTINCHAN", channel->name);
+                }
+                */
+            }
+        }
         return 0;
+    }
 
     if((actee = GetTrueChannelAccess(channel->channel_info, handle)))
     {
@@ -2636,7 +2790,7 @@ static CHANSERV_FUNC(cmd_up)
         change.args[0].mode = MODE_HALFOP;
         errmsg = "CSMSG_ALREADY_HALFOPPED";
     }
-    else if(uData->access >= UL_PEON /* channel->channel_info->lvlOpts[lvlGiveVoice]*/)
+    else if(uData->access >= UL_PEON && (channel->channel_info->chOpts[chVoice] == 'p' || channel->channel_info->chOpts[chVoice] == 'a'))
     {
         change.args[0].mode = MODE_VOICE;
         errmsg = "CSMSG_ALREADY_VOICED";
@@ -3342,7 +3496,7 @@ static CHANSERV_FUNC(cmd_myaccess)
             continue;
         sbuf.used = 0;
         string_buffer_append_printf(&sbuf, "[%s (%d", cData->channel->name, uData->access);
-        if(uData->flags != USER_AUTO_OP)
+        if(uData->flags == USER_AUTO_OP)
             string_buffer_append(&sbuf, ',');
         if(IsUserSuspended(uData))
             string_buffer_append(&sbuf, 's');
@@ -3632,8 +3786,6 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
         lData.table.contents[matches] = ary;
         /* ary[0] = strtab(uData->access);*/
         ary[0] = user_level_name_from_level(uData->access);
-        /* TODO: replace above with func that returns static string
-         * of userlevel for that level. eg OP/MANAGER etc. -rubin */
         ary[1] = strtab(uData->access);
         ary[2] = uData->handle->handle;
         if(uData->present)
@@ -3661,6 +3813,14 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
     return 1;
 }
 
+static CHANSERV_FUNC(cmd_pending)
+{
+    struct adduserPending *ap;
+    for(ap = adduser_pendings;ap;ap = ap->next)
+        reply("CSMSG_ADDUSER_PENDING_LIST", ap->channel->name, ap->user->nick);
+    return 1;
+}
+
 static CHANSERV_FUNC(cmd_users)
 {
     return cmd_list_users(CSFUNC_ARGS, 1, UL_OWNER);
@@ -5422,6 +5582,11 @@ channel_multiple_option(enum charOption option, struct userNode *user, struct ch
     return 1;
 }
 
+static MODCMD_FUNC(chan_opt_voice)
+{
+    return channel_multiple_option(chVoice, CSFUNC_ARGS);
+}
+
 static MODCMD_FUNC(chan_opt_protect)
 {
     return channel_multiple_option(chProtect, CSFUNC_ARGS);
@@ -5543,7 +5708,7 @@ user_binary_option(char *name, unsigned long mask, struct userNode *user, struct
     return 1;
 }
 
-static MODCMD_FUNC(user_opt_noautoop)
+static MODCMD_FUNC(user_opt_autoop)
 {
     struct userData *uData;
 
@@ -5554,9 +5719,9 @@ static MODCMD_FUNC(user_opt_noautoop)
         return 0;
     }
     if(uData->access < UL_OP /*channel->channel_info->lvlOpts[lvlGiveOps]*/)
-        return user_binary_option("CSMSG_USET_NOAUTOVOICE", USER_AUTO_OP, CSFUNC_ARGS);
+        return user_binary_option("CSMSG_USET_AUTOVOICE", USER_AUTO_OP, CSFUNC_ARGS);
     else
-        return user_binary_option("CSMSG_USET_NOAUTOOP", USER_AUTO_OP, CSFUNC_ARGS);
+        return user_binary_option("CSMSG_USET_AUTOOP", USER_AUTO_OP, CSFUNC_ARGS);
     /* TODO: add halfops error message? or is the op one generic enough? */
 }
 
@@ -5623,7 +5788,7 @@ static CHANSERV_FUNC(cmd_uset)
     {
         char *options[] =
         {
-            "NoAutoOp", "AutoInvite", "Info"
+            "AutoOp", "AutoInvite", "Info"
         };
 
         if(!uset_shows_list.size)
@@ -6168,16 +6333,12 @@ handle_join(struct modeNode *mNode)
 
     if(channel->join_flooded)
     {
-        /* don't automatically give ops or voice during a join flood */
+        /* don't automatically give non users ops or voice during a join flood */
     }
-    /* I don't understand why we do this, so im removing it -rubin *
-    else if(cData->lvlOpts[lvlGiveOps] == 0)
-        modes |= MODE_CHANOP;
-    else if(cData->lvlOpts[lvlGiveHalfOps] == 0)
-        modes |= MODE_HALFOP;
-    else if(cData->lvlOpts[lvlGiveVoice] == 0)
+    /* EVERYONE is to get voice */
+    else if(cData->chOpts[chVoice] == 'a')
         modes |= MODE_VOICE;
-    */
+
     greeting = cData->greeting;
     if(user->handle_info)
     {
@@ -6199,14 +6360,14 @@ handle_join(struct modeNode *mNode)
         uData = GetTrueChannelAccess(cData, handle);
         if(uData && !IsUserSuspended(uData))
         {
-            /* Ops and above were handled by the above case. */
+            /* non users getting voice are handled above. */
             if(IsUserAutoOp(uData))
             {
                 if(uData->access >= UL_OP /*cData->lvlOpts[lvlGiveOps]*/)
                     modes |= MODE_CHANOP;
                 if(uData->access >= UL_HALFOP /*cData->lvlOpts[lvlGiveHalfOps]*/)
                     modes |= MODE_HALFOP;
-                else if(uData->access >= UL_PEON /* cData->lvlOpts[lvlGiveVoice] */)
+                else if(uData->access >= UL_PEON && cData->chOpts[chVoice] == 'p')
                     modes |= MODE_VOICE;
             }
             if(uData->access >= UL_PRESENT)
@@ -6848,6 +7009,18 @@ user_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
 
     uData = add_channel_user(chan, handle, access, last_seen, inf);
     uData->flags = flags ? strtoul(flags, NULL, 0) : 0;
+
+    /* Upgrade: set autoop to the inverse of noautoop */
+    if(chanserv_read_version < 2)
+    {
+        /* if noautoop is true, set autoop false, and vice versa */
+        if(uData->flags & USER_NOAUTO_OP)
+            uData->flags = uData->flags & ~USER_AUTO_OP;
+        else
+            uData->flags = uData->flags | USER_AUTO_OP;
+        log_module(CS_LOG, LOG_INFO, "UPGRADE: to db version 2 from %u. Changing flag to %d for %s in %s.", chanserv_read_version, uData->flags, key, chan->channel->name);
+    }
+    
 }
 
 static void
@@ -7148,12 +7321,26 @@ chanserv_dnr_read(const char *key, struct record_data *hir)
         dnr->set = 0;
 }
 
+static void
+chanserv_version_read(struct dict *section)
+{
+    /* global var.. */
+    char *str;
+    str = database_get_data(section, KEY_VERSION_NUMBER, RECDB_QSTRING);
+    if(str)
+       chanserv_read_version = atoi(str);
+    log_module(CS_LOG, LOG_DEBUG, "Chanserv db version is %d.", chanserv_read_version);
+}
+
 static int
 chanserv_saxdb_read(struct dict *database)
 {
     struct dict *section;
     dict_iterator_t it;
 
+    if((section = database_get_data(database, KEY_VERSION_CONTROL, RECDB_OBJECT)))
+        chanserv_version_read(section);
+
     if((section = database_get_data(database, KEY_NOTE_TYPES, RECDB_OBJECT)))
         for(it = dict_first(section); it; it = iter_next(it))
             chanserv_note_type_read(iter_key(it), iter_data(it));
@@ -7352,6 +7539,11 @@ chanserv_saxdb_write(struct saxdb_context *ctx)
     dict_iterator_t it;
     struct chanData *channel;
 
+    /* Version Control*/
+    saxdb_start_record(ctx, KEY_VERSION_CONTROL, 1);
+      saxdb_write_int(ctx, KEY_VERSION_NUMBER, CHANSERV_DB_VERSION);
+    saxdb_end_record(ctx);
+
     /* Notes */
     saxdb_start_record(ctx, KEY_NOTE_TYPES, 1);
     for(it = dict_first(note_types); it; it = iter_next(it))
@@ -7430,6 +7622,8 @@ init_chanserv(const char *nick)
     dict_set_free_data(plain_dnrs, free);
     mask_dnrs = dict_new();
     dict_set_free_data(mask_dnrs, free);
+    //TODO
+    //adduser_pending
 
     reg_svccmd_unbind_func(handle_svccmd_unbind);
     chanserv_module = module_register("ChanServ", CS_LOG, "chanserv.help", chanserv_expand_variable);
@@ -7442,6 +7636,8 @@ init_chanserv(const char *nick)
     DEFINE_COMMAND(createnote, 5, 0, "level", "800", NULL);
     DEFINE_COMMAND(removenote, 2, 0, "level", "800", NULL);
 
+    DEFINE_COMMAND(pending, 1, MODCMD_REQUIRE_AUTHED, "flags", "+helping", NULL);
+
     DEFINE_COMMAND(unregister, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_REGCHAN, "flags", "+loghostmask", NULL);
     DEFINE_COMMAND(merge, 2, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_REGCHAN, "access", "owner", NULL);
 
@@ -7544,6 +7740,7 @@ init_chanserv(const char *nick)
     /*DEFINE_CHANNEL_OPTION(giveops);
     DEFINE_CHANNEL_OPTION(givehalfops);
     */
+    DEFINE_CHANNEL_OPTION(voice);
     DEFINE_CHANNEL_OPTION(protect);
     DEFINE_CHANNEL_OPTION(enfmodes);
     DEFINE_CHANNEL_OPTION(enftopic);
@@ -7568,12 +7765,12 @@ init_chanserv(const char *nick)
     modcmd_register(chanserv_module, "set topic", chan_opt_defaulttopic, 1, 0, NULL);
 
     /* User options */
-    DEFINE_USER_OPTION(noautoop);
     DEFINE_USER_OPTION(autoinvite);
     DEFINE_USER_OPTION(info);
+    DEFINE_USER_OPTION(autoop);
 
     /* Alias uset autovoice to uset autoop. */
-    modcmd_register(chanserv_module, "uset noautovoice", user_opt_noautoop, 1, 0, NULL);
+    modcmd_register(chanserv_module, "uset autovoice", user_opt_autoop, 1, 0, NULL);
 
     note_types = dict_new();
     dict_set_free_data(note_types, chanserv_deref_note_type);
@@ -7584,6 +7781,7 @@ init_chanserv(const char *nick)
         service_register(chanserv)->trigger = '!';
         reg_chanmsg_func('\001', chanserv, chanserv_ctcp_check);
     }
+
     saxdb_register("ChanServ", chanserv_saxdb_read, chanserv_saxdb_write);
 
     if(chanserv_conf.channel_expire_frequency)