added modes parameter to srvx.conf to control user modes
[srvx.git] / src / chanserv.c
index e8fbe4b4a917831110539d3aadfc6c4f92d6ce1c..f93a75fad6d00f3c3ce319c1350c23577a4a9843 100644 (file)
@@ -54,6 +54,7 @@
 #define KEY_NETWORK_HELPER_EPITHET  "network_helper_epithet"
 #define KEY_SUPPORT_HELPER_EPITHET  "support_helper_epithet"
 #define KEY_NODELETE_LEVEL      "nodelete_level"
+#define KEY_MAX_USERINFO_LENGTH "max_userinfo_length"
 
 /* ChanServ database */
 #define KEY_CHANNELS           "channels"
 #define KEY_EXPIRES             "expires"
 #define KEY_TRIGGERED          "triggered"
 
-#define CHANNEL_DEFAULT_FLAGS   (0)
+#define CHANNEL_DEFAULT_FLAGS   (CHANNEL_OFFCHANNEL)
 #define CHANNEL_DEFAULT_OPTIONS "lmoooanpcnat"
 
 /* Administrative messages */
@@ -251,6 +252,7 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_SET_MODES",         "$bModes       $b %s" },
     { "CSMSG_SET_NODELETE",      "$bNoDelete    $b %s" },
     { "CSMSG_SET_DYNLIMIT",      "$bDynLimit    $b %s" },
+    { "CSMSG_SET_OFFCHANNEL",    "$bOffChannel  $b %s" },
     { "CSMSG_SET_USERINFO",      "$bUserInfo    $b %d" },
     { "CSMSG_SET_GIVE_VOICE",    "$bGiveVoice   $b %d" },
     { "CSMSG_SET_TOPICSNARF",    "$bTopicSnarf  $b %d" },
@@ -301,6 +303,8 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_ALREADY_PRESENT", "%s is already in $b%s$b." },
     { "CSMSG_YOU_ALREADY_PRESENT", "You are already in $b%s$b." },
     { "CSMSG_LOW_CHANNEL_ACCESS", "You lack sufficient access in %s to use this command." },
+    { "CSMSG_INFOLINE_TOO_LONG", "Your infoline may not exceed %u characters." },
+    { "CSMSG_BAD_INFOLINE", "You may not use the character \\%03o in your infoline." },
 
     { "CSMSG_KICK_DONE", "Kicked $b%s$b from %s." },
     { "CSMSG_NO_BANS", "No channel bans found on $b%s$b." },
@@ -428,9 +432,9 @@ static const struct message_entry msgtab[] = {
     { "CSMSG_WUT_RESPONSE", "wut" },
     { "CSMSG_BAD_NUMBER", "$b%s$b is an invalid number.  Please use a number greater than 1 with this command." },
     { "CSMSG_BAD_DIE_FORMAT", "I do not understand $b%s$b.  Please use either a single number or standard 4d6+3 format." },
-    { "CSMSG_BAD_DICE_COUNT", "%d is too many dice.  Please use at most %d." },
-    { "CSMSG_DICE_ROLL", "The total is $b%d$b from rolling %dd%d+%d." },
-    { "CSMSG_DIE_ROLL", "A $b%d$b shows on the %d-sided die." },
+    { "CSMSG_BAD_DICE_COUNT", "%lu is too many dice.  Please use at most %lu." },
+    { "CSMSG_DICE_ROLL", "The total is $b%lu$b from rolling %lud%lu+%lu." },
+    { "CSMSG_DIE_ROLL", "A $b%lu$b shows on the %lu-sided die." },
     { "CSMSG_HUGGLES_HIM", "\001ACTION huggles %s\001" },
     { "CSMSG_HUGGLES_YOU", "\001ACTION huggles you\001" },
 
@@ -468,6 +472,7 @@ static int eject_user(struct userNode *user, struct chanNode *channel, unsigned
 
 struct userNode *chanserv;
 dict_t note_types;
+int off_channel;
 static dict_t plain_dnrs, mask_dnrs, handle_dnrs;
 static struct log_type *CS_LOG;
 
@@ -493,6 +498,9 @@ static struct
     unsigned int        max_owned;
     unsigned int       max_chan_users;
     unsigned int       max_chan_bans;
+    unsigned int        max_userinfo_length;
+
+    unsigned int       use_registered_mode;
 
     struct string_list  *set_shows;
     struct string_list  *eightball;
@@ -643,7 +651,7 @@ user_level_from_name(const char *name, unsigned short clamp_level)
 {
     unsigned int level = 0, ii;
     if(isdigit(name[0]))
-        level = atoi(name);
+        level = strtoul(name, NULL, 10);
     else for(ii = 0; (ii < ArrayLength(accessLevels)) && !level; ++ii)
         if(!irccasecmp(name, accessLevels[ii].name))
             level = accessLevels[ii].level;
@@ -1004,10 +1012,10 @@ mode_lock_violated(const struct mod_chanmode *orig, const struct mod_chanmode *c
         return 1;
     if(orig->modes_clear & change->modes_set)
         return 1;
-    if((orig->modes_set & MODE_KEY)
+    if((orig->modes_set & MODE_KEY) && (change->modes_set & MODE_KEY)
        && strcmp(orig->new_key, change->new_key))
         return 1;
-    if((orig->modes_set & MODE_LIMIT)
+    if((orig->modes_set & MODE_LIMIT) && (change->modes_set & MODE_LIMIT)
        && (orig->new_limit != change->new_limit))
         return 1;
     return 0;
@@ -1264,6 +1272,7 @@ static void chanserv_expire_suspension(void *data);
 static void
 unregister_channel(struct chanData *channel, const char *reason)
 {
+    struct mod_chanmode change;
     char msgbuf[MAXLEN];
 
     /* After channel unregistration, the following must be cleaned
@@ -1280,22 +1289,31 @@ unregister_channel(struct chanData *channel, const char *reason)
 
     timeq_del(0, NULL, channel, TIMEQ_IGNORE_FUNC | TIMEQ_IGNORE_WHEN);
 
+    if (chanserv_conf.use_registered_mode) {
+      mod_chanmode_init(&change);
+      change.modes_clear |= MODE_REGISTERED;
+      mod_chanmode_announce(chanserv, channel->channel, &change);
+    }
+
     while(channel->users)
        del_channel_user(channel->users, 0);
 
     while(channel->bans)
        del_channel_ban(channel->bans);
 
-    if(channel->topic) free(channel->topic);
-    if(channel->registrar) free(channel->registrar);
-    if(channel->greeting) free(channel->greeting);
-    if(channel->user_greeting) free(channel->user_greeting);
-    if(channel->topic_mask) free(channel->topic_mask);
+    free(channel->topic);
+    free(channel->registrar);
+    free(channel->greeting);
+    free(channel->user_greeting);
+    free(channel->topic_mask);
 
-    if(channel->prev) channel->prev->next = channel->next;
-    else channelList = channel->next;
+    if(channel->prev)
+        channel->prev->next = channel->next;
+    else
+        channelList = channel->next;
 
-    if(channel->next) channel->next->prev = channel->prev;
+    if(channel->next)
+        channel->next->prev = channel->prev;
 
     if(channel->suspended)
     {
@@ -1317,8 +1335,7 @@ unregister_channel(struct chanData *channel, const char *reason)
     }
     channel->channel->channel_info = NULL;
 
-    if(channel->notes)
-        dict_delete(channel->notes);
+    dict_delete(channel->notes);
     sprintf(msgbuf, "%s %s", channel->channel->name, reason);
     if(!IsSuspended(channel))
         DelChannelUser(chanserv, channel->channel, msgbuf, 0);
@@ -1732,6 +1749,8 @@ static CHANSERV_FUNC(cmd_register)
     cData = register_channel(channel, user->handle_info->handle);
     scan_user_presence(add_channel_user(cData, handle, UL_OWNER, 0, NULL), NULL);
     cData->modes = chanserv_conf.default_modes;
+    if (chanserv_conf.use_registered_mode)
+      cData->modes.modes_set |= MODE_REGISTERED;
     change = mod_chanmode_dup(&cData->modes, 1);
     change->args[change->argc].mode = MODE_CHANOP;
     change->args[change->argc].member = AddChannelUser(chanserv, channel);
@@ -2505,11 +2524,17 @@ static CHANSERV_FUNC(cmd_up)
         change.args[0].mode = MODE_CHANOP;
         errmsg = "CSMSG_ALREADY_OPPED";
     }
-    else
+    else if(uData->access >= channel->channel_info->lvlOpts[lvlGiveVoice])
     {
         change.args[0].mode = MODE_VOICE;
         errmsg = "CSMSG_ALREADY_VOICED";
     }
+    else
+    {
+        if(argc)
+            reply("CSMSG_NO_ACCESS");
+        return 0;
+    }
     change.args[0].mode &= ~change.args[0].member->modes;
     if(!change.args[0].mode)
     {
@@ -2921,7 +2946,7 @@ eject_user(struct userNode *user, struct chanNode *channel, unsigned int argc, c
     if(action & ACTION_KICK)
     {
         char kick_reason[MAXLEN];
-       sprintf(kick_reason, "%s (%s)", reason, user->nick);
+       sprintf(kick_reason, "(%s) %s", user->nick, reason);
 
        for(n = 0; n < victimCount; n++)
            KickChannelUser(victims[n]->user, channel, chanserv, kick_reason);
@@ -3146,9 +3171,9 @@ static CHANSERV_FUNC(cmd_open)
 
 static CHANSERV_FUNC(cmd_myaccess)
 {
+    static struct string_buffer sbuf;
     struct handle_info *target_handle;
     struct userData *uData;
-    const char *chanName;
 
     if(argc < 2)
         target_handle = user->handle_info;
@@ -3177,11 +3202,27 @@ static CHANSERV_FUNC(cmd_myaccess)
            && (target_handle != user->handle_info)
            && !GetTrueChannelAccess(cData, user->handle_info))
             continue;
-        chanName = cData->channel->name;
+        sbuf.used = 0;
+        string_buffer_append_printf(&sbuf, "[%s (%d", cData->channel->name, uData->access);
+        if(uData->flags != USER_AUTO_OP)
+            string_buffer_append(&sbuf, ',');
+        if(IsUserSuspended(uData))
+            string_buffer_append(&sbuf, 's');
+        if(IsUserAutoOp(uData))
+        {
+            if(uData->access >= cData->lvlOpts[lvlGiveOps])
+                string_buffer_append(&sbuf, 'o');
+            else if(uData->access >= cData->lvlOpts[lvlGiveVoice])
+                string_buffer_append(&sbuf, 'v');
+        }
+        if(IsUserAutoInvite(uData) && (uData->access >= cData->lvlOpts[lvlInviteMe]))
+            string_buffer_append(&sbuf, 'i');
         if(uData->info)
-            send_message_type(4, user, cmd->parent->bot, "[%s (%d)] %s", chanName, uData->access, uData->info);
+            string_buffer_append_printf(&sbuf, ")] %s", uData->info);
         else
-            send_message_type(4, user, cmd->parent->bot, "[%s (%d)]", chanName, uData->access);
+            string_buffer_append_string(&sbuf, ")]");
+        string_buffer_append(&sbuf, '\0');
+        send_message_type(4, user, cmd->parent->bot, sbuf.list);
     }
 
     return 1;
@@ -3403,14 +3444,15 @@ cmd_list_users(struct userNode *user, struct chanNode *channel, unsigned int arg
     lData.lowest = lowest;
     lData.highest = highest;
     lData.search = (argc > 1) ? argv[1] : NULL;
-    send_list = zoot_list;
+    send_list = def_list;
+    (void)zoot_list; /* since it doesn't show user levels */
 
     if(user->handle_info)
     {
        switch(user->handle_info->userlist_style)
        {
        case HI_STYLE_DEF: send_list = def_list; break;
-        case HI_STYLE_ZOOT: send_list = zoot_list; break;
+        case HI_STYLE_ZOOT: send_list = def_list; break;
        }
     }
 
@@ -3697,7 +3739,7 @@ static CHANSERV_FUNC(cmd_mode)
        return 1;
     }
 
-    change = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE);
+    change = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED);
     if(!change)
     {
        reply("MSG_INVALID_MODES", unsplit_string(argv+1, argc-1, NULL));
@@ -4139,7 +4181,7 @@ static MODCMD_FUNC(cmd_names)
         targData = GetTrueChannelAccess(channel->channel_info, targ->handle_info);
         if(!targData)
             continue;
-        if(pos + strlen(targ->nick) + strlen(targ->handle_info->handle) + 6 > sizeof(buf))
+        if(pos + strlen(targ->nick) + strlen(targ->handle_info->handle) + 8 > sizeof(buf))
         {
             buf[pos] = 0;
             reply("CSMSG_CHANNEL_NAMES", channel->name, buf);
@@ -4298,12 +4340,14 @@ static CHANSERV_FUNC(cmd_events)
     unsigned int matches, limit;
 
     limit = (argc > 1) ? atoi(argv[1]) : 10;
-    if(limit < 1 || limit > 200) limit = 10;
+    if(limit < 1 || limit > 200)
+        limit = 10;
 
     memset(&discrim, 0, sizeof(discrim));
     discrim.masks.bot = chanserv;
     discrim.masks.channel_name = channel->name;
-    if(argc > 2) discrim.masks.command = argv[2];
+    if(argc > 2)
+        discrim.masks.command = argv[2];
     discrim.limit = limit;
     discrim.max_time = INT_MAX;
     discrim.severities = 1 << LOG_COMMAND;
@@ -4815,7 +4859,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)))
+       else if(!(new_modes = mod_chanmode_parse(channel, argv+1, argc-1, MCP_KEY_FREE|MCP_REGISTERED)))
        {
             reply("CSMSG_INVALID_MODE_LOCK", unsplit_string(argv+1, argc-1, NULL));
             return 0;
@@ -4897,6 +4941,54 @@ static MODCMD_FUNC(chan_opt_dynlimit)
     CHANNEL_BINARY_OPTION("CSMSG_SET_DYNLIMIT", CHANNEL_DYNAMIC_LIMIT);
 }
 
+static MODCMD_FUNC(chan_opt_offchannel)
+{
+    struct chanData *cData = channel->channel_info;
+    int value;
+
+    if(argc > 1)
+    {
+       /* Set flag according to value. */
+       if(enabled_string(argv[1]))
+       {
+            if(!IsOffChannel(cData))
+                DelChannelUser(chanserv, channel, "Going off-channel.", 0);
+           cData->flags |= CHANNEL_OFFCHANNEL;
+           value = 1;
+       }
+       else if(disabled_string(argv[1]))
+       {
+            if(IsOffChannel(cData))
+            {
+                struct mod_chanmode change;
+                mod_chanmode_init(&change);
+                change.argc = 1;
+                change.args[0].mode = MODE_CHANOP;
+                change.args[0].member = AddChannelUser(chanserv, channel);
+                mod_chanmode_announce(chanserv, channel, &change);
+            }
+           cData->flags &= ~CHANNEL_OFFCHANNEL;
+           value = 0;
+       }
+       else
+       {
+           reply("MSG_INVALID_BINARY", argv[1]);
+           return 0;
+       }
+    }
+    else
+    {
+       /* Find current option value. */
+       value = (cData->flags & CHANNEL_OFFCHANNEL) ? 1 : 0;
+    }
+
+    if(value)
+        reply("CSMSG_SET_OFFCHANNEL", user_find_message(user, "MSG_ON"));
+    else
+        reply("CSMSG_SET_OFFCHANNEL", user_find_message(user, "MSG_OFF"));
+    return 1;
+}
+
 static MODCMD_FUNC(chan_opt_defaults)
 {
     struct userData *uData;
@@ -5243,7 +5335,19 @@ static MODCMD_FUNC(user_opt_info)
 
     if(argc > 1)
     {
+        size_t bp;
         infoline = unsplit_string(argv + 1, argc - 1, NULL);
+        if(strlen(infoline) > chanserv_conf.max_userinfo_length)
+        {
+            reply("CSMSG_INFOLINE_TOO_LONG", chanserv_conf.max_userinfo_length);
+            return 0;
+        }
+        bp = strcspn(infoline, "\001");
+        if(infoline[bp])
+        {
+            reply("CSMSG_BAD_INFOLINE", infoline[bp]);
+            return 0;
+        }
         if(uData->info)
             free(uData->info);
         if(infoline[0] == '*' && infoline[1] == 0)
@@ -5734,7 +5838,7 @@ handle_join(struct modeNode *mNode)
         if(bData)
         {
             char kick_reason[MAXLEN];
-            sprintf(kick_reason, "%s (%s)", bData->reason, bData->owner);
+            sprintf(kick_reason, "(%s) %s", bData->owner, bData->reason);
 
             bData->triggered = now;
             if(bData != cData->bans)
@@ -5874,8 +5978,8 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
             if(!IsUserSuspended(channel)
                && IsUserAutoInvite(channel)
                && (channel->access >= channel->channel->lvlOpts[lvlInviteMe])
-               && (cn->modes & (MODE_KEY | MODE_INVITEONLY))
-               && !self->burst)
+               && !self->burst
+               && !user->uplink->burst)
                 irc_invite(chanserv, user, cn);
             continue;
         }
@@ -5942,45 +6046,45 @@ handle_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
 }
 
 static void
-handle_part(struct userNode *user, struct chanNode *channel, UNUSED_ARG(const char *reason))
+handle_part(struct modeNode *mn, UNUSED_ARG(const char *reason))
 {
     struct chanData *cData;
     struct userData *uData;
 
-    cData = channel->channel_info;
-    if(!cData || IsSuspended(cData) || IsLocal(user))
+    cData = mn->channel->channel_info;
+    if(!cData || IsSuspended(cData) || IsLocal(mn->user))
         return;
 
-    if((cData->flags & CHANNEL_DYNAMIC_LIMIT) && !channel->join_flooded)
+    if((cData->flags & CHANNEL_DYNAMIC_LIMIT) && !mn->channel->join_flooded)
     {
        /* Allow for a bit of padding so that the limit doesn't
           track the user count exactly, which could get annoying. */
-       if((channel->limit - channel->members.used) > chanserv_conf.adjust_threshold + 5)
+       if((mn->channel->limit - mn->channel->members.used) > chanserv_conf.adjust_threshold + 5)
        {
            timeq_del(0, chanserv_adjust_limit, cData, TIMEQ_IGNORE_WHEN);
            timeq_add(now + chanserv_conf.adjust_delay, chanserv_adjust_limit, cData);
        }
     }
 
-    if((uData = GetTrueChannelAccess(cData, user->handle_info)))
+    if((uData = GetTrueChannelAccess(cData, mn->user->handle_info)))
     {
-       scan_user_presence(uData, user);
+       scan_user_presence(uData, mn->user);
         uData->seen = now;
     }
 
-    if(IsHelping(user) && IsSupportHelper(user))
+    if(IsHelping(mn->user) && IsSupportHelper(mn->user))
     {
         unsigned int ii, jj;
         for(ii = 0; ii < chanserv_conf.support_channels.used; ++ii)
         {
-            for(jj = 0; jj < user->channels.used; ++jj)
-                if(user->channels.list[jj]->channel == chanserv_conf.support_channels.list[ii])
+            for(jj = 0; jj < mn->user->channels.used; ++jj)
+                if(mn->user->channels.list[jj]->channel == chanserv_conf.support_channels.list[ii])
                     break;
-            if(jj < user->channels.used)
+            if(jj < mn->user->channels.used)
                 break;
         }
         if(ii == chanserv_conf.support_channels.used)
-            HANDLE_CLEAR_FLAG(user->handle_info, HELPING);
+            HANDLE_CLEAR_FLAG(mn->user->handle_info, HELPING);
     }
 }
 
@@ -6254,6 +6358,8 @@ chanserv_conf_read(void)
     chanserv_conf.max_chan_users = str ? atoi(str) : 512;
     str = database_get_data(conf_node, KEY_MAX_CHAN_BANS, RECDB_QSTRING);
     chanserv_conf.max_chan_bans = str ? atoi(str) : 512;
+    str = database_get_data(conf_node, KEY_MAX_USERINFO_LENGTH, RECDB_QSTRING);
+    chanserv_conf.max_userinfo_length = str ? atoi(str) : 400;
     str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
     if(chanserv && str)
         NickChange(chanserv, str, 0);
@@ -6332,6 +6438,12 @@ chanserv_conf_read(void)
     else
         strlist = alloc_string_list(2);
     chanserv_conf.old_ban_names = strlist;
+    /* the variable itself is actually declared in proto-common.c; this is equally 
+     * parse issue. */
+    str = database_get_data(conf_node, "off_channel", RECDB_QSTRING);
+    off_channel = (str && enabled_string(str)) ? 1 : 0;
+    str = database_get_data(conf_node, "use_registered_mode", RECDB_QSTRING);
+    chanserv_conf.use_registered_mode = (str && enabled_string(str)) ? 1 : 0;
 }
 
 static void
@@ -6583,7 +6695,7 @@ chanserv_channel_read(const char *key, struct record_data *hir)
         /* We could use suspended->expires and suspended->revoked to
          * set the CHANNEL_SUSPENDED flag, but we don't. */
     }
-    else if(cData->flags & CHANNEL_SUSPENDED)
+    else if(IsSuspended(cData))
     {
         suspended = calloc(1, sizeof(*suspended));
         suspended->issued = 0;
@@ -6598,17 +6710,16 @@ chanserv_channel_read(const char *key, struct record_data *hir)
         suspended->cData = cData;
     }
     else
-        suspended = NULL;
+        suspended = NULL; /* to squelch a warning */
 
-    if((cData->flags & CHANNEL_SUSPENDED)
-       && suspended->expires
-       && (suspended->expires <= now))
-    {
-        cData->flags &= ~CHANNEL_SUSPENDED;
+    if(IsSuspended(cData)) {
+        if(suspended->expires > now)
+            timeq_add(suspended->expires, chanserv_expire_suspension, suspended);
+        else if(suspended->expires)
+            cData->flags &= ~CHANNEL_SUSPENDED;
     }
 
-    if(!(cData->flags & CHANNEL_SUSPENDED))
-    {
+    if((!off_channel || !IsOffChannel(cData)) && !IsSuspended(cData)) {
         struct mod_chanmode change;
         mod_chanmode_init(&change);
         change.argc = 1;
@@ -6616,10 +6727,6 @@ chanserv_channel_read(const char *key, struct record_data *hir)
         change.args[0].member = AddChannelUser(chanserv, cNode);
         mod_chanmode_announce(chanserv, cNode, &change);
     }
-    else if(suspended->expires > now)
-    {
-        timeq_add(suspended->expires, chanserv_expire_suspension, suspended);
-    }
 
     str = database_get_data(channel, KEY_REGISTERED, RECDB_QSTRING);
     cData->registered = str ? (time_t)strtoul(str, NULL, 0) : now;
@@ -6636,14 +6743,16 @@ chanserv_channel_read(const char *key, struct record_data *hir)
     str = database_get_data(channel, KEY_TOPIC, RECDB_QSTRING);
     cData->topic = str ? strdup(str) : NULL;
 
-    if((str = database_get_data(channel, KEY_MODES, RECDB_QSTRING))
+    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))) {
         cData->modes = *modes;
+       if (chanserv_conf.use_registered_mode)
+          cData->modes.modes_set |= MODE_REGISTERED;
         if(cData->modes.argc > 1)
             cData->modes.argc = 1;
-        if(!IsSuspended(cData))
-            mod_chanmode_announce(chanserv, cNode, &cData->modes);
+        mod_chanmode_announce(chanserv, cNode, &cData->modes);
         mod_chanmode_free(modes);
     }
 
@@ -7121,6 +7230,8 @@ init_chanserv(const char *nick)
     DEFINE_CHANNEL_OPTION(ctcpusers);
     DEFINE_CHANNEL_OPTION(ctcpreaction);
     DEFINE_CHANNEL_OPTION(inviteme);
+    if(off_channel)
+        DEFINE_CHANNEL_OPTION(offchannel);
     modcmd_register(chanserv_module, "set defaults", chan_opt_defaults, 1, 0, "access", "owner", NULL);
 
     /* Alias set topic to set defaulttopic for compatibility. */
@@ -7138,8 +7249,9 @@ init_chanserv(const char *nick)
     dict_set_free_data(note_types, chanserv_deref_note_type);
     if(nick)
     {
-        chanserv = AddService(nick, "Channel Services");
-        service_register(chanserv, '!');
+        const char *modes = conf_get_data("services/chanserv/modes", RECDB_QSTRING);
+        chanserv = AddService(nick, modes ? modes : NULL, "Channel Services", NULL);
+        service_register(chanserv)->trigger = '!';
         reg_chanmsg_func('\001', chanserv, chanserv_ctcp_check);
     }
     saxdb_register("ChanServ", chanserv_saxdb_read, chanserv_saxdb_write);