]> jfr.im git - irc/evilnet/x3.git/blobdiff - src/chanserv.c
more python work. Not safe to run yet
[irc/evilnet/x3.git] / src / chanserv.c
index 50625ea7b1d3d9bb078ccbbfa5cf01bc9dc7dc8b..ced5b2c91b981fd6bfe92f751c072c6ae08f9f8a 100644 (file)
 #include "chanserv.h"
 #include "conf.h"
 #include "global.h"
+#include "gline.h"
+#include "ioset.h"
 #include "modcmd.h"
 #include "opserv.h" /* for opserv_bad_channel() */
 #include "saxdb.h"
+#include "shun.h"
 #include "spamserv.h"
 #include "timeq.h"
 
@@ -56,6 +59,7 @@
 #define KEY_NODELETE_LEVEL      "nodelete_level"
 #define KEY_MAX_USERINFO_LENGTH "max_userinfo_length"
 #define KEY_GIVEOWNERSHIP_PERIOD "giveownership_timeout"
+#define KEY_VALID_CHANNEL_REGEX "valid_channel_regex"
 
 /* ChanServ database */
 #define KEY_VERSION_CONTROL     "version_control"
@@ -196,6 +200,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_ALREADY_DOWN", "You are not opped, halfopped, or voiced in $b%s$b." },
     { "CSMSG_ALREADY_OPCHANNED", "There has been no net.join since the last opchan in $b%s$b." },
     { "CSMSG_OPCHAN_DONE", "I have (re-)opped myself in $b%s$b." },
+    { "CSMSG_NOT_IN_CHANNEL", "I am not in %s." },
 
 /* Removing yourself from a channel. */
     { "CSMSG_NO_OWNER_DELETEME", "You cannot delete your owner access in $b%s$b." },
@@ -275,7 +280,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_BAD_SETLEVEL", "You cannot change any setting to above your level." },
     { "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_INVALID_NUMERIC",   "$b%s$b is not a valid choice.  Choose one:" },
     { "CSMSG_SET_DEFAULT_TOPIC", "$bDefaultTopic$b %s" },
     { "CSMSG_SET_TOPICMASK",     "$bTopicMask   $b %s" },
     { "CSMSG_SET_GREETING",      "$bGreeting    $b %s" },
@@ -321,7 +326,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_DEVOICED_USERS", "Devoiced users in $b%s$b." },
 
     { "CSMSG_AUTOMODE_NONE", "Noone will be automatically oped, half-oped, or voiced." },
-    { "CSMSG_AUTOMODE_NORMAL", "Give voice to peons, half-op to halfops, and op to ops." },
+    { "CSMSG_AUTOMODE_NORMAL", "Give voice to pals, half-op to halfops, and op to ops." },
     { "CSMSG_AUTOMODE_VOICE", "#1 plus give voice to everyone." },
     { "CSMSG_AUTOMODE_HOP", "#1 plus give halfops to everyone." },
     { "CSMSG_AUTOMODE_OP", "#1 plus give ops to everyone (not advised)" },
@@ -520,6 +525,30 @@ static const struct message_entry msgtab[] = {
     { "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" },
+    { "CSMSG_ROULETTE_LOADS",  "\001ACTION loads the gun and sets it on the table\001" },
+    { "CSMSG_ROULETTE_NEW", "Please type %croulette to start a new round" } ,
+    { "CSMSG_ROULETTE_BETTER_LUCK", "Better luck next time, %s" },
+    { "CSMSG_ROULETTE_BANG", "Bang!!!" } ,
+    { "CSMSG_ROULETTE_CLICK", "Click" } ,
+
+    { "CSMSG_SPIN_WHEEL1", "\001ACTION spins the wheel of misfortune for: %s\001" } ,
+    { "CSMSG_SPIN_WHEEL2", "Round and round she goes, where she stops, nobody knows...!" } ,
+    { "CSMSG_SPIN_WHEEL3", "The wheel of misfortune has stopped on..." } ,
+
+    { "CSMSG_SPIN_PEER", "Peer: Peer's gonna eat you!!!!" } ,
+    { "CSMSG_SPIN_PARTALL", "Part all: Part all channels" } ,
+    { "CSMSG_SPIN_Gline", "Gline: /gline for random amount of time" } ,
+    { "CSMSG_SPIN_SHUN", "Shun: /shun for random amount of time" } ,
+    { "CSMSG_SPIN_NOTHING", "Nothing: Absolutely nothing" } ,
+    { "CSMSG_SPIN_RANDJOIN", "Random join: Join a bunch of random channels, then /part all of 'em several times" } ,
+    { "CSMSG_SPIN_ABUSEWHOIS", "Abuse whois: Abuse line added to /whois info" } ,
+    { "CSMSG_SPIN_KICKALL", "Kick all: /kick from each channel you're in" } ,
+    { "CSMSG_SPIN_NICKCHANGE", "Nick change: Random Nick Change" } ,
+    { "CSMSG_SPIN_KILL", "Kill: /kill" } ,
+    { "CSMSG_SPIN_SVSIGNORE", "Ignore: Services ignore for random amount of time" } ,
+    { "CSMSG_SPIN_SVSIGNORE_OPER", "Ignore: I'm trying REALLY hard to ignore you, but your IRCOp smell is overwhelming!" } ,
+    { "CSMSG_SPIN_KICKBANALL", "Kickban all: /kick and ban from each channel your're in" } ,
+    { "CSMSG_SPIN_UNKNOWN", "Error: I don't know how to '%s' you, so you live for now..." },
 
 /* Other things */
     { "CSMSG_EVENT_SEARCH_RESULTS", "$bChannel Events for %s$b" },
@@ -591,10 +620,14 @@ static struct
     unsigned int       max_chan_users;
     unsigned int       max_chan_bans; /* lamers */
     unsigned int        max_userinfo_length;
+    unsigned int        valid_channel_regex_set : 1;
+
+    regex_t             valid_channel_regex;
 
     struct string_list  *set_shows;
     struct string_list  *eightball;
     struct string_list  *old_ban_names;
+    struct string_list  *wheel;
 
     const char          *ctcp_short_ban_duration;
     const char          *ctcp_long_ban_duration;
@@ -630,6 +663,9 @@ enum note_visible_type
     NOTE_VIS_PRIVILEGED
 };
 
+struct io_fd *socket_io_fd;
+extern struct cManagerNode cManager;
+
 struct note_type
 {
     enum note_access_type set_access_type;
@@ -659,6 +695,7 @@ static const struct {
     unsigned short level;
     char ch;
 } accessLevels[] = { /* MUST be orderd less to most! */
+    { "pal", "Pal", UL_PEON, '+' },
     { "peon", "Peon", UL_PEON, '+' },
     { "halfop", "HalfOp", UL_HALFOP, '%' },
     { "op", "Op", UL_OP, '@' },
@@ -764,6 +801,28 @@ unsigned int chanserv_read_version = 0; /* db version control */
 #define GetChannelAccess(channel, handle) _GetChannelUser(channel, handle, 0, 0)
 #define GetTrueChannelAccess(channel, handle) _GetChannelUser(channel, handle, 0, 1)
 
+void sputsock(const char *text, ...) PRINTF_LIKE(1, 2);
+
+void
+sputsock(const char *text, ...)
+{
+    va_list arg_list;
+    char buffer[MAXLEN];
+    int pos;
+
+    if (!cManager.uplink || cManager.uplink->state == DISCONNECTED) return;
+    buffer[0] = '\0';
+    va_start(arg_list, text);
+    pos = vsnprintf(buffer, MAXLEN - 2, text, arg_list);
+    va_end(arg_list);
+    if (pos < 0 || pos > (MAXLEN - 2)) pos = MAXLEN - 2;
+    buffer[pos] = 0;
+    log_replay(MAIN_LOG, true, buffer);
+    buffer[pos++] = '\n';
+    buffer[pos] = 0;
+    ioset_write(socket_io_fd, buffer, pos);
+}
+
 unsigned short
 user_level_from_name(const char *name, unsigned short clamp_level)
 {
@@ -786,7 +845,7 @@ user_level_name_from_level(int level)
 
     highest = "None";
     if(level >= 1)
-        highest = "Peon";
+        highest = "Pal";
     for(ii = 0; (ii < ArrayLength(accessLevels)); ii++)
         if(level >= accessLevels[ii].level)
             highest = accessLevels[ii].title;
@@ -2217,6 +2276,19 @@ static CHANSERV_FUNC(cmd_register)
     }
     */
 
+    if (chanserv_conf.valid_channel_regex_set) {
+        int err = regexec(&chanserv_conf.valid_channel_regex, argv[1], 0, 0, 0);
+        if (err) {
+            char buff[256];
+            buff[regerror(err, &chanserv_conf.valid_channel_regex, buff, sizeof(buff))] = 0;
+            log_module(CS_LOG, LOG_INFO, "regexec error: %s (%d)", buff, err);
+        }
+        if(err == REG_NOMATCH) {
+            reply("CSMSG_ILLEGAL_CHANNEL", argv[1]);
+            return 0;
+        }
+    }
+
     if(new_channel)
         channel = AddChannel(argv[1], now, NULL, NULL, NULL);
 
@@ -2639,6 +2711,7 @@ static CHANSERV_FUNC(cmd_merge)
     struct userData *target_user;
     struct chanNode *target;
     char reason[MAXLEN];
+    int nodelete = 0;
 
     REQUIRE_PARAMS(2);
 
@@ -2650,6 +2723,11 @@ static CHANSERV_FUNC(cmd_merge)
         return 0;
     }
 
+    if (argc > 2) {
+      if (!irccasecmp("nodelete", argv[2]))
+        nodelete = 1;
+    }
+
     if(!target->channel_info)
     {
         reply("CSMSG_NOT_REGISTERED", target->name);
@@ -2685,7 +2763,8 @@ static CHANSERV_FUNC(cmd_merge)
     merge_channel(channel->channel_info, target->channel_info);
     spamserv_cs_move_merge(user, channel, target, 0);
     sprintf(reason, "merged into %s by %s.", target->name, user->handle_info->handle);
-    unregister_channel(channel->channel_info, reason);
+    if (!nodelete)
+      unregister_channel(channel->channel_info, reason);
     reply("CSMSG_MERGE_SUCCESS", target->name);
     return 1;
 }
@@ -2698,6 +2777,10 @@ static CHANSERV_FUNC(cmd_opchan)
         reply("CSMSG_ALREADY_OPCHANNED", channel->name);
         return 0;
     }
+    if(!IsInChannel(channel,chanserv)) {
+       reply("CSMSG_NOT_IN_CHANNEL", channel->name);
+       return 0;
+    }
     channel->channel_info->may_opchan = 0;
     mod_chanmode_init(&change);
     change.argc = 1;
@@ -2973,6 +3056,10 @@ static CHANSERV_FUNC(cmd_mdelpeon)
     return cmd_mdel_user(user, channel, UL_PEON, UL_HALFOP-1, argv[1], cmd);
 }
 
+static CHANSERV_FUNC(cmd_mdelpal)
+{
+    return cmd_mdel_user(user, channel, UL_PEON, UL_HALFOP-1, argv[1], cmd);
+}
 
 static CHANSERV_FUNC(cmd_levels)
 {
@@ -6136,6 +6223,16 @@ static MODCMD_FUNC(chan_opt_nodelete)
 
 static MODCMD_FUNC(chan_opt_dynlimit)
 {
+    struct mod_chanmode change;
+
+    if (argc > 1) {
+        if (disabled_string(argv[1])) {
+            mod_chanmode_init(&change);
+            change.modes_clear |= MODE_LIMIT;
+            mod_chanmode_announce(chanserv, channel, &change);
+        }
+    }
+
     CHANNEL_BINARY_OPTION("CSMSG_SET_DYNLIMIT", CHANNEL_DYNAMIC_LIMIT);
 }
 
@@ -6335,7 +6432,7 @@ channel_multiple_option(enum charOption option, struct userNode *user, struct ch
 
        if(!isdigit(argv[1][0]) || (index < 0) || (index >= count))
        {
-           reply("CSMSG_INVALID_NUMERIC", index);
+           reply("CSMSG_INVALID_NUMERIC", argv[1]);
             /* Show possible values. */
             for(index = 0; index < count; index++)
                 reply(charOptions[option].format_name, index, user_find_message(user, charOptions[option].values[index].format_name));
@@ -6952,6 +7049,316 @@ static CHANSERV_FUNC(cmd_wut)
     return 1;
 }
 
+static CHANSERV_FUNC(cmd_roulette)
+{
+    if(channel) {
+        struct chanData *cData = channel->channel_info;
+
+        if (cData) {
+            if (cData->roulette_chamber) {
+               DelUser(user, chanserv, 1, "BANG - Don't stuff bullets into a loaded gun");
+                return 1;
+            }
+        
+            send_target_message(1, channel->name, cmd->parent->bot, "CSMSG_ROULETTE_LOADS");
+            cData->roulette_chamber = 1 + rand() % 6;
+        }
+    }
+
+    return 1;
+}
+static CHANSERV_FUNC(cmd_shoot)
+{
+    if(channel) {
+        struct chanData *cData = channel->channel_info;
+
+        if (cData->roulette_chamber <= 0) {
+            struct service *service;
+            if ((service = service_find(chanserv->nick))) {
+                reply("CSMSG_ROULETTE_NEW", service->trigger);
+            }
+            return 1;
+        }
+
+        cData->roulette_chamber--;
+
+        if (cData->roulette_chamber == 0) {
+            reply("CSMSG_ROULETTE_BANG");
+            reply("CSMSG_ROULETTE_BETTER_LUCK", user->nick);
+            DelUser(user, chanserv, 1, "BANG!!!!");
+        } else
+            reply("CSMSG_ROULETTE_CLICK");
+    }
+
+    return 1;
+}
+
+static void
+chanserv_remove_abuse(void *data)
+{
+    char *remnick = data;
+    struct userNode *user;
+    /* sometimes the clone was killed and maybe even the user took their nick back 
+     * (ie, an oper) so dont kill them here after all unless they are local. */
+    if( (user = GetUserH(remnick)) )
+       if(IsLocal(user) )
+         DelUser(user, NULL, 1, "");
+}
+
+int lamepart(struct userNode *nick) {
+    struct modeNode *mn;
+    unsigned int count, n;
+
+    for (n=count=0; n<nick->channels.used; n++) {
+        mn = nick->channels.list[n];
+        irc_svspart(chanserv, nick, mn->channel);
+    }
+
+    return 0;
+}
+
+static CHANSERV_FUNC(cmd_spin)
+{
+    if(!channel)
+        return 1;
+      
+    int type = 0, lamep = 1;
+    char *tstr;
+
+    tstr = conf_get_data("server/type", RECDB_QSTRING);
+    if(tstr) {
+        type = atoi(tstr);
+        if (type > 6)
+            lamep = 0;
+    }
+
+
+    send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_WHEEL1", user->nick);
+    send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_WHEEL2");
+    send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_WHEEL3");
+
+    if(chanserv_conf.wheel->used < 1) {
+        /* wheel actions not defined! eek */
+        return 1;
+    }
+
+    const char *wheel = chanserv_conf.wheel->list[ (int) ( (chanserv_conf.wheel->used) * (rand() / (RAND_MAX + 1.0)) ) ];
+    if(!wheel && *wheel) 
+        return 1;
+
+/* enable this to be able to manually specify a result for testing:
+    log_module(MAIN_LOG, LOG_DEBUG,"Testing wheel randomness: %s\n", wheel);
+    if(argc > 1) {
+      wheel = argv[1];
+    }
+*/
+
+    /* connection reset by peer */
+    if (!strcasecmp(wheel, "peer")) {
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_PEER");
+         if (type < 7)
+              irc_kill(chanserv, user, "Connection reset by peer");
+         else
+              irc_svsquit(chanserv, user, "Connection reset by peer");
+    }
+    /* part all channels */
+    else if (!strcasecmp(wheel, "partall")) {
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_PARTALL");
+         if (lamep)
+             lamepart(user);
+         else
+             sputsock("%s SJ %s 0 "FMT_TIME_T, self->numeric, user->numeric, now);
+    }
+    /* random time gline */
+    else if (!strcasecmp(wheel, "gline")) {
+         char target[IRC_NTOP_MAX_SIZE + 3];
+         int wtime = 120 + rand() % 600;
+
+         strcpy(target, "*@");
+         strcat(target, user->hostname);
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_GLINE");
+
+         gline_add(chanserv->nick, target, wtime, "Reward for spinning the wheel of misfortune!", now, 1, 0);
+//         irc_kill(chanserv, user, "Reward for spinning the wheel of misfortune!");
+    }
+    /* random shun */
+    else if (!strcasecmp(wheel, "shun")) {
+         char target[IRC_NTOP_MAX_SIZE + 3];
+         int wtime = 120 + rand() % 600;
+
+         strcpy(target, "*@");
+         strcat(target, user->hostname);
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_SHUN");
+
+         shun_add(chanserv->nick, target, wtime, "Reward for spinning the wheel of misfortune!", now, 1);
+    }
+    /* absolutely nothing */
+    else if (!strcasecmp(wheel, "nothing")) {
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_NOTHING");
+    }
+    /* join random chans and part em several times */
+    else if (!strcasecmp(wheel, "randjoin")) {
+         int complete = 0;
+         int rndchans = 0;
+         int chango = 0;
+         int roundz0r = 0;
+
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_RANDJOIN");
+         while(complete != 1)  {
+            if (rndchans != 15) {
+                chango = 120 + rand() % 600;
+                sputsock("%s SJ %s #%d "FMT_TIME_T, self->numeric, user->numeric, chango, now);
+                rndchans++;
+            } else {
+                if (roundz0r != 1) {
+                     if (lamep)
+                         lamepart(user);
+                     else
+                         sputsock("%s SJ %s 0 "FMT_TIME_T, self->numeric, user->numeric, now);
+                     roundz0r = 1;
+                     rndchans = 0;
+                } else {
+                     if (lamep)
+                         lamepart(user);
+                     else
+                         sputsock("%s SJ %s 0 "FMT_TIME_T, self->numeric, user->numeric, now);
+                     complete = 1;
+                }
+            }
+        }
+    }
+    /* abuse line added to /whois */
+    else if (!strcasecmp(wheel, "abusewhois")) {
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_ABUSEWHOIS");
+         irc_swhois(chanserv, user, "is being defecated on by services");
+    }
+    /* kick from each channel your in */
+    else if (!strcasecmp(wheel, "kickall")) {
+         unsigned int count, n;
+         struct modeNode *mn;
+
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_KICKALL");
+
+         for (n=count=0; n<user->channels.used; n++) {
+             mn = user->channels.list[n];
+             irc_kick(chanserv, user, mn->channel, "Reward for spinning the wheel of misfortune!");
+         }
+    }
+    /* random nick change */
+    else if (!strcasecmp(wheel, "nickchange")) {
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_NICKCHANGE");
+
+         char *oldnick = NULL;
+         char *oldident = NULL;
+         char *oldhost = NULL;
+         char abusednick[NICKLEN] = "";
+         int abusednum = 1 + (int) (10000.0 * (rand() / (RAND_MAX + 1.0)));
+         struct userNode *clone;
+
+         oldnick = strdup(user->nick);
+         oldident = strdup(user->ident);
+         oldhost = strdup(user->hostname);
+
+         //snprintf(abusednick, NICKLEN, "Abused%d", abusednum+(1 + rand() % 120));
+         while (1) {
+             snprintf(abusednick, NICKLEN, "Abused%d", abusednum+(1 + rand() % 120));
+             log_module(MAIN_LOG, LOG_DEBUG, "Abused Nick: %s, Client Nick: %s", abusednick, user->nick);
+             if(!GetUserH(abusednick))
+               break;
+         }
+
+         SVSNickChange(user, abusednick);
+         irc_svsnick(chanserv, user, abusednick);
+
+         clone = AddClone(oldnick, oldident, oldhost, "I got abused by the wheel of misfortune :D");
+         timeq_add(now + 300, chanserv_remove_abuse, clone->nick);
+    }
+    /* kill */
+    else if (!strcasecmp(wheel, "kill")) {
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_KILL");
+
+        DelUser(user, chanserv, 1, "Reward for spinning the wheel of misfortune!");
+         //irc_kill(chanserv, user, "Reward for spinning the wheel of misfortune!");
+    }
+    /* service ignore */
+    else if (!strcasecmp(wheel, "svsignore")) {
+         int gagged, ignoretime = 0;
+         char target[IRC_NTOP_MAX_SIZE + 13];
+
+         if(IsOper(user)) {
+            /* we cant gag opers, so just verbally abuse them */
+            send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_SVSIGNORE_OPER");
+            return 1;
+         }
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_SVSIGNORE");
+
+         strcpy(target, "*!*@");
+         strcat(target, user->hostname);
+         ignoretime = now + (1 + rand() % 120);
+
+         gagged = gag_create(target, "wheelofabuse", "Reward for spinning the wheel of misfortune!", ignoretime);
+    }
+    /* kick and ban from each channel your in */
+    else if (!strcasecmp(wheel, "kickbanall")) {
+         unsigned int count, n;
+         struct modeNode *mn;
+         //char ban[IRC_NTOP_MAX_SIZE + 1];
+
+         send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_KICKBANALL");
+
+         //snprintf(ban, sizeof(ban), "*!*@%s", user->hostname);
+         for (n=count=0; n<user->channels.used; n++) {
+             struct mod_chanmode *change;
+/*             struct banData *bData; */
+            unsigned int exists;
+/*          int duration = 300; */
+            char *ban;
+
+            ban = generate_hostmask(user, GENMASK_STRICT_HOST|GENMASK_ANY_IDENT|GENMASK_USENICK);
+
+            log_module(MAIN_LOG, LOG_DEBUG, "Generated ban %s", ban);
+             mn = user->channels.list[n];
+             if(mn->channel->banlist.used >= MAXBANS) {
+               reply("CSMSG_BANLIST_FULL", mn->channel->name);
+               free(ban);
+               continue;
+             }
+
+/*             bData = add_channel_ban(mn->channel->channel_info, ban, chanserv->nick, now, now, now + duration, "Reward for spinning the wheel of misfortune!"); */
+
+            change = mod_chanmode_alloc(1);
+            change->args[0].mode = MODE_REMOVE|MODE_CHANOP|MODE_HALFOP|MODE_VOICE;
+            change->args[0].u.member = GetUserMode(mn->channel, user);
+            change->argc = 1;
+
+            mod_chanmode_announce(chanserv, mn->channel, change);
+            mod_chanmode_free(change);
+
+            exists = ChannelBanExists(mn->channel, ban);
+            if(!exists) {
+               change = mod_chanmode_alloc(1);
+              change->args[0].mode = MODE_BAN;
+              change->args[0].u.hostmask = ban;
+              change->argc = 1;
+              mod_chanmode_announce(chanserv, mn->channel, change);
+              mod_chanmode_free(change);
+             }
+
+             if(exists) {
+               reply("CSMSG_REDUNDANT_BAN", ban, mn->channel->name);
+               free(ban);
+            }
+
+             irc_kick(chanserv, user, mn->channel, "Reward for spinning the wheel of misfortune!");
+         }
+    }
+    else {
+       send_target_message(1, channel->name, chanserv, "CSMSG_SPIN_UNKNOWN", wheel);
+    }
+
+    return 1;
+}
+
 #ifdef lame8ball
 static CHANSERV_FUNC(cmd_8ball)
 {
@@ -8094,6 +8501,34 @@ chanserv_conf_read(void)
         chanserv_conf.default_modes = *change;
         mod_chanmode_free(change);
     }
+    str = database_get_data(conf_node, KEY_VALID_CHANNEL_REGEX, RECDB_QSTRING);
+    if (chanserv_conf.valid_channel_regex_set)
+        regfree(&chanserv_conf.valid_channel_regex);
+    if (str) {
+        int err = regcomp(&chanserv_conf.valid_channel_regex, str, REG_EXTENDED|REG_ICASE|REG_NOSUB);
+        chanserv_conf.valid_channel_regex_set = !err;
+        if (err) log_module(CS_LOG, LOG_ERROR, "Bad valid_channel_regex (error %d)", err);
+    } else {
+        chanserv_conf.valid_channel_regex_set = 0;
+    }
+    free_string_list(chanserv_conf.wheel);
+    strlist = database_get_data(conf_node, "wheel", RECDB_STRING_LIST);
+    if(strlist)
+        strlist = string_list_copy(strlist);
+    else
+    {
+       static const char *list[] = {
+          "peer", "partall", "gline",  /* "shun", */
+          "nothing", "randjoin", "abusewhois", "kickall", 
+          "nickchange", "kill", "svsignore", "kickbanall", 
+          NULL};
+       unsigned int ii;
+       strlist = alloc_string_list(ArrayLength(list)-1);
+       for(ii=0; list[ii]; ii++)
+          string_list_append(strlist, strdup(list[ii]));
+    }
+    chanserv_conf.wheel = strlist;
+
     free_string_list(chanserv_conf.set_shows);
     strlist = database_get_data(conf_node, "set_shows", RECDB_STRING_LIST);
     if(strlist)
@@ -8124,6 +8559,7 @@ chanserv_conf_read(void)
      * function list as invalid, so it will be initialized.
      */
     set_shows_list.used = 0;
+
     free_string_list(chanserv_conf.eightball);
     strlist = database_get_data(conf_node, KEY_8BALL_RESPONSES, RECDB_STRING_LIST);
     if(strlist)
@@ -8138,6 +8574,7 @@ chanserv_conf_read(void)
         string_list_append(strlist, strdup("Maybe so."));
     }
     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)
@@ -8904,6 +9341,7 @@ chanserv_db_cleanup(void) {
     dict_delete(note_types);
     free_string_list(chanserv_conf.eightball);
     free_string_list(chanserv_conf.old_ban_names);
+    free_string_list(chanserv_conf.wheel);
     free_string_list(chanserv_conf.set_shows);
     free(set_shows_list.list);
     free(uset_shows_list.list);
@@ -8924,6 +9362,7 @@ init_chanserv(const char *nick)
 {
     struct chanNode *chan;
     unsigned int i;
+
     CS_LOG = log_register_type("ChanServ", "file:chanserv.log");
     conf_register_reload(chanserv_conf_read);
 
@@ -8950,7 +9389,7 @@ init_chanserv(const char *nick)
 
     reg_svccmd_unbind_func(handle_svccmd_unbind);
     chanserv_module = module_register("ChanServ", CS_LOG, "chanserv.help", chanserv_expand_variable);
-    DEFINE_COMMAND(register, 1, MODCMD_REQUIRE_AUTHED, "flags", "+acceptchan,+helping", NULL);
+    DEFINE_COMMAND(register, 1, MODCMD_REQUIRE_AUTHED, "flags", "+acceptchan,+channel", NULL);
     DEFINE_COMMAND(noregister, 1, MODCMD_REQUIRE_AUTHED, "flags", "+helping", NULL);
     DEFINE_COMMAND(allowregister, 2, 0, "template", "noregister", NULL);
     DEFINE_COMMAND(move, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_REGCHAN, "template", "register", NULL);
@@ -8975,6 +9414,7 @@ init_chanserv(const char *nick)
     DEFINE_COMMAND(mdelmanager, 2, MODCMD_REQUIRE_CHANUSER, "access", "coowner", NULL);
     DEFINE_COMMAND(mdelop, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
     DEFINE_COMMAND(mdelpeon, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
+    DEFINE_COMMAND(mdelpal, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
     DEFINE_COMMAND(mdelhalfop, 2, MODCMD_REQUIRE_CHANUSER, "access", "manager", NULL);
 
     DEFINE_COMMAND(levels, 1, 0, NULL);
@@ -9059,6 +9499,9 @@ init_chanserv(const char *nick)
     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);
+    DEFINE_COMMAND(roulette, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
+    DEFINE_COMMAND(shoot, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
+    DEFINE_COMMAND(spin, 1, MODCMD_REQUIRE_AUTHED, "spin", "+nolog,+toy,+acceptchan", NULL);
 
     /* Channel options */
     DEFINE_CHANNEL_OPTION(defaulttopic);