]> jfr.im git - irc/evilnet/x3.git/blobdiff - src/chanserv.c
fix for 1551717- automode'ing when authing in a channel
[irc/evilnet/x3.git] / src / chanserv.c
index 2270c1a389891ee673af9ccb8f094b5639b55a83..eedb7bcffe56192be88124a6013c336ad7745bb2 100644 (file)
@@ -3,7 +3,7 @@
  *
  * This file is part of x3.
  *
- * srvx is free software; you can redistribute it and/or modify
+ * x3 is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version.
@@ -24,6 +24,7 @@
 #include "modcmd.h"
 #include "opserv.h" /* for opserv_bad_channel() */
 #include "saxdb.h"
+#include "spamserv.h"
 #include "timeq.h"
 
 #define CHANSERV_CONF_NAME     "services/chanserv"
 #define KEY_EXPIRES             "expires"
 #define KEY_TRIGGERED          "triggered"
 
+#define KEY_GOD_TIMEOUT         "god_timeout"
+
 #define CHANNEL_DEFAULT_FLAGS   (CHANNEL_OFFCHANNEL)
 #define CHANNEL_DEFAULT_OPTIONS "lmoooanpcnat"
 
@@ -215,6 +218,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_NO_SELF_CLVL", "You cannot change your own access." },
     { "CSMSG_NO_BUMP_ACCESS", "You cannot give users access greater than or equal to your own." },
     { "CSMSG_MULTIPLE_OWNERS", "There is more than one owner in %s; please use $bCLVL$b, $bDELOWNER$b and/or $bADDOWNER$b instead." },
+    { "CSMSG_NO_OWNER", "There is no owner for %s; please use $bCLVL$b and/or $bADDOWNER$b instead." },
     { "CSMSG_TRANSFER_WAIT", "You must wait %s before you can give ownership of $b%s$b to someone else." },
     { "CSMSG_NO_TRANSFER_SELF", "You cannot give ownership to your own account." },
     { "CSMSG_OWNERSHIP_GIVEN", "Ownership of $b%s$b has been transferred to account $b%s$b." },
@@ -287,11 +291,13 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_SET_TOYS",          "$bToys        $b %d - %s" },
     { "CSMSG_SET_CTCPREACTION",  "$bCTCPReaction$b %d - %s" },
     { "CSMSG_SET_TOPICREFRESH",  "$bTopicRefresh$b %d - %s" },
+    { "CSMSG_SET_RESYNC",        "$bResync      $b %d - %s" },
     { "CSMSG_SET_BANTIMEOUT",    "$bBanTimeout  $b %d - %s" },
 
     { "CSMSG_USET_AUTOOP",       "$bAutoOp      $b %s" },
     { "CSMSG_USET_AUTOVOICE",    "$bAutoVoice   $b %s" },
     { "CSMSG_USET_AUTOINVITE",   "$bAutoInvite  $b %s" },
+    { "CSMSG_USET_AUTOJOIN",     "$bAutoJoin    $b %s" },
     { "CSMSG_USET_INFO",         "$bInfo        $b %s" },
 
     { "CSMSG_USER_PROTECTED", "Sorry, $b%s$b is protected." },
@@ -312,6 +318,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_AUTOMODE_HOP", "#1 plus give halfops to everyone." },
     { "CSMSG_AUTOMODE_OP", "#1 plus give ops to everyone (not advised)" },
     { "CSMSG_AUTOMODE_MUTE", "Give half-op to halfops, and op to ops only." },
+    { "CSMSG_AUTOMODE_ONLYVOICE", "Just voice everyone with access." },
 
     { "CSMSG_PROTECT_ALL", "Non-users and users will be protected from those of equal or lower access." },
     { "CSMSG_PROTECT_EQUAL", "Users will be protected from those of equal or lower access." },
@@ -327,6 +334,12 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_TOPICREFRESH_12_HOURS", "Refresh every 12 hours." },
     { "CSMSG_TOPICREFRESH_24_HOURS", "Refresh every 24 hours." },
 
+    { "CSMSG_RESYNC_NEVER", "Never automaticly resync userlist." },
+    { "CSMSG_RESYNC_3_HOURS", "Resync userlist every 3 hours." },
+    { "CSMSG_RESYNC_6_HOURS", "Resync userlist every 6 hours." },
+    { "CSMSG_RESYNC_12_HOURS", "Resync userlist every 12 hours." },
+    { "CSMSG_RESYNC_24_HOURS", "Resync userlist every 24 hours." },
+
     { "CSMSG_CTCPREACTION_NONE", "CTCPs are allowed" },
     { "CSMSG_CTCPREACTION_KICK", "Kick on disallowed CTCPs" },
     { "CSMSG_CTCPREACTION_KICKBAN", "Kickban on disallowed CTCPs" },
@@ -343,6 +356,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_INVITED_USER", "Invited $b%s$b to join %s." },
     { "CSMSG_INVITING_YOU_REASON", "$b%s$b invites you to join %s: %s" },
     { "CSMSG_INVITING_YOU", "$b%s$b invites you to join %s." },
+    { "CSMSG_CANNOT_INVITE", "You cannot invite %s to %s." },
     { "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." },
@@ -361,6 +375,8 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_ACCESS_SEARCH_HEADER_CLEAN",    "$b%s Users From Level %s To %s Matching %s$b" },
     { "CSMSG_ACCESS_ALL_HEADER_ADVANCED",    "$b%s Users From Level %s To %s$b" },
     { "CSMSG_ACCESS_SEARCH_HEADER_ADVANCED", "$b%s Users From Level %s To %s Matching %s$b" },
+    { "CSMSG_ACCESS_ALL_HEADER_CLASSIC",     "$b%s Users From Level %s To %s$b" },
+    { "CSMSG_ACCESS_SEARCH_HEADER_CLASSIC",  "$b%s Users From Level %s To %s Matching %s$b" },
     */
     { "CSMSG_INVALID_ACCESS", "$b%s$b is an invalid access level." },
     { "CSMSG_CHANGED_ACCESS", "%s now has access $b%s$b (%u) in %s." },
@@ -500,6 +516,8 @@ static const struct message_entry msgtab[] = {
 /* Other things */
     { "CSMSG_EVENT_SEARCH_RESULTS", "$bChannel Events for %s$b" },
     { "CSMSG_LAST_INVALID", "Invalid argument.  must be 1-200" },
+    { "CSMSG_DEFCON_NO_NEW_CHANNELS", "You cannot register new channels at this time, please try again soon." },
+    { "CSMSG_DEFCON_NO_MODE_CHANGE", "You cannot change the MODE at this time, please try again soon." },
     { NULL, NULL }
 };
 
@@ -533,10 +551,12 @@ static int eject_user(struct userNode *user, struct chanNode *channel, unsigned
 struct userNode *chanserv;
 dict_t note_types;
 int off_channel;
+extern struct string_list *autojoin_channels;
 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;
+unsigned long god_timeout;
 
 static struct
 {
@@ -669,7 +689,8 @@ struct charOptionValues {
     { 'v', "CSMSG_AUTOMODE_VOICE" },
     { 'h', "CSMSG_AUTOMODE_HOP" },
     { 'o', "CSMSG_AUTOMODE_OP" },
-    { 'm', "CSMSG_AUTOMODE_MUTE" }
+    { 'm', "CSMSG_AUTOMODE_MUTE" },
+    { 'l', "CSMSG_AUTOMODE_ONLYVOICE" }
 }, protectValues[] = {
     { 'a', "CSMSG_PROTECT_ALL" },
     { 'e', "CSMSG_PROTECT_EQUAL" },
@@ -698,6 +719,13 @@ struct charOptionValues {
     { '3', "CSMSG_BANTIMEOUT_4H" },
     { '4', "CSMSG_BANTIMEOUT_1D" },
     { '5', "CSMSG_BANTIMEOUT_1W" }
+},
+resyncValues[] = {
+    { 'n', "CSMSG_RESYNC_NEVER" },
+    { '1', "CSMSG_RESYNC_3_HOURS" },
+    { '2', "CSMSG_RESYNC_6_HOURS" },
+    { '3', "CSMSG_RESYNC_12_HOURS" },
+    { '4', "CSMSG_RESYNC_24_HOURS" }
 };
 
 static const struct {
@@ -713,7 +741,8 @@ static const struct {
     { "CSMSG_SET_TOYS",         "toys",         'p', 6, ArrayLength(toysValues), toysValues },
     { "CSMSG_SET_TOPICREFRESH", "topicrefresh", 'n', 8, ArrayLength(topicRefreshValues), topicRefreshValues },
     { "CSMSG_SET_CTCPREACTION", "ctcpreaction", 'n', 10, ArrayLength(ctcpReactionValues), ctcpReactionValues },
-    { "CSMSG_SET_BANTIMEOUT",   "bantimeout", '0', 11, ArrayLength(banTimeoutValues), banTimeoutValues }
+    { "CSMSG_SET_BANTIMEOUT",   "bantimeout",   '0', 11, ArrayLength(banTimeoutValues), banTimeoutValues },
+    { "CSMSG_SET_RESYNC",       "resync",       'n', 12, ArrayLength(resyncValues), resyncValues },
 };
 
 struct userData *helperList;
@@ -724,7 +753,6 @@ 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)
 #define GetTrueChannelAccess(channel, handle) _GetChannelUser(channel, handle, 0, 1)
 
@@ -1269,8 +1297,10 @@ del_channel_user(struct userData *user, int do_gc)
 
     free(user->info);
     free(user);
-    if(do_gc && !channel->users && !IsProtected(channel))
+    if(do_gc && !channel->users && !IsProtected(channel)) {
+        spamserv_cs_unregister(NULL, channel->channel, lost_all_users, NULL);
         unregister_channel(channel, "lost all users.");
+    }
 }
 
 static struct adduserPending* 
@@ -1388,7 +1418,7 @@ expire_adduser_pending()
 
 static void expire_ban(void *data);
 
-static struct banData*
+struct banData*
 add_channel_ban(struct chanData *channel, const char *mask, char *owner, time_t set, time_t triggered, time_t expires, char *reason)
 {
     struct banData *bd;
@@ -1599,6 +1629,7 @@ expire_channels(UNUSED_ARG(void *data))
 
         /* Unregister the channel */
         log_module(CS_LOG, LOG_INFO, "(%s) Channel registration expired.", channel->channel->name);
+        spamserv_cs_unregister(NULL, channel->channel, expire, NULL);
         unregister_channel(channel, "registration expired.");
     }
 
@@ -1651,7 +1682,7 @@ protect_user(const struct userNode *victim, const struct userNode *aggressor, st
 }
 
 static int
-validate_op(struct userNode *user, struct chanNode *channel, struct userNode *victim)
+validate_op(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, struct userNode *victim)
 {
     struct chanData *cData = channel->channel_info;
     struct userData *cs_victim;
@@ -1660,7 +1691,10 @@ validate_op(struct userNode *user, struct chanNode *channel, struct userNode *vi
         || (cs_victim->access < UL_OP /* cData->lvlOpts[lvlGiveOps]*/))
        && !check_user_level(channel, user, lvlEnfOps, 0, 0))
     {
-       send_message(user, chanserv, "CSMSG_OPBY_LOCKED");
+        if(cmd)
+           reply("CSMSG_OPBY_LOCKED");
+        else
+            send_message(user, chanserv, "CSMSG_OPBY_LOCKED");
        return 0;
     }
 
@@ -1668,7 +1702,7 @@ validate_op(struct userNode *user, struct chanNode *channel, struct userNode *vi
 }
 
 static int
-validate_halfop(struct userNode *user, struct chanNode *channel, struct userNode *victim)
+validate_halfop(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, struct userNode *victim)
 {
     struct chanData *cData = channel->channel_info;
     struct userData *cs_victim;
@@ -1677,7 +1711,7 @@ validate_halfop(struct userNode *user, struct chanNode *channel, struct userNode
         || (cs_victim->access < UL_HALFOP /* cData->lvlOpts[lvlGiveHalfOps] */))
        && !check_user_level(channel, user, lvlEnfHalfOps, 0, 0))
     {
-        send_message(user, chanserv, "CSMSG_HOPBY_LOCKED");
+        reply("CSMSG_HOPBY_LOCKED");
         return 0;
     }
 
@@ -1686,17 +1720,17 @@ validate_halfop(struct userNode *user, struct chanNode *channel, struct userNode
 
 
 static int
-validate_deop(struct userNode *user, struct chanNode *channel, struct userNode *victim)
+validate_deop(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, struct userNode *victim)
 {
     if(IsService(victim))
     {
-       send_message(user, chanserv, "MSG_SERVICE_IMMUNE", victim->nick);
+       reply("MSG_SERVICE_IMMUNE", victim->nick);
        return 0;
     }
 
     if(protect_user(victim, user, channel->channel_info))
     {
-       send_message(user, chanserv, "CSMSG_USER_PROTECTED", victim->nick);
+       reply("CSMSG_USER_PROTECTED", victim->nick);
        return 0;
     }
 
@@ -1704,17 +1738,17 @@ validate_deop(struct userNode *user, struct chanNode *channel, struct userNode *
 }
 
 static int
-validate_dehop(struct userNode *user, struct chanNode *channel, struct userNode *victim)
+validate_dehop(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, struct userNode *victim)
 {
     if(IsService(victim))
     {
-        send_message(user, chanserv, "MSG_SERVICE_IMMUNE", victim->nick);
+        reply("MSG_SERVICE_IMMUNE", victim->nick);
         return 0;
     }
 
     if(protect_user(victim, user, channel->channel_info))
     {
-        send_message(user, chanserv, "CSMSG_USER_PROTECTED", victim->nick);
+        reply("CSMSG_USER_PROTECTED", victim->nick);
         return 0;
     }
 
@@ -1935,6 +1969,10 @@ static CHANSERV_FUNC(cmd_register)
     struct do_not_register *dnr;
     unsigned int n;
 
+    if (checkDefCon(DEFCON_NO_NEW_CHANNELS) && !IsOper(user)) {
+        reply("CSMSG_DEFCON_NO_NEW_CHANNELS");
+        return 0;
+    }
 
     if(channel)
     {
@@ -2071,10 +2109,10 @@ static CHANSERV_FUNC(cmd_register)
     if(handle != user->handle_info)
         reply("CSMSG_PROXY_SUCCESS", handle->handle, channel->name);
     else
-        reply("CSMSG_REG_SUCCESS", channel->name);
 
     sprintf(reason, "%s registered to %s by %s.", channel->name, handle->handle, user->handle_info->handle);
-    global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+    global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_REGISTERED_TO", channel->name, 
+                        handle->handle, user->handle_info->handle);
     return 1;
 }
 
@@ -2141,19 +2179,47 @@ static CHANSERV_FUNC(cmd_unregister)
     sprintf(reason, "unregistered by %s.", user->handle_info->handle);
     name = strdup(channel->name);
     unregister_channel(cData, reason);
+    spamserv_cs_unregister(user, channel, manually, "unregistered");
     reply("CSMSG_UNREG_SUCCESS", name);
     free(name);
     return 1;
 }
 
+static void
+ss_cs_join_channel(struct chanNode *channel, int spamserv_join)
+{
+    extern struct userNode *spamserv;
+    struct mod_chanmode *change;
+
+    if(spamserv && spamserv_join && get_chanInfo(channel->name))
+    {
+        change = mod_chanmode_alloc(2);
+        change->argc = 2;
+        change->args[0].mode = MODE_CHANOP;
+        change->args[0].u.member = AddChannelUser(chanserv, channel);
+        change->args[1].mode = MODE_CHANOP;
+        change->args[1].u.member = AddChannelUser(spamserv, channel);
+    }
+    else
+    {
+        change = mod_chanmode_alloc(1);
+        change->argc = 1;
+        change->args[0].mode = MODE_CHANOP;
+        change->args[0].u.member = AddChannelUser(chanserv, channel);
+    }
+
+    mod_chanmode_announce(chanserv, channel, change);
+       mod_chanmode_free(change);
+}
+
 static CHANSERV_FUNC(cmd_move)
 {
     struct mod_chanmode change;
     struct chanNode *target;
     struct modeNode *mn;
     struct userData *uData;
-    char reason[MAXLEN];
     struct do_not_register *dnr;
+    int chanserv_join = 0, spamserv_join;
 
     REQUIRE_PARAMS(2);
 
@@ -2195,7 +2261,7 @@ static CHANSERV_FUNC(cmd_move)
     {
         target = AddChannel(argv[1], now, NULL, NULL, NULL);
         if(!IsSuspended(channel->channel_info))
-            AddChannelUser(chanserv, target);
+            chanserv_join = 1;
     }
     else if(target->channel_info)
     {
@@ -2209,12 +2275,7 @@ static CHANSERV_FUNC(cmd_move)
         return 0;
     }
     else if(!IsSuspended(channel->channel_info))
-    {
-        change.argc = 1;
-        change.args[0].mode = MODE_CHANOP;
-        change.args[0].u.member = AddChannelUser(chanserv, target);
-        mod_chanmode_announce(chanserv, target, &change);
-    }
+        chanserv_join = 1;
 
     if(off_channel > 0)
     {
@@ -2234,18 +2295,24 @@ static CHANSERV_FUNC(cmd_move)
     target->channel_info->channel = target;
     channel->channel_info = NULL;
 
-    reply("CSMSG_MOVE_SUCCESS", target->name);
+    spamserv_join = spamserv_cs_move_merge(user, channel, target, 1);
+
+    if (chanserv_join)
+        ss_cs_join_channel(target, spamserv_join);
 
-    sprintf(reason, "%s moved to %s by %s.", channel->name, target->name, user->handle_info->handle);
     if(!IsSuspended(target->channel_info))
     {
         char reason2[MAXLEN];
-       sprintf(reason2, "Channel moved to %s by %s.", target->name, user->handle_info->handle);
-       DelChannelUser(chanserv, channel, reason2, 0);
+        sprintf(reason2, "Channel moved to %s by %s.", target->name, user->handle_info->handle);
+        DelChannelUser(chanserv, channel, reason2, 0);
     }
+
     UnlockChannel(channel);
     LockChannel(target);
-    global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+    global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_CHANNEL_MOVED",
+                        channel->name, target->name, user->handle_info->handle);
+
+    reply("CSMSG_MOVE_SUCCESS", target->name);
     return 1;
 }
 
@@ -2479,6 +2546,7 @@ static CHANSERV_FUNC(cmd_merge)
 
     /* Merge the channel structures and associated data. */
     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);
     reply("CSMSG_MERGE_SUCCESS", target->name);
@@ -2741,7 +2809,7 @@ static CHANSERV_FUNC(cmd_mdelhalfop)
 
 /* trim_lamers.. */
 static int
-cmd_trim_bans(struct userNode *user, struct chanNode *channel, unsigned long duration)
+cmd_trim_bans(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, unsigned long duration)
 {
     struct banData *bData, *next;
     char interval[INTERVALLEN];
@@ -2762,12 +2830,12 @@ cmd_trim_bans(struct userNode *user, struct chanNode *channel, unsigned long dur
     }
 
     intervalString(interval, duration, user->handle_info);
-    send_message(user, chanserv, "CSMSG_TRIMMED_LAMERS", count, channel->name, interval);
+    reply("CSMSG_TRIMMED_LAMERS", count, channel->name, interval);
     return 1;
 }
 
 static int
-cmd_trim_users(struct userNode *user, struct chanNode *channel, unsigned short min_access, unsigned short max_access, unsigned long duration)
+cmd_trim_users(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, unsigned short min_access, unsigned short max_access, unsigned long duration, int vacation)
 {
     struct userData *actor, *uData, *next;
     char interval[INTERVALLEN];
@@ -2777,13 +2845,13 @@ cmd_trim_users(struct userNode *user, struct chanNode *channel, unsigned short m
     actor = GetChannelUser(channel->channel_info, user->handle_info);
     if(min_access > max_access)
     {
-        send_message(user, chanserv, "CSMSG_BAD_RANGE", min_access, max_access);
+        reply("CSMSG_BAD_RANGE", min_access, max_access);
         return 0;
     }
 
     if((actor->access <= max_access) && !IsHelping(user))
     {
-       send_message(user, chanserv, "CSMSG_NO_ACCESS");
+       reply("CSMSG_NO_ACCESS");
        return 0;
     }
 
@@ -2793,7 +2861,9 @@ cmd_trim_users(struct userNode *user, struct chanNode *channel, unsigned short m
     {
        next = uData->next;
 
-       if((uData->seen > limit) || uData->present)
+       if((uData->seen > limit)
+           || uData->present
+           || (HANDLE_FLAGGED(uData->handle, FROZEN) && !vacation))
            continue;
 
        if(((uData->access >= min_access) && (uData->access <= max_access))
@@ -2809,7 +2879,7 @@ cmd_trim_users(struct userNode *user, struct chanNode *channel, unsigned short m
         min_access = 1;
         max_access = (actor->access > UL_OWNER) ? UL_OWNER : (actor->access - 1);
     }
-    send_message(user, chanserv, "CSMSG_TRIMMED_USERS", count, min_access, max_access, channel->name, intervalString(interval, duration, user->handle_info));
+    reply("CSMSG_TRIMMED_USERS", count, min_access, max_access, channel->name, intervalString(interval, duration, user->handle_info));
     return 1;
 }
 
@@ -2817,9 +2887,11 @@ static CHANSERV_FUNC(cmd_trim)
 {
     unsigned long duration;
     unsigned short min_level, max_level;
+    int vacation;
 
     REQUIRE_PARAMS(3);
 
+    vacation = argc > 3 && !strcmp(argv[3], "vacation");
     duration = ParseInterval(argv[2]);
     if(duration < 60)
     {
@@ -2829,22 +2901,22 @@ static CHANSERV_FUNC(cmd_trim)
 
     if(!irccasecmp(argv[1], "lamers"))
     {
-       cmd_trim_bans(user, channel, duration); /* trim_lamers.. */
+       cmd_trim_bans(cmd, user, channel, duration); /* trim_lamers.. */
        return 1;
     }
     else if(!irccasecmp(argv[1], "users"))
     {
-       cmd_trim_users(user, channel, 0, 0, duration);
+       cmd_trim_users(cmd, user, channel, 0, 0, duration, vacation);
        return 1;
     }
     else if(parse_level_range(&min_level, &max_level, argv[1]))
     {
-       cmd_trim_users(user, channel, min_level, max_level, duration);
+       cmd_trim_users(cmd, user, channel, min_level, max_level, duration, vacation);
        return 1;
     }
     else if((min_level = user_level_from_name(argv[1], UL_OWNER)))
     {
-       cmd_trim_users(user, channel, min_level, min_level, duration);
+       cmd_trim_users(cmd, user, channel, min_level, min_level, duration, vacation);
        return 1;
     }
     else
@@ -2964,7 +3036,7 @@ static CHANSERV_FUNC(cmd_downall)
     return cmd_all(CSFUNC_ARGS, cmd_down);
 }
 
-typedef int validate_func_t(struct userNode *user, struct chanNode *channel, struct userNode *victim);
+typedef int validate_func_t(struct svccmd *cmd, struct userNode *user, struct chanNode *channel, struct userNode *victim);
 typedef void process_func_t(unsigned int num, struct userNode **newops, struct chanNode *channel, struct userNode *who, int announce);
 
 static int
@@ -2984,7 +3056,7 @@ modify_users(struct userNode *user, struct chanNode *channel, unsigned int argc,
         change->args[valid].u.member = GetUserMode(channel, victim);
         if(!change->args[valid].u.member)
             continue;
-        if(validate && !validate(user, channel, victim))
+        if(validate && !validate(cmd, user, channel, victim))
            continue;
         valid++;
     }
@@ -3045,7 +3117,7 @@ bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban
         if(IsService(mn->user))
             continue;
 
-        if(!user_matches_glob(mn->user, ban, 1))
+        if(!user_matches_glob(mn->user, ban, MATCH_USENICK | MATCH_VISIBLE))
             continue;
 
         if(protect_user(mn->user, user, channel->channel_info))
@@ -3405,7 +3477,8 @@ find_matching_bans(struct banList *bans, struct userNode *actee, const char *mas
     {
         for(ii = count = 0; ii < bans->used; ++ii)
         {
-            match[ii] = user_matches_glob(actee, bans->list[ii]->ban, 1);
+            match[ii] = user_matches_glob(actee, bans->list[ii]->ban,
+                                          MATCH_USENICK | MATCH_VISIBLE);
             if(match[ii])
                 count++;
         }
@@ -3542,7 +3615,7 @@ unban_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
        while(ban)
        {
            if(actee)
-               for( ; ban && !user_matches_glob(actee, ban->mask, 1);
+               for( ; ban && !user_matches_glob(actee, ban->mask, MATCH_USENICK | MATCH_VISIBLE);
                     ban = ban->next);
            else
                for( ; ban && !match_ircglobs(mask, ban->mask);
@@ -3685,6 +3758,8 @@ static CHANSERV_FUNC(cmd_myaccess)
         }
         if(IsUserAutoInvite(uData) && (uData->access >= cData->lvlOpts[lvlInviteMe]))
             string_buffer_append(&sbuf, 'i');
+        if(IsUserAutoJoin(uData) && (uData->access >= cData->lvlOpts[lvlInviteMe]))
+            string_buffer_append(&sbuf, 'j');
         if(uData->info)
             string_buffer_append_printf(&sbuf, ")] %s", uData->info);
         else
@@ -3922,6 +3997,22 @@ advanced_list(struct listData *list)
         table_send(list->bot, list->user->nick, 0, NULL, list->table);
 } 
 
+static void
+classic_list(struct listData *list)
+{
+    const char *msg;
+    if(list->search)
+        send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER_CLASSIC", list->channel->name, list->lowest, list->highest, list->search);
+    else
+        send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER_CLASSIC", list->channel->name, list->lowest, list->highest);
+    if(list->table.length == 1)
+    {
+        msg = user_find_message(list->user, "MSG_NONE");
+        send_message_type(4, list->user, list->bot, "  %s", msg);
+    }
+    else
+        table_send(list->bot, list->user->nick, 0, NULL, list->table);
+}
 */
 
 static int
@@ -3969,6 +4060,9 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
             case HI_STYLE_ADVANCED: 
                 send_list = advanced_list; 
                 break;
+            case HI_STYLE_CLASSIC: 
+                send_list = classic_list; 
+                break;
             case HI_STYLE_NORMAL: 
             default: 
                 send_list = normal_list; 
@@ -3994,15 +4088,33 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
     lData.table.flags = TABLE_NO_FREE;
     lData.table.contents = malloc(lData.table.length*sizeof(*lData.table.contents));
 
-    if(user->handle_info->userlist_style == HI_STYLE_ADVANCED)
+    if(user->handle_info && user->handle_info->userlist_style == HI_STYLE_ADVANCED)
         lData.table.width = 5; /* with level = 5 */
     else
         lData.table.width = 4; /* without = 4 */
     ary = malloc(lData.table.width*sizeof(**lData.table.contents));
     lData.table.contents[0] = ary;
-    ary[i++] = "Access";
-    if(user->handle_info->userlist_style == HI_STYLE_ADVANCED)
-        ary[i++] = "Level"; /* Only on advanced view */
+    if(user->handle_info) {
+       switch(user->handle_info->userlist_style) {
+          case HI_STYLE_CLASSIC:
+            ary[i++] = "Level";
+       break;
+          case HI_STYLE_ADVANCED:
+            ary[i++] = "Access";
+            ary[i++] = "Level";
+       break;
+          case HI_STYLE_CLEAN:
+            ary[i++] = "Access";
+       break;
+          case HI_STYLE_NORMAL:
+            default:
+            ary[i++] = "Access";
+       break;
+        }
+    }
+    else {
+      ary[i++] = "Access";
+    }  
     ary[i++] = "Account";
     ary[i] = "Last Seen";
     seen_index = i++;
@@ -4015,9 +4127,27 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
         i = 0;
         ary = malloc(lData.table.width*sizeof(**lData.table.contents));
         lData.table.contents[matches] = ary;
-        ary[i++] = user_level_name_from_level(uData->access);
-        if(user->handle_info->userlist_style == HI_STYLE_ADVANCED)
-            ary[i++] = strtab(uData->access);
+        if(user->handle_info) {
+           switch(user->handle_info->userlist_style) {
+              case HI_STYLE_CLASSIC:
+                ary[i++] = strtab(uData->access);
+           break;
+              case HI_STYLE_ADVANCED:
+                ary[i++] = user_level_name_from_level(uData->access);
+                ary[i++] = strtab(uData->access);
+           break;
+              case HI_STYLE_CLEAN:
+                ary[i++] = user_level_name_from_level(uData->access);
+           break;
+              case HI_STYLE_NORMAL:
+                default:
+                ary[i++] = user_level_name_from_level(uData->access);
+           break;
+            }
+        }
+     else {
+           ary[i++] = user_level_name_from_level(uData->access);
+        }
         ary[i++] = uData->handle->handle;
         if(uData->present)
             ary[i] = "Here";
@@ -4269,6 +4399,7 @@ static CHANSERV_FUNC(cmd_topic)
     {
         if(cData->topic)
         {
+            /*XXX Why would we ever want to send chanserv as the setter? I dont understand  -Rubin */
             SetChannelTopic(channel, chanserv, p10 ? user : chanserv, cData->topic, 1);
             reply("CSMSG_TOPIC_SET", cData->topic);
             return 1;
@@ -4323,10 +4454,17 @@ static CHANSERV_FUNC(cmd_topic)
 
 static CHANSERV_FUNC(cmd_mode)
 {
+    struct userData *uData;
     struct mod_chanmode *change;
+    short base_oplevel;
     
     if(argc < 2)
     {
+        if (checkDefCon(DEFCON_NO_MODE_CHANGE) && !IsOper(user)) {
+            reply("CSMSG_DEFCON_NO_MODE_CHANGE");
+            return 0;
+        }
+
         change = &channel->channel_info->modes;
        if(change->modes_set || change->modes_clear) {
             modcmd_chanmode_announce(change);
@@ -4336,7 +4474,15 @@ static CHANSERV_FUNC(cmd_mode)
        return 1;
     }
 
-    change = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED);
+    uData = GetChannelUser(channel->channel_info, user->handle_info);
+    if (!uData)
+        base_oplevel = MAXOPLEVEL;
+    else if (uData->access >= UL_OWNER)
+        base_oplevel = 1;
+    else
+        base_oplevel = 1 + UL_OWNER - uData->access;
+    change = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED, base_oplevel);
+
     if(!change)
     {
        reply("MSG_INVALID_MODES", unsplit_string(argv+1, argc-1, NULL));
@@ -4392,6 +4538,17 @@ static CHANSERV_FUNC(cmd_invite)
         else
             send_message(invite, chanserv, "CSMSG_INVITING_YOU", user->nick, channel->name);
     }
+
+    if (invite->handle_info && invite->handle_info->ignores->used && (argc > 1)) {
+        unsigned int i;
+        for (i=0; i < invite->handle_info->ignores->used; i++) {
+            if (user_matches_glob(user, invite->handle_info->ignores->list[i], MATCH_USENICK)) {
+              reply("CSMSG_CANNOT_INVITE", argv[1], channel->name);
+              return 0;
+            }
+        }
+    }
+
     irc_invite(chanserv, invite, channel);
     if(argc > 1)
        reply("CSMSG_INVITED_USER", argv[1], channel->name);
@@ -4540,9 +4697,9 @@ static CHANSERV_FUNC(cmd_info)
     reply("CSMSG_CHANNEL_VISITED", intervalString(buffer, now - cData->visited, user->handle_info));
 
     privileged = IsStaff(user);
-    if(privileged)
+    /* if(privileged) */
         reply("CSMSG_CHANNEL_REGISTERED", intervalString(buffer, now - cData->registered, user->handle_info));
-    if(((uData && uData->access >= UL_COOWNER) || privileged) && cData->registrar)
+    if(/*((uData && uData->access >= UL_COOWNER) || privileged) && */cData->registrar)
         reply("CSMSG_CHANNEL_REGISTRAR", cData->registrar);
 
     if(privileged && (dnr = chanserv_is_dnr(channel->name, NULL)))
@@ -4713,7 +4870,8 @@ static MODCMD_FUNC(cmd_wipeinfo)
     return 1;
 }
 
-static CHANSERV_FUNC(cmd_resync)
+static void
+resync_channel(struct chanNode *channel)
 {
     struct mod_chanmode *changes;
     struct chanData *cData = channel->channel_info;
@@ -4855,8 +5013,13 @@ static CHANSERV_FUNC(cmd_resync)
         }
     }
     changes->argc = used;
-    modcmd_chanmode_announce(changes);
+    mod_chanmode_announce(chanserv, channel, changes);
     mod_chanmode_free(changes);
+}
+
+static CHANSERV_FUNC(cmd_resync)
+{
+    resync_channel(channel);
     reply("CSMSG_RESYNCED_USERS", channel->name);
     return 1;
 }
@@ -5186,12 +5349,8 @@ chanserv_expire_suspension(void *data)
     suspended->cData->flags &= ~CHANNEL_SUSPENDED;
     if(!IsOffChannel(suspended->cData))
     {
-        struct mod_chanmode change;
-        mod_chanmode_init(&change);
-        change.argc = 1;
-        change.args[0].mode = MODE_CHANOP;
-        change.args[0].u.member = AddChannelUser(chanserv, channel);
-        mod_chanmode_announce(chanserv, channel, &change);
+        spamserv_cs_suspend(channel, 0, 0, NULL);
+        ss_cs_join_channel(channel, 1);
     }
 }
 
@@ -5249,8 +5408,9 @@ static CHANSERV_FUNC(cmd_csuspend)
         suspended->previous->revoked = now;
         if(suspended->previous->expires)
             timeq_del(suspended->previous->expires, chanserv_expire_suspension, suspended->previous, 0);
-        sprintf(reason, "%s suspension modified by %s.", channel->name, suspended->suspender);
-        global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+
+        global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_SUSPENSION_MODIFIED",
+                            channel->name, suspended->suspender);
     }
     else
     {
@@ -5266,10 +5426,11 @@ static CHANSERV_FUNC(cmd_csuspend)
 
         /* Mark the channel as suspended, then part. */
         channel->channel_info->flags |= CHANNEL_SUSPENDED;
+        spamserv_cs_suspend(channel, expiry, 1, suspended->reason);
         DelChannelUser(chanserv, channel, suspended->reason, 0);
         reply("CSMSG_SUSPENDED", channel->name);
-        sprintf(reason, "%s suspended by %s.", channel->name, suspended->suspender);
-        global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+        global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_SUSPENDED_BY",
+                            channel->name, suspended->suspender);
     }
     return 1;
 }
@@ -5277,7 +5438,6 @@ static CHANSERV_FUNC(cmd_csuspend)
 static CHANSERV_FUNC(cmd_cunsuspend)
 {
     struct suspended *suspended;
-    char message[MAXLEN];
 
     if(!IsSuspended(channel->channel_info))
     {
@@ -5291,8 +5451,8 @@ static CHANSERV_FUNC(cmd_cunsuspend)
     timeq_del(suspended->expires, chanserv_expire_suspension, suspended, 0);
     chanserv_expire_suspension(suspended);
     reply("CSMSG_UNSUSPENDED", channel->name);
-    sprintf(message, "%s unsuspended by %s.", channel->name, user->handle_info->handle);
-    global_message(MESSAGE_RECIPIENT_OPERS|MESSAGE_RECIPIENT_HELPERS, message);
+    global_message_args(MESSAGE_RECIPIENT_OPERS|MESSAGE_RECIPIENT_HELPERS, "CSMSG_UNSUSPENDED_BY",
+                        channel->name, user->handle_info->handle);
     return 1;
 }
 
@@ -5311,7 +5471,7 @@ typedef struct chanservSearch
 typedef void (*channel_search_func)(struct chanData *channel, void *data);
 
 static search_t
-chanserv_search_create(struct userNode *user, unsigned int argc, char *argv[])
+chanserv_search_create(struct svccmd *cmd, struct userNode *user, unsigned int argc, char *argv[])
 {
     search_t search;
     unsigned int i;
@@ -5325,7 +5485,7 @@ chanserv_search_create(struct userNode *user, unsigned int argc, char *argv[])
        /* Assume all criteria require arguments. */
        if(i == (argc - 1))
        {
-           send_message(user, chanserv, "MSG_MISSING_PARAMS", argv[i]);
+           reply("MSG_MISSING_PARAMS", argv[i]);
             goto fail;
        }
 
@@ -5346,7 +5506,7 @@ chanserv_search_create(struct userNode *user, unsigned int argc, char *argv[])
                search->flags |= CHANNEL_SUSPENDED;
            else
            {
-               send_message(user, chanserv, "CSMSG_INVALID_CFLAG", argv[i]);
+               reply("CSMSG_INVALID_CFLAG", argv[i]);
                goto fail;
            }
        }
@@ -5354,7 +5514,7 @@ chanserv_search_create(struct userNode *user, unsigned int argc, char *argv[])
            search->limit = strtoul(argv[++i], NULL, 10);
        else
        {
-           send_message(user, chanserv, "MSG_INVALID_CRITERIA", argv[i]);
+           reply("MSG_INVALID_CRITERIA", argv[i]);
            goto fail;
        }
     }
@@ -5431,7 +5591,7 @@ static CHANSERV_FUNC(cmd_search)
        return 0;
     }
 
-    search = chanserv_search_create(user, argc - 2, argv + 2);
+    search = chanserv_search_create(cmd, user, argc - 2, argv + 2);
     if(!search)
         return 0;
 
@@ -5512,7 +5672,7 @@ static MODCMD_FUNC(chan_opt_defaulttopic)
                && !match_ircglob(channel->channel_info->topic, channel->channel_info->topic_mask))
                 reply("CSMSG_TOPIC_MISMATCH", channel->name);
        }
-        SetChannelTopic(channel, chanserv, chanserv, topic ? topic : "", 1);
+        SetChannelTopic(channel, chanserv, user, topic ? topic : "", 1);
     }
 
     if(channel->channel_info->topic)
@@ -5608,6 +5768,11 @@ static MODCMD_FUNC(chan_opt_modes)
 
     if(argc > 1)
     {
+        if (checkDefCon(DEFCON_NO_MODE_CHANGE) && !IsOper(user)) {
+            reply("CSMSG_DEFCON_NO_MODE_CHANGE");
+            return 0;
+        }
+
         if(!check_user_level(channel, user, lvlEnfModes, 1, 0))
         {
             reply("CSMSG_NO_ACCESS");
@@ -5617,7 +5782,7 @@ static MODCMD_FUNC(chan_opt_modes)
        {
             memset(&channel->channel_info->modes, 0, sizeof(channel->channel_info->modes));
        }
-       else if(!(new_modes = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED)))
+        else if(!(new_modes = mod_chanmode_parse(channel, argv+1, argc-1,MCP_KEY_FREE|MCP_REGISTERED, 0)))
        {
             reply("CSMSG_INVALID_MODE_LOCK", unsplit_string(argv+1, argc-1, NULL));
             return 0;
@@ -5953,6 +6118,11 @@ static MODCMD_FUNC(chan_opt_topicrefresh)
     return channel_multiple_option(chTopicRefresh, CSFUNC_ARGS);
 }
 
+static MODCMD_FUNC(chan_opt_resync)
+{
+    return channel_multiple_option(chResync, CSFUNC_ARGS);
+}
+
 static struct svccmd_list set_shows_list;
 
 static void
@@ -6065,11 +6235,10 @@ static MODCMD_FUNC(user_opt_autoop)
         reply("CSMSG_NOT_USER", channel->name);
         return 0;
     }
-    if(uData->access < UL_OP /*channel->channel_info->lvlOpts[lvlGiveOps]*/)
+    if(uData->access < UL_HALFOP /*channel->channel_info->lvlOpts[lvlGiveOps]*/)
         return user_binary_option("CSMSG_USET_AUTOVOICE", USER_AUTO_OP, CSFUNC_ARGS);
     else
         return user_binary_option("CSMSG_USET_AUTOOP", USER_AUTO_OP, CSFUNC_ARGS);
-    /* TODO: add halfops error message? or is the op one generic enough? */
 }
 
 static MODCMD_FUNC(user_opt_autoinvite)
@@ -6077,6 +6246,11 @@ static MODCMD_FUNC(user_opt_autoinvite)
     return user_binary_option("CSMSG_USET_AUTOINVITE", USER_AUTO_INVITE, CSFUNC_ARGS);
 }
 
+static MODCMD_FUNC(user_opt_autojoin)
+{
+    return user_binary_option("CSMSG_USET_AUTOJOIN", USER_AUTO_JOIN, CSFUNC_ARGS);
+}
+
 static MODCMD_FUNC(user_opt_info)
 {
     struct userData *uData;
@@ -6135,7 +6309,7 @@ static CHANSERV_FUNC(cmd_uset)
     {
         char *options[] =
         {
-            "AutoOp", "AutoInvite", "Info"
+            "AutoOp", "AutoInvite", "AutoJoin", "Info"
         };
 
         if(!uset_shows_list.size)
@@ -6186,7 +6360,7 @@ static CHANSERV_FUNC(cmd_giveownership)
     struct giveownership *giveownership;
     unsigned int force, override;
     unsigned short co_access, new_owner_old_access;
-    char reason[MAXLEN], transfer_reason[MAXLEN];
+    char transfer_reason[MAXLEN];
 
     REQUIRE_PARAMS(2);
     curr_user = GetChannelAccess(cData, user->handle_info);
@@ -6224,6 +6398,10 @@ static CHANSERV_FUNC(cmd_giveownership)
         reply("CSMSG_TRANSFER_WAIT", delay, channel->name);
         return 0;
     }
+    if (!curr_user) {
+        reply("CSMSG_NO_OWNER", channel->name);
+        return 0;
+    }
     if(!(new_owner_hi = modcmd_get_handle_info(user, argv[1])))
         return 0;
     if(new_owner_hi == user->handle_info)
@@ -6286,8 +6464,8 @@ static CHANSERV_FUNC(cmd_giveownership)
     channel->channel_info->giveownership = giveownership;
 
     reply("CSMSG_OWNERSHIP_GIVEN", channel->name, new_owner_hi->handle);
-    sprintf(reason, "%s ownership transferred to %s by %s.", channel->name, new_owner_hi->handle, user->handle_info->handle);
-    global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
+    global_message_args(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, "CSMSG_OWNERSHIP_TRANSFERRED",
+                       channel->name, new_owner_hi->handle, user->handle_info->handle);
     return 1;
 }
 
@@ -6438,6 +6616,25 @@ chanserv_refresh_topics(UNUSED_ARG(void *data))
     timeq_add(now + chanserv_conf.refresh_period, chanserv_refresh_topics, NULL);
 }
 
+static void
+chanserv_auto_resync(UNUSED_ARG(void *data))
+{
+    unsigned int refresh_num = (now - self->link) / chanserv_conf.refresh_period;
+    struct chanData *cData;
+    char opt;
+
+    for(cData = channelList; cData; cData = cData->next)
+    {
+        if(IsSuspended(cData)) continue;
+        opt = cData->chOpts[chResync];
+        if(opt == 'n') continue;
+        if((refresh_num - cData->last_resync) < (unsigned int)(1 << (opt - '1'))) continue;
+               resync_channel(cData->channel);
+        cData->last_resync = refresh_num;
+    }
+    timeq_add(now + chanserv_conf.refresh_period, chanserv_auto_resync, NULL);
+}
+
 static CHANSERV_FUNC(cmd_unf)
 {
     if(channel)
@@ -6751,6 +6948,19 @@ static CHANSERV_FUNC(cmd_calc)
     return 1;
 }
 
+static CHANSERV_FUNC(cmd_reply)
+{
+
+    REQUIRE_PARAMS(2);
+    unsplit_string(argv + 1, argc - 1, NULL);
+
+    if(channel)
+        send_channel_message(channel, cmd->parent->bot, "$b%s$b: %s", user->nick, unsplit_string(argv + 1, argc - 1, NULL));
+    else
+        send_message_type(4, user, cmd->parent->bot, "%s", unsplit_string(argv + 1, argc - 1, NULL));
+    return 1;
+}
+
 static void
 chanserv_adjust_limit(void *data)
 {
@@ -6815,14 +7025,40 @@ handle_join(struct modeNode *mNode)
     if(channel->members.used > cData->max)
         cData->max = channel->members.used;
 
+#ifdef notdef
+    /* Check for bans.  If they're joining through a ban, one of two
+     * cases applies:
+     *   1: Join during a netburst, by riding the break.  Kick them
+     *      unless they have ops or voice in the channel.
+     *   2: They're allowed to join through the ban (an invite in
+     *   ircu2.10, or a +e on Hybrid, or something).
+     * If they're not joining through a ban, and the banlist is not
+     * full, see if they're on the banlist for the channel.  If so,
+     * kickban them.
+     */
+    if(user->uplink->burst && !mNode->modes)
+    {
+        unsigned int ii;
+        for(ii = 0; ii < channel->banlist.used; ii++)
+        {
+            if(user_matches_glob(user, channel->banlist.list[ii]->ban, MATCH_USENICK))
+            {
+                /* Riding a netburst.  Naughty. */
+                KickChannelUser(user, channel, chanserv, "User from far side of netsplit should have been banned - bye.");
+                return 1;
+            }
+        }
+    }
+#endif
+
     mod_chanmode_init(&change);
     change.argc = 1;
     if(channel->banlist.used < MAXBANS)
     {
         /* Not joining through a ban. */
         for(bData = cData->bans;
-                bData && !user_matches_glob(user, bData->mask, 1);
-                bData = bData->next);
+            bData && !user_matches_glob(user, bData->mask, MATCH_USENICK);
+            bData = bData->next);
 
         if(bData)
         {
@@ -6906,7 +7142,11 @@ handle_join(struct modeNode *mNode)
             /* non users getting automodes are handled above. */
             if(IsUserAutoOp(uData) && cData->chOpts[chAutomode] != 'n')
             {
-                if(uData->access >= UL_OP )
+                /* just op everyone with access */
+                if(uData->access >= UL_PEON && cData->chOpts[chAutomode] == 'l')
+                    modes |= MODE_VOICE;
+                /* or do their access level */
+                else if(uData->access >= UL_OP )
                     modes |= MODE_CHANOP;
                 else if(uData->access >= UL_HALFOP )
                     modes |= MODE_HALFOP;
@@ -6926,6 +7166,10 @@ handle_join(struct modeNode *mNode)
             uData->present = 1;
         }
     }
+
+    /* If user joining normally (not during burst), apply op or voice,
+     * and send greeting/userinfo as appropriate.
+     */
     if(!user->uplink->burst)
     {
         if(modes)
@@ -6940,7 +7184,7 @@ handle_join(struct modeNode *mNode)
             change.args[0].u.member = mNode;
             mod_chanmode_announce(chanserv, channel, &change);
         }
-        if(greeting && !user->uplink->burst)
+        if(greeting)
             send_message_type(4, user, chanserv, "(%s) %s", channel->name, greeting);
         if(uData && info)
             send_target_message(5, channel->name, chanserv, "[%s] %s", user->nick, uData->info);
@@ -6948,12 +7192,41 @@ handle_join(struct modeNode *mNode)
     return 0;
 }
 
+static void
+chanserv_autojoin_channel(void *data)
+{
+    struct userData *channel;
+    struct userNode *user = data;
+
+    for(channel = user->handle_info->channels; channel; channel = channel->u_next)
+    {
+        struct chanNode *cn;
+        struct modeNode *mn;
+
+        if(IsUserSuspended(channel)
+           || IsSuspended(channel->channel)
+           || !(cn = channel->channel->channel))
+            continue;
+
+        mn = GetUserMode(cn, user);
+        if(!mn)
+        {
+            if(!IsUserSuspended(channel)
+               && IsUserAutoJoin(channel)
+               && (channel->access >= channel->channel->lvlOpts[lvlInviteMe])
+               && !self->burst
+               && !user->uplink->burst)
+                irc_svsjoin(chanserv, user, cn);
+        }
+    }
+}
+
 static void
 handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
 {
     struct mod_chanmode change;
     struct userData *channel;
-    unsigned int ii, jj;
+    unsigned int ii, jj, i;
 
     if(!user->handle_info)
        return;
@@ -6963,12 +7236,14 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
     for(channel = user->handle_info->channels; channel; channel = channel->u_next)
     {
         struct chanNode *cn;
+        struct chanData *cData;
         struct modeNode *mn;
         if(IsUserSuspended(channel)
            || IsSuspended(channel->channel)
            || !(cn = channel->channel->channel))
             continue;
 
+        cData = cn->channel_info;
         mn = GetUserMode(cn, user);
         if(!mn)
         {
@@ -6984,7 +7259,7 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
        if(channel->access >= UL_PRESENT)
            channel->channel->visited = now;
 
-        if(IsUserAutoOp(channel))
+        if(IsUserAutoOp(channel) && cData->chOpts[chAutomode] != 'n')
         {
             if(channel->access >= UL_OP )
                 change.args[0].mode = MODE_CHANOP;
@@ -7013,14 +7288,14 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
            || IsSuspended(channel->channel_info))
             continue;
         for(jj = 0; jj < channel->banlist.used; ++jj)
-            if(user_matches_glob(user, channel->banlist.list[jj]->ban, 1))
+            if(user_matches_glob(user, channel->banlist.list[jj]->ban, MATCH_USENICK))
                 break;
         if(jj < channel->banlist.used)
             continue;
         for(ban = channel->channel_info->bans; ban; ban = ban->next)
         {
             char kick_reason[MAXLEN];
-            if(!user_matches_glob(user, ban->mask, 1))
+            if(!user_matches_glob(user, ban->mask,MATCH_USENICK | MATCH_VISIBLE))
                 continue;
             change.args[0].mode = MODE_BAN;
             change.args[0].u.hostmask = ban->mask;
@@ -7043,6 +7318,19 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
             }
         }
     }
+
+    if (user->handle_info->ignores->used) {
+        for (i=0; i < user->handle_info->ignores->used; i++) {
+            irc_silence(user, user->handle_info->ignores->list[i], 1);
+        }
+    }
+
+    if (user->handle_info->epithet)
+      irc_swhois(chanserv, user, user->handle_info->epithet);
+
+   /* process autojoin channels 5 seconds later as this sometimes 
+      happens before autohide */
+   timeq_add(now + 5, chanserv_autojoin_channel, user);
 }
 
 static void
@@ -7131,12 +7419,12 @@ handle_topic(struct userNode *user, struct chanNode *channel, const char *old_to
         conform_topic(cData->topic_mask, channel->topic, new_topic);
         if(*new_topic)
         {
-           SetChannelTopic(channel, chanserv, chanserv, new_topic, 1);
+           SetChannelTopic(channel, chanserv, user, new_topic, 1);
            /* and fall through to topicsnarf code below.. */
         }
         else /* Topic couldnt fit into mask, was too long */
         {
-            SetChannelTopic(channel, chanserv, chanserv, old_topic, 1);
+            SetChannelTopic(channel, chanserv, user, old_topic, 1);
             send_message(user, chanserv, "CSMSG_TOPICMASK_CONFLICT1", channel->name, cData->topic_mask);
             send_message(user, chanserv, "CSMSG_TOPICMASK_CONFLICT2", TOPICLEN);
             return 1;
@@ -7194,7 +7482,7 @@ handle_mode(struct chanNode *channel, struct userNode *user, const struct mod_ch
         else if(change->args[ii].mode & MODE_CHANOP)
         {
             const struct userNode *victim = change->args[ii].u.member->user;
-            if(IsService(victim) || validate_op(user, channel, (struct userNode*)victim))
+            if(IsService(victim) || validate_op(NULL, user, channel, (struct userNode*)victim))
                 continue;
             if(!bounce)
                 bounce = mod_chanmode_alloc(change->argc + 1 - ii);
@@ -7251,7 +7539,7 @@ handle_nick_change(struct userNode *user, UNUSED_ARG(const char *old_nick))
             continue;
         /* Look for a matching ban already on the channel. */
         for(jj = 0; jj < channel->banlist.used; ++jj)
-            if(user_matches_glob(user, channel->banlist.list[jj]->ban, 1))
+            if(user_matches_glob(user, channel->banlist.list[jj]->ban, MATCH_USENICK))
                 break;
         /* Need not act if we found one. */
         if(jj < channel->banlist.used)
@@ -7259,7 +7547,7 @@ handle_nick_change(struct userNode *user, UNUSED_ARG(const char *old_nick))
         /* Look for a matching ban in this channel. */
         for(bData = channel->channel_info->bans; bData; bData = bData->next)
         {
-            if(!user_matches_glob(user, bData->mask, 1))
+            if(!user_matches_glob(user, bData->mask, MATCH_USENICK | MATCH_VISIBLE))
                 continue;
             change.args[0].u.hostmask = bData->mask;
             mod_chanmode_announce(chanserv, channel, &change);
@@ -7402,12 +7690,15 @@ chanserv_conf_read(void)
     chanserv_conf.network_helper_epithet = str ? str : "a wannabe tyrant";
     str = database_get_data(conf_node, KEY_SUPPORT_HELPER_EPITHET, RECDB_QSTRING);
     chanserv_conf.support_helper_epithet = str ? str : "a wannabe tyrant";
+    str = database_get_data(conf_node, KEY_GOD_TIMEOUT, RECDB_QSTRING);
+    god_timeout = str ? ParseInterval(str) : 60*15;
     str = database_get_data(conf_node, "default_modes", RECDB_QSTRING);
     if(!str)
         str = "+nt";
     safestrncpy(mode_line, str, sizeof(mode_line));
     ii = split_line(mode_line, 0, ArrayLength(modes), modes);
-    if((change = mod_chanmode_parse(NULL, modes, ii, MCP_KEY_FREE)) && (change->argc < 2))
+    if((change = mod_chanmode_parse(NULL, modes, ii, MCP_KEY_FREE, 0))
+       && (change->argc < 2))
     {
         chanserv_conf.default_modes = *change;
         mod_chanmode_free(change);
@@ -7425,7 +7716,7 @@ chanserv_conf_read(void)
             "PubCmd", "InviteMe", "UserInfo","EnfOps",
             "EnfHalfOps", "EnfModes", "EnfTopic", "TopicSnarf", "Setters", 
             /* multiple choice options */
-            "AutoMode", "CtcpReaction", "Protect", "Toys", "TopicRefresh",
+            "AutoMode", "CtcpReaction", "Protect", "Toys", "TopicRefresh", "Resync",
             /* binary options */
             "DynLimit", "NoDelete", "BanTimeout",
             /* delimiter */
@@ -7829,7 +8120,7 @@ chanserv_channel_read(const char *key, struct record_data *hir)
     if(!IsSuspended(cData)
        && (str = database_get_data(channel, KEY_MODES, RECDB_QSTRING))
        && (argc = split_line(str, 0, ArrayLength(argv), argv))
-       && (modes = mod_chanmode_parse(cNode, argv, argc, MCP_KEY_FREE))) {
+       && (modes = mod_chanmode_parse(cNode, argv, argc, MCP_KEY_FREE, 0))) {
         cData->modes = *modes;
        if(off_channel > 0)
           cData->modes.modes_set |= MODE_REGISTERED;
@@ -8216,6 +8507,8 @@ chanserv_db_cleanup(void) {
 void
 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);
 
@@ -8348,6 +8641,7 @@ init_chanserv(const char *nick)
     DEFINE_COMMAND(d, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
     DEFINE_COMMAND(huggle, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
     DEFINE_COMMAND(calc, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
+    DEFINE_COMMAND(reply, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
 
     /* Channel options */
     DEFINE_CHANNEL_OPTION(defaulttopic);
@@ -8369,6 +8663,7 @@ init_chanserv(const char *nick)
     DEFINE_CHANNEL_OPTION(toys);
     DEFINE_CHANNEL_OPTION(setters);
     DEFINE_CHANNEL_OPTION(topicrefresh);
+    DEFINE_CHANNEL_OPTION(resync);
     DEFINE_CHANNEL_OPTION(ctcpreaction);
     DEFINE_CHANNEL_OPTION(bantimeout);
     DEFINE_CHANNEL_OPTION(inviteme);
@@ -8381,6 +8676,7 @@ init_chanserv(const char *nick)
 
     /* User options */
     DEFINE_USER_OPTION(autoinvite);
+    DEFINE_USER_OPTION(autojoin);
     DEFINE_USER_OPTION(info);
     DEFINE_USER_OPTION(autoop);
 
@@ -8410,6 +8706,14 @@ init_chanserv(const char *nick)
         time_t next_refresh;
         next_refresh = (now + chanserv_conf.refresh_period - 1) / chanserv_conf.refresh_period * chanserv_conf.refresh_period;
         timeq_add(next_refresh, chanserv_refresh_topics, NULL);
+        timeq_add(next_refresh, chanserv_auto_resync, NULL);
+    }
+
+    if (autojoin_channels && chanserv) {
+        for (i = 0; i < autojoin_channels->used; i++) {
+            chan = AddChannel(autojoin_channels->list[i], now, "+nt", NULL, NULL);
+            AddChannelUser(chanserv, chan)->modes |= MODE_CHANOP;
+        }    
     }
 
     reg_exit_func(chanserv_db_cleanup);