Merge remote branch 'upstream/master'
authorroot <root@mail.pk910.de>
Tue, 1 Nov 2011 21:02:19 +0000 (22:02 +0100)
committerroot <root@mail.pk910.de>
Tue, 1 Nov 2011 21:02:19 +0000 (22:02 +0100)
1  2 
src/chanserv.c
src/helpfile.c
src/mod-helpserv.c
src/modcmd.c
src/nickserv.c
src/proto-p10.c

diff --combined src/chanserv.c
index a0aca8e93cba58d3bbbb6a5104b91616b34253a1,8bdad625b8f63bec3d3224049d6a7917ae0db7ad..9e856960333c238dd26b214bf890b304ce2c21f0
  #include "conf.h"
  #include "global.h"
  #include "modcmd.h"
 -#include "opserv.h" /* for opserv_bad_channel() */
 +#include "opserv.h" /* for opserv_bad_channel() and devnull management */
  #include "nickserv.h" /* for oper_outranks() */
  #include "saxdb.h"
 +#include "spamserv.h"
  #include "timeq.h"
  
  #define CHANSERV_CONF_NAME  "services/chanserv"
  #define KEY_NODELETE_LEVEL          "nodelete_level"
  #define KEY_MAX_USERINFO_LENGTH     "max_userinfo_length"
  #define KEY_GIVEOWNERSHIP_PERIOD    "giveownership_timeout"
 +#define KEY_INVITED_INTERVAL          "invite_timeout"
 +#define KEY_NEW_CHANNEL_AUTHED      "new_channel_authed_join"
 +#define KEY_NEW_CHANNEL_UNAUTHED    "new_channel_unauthed_join"
 +#define KEY_NEW_CHANNEL_MSG         "new_channel_message"
  
  /* ChanServ database */
  #define KEY_CHANNELS                "channels"
  #define KEY_USERS           "users"
  #define KEY_BANS            "bans"
  #define KEY_MAX             "max"
 +#define KEY_MAX_TIME        "max_time"
  #define KEY_NOTES           "notes"
  #define KEY_TOPIC_MASK      "topic_mask"
  #define KEY_OWNER_TRANSFER  "owner_transfer"
 +#define KEY_EXPIRE          "expire"
  
  /* User data */
  #define KEY_LEVEL   "level"
  #define KEY_INFO    "info"
  #define KEY_SEEN    "seen"
  
 +/* Votes */
 +#define KEY_VOTE    "vote"
 +#define KEY_VOTE_START    "votestart"
 +#define KEY_VOTE_OPTIONS    "voptions"
 +#define KEY_VOTE_OPTION_NAME    "voptionname"
 +#define KEY_VOTE_VOTED    "vvoted"
 +#define KEY_VOTE_VOTEDFOR    "vvotefor"
 +#define KEY_VOTE_OPTION_ID    "voptionid"
 +#define KEY_VOTE_OPTION_VOTED    "voptionvoted"
 +
  /* Ban data */
  #define KEY_OWNER       "owner"
  #define KEY_REASON      "reason"
@@@ -209,7 -192,7 +209,7 @@@ static const struct message_entry msgta
  /* User management */
      { "CSMSG_ADDED_USER", "Added %s to the %s user list with access %d." },
      { "CSMSG_DELETED_USER", "Deleted %s (with access %d) from the %s user list." },
 -    { "CSMSG_BAD_RANGE", "Invalid access range; minimum (%d) must be greater than maximum (%d)." },
 +    { "CSMSG_BAD_RANGE", "Invalid access range; minimum (%d) must be lower than maximum (%d)." },
      { "CSMSG_DELETED_USERS", "Deleted accounts matching $b%s$b with access from $b%d$b to $b%d$b from the %s user list." },
      { "CSMSG_TRIMMED_USERS", "Trimmed $b%d users$b with access from %d to %d from the %s user list who were inactive for at least %s." },
      { "CSMSG_INCORRECT_ACCESS", "%s has access $b%d$b, not %s." },
      { "CSMSG_SET_USERINFO",      "$bUserInfo    $b %d" },
      { "CSMSG_SET_GIVE_VOICE",    "$bGiveVoice   $b %d" },
      { "CSMSG_SET_TOPICSNARF",    "$bTopicSnarf  $b %d" },
 +    { "CSMSG_SET_VOTE",          "$bVote        $b %d" },
      { "CSMSG_SET_INVITEME",      "$bInviteMe    $b %d" },
      { "CSMSG_SET_ENFOPS",        "$bEnfOps      $b %d" },
      { "CSMSG_SET_GIVE_OPS",      "$bGiveOps     $b %d" },
      { "CSMSG_SET_CTCPREACTION",  "$bCTCPReaction$b %d - %s" },
      { "CSMSG_SET_TOPICREFRESH",  "$bTopicRefresh$b %d - %s" },
      { "CSMSG_SET_UNREVIEWED",    "$bUnreviewed  $b %s" },
 +    { "CSMSG_SET_EXPIRE",        "$bExpire      $b %s" },
 +    { "CSMSG_SET_EXPIRE_OFF",    "$bExpire      $b off" },
      { "CSMSG_USET_NOAUTOOP",     "$bNoAutoOp    $b %s" },
      { "CSMSG_USET_NOAUTOVOICE",  "$bNoAutoVoice $b %s" },
      { "CSMSG_USET_AUTOINVITE",   "$bAutoInvite  $b %s" },
      { "CSMSG_ACCESS_SEARCH_HEADER", "%s users from level %d to %d matching %s:" },
      { "CSMSG_INVALID_ACCESS", "$b%s$b is an invalid access level." },
      { "CSMSG_CHANGED_ACCESS", "%s now has access $b%d$b in %s." },
 +    { "CSMSG_TOTAL_USERS", "There are $b%d$b users in %s." },
  
  /* Channel note list */
      { "CSMSG_NOTELIST_HEADER", "Notes for $b%s$b:" },
      { "CSMSG_UC_H_TITLE", "network helper" },
      { "CSMSG_LC_H_TITLE", "support helper" },
      { "CSMSG_LAME_SMURF_TARGET", "%s is an IRC operator." },
 +    { "CSMSG_MYACCESS_COUNT", "%s has access in $b%d$b channels and is owner of $b%d$b channel(s)." },
 +      { "CSMSG_MYACCESS_COUNT_1", "%s has access in $b%d$b channel and is owner of $b%d$b channel(s)." },
 +
  
  /* Seen information */
      { "CSMSG_NEVER_SEEN", "%s has never been seen in $b%s$b." },
      { "CSMSG_CHANNEL_MODES", "$bMode Lock:           $b%s" },
      { "CSMSG_CHANNEL_NOTE", "$b%s:%*s$b%s" },
      { "CSMSG_CHANNEL_MAX", "$bRecord Visitors:     $b%i" },
 +    { "CSMSG_CHANNEL_MAX_TIME", "$bRecord Visitors:     $b%i (%s ago.)" },
      { "CSMSG_CHANNEL_OWNER", "$bOwner:               $b%s" },
      { "CSMSG_CHANNEL_BANS", "$bBan Count:           $b%i" },
      { "CSMSG_CHANNEL_USERS", "$bTotal User Count:    $b%i" },
      { "CSMSG_HUGGLES_HIM", "\001ACTION huggles %s\001" },
      { "CSMSG_HUGGLES_YOU", "\001ACTION huggles you\001" },
  
 +/* Vote */
 +    { "CSMSG_ADDVOTE_DONE", "Vote added. Use $baddoption$b to add at least 2 vote options and then $bstartvote$b to start the voting." },
 +    { "CSMSG_ADDVOTE_FULL", "There is already a vote in this channel. Use $bdelvote$b to delete it." },
 +    { "CSMSG_DELVOTE_DONE", "Vote deleted." },
 +    { "CSMSG_NO_VOTE", "There is no vote in this channel." },
 +    { "CSMSG_ADDOPTION_DONE", "Vote option added with id %i (%i - %i)." },
 +    { "CSMSG_DELOPTION_DONE", "Vote option deleted." },
 +    { "CSMSG_DELOPTION_NONE", "Vote option does not exist." },
 +    { "CSMSG_VOTE_NEED_OPTIONS", "There must be at least 2 options in a vote." },
 +    { "CSMSG_VOTE_NOT_STARTED", "The vote is not started. Use $bstartvote$b to allow voting." },
 +    { "CSMSG_VOTE_QUESTION", "Question:   %s" },
 +    { "CSMSG_VOTE_OPTION", "$b%i$b:   %s ($b%i$b votes)" },
 +    { "CSMSG_VOTERES_QUESTION", "Question:   %s" },
 +    { "CSMSG_VOTERES_OPTION", "\ 2%i\ 2:   %s (\ 2%i\ 2 votes)" },
 +    { "CSMSG_STARTVOTE_TOP", "\ 2%s\ 2 has started a voting:" },
 +    { "CSMSG_STARTVOTE_QUESTION", "\ 2Question:\ 2 %s" },
 +    { "CSMSG_STARTVOTE_OPTION", "\ 2%i:\ 2  %s" },
 +    { "CSMSG_STARTVOTE_ACCESS", "All channel users with at least \ 2%i\ 2 access can vote." },
 +    { "CSMSG_STARTVOTE_HOWTO", "To vote for an option, use \ 2vote ID\ 2. To see the available options and the current votes, use \ 2vote\ 2 without parameters." },
 +    { "CSMSG_STARTVOTE_RUNNING", "The vote is already running." },
 +    { "CSMSG_VOTE_VOTED", "You have already voted." },
 +    { "CSMSG_VOTE_INVALID", "Vote option does not exist." },
 +    { "CSMSG_VOTE_DONE", "You voted for $b%s$b." },
 +    { "CSMSG_ENDVOTE_DONE", "The vote has been finished." },
 +    { "CSMSG_ENDVOTE_STOPPED", "The vote is not running." },
 +
  /* Other things */
      { "CSMSG_EVENT_SEARCH_RESULTS", "The following channel events were found:" },
      { NULL, NULL }
@@@ -549,8 -498,6 +549,8 @@@ static struc
      unsigned long   db_backup_frequency;
      unsigned long   channel_expire_frequency;
      unsigned long   dnr_expire_frequency;
 +    
 +    unsigned long   invited_timeout;
  
      unsigned long   info_delay;
      unsigned long   adjust_delay;
      const char          *irc_operator_epithet;
      const char          *network_helper_epithet;
      const char          *support_helper_epithet;
 +
 +    const char          *new_channel_authed;
 +    const char          *new_channel_unauthed;
 +    const char          *new_channel_msg;
  } chanserv_conf;
  
  struct listData
      struct helpfile_table table;
  };
  
 +struct ChanUser
 +{
 +      struct userNode *user;
 +    struct chanNode *chan;
 +};
 +
  enum note_access_type
  {
      NOTE_SET_CHANNEL_ACCESS,
@@@ -672,8 -609,7 +672,8 @@@ static const struct 
      { "CSMSG_SET_CTCPUSERS", "ctcpusers", 0, 9, 0, 0 },
      { "CSMSG_SET_USERINFO", "userinfo", 1, ~0, CHANNEL_INFO_LINES, 1 },
      { "CSMSG_SET_INVITEME", "inviteme", 1, ~0, CHANNEL_PEON_INVITE, 200 },
 -    { "CSMSG_SET_TOPICSNARF", "topicsnarf", 501, ~0, CHANNEL_TOPIC_SNARF, 1 }
 +    { "CSMSG_SET_TOPICSNARF", "topicsnarf", 501, ~0, CHANNEL_TOPIC_SNARF, 1 },
 +    { "CSMSG_SET_VOTE", "vote", 100, ~0, 0, 0 }
  };
  
  struct charOptionValues {
@@@ -720,9 -656,9 +720,9 @@@ struct chanData *channelList
  static struct module *chanserv_module;
  static unsigned int userCount;
  
 -#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)
 +static void unregister_channel(struct chanData *channel, const char *reason);
  
  unsigned short
  user_level_from_name(const char *name, unsigned short clamp_level)
@@@ -914,15 -850,6 +914,15 @@@ chanserv_create_note_type(const char *n
      return ntype;
  }
  
 +static void
 +free_vote_options(void *data)
 +{
 +    struct vote_option *vOpt = data;
 +    free(vOpt->name);
 +    free(vOpt->option_str);
 +    free(vOpt);
 +}
 +
  static void
  chanserv_deref_note_type(void *data)
  {
@@@ -1090,65 -1017,6 +1090,65 @@@ static MODCMD_FUNC(cmd_removenote) 
      return 1;
  }
  
 +static void
 +chanserv_expire_channel(void *data)
 +{
 +    struct chanData *channel = data;
 +    char reason[MAXLEN];
 +    sprintf(reason, "channel expired.");
 +    channel->expiry = 0;
 +    spamserv_cs_unregister(NULL, channel->channel, expire, NULL);
 +    unregister_channel(channel, reason);
 +}
 +
 +static MODCMD_FUNC(chan_opt_expire)
 +{
 +    struct chanData *cData = channel->channel_info;
 +    unsigned long value = cData->expiry;
 +
 +    if(argc > 1)
 +    {
 +        if((!IsOper(user) || !user->handle_info || (user->handle_info->opserv_level < chanserv_conf.nodelete_level)))
 +        {
 +            reply("MSG_SETTING_PRIVILEGED", argv[0]);
 +            return 0;
 +        }
 +        unsigned long expiry,duration;
 +
 +        /* The two directions can have different ACLs. */
 +        if(!strcmp(argv[1], "0"))
 +            expiry = 0;
 +        else if((duration = ParseInterval(argv[1])))
 +            expiry = now + duration;
 +        else
 +        {
 +            reply("MSG_INVALID_DURATION", argv[1]);
 +            return 0;
 +        }
 +
 +        if (expiry != value)
 +        {
 +            if(value) {
 +                //unset old timer
 +                timeq_del(value, chanserv_expire_channel, cData, 0);
 +            }
 +            value = expiry;
 +            cData->expiry = value;
 +            if(value > 0) {
 +                //New timer!
 +                timeq_add(expiry, chanserv_expire_channel, cData);
 +            }
 +        }
 +    }
 +
 +    if(cData->expiry > now) {
 +        char expirestr[INTERVALLEN];
 +        reply("CSMSG_SET_EXPIRE", intervalString(expirestr, cData->expiry - now, user->handle_info));
 +    } else
 +        reply("CSMSG_SET_EXPIRE_OFF");
 +    return 1;
 +}
 +
  static int
  mode_lock_violated(const struct mod_chanmode *orig, const struct mod_chanmode *change)
  {
@@@ -1244,8 -1112,6 +1244,8 @@@ register_channel(struct chanNode *cNode
      channel->channel = cNode;
      LockChannel(cNode);
      cNode->channel_info = channel;
 +    
 +    channel->vote = NULL;
  
      return channel;
  }
@@@ -1283,6 -1149,8 +1283,6 @@@ add_channel_user(struct chanData *chann
      return ud;
  }
  
 -static void unregister_channel(struct chanData *channel, const char *reason);
 -
  void
  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 void expire_ban(void *data);
  
 -static struct banData*
 +struct banData*
  add_channel_ban(struct chanData *channel, const char *mask, char *owner, unsigned long set, unsigned long triggered, unsigned long expires, char *reason)
  {
      struct banData *bd;
@@@ -1483,8 -1349,6 +1483,8 @@@ unregister_channel(struct chanData *cha
          if(cNode)
              cNode->channel_info = NULL;
      }
 +    if(channel->expiry)
 +        timeq_del(channel->expiry, chanserv_expire_channel, channel, 0);
      channel->channel->channel_info = NULL;
  
      dict_delete(channel->notes);
@@@ -1525,7 -1389,6 +1525,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.");
      }
  
@@@ -2235,7 -2098,6 +2235,7 @@@ static CHANSERV_FUNC(cmd_register
  
      /* Initialize the channel's max user record. */
      cData->max = channel->members.used;
 +    cData->max_time = 0;
  
      if(handle != user->handle_info)
          reply("CSMSG_PROXY_SUCCESS", handle->handle, channel->name);
@@@ -2285,7 -2147,7 +2285,7 @@@ static CHANSERV_FUNC(cmd_unregister
          return 0;
      }
  
 -    if(IsProtected(cData))
 +    if(IsProtected(cData) && !IsOper(user))
      {
          reply("CSMSG_UNREG_NODELETE", channel->name);
          return 0;
      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 userData *uData;
      char reason[MAXLEN];
      struct do_not_register *dnr;
 +    int chanserv_join = 0, spamserv_join;
  
      REQUIRE_PARAMS(2);
  
      {
          target = AddChannel(argv[1], now, NULL, NULL);
          if(!IsSuspended(channel->channel_info))
 -            AddChannelUser(chanserv, target);
 +            chanserv_join = 1;
      }
      else if(target->channel_info)
      {
          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)
      {
      for(uData = target->channel_info->users; uData; uData = uData->next)
          scan_user_presence(uData, 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))
      UnlockChannel(channel);
      LockChannel(target);
      global_message(MESSAGE_RECIPIENT_OPERS | MESSAGE_RECIPIENT_HELPERS, reason);
 +    reply("CSMSG_MOVE_SUCCESS", target->name);
      return 1;
  }
  
@@@ -2627,10 -2461,8 +2627,10 @@@ merge_data(struct chanData *source, str
          target->ownerTransfer = source->ownerTransfer;
      if(source->may_opchan)
          target->may_opchan = 1;
 -    if(source->max > target->max)
 +    if(source->max > target->max) {
          target->max = source->max;
 +        target->max_time = source->max_time;
 +    }
  }
  
  static void
@@@ -2690,7 -2522,6 +2690,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);
@@@ -3236,44 -3067,6 +3236,44 @@@ static CHANSERV_FUNC(cmd_devoice
      return modify_users(CSFUNC_ARGS, NULL, MODE_REMOVE|MODE_VOICE, "CSMSG_DEVOICED_USERS");
  }
  
 +static CHANSERV_FUNC(cmd_opme)
 +{
 +    struct mod_chanmode change;
 +    const char *errmsg;
 +
 +    mod_chanmode_init(&change);
 +    change.argc = 1;
 +    change.args[0].u.member = GetUserMode(channel, user);
 +    if(!change.args[0].u.member)
 +    {
 +        if(argc)
 +            reply("MSG_CHANNEL_ABSENT", channel->name);
 +        return 0;
 +    }
 +
 +    struct devnull_class *devnull;
 +    if(user->handle_info->devnull && (devnull = devnull_get(user->handle_info->devnull)) && (devnull->modes & DEVNULL_MODE_OPME))
 +    {
 +        change.args[0].mode = MODE_CHANOP;
 +        errmsg = "CSMSG_ALREADY_OPPED";
 +    }
 +    else
 +    {
 +        if(argc)
 +            reply("CSMSG_NO_ACCESS");
 +        return 0;
 +    }
 +    change.args[0].mode &= ~change.args[0].u.member->modes;
 +    if(!change.args[0].mode)
 +    {
 +        if(argc)
 +            reply(errmsg, channel->name);
 +        return 0;
 +    }
 +    modcmd_chanmode_announce(&change);
 +    return 1;
 +}
 +
  static int
  bad_channel_ban(struct chanNode *channel, struct userNode *user, const char *ban, unsigned int *victimCount, struct modeNode **victims)
  {
@@@ -3311,20 -3104,6 +3311,20 @@@ eject_user(struct userNode *user, struc
  
      offset = (action & ACTION_ADD_TIMED_BAN) ? 3 : 2;
      REQUIRE_PARAMS(offset);
 +    if(argc > offset && IsNetServ(user))
 +    {
 +        if(*argv[offset] == '$') {
 +            struct userNode *hib;
 +            const char *accountnameb = argv[offset] + 1;
 +            if(!(hib = GetUserH(accountnameb)))
 +            {
 +                reply("MSG_HANDLE_UNKNOWN", accountnameb);
 +                return 0;
 +            }
 +            user=hib;
 +            offset++;
 +        }
 +    }
      if(argc > offset)
      {
          reason = unsplit_string(argv + offset, argc - offset, NULL);
@@@ -3661,7 -3440,7 +3661,7 @@@ static CHANSERV_FUNC(cmd_addtimedban
      return eject_user(CSFUNC_ARGS, ACTION_KICK | ACTION_BAN | ACTION_ADD_BAN | ACTION_ADD_TIMED_BAN);
  }
  
 -static struct mod_chanmode *
 +struct mod_chanmode *
  find_matching_bans(struct banList *bans, struct userNode *actee, const char *mask)
  {
      struct mod_chanmode *change;
@@@ -3858,8 -3637,6 +3858,8 @@@ static CHANSERV_FUNC(cmd_myaccess
      static struct string_buffer sbuf;
      struct handle_info *target_handle;
      struct userData *uData;
 +    int ccount = 0;
 +      int ocount = 0;
  
      if(argc < 2)
          target_handle = user->handle_info;
      for(uData = target_handle->channels; uData; uData = uData->u_next)
      {
          struct chanData *cData = uData->channel;
 +        ccount++;
  
          if(uData->access > UL_OWNER)
              continue;
 +        if(uData->access == UL_OWNER)
 +            ocount++;
 +
          if(IsProtected(cData)
             && (target_handle != user->handle_info)
 -           && !GetTrueChannelAccess(cData, user->handle_info))
 +           && !GetTrueChannelAccess(cData, user->handle_info)
 +           && !IsNetworkHelper(user))
              continue;
          sbuf.used = 0;
          string_buffer_append_printf(&sbuf, "[%s (%d", cData->channel->name, uData->access);
          send_message_type(4, user, cmd->parent->bot, "%s", sbuf.list);
      }
  
 +    if(ccount == 1) {
 +        reply("CSMSG_MYACCESS_COUNT_1", target_handle->handle, ccount, ocount);
 +    } else {
 +        reply("CSMSG_MYACCESS_COUNT", target_handle->handle, ccount, ocount);
 +    }
 +
      return 1;
  }
  
@@@ -4050,56 -3816,6 +4050,6 @@@ static CHANSERV_FUNC(cmd_access
      return 1;
  }
  
- static void
- zoot_list(struct listData *list)
- {
-     struct userData *uData;
-     unsigned int start, curr, highest, lowest;
-     struct helpfile_table tmp_table;
-     const char **temp, *msg;
-     if(list->table.length == 1)
-     {
-         if(list->search)
-             send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER", list->channel->name, list->lowest, list->highest, list->search);
-         else
-             send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER", list->channel->name, list->lowest, list->highest);
-         msg = user_find_message(list->user, "MSG_NONE");
-         send_message_type(4, list->user, list->bot, "  %s", msg);
-     }
-     tmp_table.width = list->table.width;
-     tmp_table.flags = list->table.flags;
-     list->table.contents[0][0] = " ";
-     highest = list->highest;
-     if(list->lowest != 0)
-         lowest = list->lowest;
-     else if(highest < 100)
-         lowest = 1;
-     else
-         lowest = highest - 100;
-     for(start = curr = 1; curr < list->table.length; )
-     {
-         uData = list->users[curr-1];
-         list->table.contents[curr++][0] = " ";
-         if((curr == list->table.length) || (list->users[curr-1]->access < lowest))
-         {
-             if(list->search)
-                 send_message(list->user, list->bot, "CSMSG_ACCESS_SEARCH_HEADER", list->channel->name, lowest, highest, list->search);
-             else
-                 send_message(list->user, list->bot, "CSMSG_ACCESS_ALL_HEADER", list->channel->name, lowest, highest);
-             temp = list->table.contents[--start];
-             list->table.contents[start] = list->table.contents[0];
-             tmp_table.contents = list->table.contents + start;
-             tmp_table.length = curr - start;
-             table_send(list->bot, list->user->nick, 0, NULL, tmp_table);
-             list->table.contents[start] = temp;
-             start = curr;
-             highest = lowest - 1;
-             lowest = (highest < 100) ? 0 : (highest - 99);
-         }
-     }
- }
  static void
  def_list(struct listData *list)
  {
@@@ -4145,7 -3861,6 +4095,6 @@@ cmd_list_users(struct userNode *user, s
      lData.highest = highest;
      lData.search = (argc > 1) ? argv[1] : NULL;
      send_list = def_list;
-     (void)zoot_list; /* since it doesn't show user levels */
  
      if(user->handle_info)
      {
      }
      free(lData.table.contents[0]);
      free(lData.table.contents);
 +    reply("CSMSG_TOTAL_USERS",(lData.table.length - 1),channel->name);
      return 1;
  }
  
@@@ -4466,7 -4180,7 +4415,7 @@@ static CHANSERV_FUNC(cmd_mode
          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|MCP_NO_APASS, base_oplevel);
 +    change = mod_chanmode_parse(channel, user, argv+1, argc-1, MCP_KEY_FREE|MCP_IGN_REGISTERED|MCP_NO_APASS, base_oplevel);
      if(!change)
      {
          reply("MSG_INVALID_MODES", unsplit_string(argv+1, argc-1, NULL));
      return 1;
  }
  
 +static void
 +chanserv_del_invite_mark(void *data)
 +{
 +      struct ChanUser *chanuser = data;
 +      struct chanNode *channel = chanuser->chan;
 +      unsigned int i;
 +      if(!channel) return;
 +      for(i = 0; i < channel->invited.used; i++)
 +    {
 +        if(channel->invited.list[i] == chanuser->user) {
 +                      userList_remove(&channel->invited, chanuser->user);
 +              }
 +      }
 +      free(chanuser);
 +}
 +
  static CHANSERV_FUNC(cmd_invite)
  {
-     struct userData *uData;
      struct userNode *invite;
 +    struct ChanUser *chanuser;
 +    unsigned int i;
  
-     uData = GetChannelUser(channel->channel_info, user->handle_info);
      if(argc > 1)
      {
          if(!(invite = GetUserH(argv[1])))
          reply("CSMSG_ALREADY_PRESENT", invite->nick, channel->name);
          return 0;
      }
 +    
 +    for(i = 0; i < channel->invited.used; i++)
 +    {
 +        if(channel->invited.list[i] == invite) {
 +            reply("CSMSG_ALREADY_INVITED", invite->nick, channel->name);
 +            return 0;
 +        }
 +    }
  
      if(user != invite)
      {
      if(argc > 1)
          reply("CSMSG_INVITED_USER", argv[1], channel->name);
  
 +    userList_append(&channel->invited, invite);
 +    chanuser = calloc(1, sizeof(*chanuser));
 +    chanuser->user=invite;
 +    chanuser->chan=channel;
 +    timeq_add(now + chanserv_conf.invited_timeout, chanserv_del_invite_mark, chanuser);
 +
      return 1;
  }
  
@@@ -4579,28 -4258,6 +4525,28 @@@ static CHANSERV_FUNC(cmd_inviteme
      return 1;
  }
  
 +static CHANSERV_FUNC(cmd_invitemeall)
 +{
 +    struct handle_info *target = user->handle_info;
 +    struct userData *uData;
 +
 +    if(!target->channels)
 +    {
 +        reply("CSMSG_SQUAT_ACCESS", target->handle);
 +        return 1;
 +    }
 +      
 +    for(uData = target->channels; uData; uData = uData->u_next)
 +    {
 +        struct chanData *cData = uData->channel;
 +        if(uData->access >= cData->lvlOpts[lvlInviteMe])
 +              {
 +            irc_invite(cmd->parent->bot, user, cData->channel);
 +        }
 +    }
 +    return 1;
 +}
 +
  static void
  show_suspension_info(struct svccmd *cmd, struct userNode *user, struct suspended *suspended)
  {
@@@ -4689,11 -4346,7 +4635,11 @@@ static CHANSERV_FUNC(cmd_info
          reply("CSMSG_CHANNEL_NOTE", iter_key(it), padding > 0 ? padding : 1, "", note->note);
      }
  
 -    reply("CSMSG_CHANNEL_MAX", cData->max);
 +    if(cData->max_time) {
 +        reply("CSMSG_CHANNEL_MAX_TIME", cData->max, intervalString(buffer, now - cData->max_time, user->handle_info));
 +    } else {
 +        reply("CSMSG_CHANNEL_MAX", cData->max);
 +    }
      for(owner = cData->users; owner; owner = owner->next)
          if(owner->access == UL_OWNER)
              reply("CSMSG_CHANNEL_OWNER", owner->handle->handle);
@@@ -4762,8 -4415,6 +4708,8 @@@ send_staff_list(struct userNode *to, st
              continue;
          if(IsBot(user))
              continue;
 +              if(IsInvi(user))
 +                  continue;
          table.contents[table.length] = alloca(table.width*sizeof(**table.contents));
          if(IsAway(user))
          {
@@@ -4899,16 -4550,8 +4845,16 @@@ static CHANSERV_FUNC(cmd_resync
          {
              if(!(mn->modes & MODE_CHANOP))
              {
 -                changes->args[used].mode = MODE_CHANOP;
 -                changes->args[used++].u.member = mn;
 +                if(!uData || IsUserAutoOp(uData)) 
 +                {
 +                    changes->args[used].mode = MODE_CHANOP;
 +                    changes->args[used++].u.member = mn;
 +                    if(!(mn->modes & MODE_VOICE))
 +                    {
 +                        changes->args[used].mode = MODE_VOICE;
 +                        changes->args[used++].u.member = mn;
 +                    }
 +                }
              }
          }
          else if(!cData->lvlOpts[lvlGiveVoice]
                  changes->args[used].mode = MODE_REMOVE | (mn->modes & ~MODE_VOICE);
                  changes->args[used++].u.member = mn;
              }
 -            if(!(mn->modes & MODE_VOICE))
 +            if(!(mn->modes & MODE_VOICE) && (!uData || IsUserAutoOp(uData)))
              {
                  changes->args[used].mode = MODE_VOICE;
                  changes->args[used++].u.member = mn;
@@@ -5285,8 -4928,12 +5231,8 @@@ chanserv_expire_suspension(void *data
      /* If appropriate, re-join ChanServ to the channel. */
      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);
      }
  
      /* Mark everyone currently in the channel as present. */
@@@ -5372,7 -5019,6 +5318,7 @@@ 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);
@@@ -5722,7 -5368,7 +5668,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|MCP_NO_APASS, 0)))
 +        else if(!(new_modes = mod_chanmode_parse(channel, user, argv+1, argc-1, MCP_KEY_FREE|MCP_IGN_REGISTERED|MCP_NO_APASS|(IsOper(user) && IsHelping(user) ? MCP_OPERMODE : 0), 0)))
          {
              reply("CSMSG_INVALID_MODE_LOCK", unsplit_string(argv+1, argc-1, NULL));
              return 0;
@@@ -6050,11 -5696,6 +5996,11 @@@ static MODCMD_FUNC(chan_opt_topicsnarf
      return channel_level_option(lvlTopicSnarf, CSFUNC_ARGS);
  }
  
 +static MODCMD_FUNC(chan_opt_vote)
 +{
 +    return channel_level_option(lvlVote, CSFUNC_ARGS);
 +}
 +
  static MODCMD_FUNC(chan_opt_inviteme)
  {
      return channel_level_option(lvlInviteMe, CSFUNC_ARGS);
@@@ -6546,363 -6187,6 +6492,363 @@@ static MODCMD_FUNC(cmd_deleteme
      return 1;
  }
  
 +static CHANSERV_FUNC(cmd_addvote)
 +{
 +    struct chanData *cData = channel->channel_info;
 +    struct userData *uData, *target;
 +    struct handle_info *hi;
 +    if (!cData) return 0;
 +    REQUIRE_PARAMS(2);
 +    hi = user->handle_info;
 +    if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
 +    {
 +        reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
 +        return 0;
 +    }
 +    if(target->access < 300) {
 +        reply("CSMSG_NO_ACCESS");
 +        return 0;
 +    }
 +    if (cData->vote) {
 +        reply("CSMSG_ADDVOTE_FULL");
 +        return 0;
 +    }
 +    char *msg;
 +    msg = unsplit_string(argv + 1, argc - 1, NULL);
 +    cData->vote = strdup(msg);
 +    cData->vote_start=0;
 +    dict_delete(cData->vote_options);
 +    cData->vote_options = dict_new();
 +    dict_set_free_data(cData->vote_options, free_vote_options);
 +    for(uData = channel->channel_info->users; uData; uData = uData->next)
 +    {
 +        uData->voted = 0;
 +        uData->votefor = 0;
 +    }
 +    reply("CSMSG_ADDVOTE_DONE");
 +    return 1;
 +}
 +
 +static CHANSERV_FUNC(cmd_delvote)
 +{
 +    struct chanData *cData = channel->channel_info;
 +    struct userData *target;
 +    struct handle_info *hi;
 +    if (!cData) return 0;
 +    hi = user->handle_info;
 +    if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
 +    {
 +        reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
 +        return 0;
 +    }
 +    if(target->access < 300) {
 +        reply("CSMSG_NO_ACCESS");
 +        return 0;
 +    }
 +    if (!cData->vote) {
 +        reply("CSMSG_NO_VOTE");
 +        return 0;
 +    }
 +    free(cData->vote);
 +    cData->vote = NULL;
 +    reply("CSMSG_DELVOTE_DONE");
 +    return 1;
 +}
 +
 +static CHANSERV_FUNC(cmd_addoption)
 +{
 +    struct chanData *cData = channel->channel_info;
 +    struct userData *target;
 +    struct handle_info *hi;
 +    if (!cData) return 0;
 +    REQUIRE_PARAMS(2);
 +    hi = user->handle_info;
 +    if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
 +    {
 +        reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
 +        return 0;
 +    }
 +    if(target->access < 300) {
 +        reply("CSMSG_NO_ACCESS");
 +        return 0;
 +    }
 +    if (!cData->vote) {
 +        reply("CSMSG_NO_VOTE");
 +        return 0;
 +    }
 +    
 +    char *msg;
 +    
 +    msg = unsplit_string(argv + 1, argc - 1, NULL);
 +    
 +    dict_iterator_t it;
 +    unsigned int lastid = 1;
 +    for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
 +        struct vote_option *cvOpt = iter_data(it);
 +        if(cvOpt->option_id > lastid)
 +            lastid = cvOpt->option_id;
 +    }
 +    struct vote_option *vOpt;
 +    vOpt = calloc(1, sizeof(*vOpt));
 +    vOpt->name = strdup(msg);
 +    vOpt->option_id = (lastid + 1);
 +    char str[50];
 +    sprintf(str,"%i",(lastid + 1));
 +    vOpt->option_str = strdup(str);
 +    vOpt->voted = 0;
 +    dict_insert(cData->vote_options,vOpt->option_str,vOpt);
 +    
 +    reply("CSMSG_ADDOPTION_DONE",dict_size(cData->vote_options),lastid,(lastid + 1));
 +    return 1;
 +}
 +
 +static CHANSERV_FUNC(cmd_deloption)
 +{
 +    struct chanData *cData = channel->channel_info;
 +    struct userData *uData, *target;
 +    struct handle_info *hi;
 +    if (!cData) return 0;
 +    REQUIRE_PARAMS(2);
 +    hi = user->handle_info;
 +    if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
 +    {
 +        reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
 +        return 0;
 +    }
 +    if(target->access < 300) {
 +        reply("CSMSG_NO_ACCESS");
 +        return 0;
 +    }
 +    if (!cData->vote) {
 +        reply("CSMSG_NO_VOTE");
 +        return 0;
 +    }
 +    if(cData->vote_start) {
 +        if(dict_size(cData->vote_options) < 3) {
 +            reply("CSMSG_VOTE_NEED_OPTIONS");
 +            return 0;
 +        }
 +    }
 +    
 +    int find_id = atoi(argv[1]);
 +    int ii = 0;
 +    unsigned int found = 0;
 +    dict_iterator_t it;
 +    
 +    for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
 +        ii++;
 +        if (find_id == ii) {
 +            struct vote_option *vOpt = iter_data(it);
 +            found = vOpt->option_id;
 +            char str[50];
 +            sprintf(str,"%i",vOpt->option_id);
 +            dict_remove(cData->vote_options, str);
 +        }
 +    }
 +    
 +    if(found > 0) {
 +        for(uData = channel->channel_info->users; uData; uData = uData->next) {
 +            if(uData->votefor == found) {
 +                uData->voted = 0;
 +                uData->votefor = 0;
 +            }
 +        }
 +        reply("CSMSG_DELOPTION_DONE");
 +        return 1;
 +    } else {
 +        reply("CSMSG_DELOPTION_NONE");
 +        return 0;
 +    }
 +}
 +
 +static CHANSERV_FUNC(cmd_vote)
 +{
 +    struct chanData *cData = channel->channel_info;
 +    struct userData *target;
 +    struct handle_info *hi;
 +    unsigned int votedfor = 0;
 +    char *votedfor_str = NULL;
 +    
 +    if (!cData || !cData->vote) {
 +        reply("CSMSG_NO_VOTE");
 +        return 0;
 +    }
 +    if(argc > 1 && cData->vote_start) {
 +        hi = user->handle_info;
 +        if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
 +        {
 +            reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
 +            return 0;
 +        }
 +        if(!check_user_level(channel, user, lvlVote, 1, 0)) {
 +            reply("CSMSG_NO_ACCESS");
 +            return 0;
 +        }
 +        if(target->voted) {
 +            reply("CSMSG_VOTE_VOTED");
 +            return 0;
 +        }
 +        int find_id = atoi(argv[1]);
 +        int ii = 0;
 +        dict_iterator_t it;
 +        for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
 +            ii++;
 +            if (find_id == ii) {
 +                struct vote_option *vOpt = iter_data(it);
 +                vOpt->voted++;
 +                target->voted = 1;
 +                target->votefor = vOpt->option_id;
 +                votedfor = vOpt->option_id;
 +                votedfor_str = vOpt->name;
 +            }
 +        }
 +        if(votedfor == 0) {
 +            reply("CSMSG_VOTE_INVALID");
 +            return 0;
 +        }
 +    }
 +    if (!cData->vote_start) {
 +        reply("CSMSG_VOTE_NOT_STARTED");
 +    }
 +    reply("CSMSG_VOTE_QUESTION",cData->vote);
 +    
 +    unsigned int voteid = 0;
 +    dict_iterator_t it;
 +    
 +    for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
 +        struct vote_option *vOpt = iter_data(it);
 +        voteid++;
 +        reply("CSMSG_VOTE_OPTION",voteid,vOpt->name,vOpt->voted);
 +    }
 +    if(argc > 1 && cData->vote_start && votedfor_str) {
 +        reply("CSMSG_VOTE_DONE",votedfor_str);
 +    }
 +    return 1;
 +}
 +
 +static CHANSERV_FUNC(cmd_startvote)
 +{
 +    struct chanData *cData = channel->channel_info;
 +    struct userData *target;
 +    struct handle_info *hi;
 +    if (!cData) return 0;
 +    hi = user->handle_info;
 +    if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
 +    {
 +        reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
 +        return 0;
 +    }
 +    if(target->access < 300) {
 +        reply("CSMSG_NO_ACCESS");
 +        return 0;
 +    }
 +    if (!cData->vote) {
 +        reply("CSMSG_NO_VOTE");
 +        return 0;
 +    }
 +    if(cData->vote_start) {
 +        reply("CSMSG_STARTVOTE_RUNNING");
 +        return 0;
 +    }
 +    if(dict_size(cData->vote_options) < 2) {
 +        reply("CSMSG_VOTE_NEED_OPTIONS");
 +        return 0;
 +    }
 +    cData->vote_start = 1;
 +    char response[MAXLEN];
 +    sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_TOP"), user->nick);
 +    irc_privmsg(cmd->parent->bot, channel->name, response);
 +    sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_QUESTION"), cData->vote);
 +    irc_privmsg(cmd->parent->bot, channel->name, response);
 +    unsigned int voteid = 0;
 +    dict_iterator_t it;
 +    for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
 +        struct vote_option *vOpt = iter_data(it);
 +        voteid++;
 +        sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_OPTION"), voteid, vOpt->name);
 +        irc_privmsg(cmd->parent->bot, channel->name, response);
 +    }
 +    sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_ACCESS"), cData->lvlOpts[lvlVote]); //Todo
 +    irc_privmsg(cmd->parent->bot, channel->name, response);
 +    sprintf(response, user_find_message(user, "CSMSG_STARTVOTE_HOWTO")); //Todo
 +    irc_privmsg(cmd->parent->bot, channel->name, response);
 +    return 1;
 +}
 +
 +static CHANSERV_FUNC(cmd_endvote)
 +{
 +    struct chanData *cData = channel->channel_info;
 +    struct userData *target;
 +    struct handle_info *hi;
 +    if (!cData) return 0;
 +    hi = user->handle_info;
 +    if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
 +    {
 +        reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
 +        return 0;
 +    }
 +    if(target->access < 300) {
 +        reply("CSMSG_NO_ACCESS");
 +        return 0;
 +    }
 +    if (!cData->vote) {
 +        reply("CSMSG_NO_VOTE");
 +        return 0;
 +    }
 +    if(!cData->vote_start) {
 +        reply("CSMSG_ENDVOTE_STOPPED");
 +        return 0;
 +    }
 +    cData->vote_start = 0;
 +    reply("CSMSG_ENDVOTE_DONE");
 +    return 1;
 +}
 +
 +static CHANSERV_FUNC(cmd_voteresults)
 +{
 +    struct chanData *cData = channel->channel_info;
 +    struct userData *target;
 +    struct handle_info *hi;
 +    if (!cData) return 0;
 +    if (!cData->vote) {
 +        reply("CSMSG_NO_VOTE");
 +        return 0;
 +    }
 +    if (argc > 1 && !irccasecmp(argv[1], "*")) {
 +        hi = user->handle_info;
 +        if(!(target = GetTrueChannelAccess(channel->channel_info, hi)))
 +        {
 +            reply("CSMSG_NO_CHAN_USER", hi->handle, channel->name);
 +            return 0;
 +        }
 +        if(target->access < 300) {
 +            reply("CSMSG_NO_ACCESS");
 +            return 0;
 +        }
 +        char response[MAXLEN];
 +        sprintf(response, user_find_message(user, "CSMSG_VOTERES_QUESTION"), cData->vote);
 +        irc_privmsg(cmd->parent->bot, channel->name, response);
 +        unsigned int voteid = 0;
 +        dict_iterator_t it;
 +        for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
 +            struct vote_option *vOpt = iter_data(it);
 +            voteid++;
 +            sprintf(response, user_find_message(user, "CSMSG_VOTERES_OPTION"), voteid, vOpt->name, vOpt->voted);
 +            irc_privmsg(cmd->parent->bot, channel->name, response);
 +        }
 +    } else {
 +        reply("CSMSG_VOTE_QUESTION",cData->vote);
 +        unsigned int voteid = 0;
 +       dict_iterator_t it;
 +        for (it = dict_first(cData->vote_options); it; it = iter_next(it)) {
 +            struct vote_option *vOpt = iter_data(it);
 +            voteid++;
 +            reply("CSMSG_VOTE_OPTION",voteid,vOpt->name,vOpt->voted);
 +        }
 +    }
 +    return 1;
 +}
 +
  static void
  chanserv_refresh_topics(UNUSED_ARG(void *data))
  {
@@@ -7103,16 -6387,6 +7049,16 @@@ handle_new_channel(struct chanNode *cha
          SetChannelTopic(channel, chanserv, channel->channel_info->topic, 1);
  }
  
 +void handle_new_channel_created(char *chan, struct userNode *user) {
 +    if(user->handle_info && chanserv_conf.new_channel_authed) {
 +        send_target_message(5, chan, chanserv, "%s", chanserv_conf.new_channel_authed);
 +    } else if(!user->handle_info && chanserv_conf.new_channel_unauthed) {
 +        send_target_message(5, chan, chanserv, "%s", chanserv_conf.new_channel_unauthed);
 +    }
 +    if(chanserv_conf.new_channel_msg)
 +        send_target_message(5, chan, chanserv, "%s", chanserv_conf.new_channel_msg);
 +}
 +
  /* Welcome to my worst nightmare. Warning: Read (or modify)
     the code below at your own risk. */
  static int
@@@ -7127,23 -6401,13 +7073,23 @@@ handle_join(struct modeNode *mNode
      struct handle_info *handle;
      unsigned int modes = 0, info = 0;
      char *greeting;
 +    unsigned int i = 0;
  
      if(IsLocal(user) || !channel->channel_info || IsSuspended(channel->channel_info))
          return 0;
  
      cData = channel->channel_info;
 -    if(channel->members.used > cData->max)
 +    if(channel->members.used > cData->max) {
          cData->max = channel->members.used;
 +        cData->max_time = now;
 +    }
 +
 +    for(i = 0; i < channel->invited.used; i++)
 +    {
 +        if(channel->invited.list[i] == user) {
 +            userList_remove(&channel->invited, user);
 +        }
 +    }
  
      /* Check for bans.  If they're joining through a ban, one of two
       * cases applies:
@@@ -7709,8 -6973,6 +7655,8 @@@ chanserv_conf_read(void
      chanserv_conf.channel_expire_delay = str ? ParseInterval(str) : 86400*30;
      str = database_get_data(conf_node, KEY_DNR_EXPIRE_FREQ, RECDB_QSTRING);
      chanserv_conf.dnr_expire_frequency = str ? ParseInterval(str) : 3600;
 +    str = database_get_data(conf_node, KEY_INVITED_INTERVAL, RECDB_QSTRING);
 +    chanserv_conf.invited_timeout = str ? ParseInterval(str) : 600*2;
      str = database_get_data(conf_node, KEY_NODELETE_LEVEL, RECDB_QSTRING);
      chanserv_conf.nodelete_level = str ? atoi(str) : 1;
      str = database_get_data(conf_node, KEY_MAX_CHAN_USERS, RECDB_QSTRING);
      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_NEW_CHANNEL_AUTHED, RECDB_QSTRING);
 +    chanserv_conf.new_channel_authed = str ? str : NULL;
 +    str = database_get_data(conf_node, KEY_NEW_CHANNEL_UNAUTHED, RECDB_QSTRING);
 +    chanserv_conf.new_channel_unauthed = str ? str : NULL;
 +    str = database_get_data(conf_node, KEY_NEW_CHANNEL_MSG, RECDB_QSTRING);
 +    chanserv_conf.new_channel_msg = str ? str : NULL;
      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|MCP_NO_APASS, 0))
 +    if((change = mod_chanmode_parse(NULL, NULL, modes, ii, MCP_KEY_FREE|MCP_NO_APASS, 0))
         && (change->argc < 2))
      {
          chanserv_conf.default_modes = *change;
              /* multiple choice options */
              "CtcpReaction", "Protect", "Toys", "TopicRefresh",
              /* binary options */
 -            "DynLimit", "NoDelete",
 +            "DynLimit", "NoDelete", "expire", "Vote",
              /* delimiter */
              NULL
          };
@@@ -7865,33 -7121,12 +7811,33 @@@ chanserv_note_type_read(const char *key
      ntype->max_length = str ? strtoul(str, NULL, 0) : 400;
  }
  
 +static void
 +vote_option_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
 +{
 +    struct vote_option *vOpt;
 +    char *str;
 +    
 +    if(rd->type != RECDB_OBJECT || !dict_size(rd->d.object))
 +    {
 +        log_module(CS_LOG, LOG_ERROR, "Invalid vote option in %s.", chan->channel->name);
 +        return;
 +    }
 +    
 +    vOpt = calloc(1, sizeof(*vOpt));
 +    vOpt->name = strdup(database_get_data(rd->d.object, KEY_VOTE_OPTION_NAME, RECDB_QSTRING));
 +    str = database_get_data(rd->d.object, KEY_VOTE_OPTION_VOTED, RECDB_QSTRING);
 +    vOpt->voted = str ? atoi(str) : 0;
 +    vOpt->option_id = str ? atoi(key) : 0;
 +    vOpt->option_str = strdup(key);
 +    dict_insert(chan->vote_options,vOpt->option_str,vOpt);
 +}
 +
  static void
  user_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
  {
      struct handle_info *handle;
      struct userData *uData;
 -    char *seen, *inf, *flags;
 +    char *seen, *inf, *flags, *voted, *votefor;
      unsigned long last_seen;
      unsigned short access_level;
  
      seen = database_get_data(rd->d.object, KEY_SEEN, RECDB_QSTRING);
      last_seen = seen ? strtoul(seen, NULL, 0) : now;
      flags = database_get_data(rd->d.object, KEY_FLAGS, RECDB_QSTRING);
 +    voted = database_get_data(rd->d.object, KEY_VOTE_VOTED, RECDB_QSTRING);
 +    votefor = database_get_data(rd->d.object, KEY_VOTE_VOTEDFOR, RECDB_QSTRING);
      handle = get_handle_info(key);
      if(!handle)
      {
  
      uData = add_channel_user(chan, handle, access_level, last_seen, inf);
      uData->flags = flags ? strtoul(flags, NULL, 0) : 0;
 +    if(chan->vote) {
 +        uData->voted = voted ? strtoul(voted, NULL, 0) : 0;
 +        uData->votefor = votefor ? strtoul(votefor, NULL, 0) : 0;
 +    } else {
 +        uData->voted = 0;
 +        uData->votefor = 0;
 +    }
  }
  
  static void
  ban_read_helper(const char *key, struct record_data *rd, struct chanData *chan)
  {
-     struct banData *bData;
      char *set, *triggered, *s_duration, *s_expires, *reason, *owner;
      unsigned long set_time, triggered_time, expires_time;
  
      if(!reason || (expires_time && (expires_time < now)))
          return;
  
-     bData = add_channel_ban(chan, key, owner, set_time, triggered_time, expires_time, reason);
+     add_channel_ban(chan, key, owner, set_time, triggered_time, expires_time, reason);
  }
  
  static struct suspended *
@@@ -8082,20 -7307,6 +8027,20 @@@ chanserv_channel_read(const char *key, 
              cData->chOpts[chOpt] = ((count <= charOptions[chOpt].old_idx) ? str : CHANNEL_DEFAULT_OPTIONS)[charOptions[chOpt].old_idx];
      }
  
 +    if((str = database_get_data(hir->d.object, KEY_EXPIRE, RECDB_QSTRING)))
 +    {
 +        cData->expiry = atoi(str);
 +        if(cData->expiry > 0) {
 +            if(cData->expiry > now) {
 +                timeq_add(cData->expiry, chanserv_expire_channel, cData);
 +            } else {
 +                timeq_add(1, chanserv_expire_channel, cData);
 +            }
 +        }
 +    } else {
 +        cData->expiry = 0;
 +    }
 +
      if((obj = database_get_data(hir->d.object, KEY_SUSPENDED, RECDB_OBJECT)))
      {
          suspended = chanserv_read_suspended(obj);
      cData->ownerTransfer = str ? strtoul(str, NULL, 0) : 0;
      str = database_get_data(channel, KEY_MAX, RECDB_QSTRING);
      cData->max = str ? atoi(str) : 0;
 +    str = database_get_data(channel, KEY_MAX_TIME, RECDB_QSTRING);
 +    cData->max_time = str ? atoi(str) : 0;
      str = database_get_data(channel, KEY_GREETING, RECDB_QSTRING);
      cData->greeting = str ? strdup(str) : NULL;
      str = database_get_data(channel, KEY_USER_GREETING, RECDB_QSTRING);
      str = database_get_data(channel, KEY_TOPIC, RECDB_QSTRING);
      cData->topic = str ? strdup(str) : NULL;
  
 +    str = database_get_data(channel, KEY_VOTE, RECDB_QSTRING);
 +    if(str) {
 +        cData->vote = str ? strdup(str) : NULL;
 +        dict_delete(cData->vote_options);
 +        cData->vote_options = dict_new();
 +        dict_set_free_data(cData->vote_options, free_vote_options);
 +        str = database_get_data(channel, KEY_VOTE_START, RECDB_QSTRING);
 +        cData->vote_start = str ? atoi(str) : 0;
 +        obj = database_get_data(channel, KEY_VOTE_OPTIONS, RECDB_OBJECT);
 +        for(it = dict_first(obj); it; it = iter_next(it)) {
 +            vote_option_read_helper(iter_key(it), iter_data(it), cData);
 +        }
 +    }
 +
      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|MCP_NO_APASS, 0))) {
 +       && (modes = mod_chanmode_parse(cNode, NULL, argv, argc, MCP_KEY_FREE|MCP_NO_APASS, 0))) {
          cData->modes = *modes;
          if(off_channel > 0)
            cData->modes.modes_set |= MODE_REGISTERED;
@@@ -8299,10 -7494,6 +8244,10 @@@ chanserv_write_users(struct saxdb_conte
          saxdb_write_int(ctx, KEY_SEEN, uData->seen);
          if(uData->flags)
              saxdb_write_int(ctx, KEY_FLAGS, uData->flags);
 +        if(uData->channel->vote && uData->voted)
 +            saxdb_write_int(ctx, KEY_VOTE_VOTED, uData->voted);
 +        if(uData->channel->vote && uData->votefor)
 +            saxdb_write_int(ctx, KEY_VOTE_VOTEDFOR, uData->votefor);
          if(uData->info)
              saxdb_write_string(ctx, KEY_INFO, uData->info);
          saxdb_end_record(ctx);
@@@ -8358,13 -7549,11 +8303,13 @@@ chanserv_write_channel(struct saxdb_con
      int high_present;
      enum levelOption lvlOpt;
      enum charOption chOpt;
 +    dict_iterator_t it;
  
      saxdb_start_record(ctx, channel->channel->name, 1);
  
      saxdb_write_int(ctx, KEY_REGISTERED, channel->registered);
      saxdb_write_int(ctx, KEY_MAX, channel->max);
 +    saxdb_write_int(ctx, KEY_MAX_TIME, channel->max_time);
      if(channel->topic)
          saxdb_write_string(ctx, KEY_TOPIC, channel->topic);
      if(channel->registrar)
          saxdb_write_string(ctx, KEY_TOPIC_MASK, channel->topic_mask);
      if(channel->suspended)
          chanserv_write_suspended(ctx, "suspended", channel->suspended);
 +    if(channel->expiry)
 +        saxdb_write_int(ctx, KEY_EXPIRE, channel->expiry);
 +
 +    if(channel->vote) {
 +        saxdb_write_string(ctx, KEY_VOTE, channel->vote);
 +        if(channel->vote_start)
 +            saxdb_write_int(ctx, KEY_VOTE_START, channel->vote_start);
 +        if (dict_size(channel->vote_options)) {
 +            saxdb_start_record(ctx, KEY_VOTE_OPTIONS, 1);
 +            for (it = dict_first(channel->vote_options); it; it = iter_next(it)) {
 +                struct vote_option *vOpt = iter_data(it);
 +                char str[50];
 +                sprintf(str,"%i",vOpt->option_id);
 +                saxdb_start_record(ctx, str, 0);
 +                if(vOpt->voted)
 +                    saxdb_write_int(ctx, KEY_VOTE_OPTION_VOTED, vOpt->voted);
 +                if(vOpt->name)
 +                    saxdb_write_string(ctx, KEY_VOTE_OPTION_NAME, vOpt->name);
 +                saxdb_end_record(ctx);
 +            }
 +            saxdb_end_record(ctx);
 +        }
 +    }
  
      saxdb_start_record(ctx, KEY_OPTIONS, 0);
      saxdb_write_int(ctx, KEY_FLAGS, channel->flags);
@@@ -8644,7 -7810,6 +8589,7 @@@ init_chanserv(const char *nick
      DEFINE_COMMAND(topic, 1, MODCMD_REQUIRE_REGCHAN, "template", "op", "flags", "+never_csuspend", NULL);
      DEFINE_COMMAND(mode, 1, MODCMD_REQUIRE_REGCHAN, "template", "op", NULL);
      DEFINE_COMMAND(inviteme, 1, MODCMD_REQUIRE_CHANNEL, "access", "1", NULL);
 +    DEFINE_COMMAND(invitemeall, 1, MODCMD_REQUIRE_AUTHED, NULL);
      DEFINE_COMMAND(invite, 1, MODCMD_REQUIRE_CHANNEL, "access", "master", NULL);
      DEFINE_COMMAND(set, 1, MODCMD_REQUIRE_CHANUSER, "access", "op", NULL);
      DEFINE_COMMAND(wipeinfo, 2, MODCMD_REQUIRE_CHANUSER, "access", "master", NULL);
      DEFINE_COMMAND(unf, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
      DEFINE_COMMAND(ping, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
      DEFINE_COMMAND(wut, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
-     DEFINE_COMMAND(8ball, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
-     DEFINE_COMMAND(d, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
+     DEFINE_COMMAND(8ball, 2, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
+     DEFINE_COMMAND(d, 2, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
      DEFINE_COMMAND(huggle, 1, 0, "flags", "+nolog,+toy,+acceptchan", NULL);
 -
 +    
 +    DEFINE_COMMAND(addvote, 1, MODCMD_REQUIRE_AUTHED, NULL);
 +    DEFINE_COMMAND(delvote, 1, MODCMD_REQUIRE_AUTHED, NULL);
 +    DEFINE_COMMAND(addoption, 1, MODCMD_REQUIRE_AUTHED, NULL);
 +    DEFINE_COMMAND(deloption, 1, MODCMD_REQUIRE_AUTHED, NULL);
 +    DEFINE_COMMAND(vote, 1, MODCMD_REQUIRE_AUTHED, NULL);
 +    DEFINE_COMMAND(startvote, 1, MODCMD_REQUIRE_AUTHED, NULL);
 +    DEFINE_COMMAND(endvote, 1, MODCMD_REQUIRE_AUTHED, NULL);
 +    DEFINE_COMMAND(voteresults, 1, MODCMD_REQUIRE_AUTHED, NULL);
 +
 +    DEFINE_COMMAND(opme, 1, MODCMD_REQUIRE_AUTHED|MODCMD_REQUIRE_CHANNEL, NULL);
 +    
      /* Channel options */
      DEFINE_CHANNEL_OPTION(defaulttopic);
      DEFINE_CHANNEL_OPTION(topicmask);
      DEFINE_CHANNEL_OPTION(userinfo);
      DEFINE_CHANNEL_OPTION(dynlimit);
      DEFINE_CHANNEL_OPTION(topicsnarf);
 +    DEFINE_CHANNEL_OPTION(vote);
      DEFINE_CHANNEL_OPTION(nodelete);
      DEFINE_CHANNEL_OPTION(toys);
      DEFINE_CHANNEL_OPTION(setters);
      DEFINE_CHANNEL_OPTION(ctcpreaction);
      DEFINE_CHANNEL_OPTION(inviteme);
      DEFINE_CHANNEL_OPTION(unreviewed);
 +    modcmd_register(chanserv_module, "set expire", chan_opt_expire, 1, 0, "flags", "+helping", NULL);
      modcmd_register(chanserv_module, "set unreviewed on", NULL, 0, 0, "flags", "+helping", NULL);
      modcmd_register(chanserv_module, "set unreviewed off", NULL, 0, 0, "flags", "+oper", NULL);
      if(off_channel > 1)
diff --combined src/helpfile.c
index b8116b225234b80ba40d57d75672ce9d29c8c1ac,de67b2cc7736ca1db94c920572c1c1828d0850e7..aaf0d80ab961eed5048225f3d241f536ff4a106b
@@@ -23,7 -23,6 +23,7 @@@
  #include "log.h"
  #include "modcmd.h"
  #include "nickserv.h"
 +#include "spamserv.h"
  
  #if defined(HAVE_DIRENT_H)
  #include <dirent.h>
@@@ -42,7 -41,7 +42,7 @@@ static const struct message_entry msgta
  #define DEFAULT_LINE_SIZE       MAX_LINE_SIZE
  #define DEFAULT_TABLE_SIZE      80
  
 -extern struct userNode *global, *chanserv, *opserv, *nickserv;
 +extern struct userNode *global, *chanserv, *opserv, *nickserv, *spamserv;
  struct userNode *message_dest;
  struct userNode *message_source;
  struct language *lang_C;
@@@ -394,7 -393,7 +394,7 @@@ vsend_message(const char *dest, struct 
      void (*irc_send)(struct userNode *from, const char *to, const char *msg);
      static struct string_buffer input;
      unsigned int size, ipos, pos, length, chars_sent, use_color;
-     unsigned int expand_pos, expand_ipos, newline_ipos;
+     unsigned int expand_ipos, newline_ipos;
      char line[MAX_LINE_SIZE];
  
      if (IsChannelName(dest) || *dest == '$') {
       * that requires a very big intermediate buffer.
       */
      expand_ipos = newline_ipos = ipos = 0;
-     expand_pos = pos = 0;
+     pos = 0;
      chars_sent = 0;
      while (input.list[ipos]) {
          char ch, *value, *free_value;
          case 'N':
              value = nickserv ? nickserv->nick : "NickServ";
              break;
 +        case 'X':
 +            value = spamserv ? spamserv->nick : "SpamServ";
 +            break;
          case 's':
              value = self->name;
              break;
 -        case 'H':
 +        case 'A':
              value = handle ? handle->handle : "Account";
              break;
 +        case 'U':
 +            value = message_dest ? message_dest->nick : "Nick";
 +            break;
 +        case 'I':
 +            value = message_dest ? (IsFakeIdent(message_dest) ? message_dest->fakeident : message_dest->ident) : "Ident";
 +            break;
 +        case 'H':
 +            value = message_dest ? (IsFakeHost(message_dest) ? message_dest->fakehost : message_dest->hostname) : "Hostname";
 +            break;
  #define SEND_LINE(TRUNCED) do { \
      line[pos] = 0; \
      if (pos > 0) { \
              free(free_value);
          pos += length;
          if ((pos < size-1) && input.list[ipos]) {
-             expand_pos = pos;
              expand_ipos = ipos;
              continue;
          }
        send_line:
-         expand_pos = pos;
          expand_ipos = ipos;
          SEND_LINE(0);
  #undef SEND_LINE
diff --combined src/mod-helpserv.c
index 384339db9e5db67037f94d8aec3467145590146c,c9eec1b025d4dfec3464f0d0522c0dde1241280e..7f645a9d73d68b4c646f07cf11f063fc17d45336
@@@ -53,7 -53,6 +53,7 @@@ const char *helpserv_module_deps[] = { 
  #define KEY_NICK "nick"
  #define KEY_DB_BADCHANS "badchans"
  #define KEY_HELP_CHANNEL "help_channel"
 +#define KEY_PUBLIC_CHANNEL "public_channel"
  #define KEY_PAGE_DEST "page_dest"
  #define KEY_CMDWORD "cmdword"
  #define KEY_PERSIST_LENGTH "persist_length"
@@@ -86,7 -85,6 +86,7 @@@
  #define KEY_PRIVMSG_ONLY "privmsg_only"
  #define KEY_REQ_ON_JOIN "req_on_join"
  #define KEY_AUTO_VOICE "auto_voice"
 +#define KEY_AUTO_JOIN "auto_join"
  #define KEY_AUTO_DEVOICE "auto_devoice"
  #define KEY_LAST_ACTIVE "last_active"
  
@@@ -172,9 -170,7 +172,9 @@@ static const struct message_entry msgta
      { "HSMSG_SET_PRIVMSGONLY",    "$bPrivmsgOnly     $b %s" },
      { "HSMSG_SET_REQONJOIN",      "$bReqOnJoin       $b %s" },
      { "HSMSG_SET_AUTOVOICE",      "$bAutoVoice       $b %s" },
 +    { "HSMSG_SET_AUTOJOIN",       "$bAutoJoin        $b %s" },
      { "HSMSG_SET_AUTODEVOICE",    "$bAutoDevoice     $b %s" },
 +                              { "HSMSG_SET_PUBLICCHAN",     "$bPublicChan      $b %s" },
      { "HSMSG_PAGE_NOTICE", "notice" },
      { "HSMSG_PAGE_PRIVMSG", "privmsg" },
      { "HSMSG_PAGE_ONOTICE", "onotice" },
@@@ -504,7 -500,6 +504,7 @@@ struct helpserv_bot 
      struct userNode *helpserv;
  
      struct chanNode *helpchan;
 +    struct chanNode *publicchan;
  
      struct chanNode *page_targets[PGSRC_COUNT];
      enum page_type page_types[PGSRC_COUNT];
      unsigned int privmsg_only : 1;
      unsigned int req_on_join : 1;
      unsigned int auto_voice : 1;
 +    unsigned int auto_join : 1;
      unsigned int auto_devoice : 1;
  
      unsigned int helpchan_empty : 1;
@@@ -1066,11 -1060,8 +1066,11 @@@ static void helpserv_usermsg(struct use
              helpserv_msguser(user, "HSMSG_USERCMD_NO_REQUEST");
              return;
          }
 -        if ((hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_PART) && !GetUserMode(hs->helpchan, user)) {
 -            helpserv_msguser(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", hs->helpchan->name);
 +        if ((hs->persist_lengths[PERSIST_T_REQUEST] == PERSIST_PART) && !GetUserMode(hs->helpchan, user) && (!hs->publicchan || (hs->publicchan && !GetUserMode(hs->publicchan, user)))) {
 +             if(hs->publicchan)
 +                helpserv_msguser(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", hs->publicchan->name);
 +             else
 +                helpserv_msguser(user, "HSMSG_REQ_YOU_NOT_IN_HELPCHAN_OPEN", hs->helpchan->name);
              return;
          }
  
@@@ -1371,7 -1362,7 +1371,7 @@@ static void helpserv_del_user(struct he
  }
  
  static int cmd_add_user(struct helpserv_bot *hs, int from_opserv, struct userNode *user, enum helpserv_level level, int argc, char *argv[]) {
-     struct helpserv_user *actor, *new_user;
+     struct helpserv_user *actor;
      struct handle_info *handle;
  
      REQUIRE_PARMS(2);
          return 0;
      }
  
-     new_user = helpserv_add_user(hs, handle, level);
+     helpserv_add_user(hs, handle, level);
  
      helpserv_notice(user, "HSMSG_ADDED_USER", helpserv_level2str(level), handle->handle);
      return 1;
@@@ -1911,11 -1902,7 +1911,11 @@@ static int helpserv_assign(int from_ops
      req->helper = GetHSUser(hs, user->handle_info);
      assert(req->helper);
      req->assigned = now;
 -
 +    
 +    if (req->user && hs->auto_join) {
 +        irc_svsjoin(hs->helpserv,req->user,hs->helpchan);
 +    }
 +    
      if (old_helper) {
          helpserv_notice(user, "HSMSG_REQ_REASSIGNED", req->id, old_helper->handle->handle);
          req->helper->reassigned_to[0]++;
          if ((change.args[0].u.member = GetUserMode(hs->helpchan, req->user)))
              mod_chanmode_announce(hs->helpserv, hs->helpchan, &change);
      }
 -
 +    
      return 1;
  }
  
@@@ -2708,6 -2695,7 +2708,6 @@@ static struct helpserv_bot *register_he
          dict_insert(helpserv_bots_bychan_dict, hs->helpchan->name, botlist);
      }
      helpserv_botlist_append(botlist, hs);
 -
      return hs;
  }
  
@@@ -2715,7 -2703,7 +2715,7 @@@ static HELPSERV_FUNC(cmd_register) 
      char *nick, *helpchan, reason[MAXLEN];
      struct handle_info *handle;
  
 -    REQUIRE_PARMS(4);
 +    REQUIRE_PARMS(3);
      nick = argv[1];
      if (!is_valid_nick(nick)) {
          helpserv_notice(user, "HSMSG_ILLEGAL_NICK", nick);
@@@ -2776,7 -2764,7 +2776,7 @@@ static void helpserv_free_bot(void *dat
  }
  
  static void helpserv_unregister(struct helpserv_bot *bot, const char *quit_fmt, const char *global_fmt, const char *actor) {
 -    char reason[MAXLEN], channame[CHANNELLEN], botname[NICKLEN];
 +    char reason[MAXLEN], channame[CHANNELLEN], *botname;
      struct helpserv_botlist *botlist;
      size_t len;
  
      helpserv_botlist_remove(botlist, bot);
      if (!botlist->used)
          dict_remove(helpserv_bots_bychan_dict, bot->helpchan->name);
 -    len = strlen(bot->helpserv->nick) + 1;
 -    safestrncpy(botname, bot->helpserv->nick, len);
 +    botname=bot->helpserv->nick;
      len = strlen(bot->helpchan->name) + 1;
      safestrncpy(channame, bot->helpchan->name, len);
      snprintf(reason, sizeof(reason), quit_fmt, actor);
@@@ -3302,72 -3291,23 +3302,72 @@@ static HELPSERV_OPTION(opt_auto_voice) 
      OPTION_BINARY(hs->auto_voice, "HSMSG_SET_AUTOVOICE");
  }
  
 +static HELPSERV_OPTION(opt_auto_join) {
 +    OPTION_BINARY(hs->auto_join, "HSMSG_SET_AUTOJOIN");
 +}
 +
  static HELPSERV_OPTION(opt_auto_devoice) {
      OPTION_BINARY(hs->auto_devoice, "HSMSG_SET_AUTODEVOICE");
  }
  
 +static HELPSERV_OPTION(opt_publicchan) {
 + char *publicchan;
 + int changed=0;
 + if (argc > 0) {
 +  publicchan = argv[0];
 +              if(strcmp(publicchan, "*")) {
 +        if (!IsChannelName(publicchan)) {
 +    helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", publicchan);
 +    HELPSERV_SYNTAX();
 +    return 0;
 +   }
 +   if (opserv_bad_channel(publicchan)) {
 +    helpserv_notice(user, "HSMSG_ILLEGAL_CHANNEL", publicchan);
 +    return 0;
 +   }
 +              }
 +              if (!hs->publicchan || (hs->publicchan && irccasecmp(hs->publicchan->name, publicchan))) {
 +   if(hs->publicchan) {
 +                //there is another public chan o.O
 +                       //part
 +                       DelChannelUser(hs->helpserv, hs->publicchan, "unregistered.", 0);
 +                              hs->publicchan = NULL;
 +               }
 +                      changed = 1;
 +                      if(strcmp(publicchan, "*")) {
 +                       if (!(hs->publicchan = GetChannel(publicchan))) {
 +     hs->publicchan = AddChannel(publicchan, now, NULL, NULL);
 +     AddChannelUser(hs->helpserv, hs->publicchan)->modes |= MODE_CHANOP;
 +    } else {
 +     struct mod_chanmode change;
 +     mod_chanmode_init(&change);
 +     change.argc = 1;
 +     change.args[0].mode = MODE_CHANOP;
 +     change.args[0].u.member = AddChannelUser(hs->helpserv, hs->publicchan);
 +     mod_chanmode_announce(hs->helpserv, hs->publicchan, &change);
 +                       }
 +                      }
 +  }
 + } else {
 +       changed = 0;
 +      }
 +      helpserv_notice(user, "HSMSG_SET_PUBLICCHAN", (hs->publicchan) ? hs->publicchan->name : user_find_message(user,"MSG_NONE")); \
 + return changed;
 +}
 +
  static HELPSERV_FUNC(cmd_set) {
      helpserv_option_func_t *opt;
  
      if (argc < 2) {
          unsigned int i;
          helpserv_option_func_t *display[] = {
 -            opt_pagetarget_command, opt_pagetarget_alert, opt_pagetarget_status,
 +            opt_publicchan, opt_pagetarget_command, opt_pagetarget_alert, opt_pagetarget_status,
              opt_pagetype, opt_alert_page_type, opt_status_page_type,
              opt_greeting, opt_req_opened, opt_req_assigned, opt_req_closed,
              opt_idle_delay, opt_whine_delay, opt_whine_interval,
              opt_empty_interval, opt_stale_delay, opt_request_persistence,
              opt_helper_persistence, opt_notification, opt_id_wrap,
 -            opt_req_maxlen, opt_privmsg_only, opt_req_on_join, opt_auto_voice,
 +            opt_req_maxlen, opt_privmsg_only, opt_req_on_join, opt_auto_voice, opt_auto_join,
              opt_auto_devoice
          };
  
@@@ -3654,7 -3594,6 +3654,7 @@@ helpserv_bot_write(const char *key, voi
  
      /* Other settings and state */
      saxdb_write_string(ctx, KEY_HELP_CHANNEL, hs->helpchan->name);
 +    if(hs->publicchan) saxdb_write_string(ctx, KEY_PUBLIC_CHANNEL, hs->publicchan->name);
      slist = alloc_string_list(PGSRC_COUNT);
      for (pagesrc=0; pagesrc<PGSRC_COUNT; pagesrc++) {
          struct chanNode *target = hs->page_targets[pagesrc];
      saxdb_write_int(ctx, KEY_PRIVMSG_ONLY, hs->privmsg_only);
      saxdb_write_int(ctx, KEY_REQ_ON_JOIN, hs->req_on_join);
      saxdb_write_int(ctx, KEY_AUTO_VOICE, hs->auto_voice);
 +    saxdb_write_int(ctx, KEY_AUTO_JOIN, hs->auto_join);
      saxdb_write_int(ctx, KEY_AUTO_DEVOICE, hs->auto_devoice);
      saxdb_write_int(ctx, KEY_LAST_ACTIVE, hs->last_active);
  
@@@ -3711,7 -3649,7 +3711,7 @@@ helpserv_saxdb_write(struct saxdb_conte
  static int helpserv_bot_read(const char *key, void *data, UNUSED_ARG(void *extra)) {
      struct record_data *br = data, *raw_record;
      struct helpserv_bot *hs;
 -    char *registrar, *helpchannel_name, *str;
 +    char *registrar, *helpchannel_name, *publicchannel_name, *str;
      dict_t users, requests;
      enum page_source pagesrc;
      enum message_type msgtype;
  
      hs = register_helpserv(key, helpchannel_name, registrar);
  
 +                              publicchannel_name = database_get_data(GET_RECORD_OBJECT(br), KEY_PUBLIC_CHANNEL, RECDB_QSTRING);
 +    if (publicchannel_name) {
 +                               if(!IsChannelName(publicchannel_name)) {
 +        log_module(HS_LOG, LOG_ERROR, "%s has an invalid channel name.", key);
 +        return 0;
 +                                      } else {
 +                                       if (!(hs->publicchan = GetChannel(publicchannel_name))) {
 +       hs->publicchan = AddChannel(publicchannel_name, now, NULL, NULL);
 +       AddChannelUser(hs->helpserv, hs->publicchan)->modes |= MODE_CHANOP;
 +      } else {
 +       struct mod_chanmode change;
 +       mod_chanmode_init(&change);
 +       change.argc = 1;
 +       change.args[0].mode = MODE_CHANOP;
 +       change.args[0].u.member = AddChannelUser(hs->helpserv, hs->publicchan);
 +       mod_chanmode_announce(hs->helpserv, hs->publicchan, &change);
 +                                              }
 +                                      }
 +    }
      raw_record = dict_find(GET_RECORD_OBJECT(br), KEY_PAGE_DEST, NULL);
      switch (raw_record ? raw_record->type : RECDB_INVALID) {
      case RECDB_QSTRING:
      hs->req_on_join = str ? enabled_string(str) : 0;
      str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_VOICE, RECDB_QSTRING);
      hs->auto_voice = str ? enabled_string(str) : 0;
 +    str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_JOIN, RECDB_QSTRING);
 +    hs->auto_join = str ? enabled_string(str) : 0;
      str = database_get_data(GET_RECORD_OBJECT(br), KEY_AUTO_DEVOICE, RECDB_QSTRING);
      hs->auto_devoice = str ? enabled_string(str) : 0;
      str = database_get_data(GET_RECORD_OBJECT(br), KEY_LAST_ACTIVE, RECDB_QSTRING);
@@@ -3920,8 -3837,6 +3920,8 @@@ static void handle_part(struct modeNod
                  struct helpserv_request *req = iter_data(it);
  
                  if (mn->user != req->user)
 +                    continue;
 +                                                                                                                              if (GetUserMode(hs->helpchan, mn->user)) //publicchan
                      continue;
                  if (req->text->used) {
                      helpserv_message(hs, mn->user, MSGTYPE_REQ_DROPPED);
@@@ -4112,7 -4027,6 +4112,7 @@@ static void associate_requests_bybot(st
          if (!nicknewest || (nicknewest->opened < req->opened))
              nicknewest = req;
  
 +        
          if (hs->auto_voice && req->helper)
          {
              struct mod_chanmode change;
      if ((force_greet && nicknewest) || (newest && (nicknewest == newest))) {
          /* Let the user know. Either the user is forced to be greeted, or the
           * above has changed which request will get their next message. */
 -        helpserv_msguser(user, "HSMSG_GREET_EXISTING_REQ", hs->helpchan->name, nicknewest->id);
 +        //helpserv_msguser(user, "HSMSG_GREET_EXISTING_REQ", hs->helpchan->name, nicknewest->id);
      }
  }
  
@@@ -4744,9 -4658,7 +4744,9 @@@ int helpserv_init() 
      helpserv_define_option("PRIVMSGONLY", opt_privmsg_only);
      helpserv_define_option("REQONJOIN", opt_req_on_join);
      helpserv_define_option("AUTOVOICE", opt_auto_voice);
 +    helpserv_define_option("AUTOJOIN", opt_auto_join);
      helpserv_define_option("AUTODEVOICE", opt_auto_devoice);
 +                              helpserv_define_option("PUBLICCHAN", opt_publicchan);
  
      helpserv_usercmd_dict = dict_new();
      dict_insert(helpserv_usercmd_dict, "WAIT", usercmd_wait);
diff --combined src/modcmd.c
index 43a553155d2e5885824ae760a4f53737181c8608,9e7d15f72325673d737b473c4c624c1179a0e377..290d75c75c79ee09c3539f683c53d888f2bb8cef
@@@ -995,9 -995,10 +995,10 @@@ check_alias_args(char *argv[], unsigne
              continue;
          } else if (isdigit(argv[arg][1])) {
              char *end_num;
-             unsigned long tmp;
+             unsigned int tmp;
  
              tmp = strtoul(argv[arg]+1, &end_num, 10);
+             (void)tmp;
              switch (end_num[0]) {
              case 0:
                  continue;
@@@ -1586,7 -1587,7 +1587,7 @@@ static MODCMD_FUNC(cmd_stats_services) 
              service = iter_data(it);
              tbl.contents[ii] = calloc(tbl.width, sizeof(tbl.contents[ii][0]));
              tbl.contents[ii][0] = service->bot->nick;
 -            tbl.contents[ii][1] = strtab(dict_size(service->commands));
 +            tbl.contents[ii][1] = (service->commands && dict_size(service->commands) ? strtab(dict_size(service->commands)) : strtab(0));
              tbl.contents[ii][2] = service->privileged ? "yes" : "no";
              extra[ii*2] = service->trigger;
              tbl.contents[ii][3] = extra+ii*2;
@@@ -2417,8 -2418,6 +2418,8 @@@ create_default_binds(void) 
              struct svccmd *svccmd;
              svccmd = service_make_alias(service, "stats", "*modcmd.joiner", NULL);
              svccmd->min_opserv_level = 101;
 +            svccmd = service_make_alias(service, "devnull", "*modcmd.joiner", NULL);
 +            svccmd->min_opserv_level = 200;
              svccmd = service_make_alias(service, "service", "*modcmd.joiner", NULL);
              svccmd->min_opserv_level = 900;
          }
diff --combined src/nickserv.c
index d04e8efa53abdd1007c722f68fe48c95c65e56e8,f8941033ec820bfa388e0035707808e01ecdfc57..a370fd4463d923d00bb6e1dd12235190501c8de9
@@@ -85,8 -85,6 +85,8 @@@
  #define KEY_REGISTER_ON "register"
  #define KEY_LAST_SEEN "lastseen"
  #define KEY_INFO "info"
 +#define KEY_DEVNULL "devnull"
 +#define KEY_WEBSITE "website"
  #define KEY_USERLIST_STYLE "user_style"
  #define KEY_SCREEN_WIDTH "screen_width"
  #define KEY_LAST_AUTHED_HOST "last_authed_host"
@@@ -214,9 -212,6 +214,9 @@@ static const struct message_entry msgta
      { "NSMSG_HANDLEINFO_COOKIE_ALLOWAUTH", "  Cookie: There is currently an allowauth cookie issued for this account" },
      { "NSMSG_HANDLEINFO_COOKIE_UNKNOWN", "  Cookie: There is currently an unknown cookie issued for this account" },
      { "NSMSG_HANDLEINFO_INFOLINE", "  Infoline: %s" },
 +    { "NSMSG_HANDLEINFO_DEVNULL", "  DevNull Class: %s" },
 +    { "NSMSG_HANDLEINFO_WEBSITE", "  Website: %s" },
 +    { "NSMSG_HANDLEINFO_ACCESS", "  Access: %i" },
      { "NSMSG_HANDLEINFO_FLAGS", "  Flags: %s" },
      { "NSMSG_HANDLEINFO_EPITHET", "  Epithet: %s" },
      { "NSMSG_HANDLEINFO_FAKEIDENT", "  Fake ident: %s" },
      { "NSMSG_SETTING_LIST", "$b$N account settings:$b" },
      { "NSMSG_INVALID_OPTION", "$b%s$b is an invalid account setting." },
      { "NSMSG_SET_INFO", "$bINFO:         $b%s" },
 +    { "NSMSG_SET_DEVNULL", "$bDEVNULL:      $b%s" },
 +    { "NSMSG_SET_AUTOHIDE", "$bAUTOHIDE:     $b%s" },
 +    { "NSMSG_SET_WEBSITE", "$bWEBSITE:      $b%s" },
      { "NSMSG_SET_WIDTH", "$bWIDTH:        $b%d" },
      { "NSMSG_SET_TABLEWIDTH", "$bTABLEWIDTH:   $b%d" },
      { "NSMSG_SET_COLOR", "$bCOLOR:        $b%s" },
@@@ -471,8 -463,6 +471,8 @@@ register_handle(const char *handle, con
      dict_insert(nickserv_handle_dict, hi->handle, hi);
  
      hi->id = id;
 +    hi->website = NULL;
 +    hi->devnull = NULL;
      dict_insert(nickserv_id_dict, strdup(id_base64), hi);
  
      return hi;
@@@ -559,8 -549,6 +559,8 @@@ free_handle_info(void *vhi
      free(hi->infoline);
      free(hi->epithet);
      free(hi->fakehost);
 +    free(hi->devnull);
 +    free(hi->website);
      free(hi->fakeident);
      if (hi->cookie) {
          timeq_del(hi->cookie->expires, nickserv_free_cookie, hi->cookie, 0);
@@@ -797,7 -785,7 +797,7 @@@ valid_user_for(struct userNode *user, s
      unsigned int ii;
  
      /* If no hostmasks on the account, allow it. */
 -    if (!hi->masks->used)
 +    if (!hi->masks->used || IsDummy(user))
          return 1;
      /* If any hostmask matches, allow it. */
      for (ii=0; ii<hi->masks->used; ii++)
@@@ -903,10 -891,6 +903,10 @@@ generate_fakehost(struct handle_info *h
          /* A leading dot indicates the stored value is actually a title. */
          snprintf(buffer, sizeof(buffer), "%s.%s.%s", handle->handle, handle->fakehost+1, titlehost_suffix);
          return buffer;
 +    } else if (handle->fakehost[0] == '$') {
 +        /* A leading $ indicates the stored value begins with the user handle. */
 +        snprintf(buffer, sizeof(buffer), "%s%s", handle->handle, handle->fakehost+1);
 +        return buffer;
      }
      return handle->fakehost;
  }
@@@ -1411,12 -1395,6 +1411,12 @@@ static NICKSERV_FUNC(cmd_handleinfo
      }
  
      reply("NSMSG_HANDLEINFO_INFOLINE", (hi->infoline ? hi->infoline : nsmsg_none));
 +    if (oper_has_access(user, cmd->parent->bot, 200, 1))
 +        reply("NSMSG_HANDLEINFO_DEVNULL", (hi->devnull ? hi->devnull : nsmsg_none));
 +    if (user->handle_info && HANDLE_FLAGGED(user->handle_info, BOT))
 +        reply("NSMSG_HANDLEINFO_WEBSITE", (hi->website ? hi->website : nsmsg_none));
 +    if(hi->opserv_level > 0 && user->handle_info && HANDLE_FLAGGED(user->handle_info, BOT))
 +        reply("NSMSG_HANDLEINFO_ACCESS", hi->opserv_level);
      if (HANDLE_FLAGGED(hi, FROZEN))
          reply("NSMSG_HANDLEINFO_VACATION");
  
@@@ -1681,7 -1659,6 +1681,7 @@@ static NICKSERV_FUNC(cmd_rename_handle
      reply("NSMSG_HANDLE_CHANGED", old_handle, hi->handle);
      global_message(MESSAGE_RECIPIENT_STAFF, msgbuf);
      free(old_handle);
 +    apply_fakehost(hi, NULL);
      return 1;
  }
  
@@@ -1802,10 -1779,6 +1802,10 @@@ static NICKSERV_FUNC(cmd_auth
              return 1;
          }
      }
 +    if (HANDLE_FLAGGED(hi, AUTOHIDE)) {
 +        //ok  we have a fakehost set... but we need to set mode +x
 +        irc_svsmode(nickserv,user,"+x");
 +    }
  
      set_user_handle_info(user, hi, 1);
      if (nickserv_conf.email_required && !hi->email_addr)
      return 1;
  }
  
 +struct handle_info *checklogin(const char *user, const char *pass, const char *numeric, const char *hostmask, const char *ipmask)
 +{
 +    struct handle_info *hi;
 +    unsigned int match = 0, ii = 0;
 +    hi = dict_find(nickserv_handle_dict, user, NULL);
 +    if(!hi)
 +        return NULL;
 +    /* If no hostmasks on the account, allow it. */
 +    if (hi->masks->used) {
 +        /* If any hostmask matches, allow it. */
 +        for (ii=0; ii<hi->masks->used; ii++)
 +          if (match_ircglob(hostmask, hi->masks->list[ii]) || match_ircglob(ipmask, hi->masks->list[ii])) {
 +            match = 1;
 +            break;
 +          }
 +        if(!match) 
 +          return NULL;
 +    }
 +    if(!checkpass(pass, hi->passwd))
 +        return NULL;
 +    if (HANDLE_FLAGGED(hi, SUSPENDED))
 +        return NULL;
 +    /** following in one of the next commits
 +    struct last_login *login,*clogin,*old;
 +    unsigned int ii = 0;
 +    login = calloc(1, sizeof(*login));
 +    login->last_login = hi->last_login;
 +    login->hostmask = strdup(hostmask);
 +    login->authtime = now;
 +    login->quittime = 0;
 +    login->quit = NULL;
 +    login->user = NULL;
 +    login->loc_pending = strdup(numeric);
 +    for (clogin = hi->last_login; clogin != NULL && ii < 9; clogin = clogin->last_login) {
 +        if(ii == 8 && clogin->last_login) {
 +            old = clogin->last_login;
 +            clogin->last_login = NULL;
 +            free(old->hostmask);
 +            if(old->quit)
 +                free(old->quit);
 +            if(old->loc_pending)
 +                free(old->loc_pending);
 +            free(old);
 +        }
 +    }
 +    hi->last_login = login;
 +    */
 +    return hi;
 +}
 +
 +char *getfakehost(const char *user)
 +{
 +    struct handle_info *hi;
 +    hi = dict_find(nickserv_handle_dict, user, NULL);
 +    if(!hi)
 +        return 0;
 +    return generate_fakehost(hi);
 +}
 +
  static allowauth_func_t *allowauth_func_list;
  static unsigned int allowauth_func_size = 0, allowauth_func_used = 0;
  
@@@ -2357,7 -2271,7 +2357,7 @@@ set_list(struct userNode *user, struct 
      unsigned int i;
      char *set_display[] = {
          "INFO", "WIDTH", "TABLEWIDTH", "COLOR", "PRIVMSG", "STYLE",
 -        "EMAIL", "MAXLOGINS", "LANGUAGE"
 +        "EMAIL", "MAXLOGINS", "LANGUAGE", "DEVNULL"
      };
  
      send_message(user, nickserv, "NSMSG_SETTING_LIST");
@@@ -2432,80 -2346,6 +2432,80 @@@ static OPTION_FUNC(opt_info
      return 1;
  }
  
 +static OPTION_FUNC(opt_devnull)
 +{
 +    const char *devnull;
 +    
 +    if (argc > 1) {
 +        if (!override) {
 +            send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
 +            return 0;
 +        }
 +        if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
 +            free(hi->devnull);
 +            hi->devnull = NULL;
 +        } else {
 +            devnull = unsplit_string(argv+1, argc-1, NULL);
 +            if(devnull_check(devnull) == 1) { 
 +                hi->devnull = strdup(devnull);
 +            }
 +        }
 +    }
 +
 +    devnull = hi->devnull ? hi->devnull : user_find_message(user, "MSG_NONE");
 +    send_message(user, nickserv, "NSMSG_SET_DEVNULL", devnull);
 +    return 1;
 +}
 +
 +void nickserv_devnull_delete(char *name) {
 +    dict_iterator_t it;
 +    struct handle_info *hi;
 +
 +    for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
 +        hi = iter_data(it);
 +        if (hi->devnull && !irccasecmp(name, hi->devnull)) {
 +            free(hi->devnull);
 +            hi->devnull = NULL;
 +        }
 +    }
 +}
 +
 +void nickserv_devnull_rename(char *oldname, char *newname) {
 +    dict_iterator_t it;
 +    struct handle_info *hi;
 +
 +    for (it = dict_first(nickserv_handle_dict); it; it = iter_next(it)) {
 +        hi = iter_data(it);
 +        if (hi->devnull && !irccasecmp(oldname, hi->devnull)) {
 +            hi->devnull = strdup(newname);
 +        }
 +    }
 +}
 +
 +static OPTION_FUNC(opt_website)
 +{
 +    const char *website;
 +    
 +    if (argc > 1) {
 +        if (!HANDLE_FLAGGED(user->handle_info, BOT)) {
 +            send_message(user, nickserv, "MSG_SETTING_PRIVILEGED", argv[0]);
 +            return 0;
 +        }
 +        if ((argv[1][0] == '*') && (argv[1][1] == 0)) {
 +            free(hi->website);
 +            hi->website = NULL;
 +        } else {
 +            website = unsplit_string(argv+1, argc-1, NULL);
 +            hi->website = strdup(website);
 +        }
 +    }
 +    if (HANDLE_FLAGGED(user->handle_info, BOT)) {
 +        website = hi->website ? hi->website : user_find_message(user, "MSG_NONE");
 +        send_message(user, nickserv, "NSMSG_SET_WEBSITE", website);
 +    }
 +    return 1;
 +}
 +
  static OPTION_FUNC(opt_width)
  {
      if (argc > 1)
@@@ -2568,23 -2408,6 +2568,23 @@@ static OPTION_FUNC(opt_privmsg
      return 1;
  }
  
 +static OPTION_FUNC(opt_autohide)
 +{
 +    if (argc > 1) {
 +        if (enabled_string(argv[1]))
 +            HANDLE_SET_FLAG(hi, AUTOHIDE);
 +        else if (disabled_string(argv[1]))
 +            HANDLE_CLEAR_FLAG(hi, AUTOHIDE);
 +        else {
 +            send_message(user, nickserv, "MSG_INVALID_BINARY", argv[1]);
 +            return 0;
 +        }
 +    }
 +
 +    send_message(user, nickserv, "NSMSG_SET_AUTOHIDE", user_find_message(user, HANDLE_FLAGGED(hi, AUTOHIDE) ? "MSG_ON" : "MSG_OFF"));
 +    return 1;
 +}
 +
  static OPTION_FUNC(opt_style)
  {
      char *style;
@@@ -2940,12 -2763,10 +2940,10 @@@ static OPTION_FUNC(opt_fakeident
  
  static NICKSERV_FUNC(cmd_reclaim)
  {
-     struct handle_info *hi;
      struct nick_info *ni;
      struct userNode *victim;
  
      NICKSERV_MIN_PARMS(2);
-     hi = user->handle_info;
      ni = dict_find(nickserv_nick_dict, argv[1], 0);
      if (!ni) {
          reply("NSMSG_UNKNOWN_NICK", argv[1]);
@@@ -3251,10 -3072,6 +3249,10 @@@ nickserv_saxdb_write(struct saxdb_conte
          saxdb_write_int(ctx, KEY_ID, hi->id);
          if (hi->infoline)
              saxdb_write_string(ctx, KEY_INFO, hi->infoline);
 +        if (hi->devnull)
 +            saxdb_write_string(ctx, KEY_DEVNULL, hi->devnull);
 +        if (hi->website)
 +            saxdb_write_string(ctx, KEY_WEBSITE, hi->website);
          if (hi->last_quit_host[0])
              saxdb_write_string(ctx, KEY_LAST_QUIT_HOST, hi->last_quit_host);
          saxdb_write_int(ctx, KEY_LAST_SEEN, hi->lastseen);
@@@ -3444,8 -3261,6 +3442,8 @@@ struct nickserv_discrim 
      const char *hostmask;
      const char *fakehostmask;
      const char *fakeidentmask;
 +    const char *website;
 +    const char *devnullclass;
      const char *handlemask;
      const char *emailmask;
  };
@@@ -3551,18 -3366,6 +3549,18 @@@ nickserv_discrim_create(struct userNod
              } else {
                  discrim->fakeidentmask = argv[i];
              }
 +        } else if (!irccasecmp(argv[i], "website")) {
 +            if (!irccasecmp(argv[++i], "*")) {
 +                discrim->website = 0;
 +            } else {
 +                discrim->website = argv[i];
 +            }
 +        } else if (!irccasecmp(argv[i], "devnull")) {
 +            if (!irccasecmp(argv[++i], "*")) {
 +                discrim->devnullclass = 0;
 +            } else {
 +                discrim->devnullclass = argv[i];
 +            }
          } else if (!irccasecmp(argv[i], "handlemask") || !irccasecmp(argv[i], "accountmask")) {
              if (!irccasecmp(argv[++i], "*")) {
                  discrim->handlemask = 0;
@@@ -3639,8 -3442,6 +3637,8 @@@ nickserv_discrim_match(struct nickserv_
          || (discrim->handlemask && !match_ircglob(hi->handle, discrim->handlemask))
          || (discrim->fakehostmask && (!hi->fakehost || !match_ircglob(hi->fakehost, discrim->fakehostmask)))
          || (discrim->fakeidentmask && (!hi->fakeident || !match_ircglob(hi->fakeident, discrim->fakeidentmask)))
 +        || (discrim->website && (!hi->website || !match_ircglob(hi->website, discrim->website)))
 +        || (discrim->devnullclass && (!hi->devnull || !match_ircglob(hi->devnull, discrim->devnullclass)))
          || (discrim->emailmask && (!hi->email_addr || !match_ircglob(hi->email_addr, discrim->emailmask)))
          || (discrim->min_level > hi->opserv_level)
          || (discrim->max_level < hi->opserv_level)
@@@ -3887,12 -3688,6 +3885,12 @@@ nickserv_db_read_handle(const char *han
      str = database_get_data(obj, KEY_INFO, RECDB_QSTRING);
      if (str)
          hi->infoline = strdup(str);
 +    str = database_get_data(obj, KEY_WEBSITE, RECDB_QSTRING);
 +    if (str)
 +        hi->website = strdup(str);
 +    str = database_get_data(obj, KEY_DEVNULL, RECDB_QSTRING);
 +    if (str)
 +        hi->devnull = strdup(str);
      str = database_get_data(obj, KEY_REGISTER_ON, RECDB_QSTRING);
      hi->registered = str ? strtoul(str, NULL, 0) : now;
      str = database_get_data(obj, KEY_LAST_SEEN, RECDB_QSTRING);
@@@ -4517,12 -4312,9 +4515,12 @@@ init_nickserv(const char *nick
      dict_insert(nickserv_opt_dict, "COLOR", opt_color);
      dict_insert(nickserv_opt_dict, "PRIVMSG", opt_privmsg);
      dict_insert(nickserv_opt_dict, "STYLE", opt_style);
 +    dict_insert(nickserv_opt_dict, "AUTOHIDE", opt_autohide);
      dict_insert(nickserv_opt_dict, "PASS", opt_password);
      dict_insert(nickserv_opt_dict, "PASSWORD", opt_password);
      dict_insert(nickserv_opt_dict, "FLAGS", opt_flags);
 +    dict_insert(nickserv_opt_dict, "WEBSITE", opt_website);
 +    dict_insert(nickserv_opt_dict, "DEVNULL", opt_devnull);
      dict_insert(nickserv_opt_dict, "ACCESS", opt_level);
      dict_insert(nickserv_opt_dict, "LEVEL", opt_level);
      dict_insert(nickserv_opt_dict, "EPITHET", opt_epithet);
diff --combined src/proto-p10.c
index 4db33d8ae38f0d57ea39a45ecd34553bf499b582,9a8840071bc170712836e376368ee899cdf639c0..e4c96207161b52632542133572ef27e368a1ef35
@@@ -40,7 -40,6 +40,7 @@@
  #define CMD_EOB_ACK             "EOB_ACK"
  #define CMD_ERROR               "ERROR"
  #define CMD_FAKEHOST            "FAKE"
 +#define CMD_FAKEHOST2           "FAKE2"
  #define CMD_GET                 "GET"
  #define CMD_GLINE               "GLINE"
  #define CMD_HASH                "HASH"
@@@ -73,7 -72,6 +73,7 @@@
  #define CMD_PROTO               "PROTO"
  #define CMD_QUIT                "QUIT"
  #define CMD_REHASH              "REHASH"
 +#define CMD_RELAY               "RELAY"
  #define CMD_RESET               "RESET"
  #define CMD_RESTART             "RESTART"
  #define CMD_RPING               "RPING"
@@@ -88,8 -86,6 +88,8 @@@
  #define CMD_SQUIT               "SQUIT"
  #define CMD_STATS               "STATS"
  #define CMD_SVSNICK             "SVSNICK"
 +#define CMD_SVSMODE             "SVSMODE"
 +#define CMD_SVSJOIN             "SVSJOIN"
  #define CMD_TIME                "TIME"
  #define CMD_TOPIC               "TOPIC"
  #define CMD_TRACE               "TRACE"
  #define TOK_EOB_ACK             "EA"
  #define TOK_ERROR               "Y"
  #define TOK_FAKEHOST            "FA"
 +#define TOK_FAKEHOST2           "NFH"
  #define TOK_GET                 "GET"
  #define TOK_GLINE               "GL"
  #define TOK_HASH                "HASH"
  #define TOK_PROTO               "PROTO"
  #define TOK_QUIT                "Q"
  #define TOK_REHASH              "REHASH"
 +#define TOK_RELAY               "RL"
  #define TOK_RESET               "RESET"
  #define TOK_RESTART             "RESTART"
  #define TOK_RPING               "RI"
  #define TOK_SQUIT               "SQ"
  #define TOK_STATS               "R"
  #define TOK_SVSNICK             "SN"
 +#define TOK_SVSMODE             "SM"
 +#define TOK_SVSJOIN             "SJ"
  #define TOK_TIME                "TI"
  #define TOK_TOPIC               "T"
  #define TOK_TRACE               "TR"
  #define P10_EOB_ACK             TYPE(EOB_ACK)
  #define P10_ERROR               TYPE(ERROR)
  #define P10_FAKEHOST            TYPE(FAKEHOST)
 +#define P10_FAKEHOST2           TYPE(FAKEHOST2)
  #define P10_GET                 TYPE(GET)
  #define P10_GLINE               TYPE(GLINE)
  #define P10_HASH                TYPE(HASH)
  #define P10_PROTO               TYPE(PROTO)
  #define P10_QUIT                TYPE(QUIT)
  #define P10_REHASH              TYPE(REHASH)
 +#define P10_RELAY               TYPE(RELAY)
  #define P10_RESET               TYPE(RESET)
  #define P10_RESTART             TYPE(RESTART)
  #define P10_RPING               TYPE(RPING)
  #define P10_SQUIT               TYPE(SQUIT)
  #define P10_STATS               TYPE(STATS)
  #define P10_SVSNICK             TYPE(SVSNICK)
 +#define P10_SVSMODE             TYPE(SVSMODE)
 +#define P10_SVSJOIN             TYPE(SVSJOIN)
  #define P10_TIME                TYPE(TIME)
  #define P10_TOPIC               TYPE(TOPIC)
  #define P10_TRACE               TYPE(TRACE)
@@@ -305,8 -293,8 +305,8 @@@ static const char *his_servername
  static const char *his_servercomment;
  static struct channelList dead_channels;
  
 -/* These correspond to 1 << X:      012345678901234567 */
 -const char irc_user_mode_chars[] = "o iw dkgn    x   I";
 +/* These correspond to 1 << X:      012345678901234567890123 */
 +const char irc_user_mode_chars[] = "o iw dkgn    x   ISDXHst";
  
  static struct userNode *AddUser(struct server* uplink, const char *nick, const char *ident, const char *hostname, const char *modes, const char *numeric, const char *userinfo, unsigned long timestamp, const char *realip);
  
@@@ -505,25 -493,7 +505,25 @@@ irc_account(struct userNode *user, cons
  void
  irc_fakehost(struct userNode *user, const char *host, const char *ident, int force)
  {
 -    putsock("%s " P10_FAKEHOST " %s %s %s%s", self->numeric, user->numeric, ident, host, force ? " FORCE" : "");
 +    /* SRVX added the possibility for FAKE IDENTS
 +     * but this is currently *NOT* supported by our IRCu
 +     */
 +    int useNewFakehost = 0;
 +     
 +    if(useNewFakehost) putsock("%s " P10_FAKEHOST2 " %s %s %s%s", self->numeric, user->numeric, ident, host, force ? " FORCE" : "");
 +    else putsock("%s " P10_FAKEHOST " %s %s", self->numeric, user->numeric, host);
 +}
 +
 +void 
 +irc_relay(char *message)
 +{
 +    putsock("%s " P10_RELAY " %s", self->numeric, message);
 +}
 +
 +void 
 +irc_simul(struct userNode *target, char *command)
 +{
 +    putsock("%s " P10_RELAY " %s SI %s :%s", self->numeric, target->numeric, target->numeric, command);
  }
  
  void
@@@ -624,24 -594,6 +624,24 @@@ irc_privmsg(struct userNode *from, cons
          putsock("%s " P10_PRIVMSG " %s :%s", from->numeric, to, message);
  }
  
 +void
 +irc_svsmode(struct userNode *from, struct userNode *user, const char *modes)
 +{
 +putsock("%s " P10_SVSMODE " %s %s", from->numeric, user->numeric, modes);
 +}
 +
 +void
 +irc_svsjoin(struct userNode *from, struct userNode *user, struct chanNode *chan)
 +{
 +putsock("%s " P10_SVSJOIN " %s %s", from->numeric, user->numeric, chan->name);
 +}
 +
 +void
 +irc_svsjoinchan(struct userNode *from, struct userNode *user, const char *chan)
 +{
 +putsock("%s " P10_SVSJOIN " %s %s", from->numeric, user->numeric, chan);
 +}
 +
  void
  irc_eob(void)
  {
@@@ -702,16 -654,12 +702,16 @@@ irc_introduce(const char *passwd
  void
  irc_gline(struct server *srv, struct gline *gline)
  {
 +    //<prefix> GL <target> [!][+|-|>|<]<mask> [<expiration>] [<lastmod>] [<lifetime>] [:<reason>]
 +    //expiration = relative time (seconds)
 +    //lastmod = timestamp
 +    //livetime = timestamp
      if (gline->lastmod)
 -        putsock("%s " P10_GLINE " %s +%s %lu %lu %lu :%s",
 -                self->numeric, (srv ? srv->numeric : "*"), gline->target, gline->expires, gline->lastmod, gline->lifetime, gline->reason);
 +        putsock("%s " P10_GLINE " %s +%s %lu %lu %lu :%s", self->numeric, (srv ? srv->numeric : "*"), 
 +                gline->target, gline->expires-now, gline->lastmod, gline->lifetime, gline->reason);
      else
 -        putsock("%s " P10_GLINE " %s +%s %lu :%s",
 -                self->numeric, (srv ? srv->numeric : "*"), gline->target, gline->expires, gline->reason);
 +        putsock("%s " P10_GLINE " %s +%s %lu :%s", self->numeric, (srv ? srv->numeric : "*"), 
 +                gline->target, gline->expires-now, gline->reason);
  }
  
  void
@@@ -727,7 -675,6 +727,7 @@@ voi
  irc_ungline(const char *mask)
  {
      putsock("%s " P10_GLINE " * -%s %lu", self->numeric, mask, now);
 +    //putsock("%s " P10_GLINE " * %s * %lu", self->numeric, mask, now);
  }
  
  /* Return negative if *(struct modeNode**)pa is "less than" pb,
@@@ -1209,8 -1156,6 +1209,8 @@@ create_helper(char *name, void *data
          return;
      }
  
 +    handle_new_channel_created(name, cd->user);
 +
      AddChannelUser(cd->user, AddChannel(name, cd->when, NULL, NULL));
  }
  
@@@ -1344,7 -1289,7 +1344,7 @@@ static CMD_FUNC(cmd_burst
              int n_modes;
              for (pos=argv[next], n_modes = 1; *pos; pos++)
                  if ((*pos == 'k') || (*pos == 'l') || (*pos == 'A')
 -                    || (*pos == 'U'))
 +                    || (*pos == 'U') || (*pos == 'a') || (*pos == 'F'))
                      n_modes++;
              if (next + n_modes > argc)
                  n_modes = argc - next;
@@@ -1489,7 -1434,7 +1489,7 @@@ static CMD_FUNC(cmd_clearmode
  static CMD_FUNC(cmd_topic)
  {
      struct chanNode *cn;
-     unsigned long chan_ts, topic_ts;
+     unsigned long topic_ts;
  
      if (argc < 3)
          return 0;
      }
      if (argc >= 5) {
          /* Looks like an Asuka style topic burst. */
-         chan_ts = atoi(argv[2]);
          topic_ts = atoi(argv[3]);
      } else {
-         chan_ts = cn->timestamp;
          topic_ts = now;
      }
      SetChannelTopic(cn, GetUserH(origin), argv[argc-1], 0);
@@@ -1601,8 -1544,6 +1599,8 @@@ static CMD_FUNC(cmd_kick
  {
      if (argc < 3)
          return 0;
 +    if (GetUserN(argv[2]) && IsOper(GetUserN(argv[2])))
 +        operpart(GetChannel(argv[1]), GetUserN(argv[2]));
      ChannelUserKicked(GetUserH(origin), GetUserN(argv[2]), GetChannel(argv[1]));
      return 1;
  }
@@@ -1725,105 -1666,6 +1723,105 @@@ static CMD_FUNC(cmd_time
      return 1;
  }
  
 +static CMD_FUNC(cmd_relay)
 +{
 +    struct server *sNode;
 +    unsigned int len;
 +    char buf[3];
 +    //<sender> RELAY <destination> <command>
 +    len = strlen(argv[1]);
 +    buf[2] = 0;
 +    switch(len) {
 +        case 2:
 +            sNode = GetServerN(argv[1]);
 +            break;
 +        case 5:
 +            buf[0] = argv[1][0];
 +            buf[1] = argv[1][1];
 +            sNode = GetServerN(buf);
 +            break;
 +        case 6:
 +            buf[0] = argv[1][1];
 +            buf[1] = argv[1][2];
 +            sNode = GetServerN(buf);
 +            break;
 +        default:
 +            /* Invalid destination. Ignore. */
 +            return 0;
 +    }
 +    if(sNode->numeric == self->numeric) {
 +        //ok  someone relayed something to us!
 +        if(strcmp("LQ", argv[2]) == 0) {
 +            //oooh thats exciting - we've got a LOC Query! :D
 +            //LQ !ABADE pk910 80.153.5.212 server.zoelle1.de ~watchcat :test
 +            //ok  let's check the login datas
 +            struct handle_info *hi;
 +            char tmp[MAXLEN], tmp2[MAXLEN];
 +            sprintf(tmp, "%s@%s",argv[7],argv[6]);
 +            sprintf(tmp2, "%s@%s",argv[7],argv[5]);
 +            if((hi = checklogin(argv[4],argv[argc-1],&argv[3][1],tmp,tmp2))) {
 +             //login ok
 +             struct devnull_class *th;
 +             char devnull[512];
 +             if(hi->devnull && (th = devnull_get(hi->devnull))) {
 +                const char *devnull_modes = DEVNULL_MODES;
 +                int ii, flen;
 +                char flags[50];
 +                for (ii=flen=0; devnull_modes[ii]; ++ii)
 +                    if (th->modes & (1 << ii))
 +                        flags[flen++] = devnull_modes[ii];
 +                flags[flen] = 0;
 +                sprintf(devnull, "+%s %s %lu %lu",flags,th->name,th->maxchan,th->maxsendq);
 +             } else {
 +                devnull[0] = 0;
 +             }
 +             if(!HANDLE_FLAGGED(hi, AUTOHIDE)) {
 +                sprintf(tmp,"%s LA %s 0 %s\n",argv[3],hi->handle,devnull);
 +             } else if(getfakehost(argv[4])) {
 +                sprintf(tmp,"%s LA %s %s %s\n",argv[3],hi->handle,getfakehost(argv[4]),devnull);
 +             } else {
 +                extern const char *hidden_host_suffix;
 +                sprintf(tmp,"%s LA %s %s.%s %s\n",argv[3],hi->handle,hi->handle,hidden_host_suffix,devnull);
 +             }
 +             irc_relay(tmp);
 +            } else {
 +             //login rejected
 +             sprintf(tmp,"%s LR\n",argv[3]);
 +             irc_relay(tmp);
 +            }
 +        } else if(strcmp("UC", argv[2]) == 0) {
 +            char tmp[MAXLEN];
 +            sprintf(tmp,"%s UC %s %s",argv[3],argv[3],argv[4]);
 +            irc_relay(tmp);
 +        } else if(strcmp("JA", argv[2]) == 0) {
 +            struct userData *uData;
 +            struct chanNode *cn;
 +            struct userNode *user;
 +            char tmp[MAXLEN];
 +            cn = GetChannel(argv[4]);
 +            if (!cn) return 0;
 +            if (!(user = GetUserN(argv[3]))) return 0;
 +            if(!cn->channel_info) {
 +                //channel not registered
 +                sprintf(tmp,"%s JAA %s %s\n",argv[3],cn->name,argv[6]);
 +            } else if((uData = GetChannelUser(cn->channel_info, user->handle_info))) {
 +                if(uData->access >= atoi(argv[5])) {
 +                    //we can join
 +                    sprintf(tmp,"%s JAA %s %s\n",argv[3],cn->name,argv[6]);
 +                } else {
 +                    //access too low
 +                    sprintf(tmp,"%s JAR %s %i %i\n",argv[3],cn->name,uData->access,uData->access);
 +                }
 +            } else {
 +                //0 access
 +                sprintf(tmp,"%s JAR %s %s %s\n",argv[3],cn->name,"0","0");
 +            }
 +            irc_relay(tmp);
 +        }
 +    }
 +    return 1;
 +}
 +
  void
  free_user(struct userNode *user)
  {
@@@ -1951,8 -1793,6 +1949,8 @@@ init_parse(void
      dict_insert(irc_func_dict, TOK_STATS, cmd_stats);
      dict_insert(irc_func_dict, CMD_SVSNICK, cmd_svsnick);
      dict_insert(irc_func_dict, TOK_SVSNICK, cmd_svsnick);
 +    dict_insert(irc_func_dict, CMD_RELAY, cmd_relay);
 +    dict_insert(irc_func_dict, TOK_RELAY, cmd_relay);
      dict_insert(irc_func_dict, CMD_WHOIS, cmd_whois);
      dict_insert(irc_func_dict, TOK_WHOIS, cmd_whois);
      dict_insert(irc_func_dict, CMD_GLINE, cmd_gline);
@@@ -2240,7 -2080,6 +2238,6 @@@ AddLocalUser(const char *nick, const ch
  {
      char numeric[COMBO_NUMERIC_LEN+1];
      int local_num = get_local_numeric();
-     unsigned long timestamp = now;
      struct userNode *old_user = GetUserH(nick);
  
      if (!modes)
      if (old_user) {
          if (IsLocal(old_user))
              return old_user;
-         timestamp = old_user->timestamp - 1;
      }
      if (local_num == -1) {
          log_module(MAIN_LOG, LOG_ERROR, "Unable to allocate numnick for service %s", nick);
@@@ -2460,10 -2298,8 +2456,10 @@@ void mod_usermode(struct userNode *user
          case 'o':
              do_user_mode(FLAGS_OPER);
              if (!add) {
 +                operdel(user);
                  userList_remove(&curr_opers, user);
              } else if (!userList_contains(&curr_opers, user)) {
 +                operadd(user);
                  userList_append(&curr_opers, user);
                  call_oper_funcs(user);
              }
          case 'g': do_user_mode(FLAGS_GLOBAL); break;
          case 'n': do_user_mode(FLAGS_NOCHAN); break;
          case 'I': do_user_mode(FLAGS_NOIDLE); break;
 +        case 'S': do_user_mode(FLAGS_NETSERV); break;
 +        case 'D': do_user_mode(FLAGS_SECURITYSERV); break;
 +        case 'X': do_user_mode(FLAGS_XTRAOP); break;
 +        case 's': do_user_mode(FLAGS_SERVERNOTICE); break;
 +        case 'H': do_user_mode(FLAGS_HIDDENOPER); break;
 +        case 't': do_user_mode(FLAGS_SEENOIDLE); break;
          case 'x': do_user_mode(FLAGS_HIDDEN_HOST); break;
          case 'r':
              if (*word) {
@@@ -2561,7 -2391,7 +2557,7 @@@ keyncpy(char output[], char input[], si
  }
  
  struct mod_chanmode *
 -mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags, short base_oplevel)
 +mod_chanmode_parse(struct chanNode *channel, struct userNode *user, char **modes, unsigned int argc, unsigned int flags, short base_oplevel)
  {
      struct mod_chanmode *change;
      unsigned int ii, in_arg, ch_arg, add;
          case 'C': do_chan_mode(MODE_NOCTCPS); break;
          case 'D': do_chan_mode(MODE_DELAYJOINS); break;
          case 'c': do_chan_mode(MODE_NOCOLORS); break;
 +        case 'M': do_chan_mode(MODE_NOAMSGS); break;
 +        case 'N': do_chan_mode(MODE_NONOTICES); break;
 +        case 'u': do_chan_mode(MODE_AUDITORIUM); break;
          case 'i': do_chan_mode(MODE_INVITEONLY); break;
          case 'm': do_chan_mode(MODE_MODERATED); break;
          case 'n': do_chan_mode(MODE_NOPRIVMSGS); break;
          case 's': do_chan_mode(MODE_SECRET); break;
          case 't': do_chan_mode(MODE_TOPICLIMIT); break;
          case 'z':
 -          if (!(flags & MCP_REGISTERED))
 -              do_chan_mode(MODE_REGISTERED);
 +          if (!(flags & MCP_REGISTERED) && (!(flags & MCP_IGN_REGISTERED) || add)) {
 +            do_chan_mode(MODE_REGISTERED);
 +          } else if (flags & MCP_IGN_REGISTERED) {
 +            /* ignore the modechange but continue parsing */
 +          } else {
 +            mod_chanmode_free(change);
 +            return NULL;
 +          }
            break;
  #undef do_chan_mode
          case 'l':
                  change->modes_clear |= MODE_LIMIT;
              }
              break;
 +        case 'a':
 +            if (add) {
 +                if (in_arg >= argc)
 +                    goto error;
 +                change->modes_set |= MODE_ACCESS;
 +                change->new_access = atoi(modes[in_arg++]);
 +            } else {
 +                change->modes_set &= ~MODE_ACCESS;
 +                change->modes_clear |= MODE_ACCESS;
 +            }
 +            break;
          case 'k':
              if (add) {
                  if ((in_arg >= argc)
                  }
              }
              break;
 +        case 'f':
 +                  if (add) {
 +                          if (in_arg >= argc)
 +                    goto error;
 +                char *mode = modes[in_arg++];
 +                if(mode[0] == '!' && !(flags & MCP_OPERMODE)) //noflood flag also for overriders
 +                   goto error;//only allow opers
 +                else if(mode[0] == '!')
 +                   mode++;
 +                
 +                if(mode[0] == '+' || mode[0] == '@') {
 +                    mode++;
 +                }
 +                char *p;
 +                int count = 0, time = 0;
 +                for(p = mode; p[0]; p++) {
 +                    if(p[0] == ':') {
 +                        char tmpchar = p[0];
 +                        p[0] = '\0';
 +                        count = strtoul(mode,0,10);
 +                        p[0] = tmpchar;
 +                                              p++;
 +                                              time = strtoul(p,0,10);
 +                                              break;
 +                              }
 +                }
 +                if(count <= 0 || time <= 0 || count > 100 || time > 600)
 +                                  goto error;
 +                              change->modes_set |= MODE_NOFLOOD;
 +                              safestrncpy(change->new_noflood, modes[in_arg - 1], sizeof(change->new_noflood));
 +                      } else {
 +                          change->modes_clear |= MODE_NOFLOOD;
 +                      }
 +            break;
 +        case 'F':
 +            if (add) {
 +                if (in_arg >= argc)
 +                    goto error;
 +                char *altchan = modes[in_arg++];
 +                struct chanNode *target;
 +                if(!IsChannelName(altchan) || !(target = GetChannel(altchan)))
 +                    goto error;
 +                if(!(flags & MCP_OPERMODE)) {
 +                    //check if the user has the permissions to use this channel as target
 +                    struct modeNode *mn;
 +                    struct userData *uData;
 +                    struct chanData *cData;
 +                    if(user && (mn = GetUserMode(target, user)) && (mn->modes & MODE_CHANOP)) {
 +                        //allow - user is opped on target channel
 +                    } else if(user && user->handle_info && 
 +                              (uData = GetChannelUser(channel->channel_info, user->handle_info)) && 
 +                              (cData = uData->channel) && 
 +                              uData->access >= cData->lvlOpts[lvlGiveOps]
 +                             ) {
 +                        //allow - user has access to get op on the channel
 +                    } else 
 +                        goto error;
 +                }
 +                change->modes_set |= MODE_ALTCHAN;
 +                safestrncpy(change->new_altchan, altchan, sizeof(change->new_altchan));
 +            } else {
 +                change->modes_clear |= MODE_ALTCHAN;
 +            }
 +            break;
          case 'U':
              if (flags & MCP_NO_APASS)
                  goto error;
@@@ -2887,16 -2633,10 +2883,16 @@@ mod_chanmode_announce(struct userNode *
          DO_MODE_CHAR(INVITEONLY, 'i');
          DO_MODE_CHAR(NOPRIVMSGS, 'n');
          DO_MODE_CHAR(LIMIT, 'l');
 +        DO_MODE_CHAR(ACCESS, 'a');
 +        DO_MODE_CHAR(ALTCHAN, 'F');
 +        DO_MODE_CHAR(NOFLOOD, 'f');
          DO_MODE_CHAR(DELAYJOINS, 'D');
          DO_MODE_CHAR(REGONLY, 'r');
          DO_MODE_CHAR(NOCOLORS, 'c');
          DO_MODE_CHAR(NOCTCPS, 'C');
 +        DO_MODE_CHAR(NONOTICES, 'N');
 +        DO_MODE_CHAR(NOAMSGS, 'M');
 +        DO_MODE_CHAR(AUDITORIUM, 'u');
          DO_MODE_CHAR(REGISTERED, 'z');
  #undef DO_MODE_CHAR
          if (change->modes_clear & channel->modes & MODE_KEY)
          DO_MODE_CHAR(REGONLY, 'r');
          DO_MODE_CHAR(NOCOLORS, 'c');
          DO_MODE_CHAR(NOCTCPS, 'C');
 +        DO_MODE_CHAR(NONOTICES, 'N');
 +        DO_MODE_CHAR(NOAMSGS, 'M');
 +        DO_MODE_CHAR(AUDITORIUM, 'u');
          DO_MODE_CHAR(REGISTERED, 'z');
  #undef DO_MODE_CHAR
          if(change->modes_set & MODE_KEY)
              sprintf(int_buff, "%d", change->new_limit);
              mod_chanmode_append(&chbuf, 'l', int_buff);
          }
 +        if(change->modes_set & MODE_ACCESS) {
 +            sprintf(int_buff, "%d", change->new_access);
 +            mod_chanmode_append(&chbuf, 'a', int_buff);
 +        }
 +        if (change->modes_set & MODE_ALTCHAN)
 +            mod_chanmode_append(&chbuf, 'F', change->new_altchan);
 +        if (change->modes_set & MODE_NOFLOOD)
 +            mod_chanmode_append(&chbuf, 'f', change->new_noflood);
      }
      for (arg = 0; arg < change->argc; ++arg) {
          if (change->args[arg].mode & MODE_REMOVE)
@@@ -3009,9 -2738,6 +3005,9 @@@ mod_chanmode_format(struct mod_chanmod
          DO_MODE_CHAR(INVITEONLY, 'i');
          DO_MODE_CHAR(NOPRIVMSGS, 'n');
          DO_MODE_CHAR(LIMIT, 'l');
 +        DO_MODE_CHAR(ACCESS, 'a');
 +        DO_MODE_CHAR(ALTCHAN, 'F');
 +        DO_MODE_CHAR(NOFLOOD, 'f');
          DO_MODE_CHAR(KEY, 'k');
          DO_MODE_CHAR(UPASS, 'U');
          DO_MODE_CHAR(APASS, 'A');
          DO_MODE_CHAR(REGONLY, 'r');
          DO_MODE_CHAR(NOCOLORS, 'c');
          DO_MODE_CHAR(NOCTCPS, 'C');
 +        DO_MODE_CHAR(NONOTICES, 'N');
 +        DO_MODE_CHAR(NOAMSGS, 'M');
 +        DO_MODE_CHAR(AUDITORIUM, 'u');
          DO_MODE_CHAR(REGISTERED, 'z');
  #undef DO_MODE_CHAR
      }
          DO_MODE_CHAR(REGONLY, 'r');
          DO_MODE_CHAR(NOCOLORS, 'c');
          DO_MODE_CHAR(NOCTCPS, 'C');
 +        DO_MODE_CHAR(NONOTICES, 'N');
 +              DO_MODE_CHAR(NOAMSGS, 'M');
 +        DO_MODE_CHAR(AUDITORIUM, 'u');
          DO_MODE_CHAR(REGISTERED, 'z');
          DO_MODE_CHAR(LIMIT, 'l'), args_used += sprintf(args + args_used, " %d", change->new_limit);
          DO_MODE_CHAR(KEY, 'k'), args_used += sprintf(args + args_used, " %s", change->new_key);
 +        DO_MODE_CHAR(ACCESS, 'a'), args_used += sprintf(args + args_used, " %d", change->new_access);
 +        DO_MODE_CHAR(ALTCHAN, 'F'), args_used += sprintf(args + args_used, " %s", change->new_altchan);
 +        DO_MODE_CHAR(NOFLOOD, 'f'), args_used += sprintf(args + args_used, " %s", change->new_noflood);
          DO_MODE_CHAR(UPASS, 'U'), args_used += sprintf(args + args_used, " %s", change->new_upass);
          DO_MODE_CHAR(APASS, 'A'), args_used += sprintf(args + args_used, " %s", change->new_apass);
  #undef DO_MODE_CHAR
@@@ -3071,14 -2788,6 +3067,14 @@@ clear_chanmode(struct chanNode *channel
          case 't': cleared |= MODE_TOPICLIMIT; break;
          case 'i': cleared |= MODE_INVITEONLY; break;
          case 'n': cleared |= MODE_NOPRIVMSGS; break;
 +        case 'F':
 +            cleared |= MODE_ALTCHAN;
 +            channel->altchan[0] = '\0';
 +            break;
 +        case 'f':
 +            cleared |= MODE_NOFLOOD;
 +            channel->noflood[0] = '\0';
 +            break;
          case 'k':
              cleared |= MODE_KEY;
              channel->key[0] = '\0';
              cleared |= MODE_LIMIT;
              channel->limit = 0;
              break;
 +        case 'a':
 +            cleared |= MODE_ACCESS;
 +            channel->access = 0;
 +            break;
          case 'b': cleared |= MODE_BAN; break;
          case 'D': cleared |= MODE_DELAYJOINS; break;
          case 'r': cleared |= MODE_REGONLY; break;
          case 'c': cleared |= MODE_NOCOLORS; break;
          case 'C': cleared |= MODE_NOCTCPS; break;
 +        case 'M': cleared |= MODE_NOAMSGS; break;
 +        case 'u': cleared |= MODE_AUDITORIUM; break;
 +        case 'N': cleared |= MODE_NONOTICES; break;
          case 'z': cleared |= MODE_REGISTERED; break;
          }
      }