]> git.netwichtig.de Git - user/henk/code/inspircd.git/commitdiff
Merge branch 'insp20' into master.
authorPeter Powell <petpow@saberuk.com>
Mon, 24 Sep 2018 17:25:06 +0000 (18:25 +0100)
committerPeter Powell <petpow@saberuk.com>
Mon, 24 Sep 2018 17:25:06 +0000 (18:25 +0100)
1  2 
src/users.cpp

diff --combined src/users.cpp
index 1ddd3ca0ef99864d0c1da9a87b8ede2654be3c96,ac87f1187992cc943d1dab44cc9de778473ffcdb..8f20b7523fca082dd751f4d8812660c7f37e95ed
  
  
  #include "inspircd.h"
 -#include <stdarg.h>
 -#include "socketengine.h"
  #include "xline.h"
 -#include "bancache.h"
 -#include "commands/cmd_whowas.h"
  
 -already_sent_t LocalUser::already_sent_id = 0;
 -
 -std::string User::ProcessNoticeMasks(const char *sm)
 -{
 -      bool adding = true, oldadding = false;
 -      const char *c = sm;
 -      std::string output;
 -
 -      while (c && *c)
 -      {
 -              switch (*c)
 -              {
 -                      case '+':
 -                              adding = true;
 -                      break;
 -                      case '-':
 -                              adding = false;
 -                      break;
 -                      case '*':
 -                              for (unsigned char d = 'a'; d <= 'z'; d++)
 -                              {
 -                                      if (!ServerInstance->SNO->masks[d - 'a'].Description.empty())
 -                                      {
 -                                              if ((!IsNoticeMaskSet(d) && adding) || (IsNoticeMaskSet(d) && !adding))
 -                                              {
 -                                                      if ((oldadding != adding) || (!output.length()))
 -                                                              output += (adding ? '+' : '-');
 -
 -                                                      this->SetNoticeMask(d, adding);
 -
 -                                                      output += d;
 -                                              }
 -                                              oldadding = adding;
 -                                              char u = toupper(d);
 -                                              if ((!IsNoticeMaskSet(u) && adding) || (IsNoticeMaskSet(u) && !adding))
 -                                              {
 -                                                      if ((oldadding != adding) || (!output.length()))
 -                                                              output += (adding ? '+' : '-');
 -
 -                                                      this->SetNoticeMask(u, adding);
 -
 -                                                      output += u;
 -                                              }
 -                                              oldadding = adding;
 -                                      }
 -                              }
 -                      break;
 -                      default:
 -                              if (isalpha(*c))
 -                              {
 -                                      if ((!IsNoticeMaskSet(*c) && adding) || (IsNoticeMaskSet(*c) && !adding))
 -                                      {
 -                                              if ((oldadding != adding) || (!output.length()))
 -                                                      output += (adding ? '+' : '-');
 -
 -                                              this->SetNoticeMask(*c, adding);
 -
 -                                              output += *c;
 -                                              oldadding = adding;
 -                                      }
 -                              }
 -                              else
 -                                      this->WriteNumeric(ERR_UNKNOWNSNOMASK, "%s %c :is unknown snomask char to me", this->nick.c_str(), *c);
 -
 -                      break;
 -              }
 -
 -              c++;
 -      }
 -
 -      std::string s = this->FormatNoticeMasks();
 -      if (s.length() == 0)
 -      {
 -              this->modes[UM_SNOMASK] = false;
 -      }
 -
 -      return output;
 -}
 -
 -void LocalUser::StartDNSLookup()
 -{
 -      try
 -      {
 -              bool cached = false;
 -              const char* sip = this->GetIPString();
 -              UserResolver *res_reverse;
 -
 -              QueryType resolvtype = this->client_sa.sa.sa_family == AF_INET6 ? DNS_QUERY_PTR6 : DNS_QUERY_PTR4;
 -              res_reverse = new UserResolver(this, sip, resolvtype, cached);
 -
 -              ServerInstance->AddResolver(res_reverse, cached);
 -      }
 -      catch (CoreException& e)
 -      {
 -              ServerInstance->Logs->Log("USERS", DEBUG,"Error in resolver: %s",e.GetReason());
 -              dns_done = true;
 -              ServerInstance->stats->statsDnsBad++;
 -      }
 -}
 +ClientProtocol::MessageList LocalUser::sendmsglist;
  
  bool User::IsNoticeMaskSet(unsigned char sm)
  {
        return (snomasks[sm-65]);
  }
  
 -void User::SetNoticeMask(unsigned char sm, bool value)
 +bool User::IsModeSet(unsigned char m) const
  {
 -      if (!isalpha(sm))
 -              return;
 -      snomasks[sm-65] = value;
 +      ModeHandler* mh = ServerInstance->Modes->FindMode(m, MODETYPE_USER);
 +      return (mh && modes[mh->GetId()]);
  }
  
 -const char* User::FormatNoticeMasks()
 +std::string User::GetModeLetters(bool includeparams) const
  {
 -      static char data[MAXBUF];
 -      int offset = 0;
 -
 -      for (int n = 0; n < 64; n++)
 -      {
 -              if (snomasks[n])
 -                      data[offset++] = n+65;
 -      }
 -
 -      data[offset] = 0;
 -      return data;
 -}
 -
 -bool User::IsModeSet(unsigned char m)
 -{
 -      if (!isalpha(m))
 -              return false;
 -      return (modes[m-65]);
 -}
 -
 -void User::SetMode(unsigned char m, bool value)
 -{
 -      if (!isalpha(m))
 -              return;
 -      modes[m-65] = value;
 -}
 -
 -const char* User::FormatModes(bool showparameters)
 -{
 -      static char data[MAXBUF];
 +      std::string ret(1, '+');
        std::string params;
 -      int offset = 0;
  
 -      for (unsigned char n = 0; n < 64; n++)
 +      for (unsigned char i = 'A'; i < 'z'; i++)
        {
 -              if (modes[n])
 +              const ModeHandler* const mh = ServerInstance->Modes.FindMode(i, MODETYPE_USER);
 +              if ((!mh) || (!IsModeSet(mh)))
 +                      continue;
 +
 +              ret.push_back(mh->GetModeChar());
 +              if ((includeparams) && (mh->NeedsParam(true)))
                {
 -                      data[offset++] = n + 65;
 -                      ModeHandler* mh = ServerInstance->Modes->FindMode(n + 65, MODETYPE_USER);
 -                      if (showparameters && mh && mh->GetNumParams(true))
 -                      {
 -                              std::string p = mh->GetUserParameter(this);
 -                              if (p.length())
 -                                      params.append(" ").append(p);
 -                      }
 +                      const std::string val = mh->GetUserParameter(this);
 +                      if (!val.empty())
 +                              params.append(1, ' ').append(val);
                }
        }
 -      data[offset] = 0;
 -      strlcat(data, params.c_str(), MAXBUF);
 -      return data;
 +
 +      ret += params;
 +      return ret;
  }
  
 -User::User(const std::string &uid, const std::string& sid, int type)
 -      : uuid(uid), server(sid), usertype(type)
 +User::User(const std::string& uid, Server* srv, UserType type)
 +      : age(ServerInstance->Time())
 +      , signon(0)
 +      , uuid(uid)
 +      , server(srv)
 +      , registered(REG_NONE)
 +      , quitting(false)
 +      , usertype(type)
  {
 -      age = ServerInstance->Time();
 -      signon = idle_lastmsg = 0;
 -      registered = 0;
 -      quietquit = quitting = exempt = dns_done = false;
 -      quitting_sendq = false;
        client_sa.sa.sa_family = AF_UNSPEC;
  
 -      ServerInstance->Logs->Log("USERS", DEBUG, "New UUID for user: %s", uuid.c_str());
 +      ServerInstance->Logs->Log("USERS", LOG_DEBUG, "New UUID for user: %s", uuid.c_str());
  
 -      user_hash::iterator finduuid = ServerInstance->Users->uuidlist->find(uuid);
 -      if (finduuid == ServerInstance->Users->uuidlist->end())
 -              (*ServerInstance->Users->uuidlist)[uuid] = this;
 -      else
 -              throw CoreException("Duplicate UUID "+std::string(uuid)+" in User constructor");
 +      // Do not insert FakeUsers into the uuidlist so FindUUID() won't return them which is the desired behavior
 +      if (type != USERTYPE_SERVER)
 +      {
 +              if (!ServerInstance->Users.uuidlist.insert(std::make_pair(uuid, this)).second)
 +                      throw CoreException("Duplicate UUID in User constructor: " + uuid);
 +      }
  }
  
  LocalUser::LocalUser(int myfd, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* servaddr)
 -      : User(ServerInstance->GetUID(), ServerInstance->Config->ServerName, USERTYPE_LOCAL), eh(this),
 -      localuseriter(ServerInstance->Users->local_users.end()),
 -      bytes_in(0), bytes_out(0), cmds_in(0), cmds_out(0), nping(0), CommandFloodPenalty(0),
 -      already_sent(0)
 -{
 +      : User(ServerInstance->UIDGen.GetUID(), ServerInstance->FakeClient->server, USERTYPE_LOCAL)
 +      , eh(this)
 +      , serializer(NULL)
 +      , bytes_in(0)
 +      , bytes_out(0)
 +      , cmds_in(0)
 +      , cmds_out(0)
 +      , quitting_sendq(false)
 +      , lastping(true)
 +      , exempt(false)
 +      , nping(0)
 +      , idle_lastmsg(0)
 +      , CommandFloodPenalty(0)
 +      , already_sent(0)
 +{
 +      signon = ServerInstance->Time();
 +      // The user's default nick is their UUID
 +      nick = uuid;
        ident = "unknown";
 -      lastping = 0;
        eh.SetFd(myfd);
        memcpy(&client_sa, client, sizeof(irc::sockets::sockaddrs));
        memcpy(&server_sa, servaddr, sizeof(irc::sockets::sockaddrs));
 -      dhost = host = GetIPString();
 +      ChangeRealHost(GetIPString(), true);
  }
  
  User::~User()
  {
 -      if (ServerInstance->Users->uuidlist->find(uuid) != ServerInstance->Users->uuidlist->end())
 -              ServerInstance->Logs->Log("USERS", DEFAULT, "User destructor for %s called without cull", uuid.c_str());
  }
  
  const std::string& User::MakeHost()
        if (!this->cached_makehost.empty())
                return this->cached_makehost;
  
 -      char nhost[MAXBUF];
 -      /* This is much faster than snprintf */
 -      char* t = nhost;
 -      for(const char* n = ident.c_str(); *n; n++)
 -              *t++ = *n;
 -      *t++ = '@';
 -      for(const char* n = host.c_str(); *n; n++)
 -              *t++ = *n;
 -      *t = 0;
 -
 -      this->cached_makehost.assign(nhost);
 -
 +      // XXX: Is there really a need to cache this?
 +      this->cached_makehost = ident + "@" + GetRealHost();
        return this->cached_makehost;
  }
  
@@@ -131,8 -262,18 +131,8 @@@ const std::string& User::MakeHostIP(
        if (!this->cached_hostip.empty())
                return this->cached_hostip;
  
 -      char ihost[MAXBUF];
 -      /* This is much faster than snprintf */
 -      char* t = ihost;
 -      for(const char* n = ident.c_str(); *n; n++)
 -              *t++ = *n;
 -      *t++ = '@';
 -      for(const char* n = this->GetIPString(); *n; n++)
 -              *t++ = *n;
 -      *t = 0;
 -
 -      this->cached_hostip = ihost;
 -
 +      // XXX: Is there really a need to cache this?
 +      this->cached_hostip = ident + "@" + this->GetIPString();
        return this->cached_hostip;
  }
  
@@@ -141,35 -282,111 +141,35 @@@ const std::string& User::GetFullHost(
        if (!this->cached_fullhost.empty())
                return this->cached_fullhost;
  
 -      char result[MAXBUF];
 -      char* t = result;
 -      for(const char* n = nick.c_str(); *n; n++)
 -              *t++ = *n;
 -      *t++ = '!';
 -      for(const char* n = ident.c_str(); *n; n++)
 -              *t++ = *n;
 -      *t++ = '@';
 -      for(const char* n = dhost.c_str(); *n; n++)
 -              *t++ = *n;
 -      *t = 0;
 -
 -      this->cached_fullhost = result;
 -
 +      // XXX: Is there really a need to cache this?
 +      this->cached_fullhost = nick + "!" + ident + "@" + GetDisplayedHost();
        return this->cached_fullhost;
  }
  
 -char* User::MakeWildHost()
 -{
 -      static char nresult[MAXBUF];
 -      char* t = nresult;
 -      *t++ = '*';     *t++ = '!';
 -      *t++ = '*';     *t++ = '@';
 -      for(const char* n = dhost.c_str(); *n; n++)
 -              *t++ = *n;
 -      *t = 0;
 -      return nresult;
 -}
 -
  const std::string& User::GetFullRealHost()
  {
        if (!this->cached_fullrealhost.empty())
                return this->cached_fullrealhost;
  
 -      char fresult[MAXBUF];
 -      char* t = fresult;
 -      for(const char* n = nick.c_str(); *n; n++)
 -              *t++ = *n;
 -      *t++ = '!';
 -      for(const char* n = ident.c_str(); *n; n++)
 -              *t++ = *n;
 -      *t++ = '@';
 -      for(const char* n = host.c_str(); *n; n++)
 -              *t++ = *n;
 -      *t = 0;
 -
 -      this->cached_fullrealhost = fresult;
 -
 +      // XXX: Is there really a need to cache this?
 +      this->cached_fullrealhost = nick + "!" + ident + "@" + GetRealHost();
        return this->cached_fullrealhost;
  }
  
 -bool LocalUser::IsInvited(const irc::string &channel)
 -{
 -      Channel* chan = ServerInstance->FindChan(channel.c_str());
 -      if (!chan)
 -              return false;
 -
 -      return (Invitation::Find(chan, this) != NULL);
 -}
 -
 -InviteList& LocalUser::GetInviteList()
 -{
 -      RemoveExpiredInvites();
 -      return invites;
 -}
 -
 -void LocalUser::InviteTo(const irc::string &channel, time_t invtimeout)
 -{
 -      Channel* chan = ServerInstance->FindChan(channel.c_str());
 -      if (chan)
 -              Invitation::Create(chan, this, invtimeout);
 -}
 -
 -void LocalUser::RemoveInvite(const irc::string &channel)
 -{
 -      Channel* chan = ServerInstance->FindChan(channel.c_str());
 -      if (chan)
 -      {
 -              Invitation* inv = Invitation::Find(chan, this);
 -              if (inv)
 -              {
 -                      inv->cull();
 -                      delete inv;
 -              }
 -      }
 -}
 -
 -void LocalUser::RemoveExpiredInvites()
 -{
 -      Invitation::Find(NULL, this);
 -}
 -
 -bool User::HasModePermission(unsigned char, ModeType)
 +bool User::HasModePermission(const ModeHandler* mh) const
  {
        return true;
  }
  
 -bool LocalUser::HasModePermission(unsigned char mode, ModeType type)
 +bool LocalUser::HasModePermission(const ModeHandler* mh) const
  {
 -      if (!IS_OPER(this))
 +      if (!this->IsOper())
                return false;
  
 +      const unsigned char mode = mh->GetModeChar();
        if (mode < 'A' || mode > ('A' + 64)) return false;
  
 -      return ((type == MODETYPE_USER ? oper->AllowedUserModes : oper->AllowedChanModes))[(mode - 'A')];
 +      return ((mh->GetModeType() == MODETYPE_USER ? oper->AllowedUserModes : oper->AllowedChanModes))[(mode - 'A')];
  
  }
  /*
@@@ -187,12 -404,17 +187,12 @@@ bool User::HasPermission(const std::str
  bool LocalUser::HasPermission(const std::string &command)
  {
        // are they even an oper at all?
 -      if (!IS_OPER(this))
 +      if (!this->IsOper())
        {
                return false;
        }
  
 -      if (oper->AllowedOperCommands.find(command) != oper->AllowedOperCommands.end())
 -              return true;
 -      else if (oper->AllowedOperCommands.find("*") != oper->AllowedOperCommands.end())
 -              return true;
 -
 -      return false;
 +      return oper->AllowedOperCommands.Contains(command);
  }
  
  bool User::HasPrivPermission(const std::string &privstr, bool noisy)
  
  bool LocalUser::HasPrivPermission(const std::string &privstr, bool noisy)
  {
 -      if (!IS_OPER(this))
 +      if (!this->IsOper())
        {
                if (noisy)
 -                      this->WriteServ("NOTICE %s :You are not an oper", this->nick.c_str());
 +                      this->WriteNotice("You are not an oper");
                return false;
        }
  
 -      if (oper->AllowedPrivs.find(privstr) != oper->AllowedPrivs.end())
 -      {
 +      if (oper->AllowedPrivs.Contains(privstr))
                return true;
 -      }
 -      else if (oper->AllowedPrivs.find("*") != oper->AllowedPrivs.end())
 -      {
 -              return true;
 -      }
  
        if (noisy)
 -              this->WriteServ("NOTICE %s :Oper type %s does not have access to priv %s", this->nick.c_str(), oper->NameStr(), privstr.c_str());
 +              this->WriteNotice("Oper type " + oper->name + " does not have access to priv " + privstr);
 +
        return false;
  }
  
@@@ -230,72 -457,49 +230,72 @@@ void UserIOHandler::OnDataReady(
                        user->nick.c_str(), (unsigned long)recvq.length(), user->MyClass->GetRecvqMax());
                return;
        }
 +
        unsigned long sendqmax = ULONG_MAX;
        if (!user->HasPrivPermission("users/flood/increased-buffers"))
                sendqmax = user->MyClass->GetSendqSoftMax();
 +
        unsigned long penaltymax = ULONG_MAX;
        if (!user->HasPrivPermission("users/flood/no-fakelag"))
                penaltymax = user->MyClass->GetPenaltyThreshold() * 1000;
  
 +      // The maximum size of an IRC message minus the terminating CR+LF.
 +      const size_t maxmessage = ServerInstance->Config->Limits.MaxLine - 2;
 +      std::string line;
 +      line.reserve(maxmessage);
 +
 +      bool eol_found;
 +      std::string::size_type qpos;
 +
        while (user->CommandFloodPenalty < penaltymax && getSendQSize() < sendqmax)
        {
 -              std::string line;
 -              line.reserve(MAXBUF);
 -              std::string::size_type qpos = 0;
 -              while (qpos < recvq.length())
 +              qpos = 0;
 +              eol_found = false;
 +
 +              const size_t qlen = recvq.length();
 +              while (qpos < qlen)
                {
                        char c = recvq[qpos++];
                        switch (c)
                        {
 -                      case '\0':
 -                              c = ' ';
 -                              break;
 -                      case '\r':
 -                              continue;
 -                      case '\n':
 -                              goto eol_found;
 +                              case '\0':
 +                                      c = ' ';
 +                                      break;
 +                              case '\r':
 +                                      continue;
 +                              case '\n':
 +                                      eol_found = true;
 +                                      break;
                        }
 -                      if (line.length() < MAXBUF - 2)
 +
 +                      if (eol_found)
 +                              break;
 +
 +                      if (line.length() < maxmessage)
                                line.push_back(c);
                }
 -              // if we got here, the recvq ran out before we found a newline
 -              return;
 -eol_found:
 +
 +              // if we return here, we haven't found a newline and make no modifications to recvq
 +              // so we can wait for more data
 +              if (!eol_found)
 +                      return;
 +
                // just found a newline. Terminate the string, and pull it out of recvq
 -              recvq = recvq.substr(qpos);
 +              recvq.erase(0, qpos);
  
                // TODO should this be moved to when it was inserted in recvq?
 -              ServerInstance->stats->statsRecv += qpos;
 +              ServerInstance->stats.Recv += qpos;
                user->bytes_in += qpos;
                user->cmds_in++;
  
 -              ServerInstance->Parser->ProcessBuffer(line, user);
 +              ServerInstance->Parser.ProcessBuffer(user, line);
                if (user->quitting)
                        return;
 +
 +              // clear() does not reclaim memory associated with the string, so our .reserve() call is safe
 +              line.clear();
        }
 +
        if (user->CommandFloodPenalty >= penaltymax && !user->MyClass->fakelag)
                ServerInstance->Users->QuitUser(user, "Excess Flood");
  }
@@@ -318,12 -522,6 +318,12 @@@ void UserIOHandler::AddWriteBuf(const s
        WriteData(data);
  }
  
 +void UserIOHandler::OnSetEndPoint(const irc::sockets::sockaddrs& server, const irc::sockets::sockaddrs& client)
 +{
 +      memcpy(&user->server_sa, &server, sizeof(irc::sockets::sockaddrs));
 +      user->SetClientIP(client);
 +}
 +
  void UserIOHandler::OnError(BufferedSocketError)
  {
        ServerInstance->Users->QuitUser(user, getError());
@@@ -333,8 -531,9 +333,8 @@@ CullResult User::cull(
  {
        if (!quitting)
                ServerInstance->Users->QuitUser(this, "Culled without QuitUser");
 -      PurgeEmptyChannels();
  
 -      if (client_sa.sa.sa_family != AF_UNSPEC)
 +      if (client_sa.family() != AF_UNSPEC)
                ServerInstance->Users->RemoveCloneCounts(this);
  
        return Extensible::cull();
  
  CullResult LocalUser::cull()
  {
 -      // The iterator is initialized to local_users.end() in the constructor. It is
 -      // overwritten in UserManager::AddUser() with the real iterator so this check
 -      // is only a precaution currently.
 -      if (localuseriter != ServerInstance->Users->local_users.end())
 -      {
 -              ServerInstance->Users->local_count--;
 -              ServerInstance->Users->local_users.erase(localuseriter);
 -      }
 -      else
 -              ServerInstance->Logs->Log("USERS", DEFAULT, "ERROR: LocalUserIter does not point to a valid entry for " + this->nick);
 -
 -      ClearInvites();
        eh.cull();
        return User::cull();
  }
@@@ -350,29 -561,20 +350,29 @@@ CullResult FakeUser::cull(
  {
        // Fake users don't quit, they just get culled.
        quitting = true;
 -      ServerInstance->Users->clientlist->erase(nick);
 -      ServerInstance->Users->uuidlist->erase(uuid);
 +      // Fake users are not inserted into UserManager::clientlist or uuidlist, so we don't need to modify those here
        return User::cull();
  }
  
  void User::Oper(OperInfo* info)
  {
 -      if (this->IsModeSet('o'))
 +      ModeHandler* opermh = ServerInstance->Modes->FindMode('o', MODETYPE_USER);
 +      if (this->IsModeSet(opermh))
                this->UnOper();
  
 -      this->modes[UM_OPERATOR] = 1;
 +      this->SetMode(opermh, true);
        this->oper = info;
 -      this->WriteServ("MODE %s :+o", this->nick.c_str());
 -      FOREACH_MOD(I_OnOper, OnOper(this, info->name));
 +
 +      LocalUser* localuser = IS_LOCAL(this);
 +      if (localuser)
 +      {
 +              Modes::ChangeList changelist;
 +              changelist.push_add(opermh);
 +              ClientProtocol::Events::Mode modemsg(ServerInstance->FakeClient, NULL, localuser, changelist);
 +              localuser->Send(modemsg);
 +      }
 +
 +      FOREACH_MOD(OnOper, (this, info->name));
  
        std::string opername;
        if (info->oper_block)
                LocalUser* l = IS_LOCAL(this);
                std::string vhost = oper->getConfig("vhost");
                if (!vhost.empty())
 -                      l->ChangeDisplayedHost(vhost.c_str());
 +                      l->ChangeDisplayedHost(vhost);
                std::string opClass = oper->getConfig("class");
                if (!opClass.empty())
                        l->SetClass(opClass);
        }
  
        ServerInstance->SNO->WriteToSnoMask('o',"%s (%s@%s) is now an IRC operator of type %s (using oper '%s')",
 -              nick.c_str(), ident.c_str(), host.c_str(), oper->NameStr(), opername.c_str());
 -      this->WriteNumeric(381, "%s :You are now %s %s", nick.c_str(), strchr("aeiouAEIOU", oper->name[0]) ? "an" : "a", oper->NameStr());
 +              nick.c_str(), ident.c_str(), GetRealHost().c_str(), oper->name.c_str(), opername.c_str());
 +      this->WriteNumeric(RPL_YOUAREOPER, InspIRCd::Format("You are now %s %s", strchr("aeiouAEIOU", oper->name[0]) ? "an" : "a", oper->name.c_str()));
  
 -      ServerInstance->Logs->Log("OPER", DEFAULT, "%s opered as type: %s", GetFullRealHost().c_str(), oper->NameStr());
        ServerInstance->Users->all_opers.push_back(this);
  
        // Expand permissions from config for faster lookup
 -      if (IS_LOCAL(this))
 +      if (localuser)
                oper->init();
  
 -      FOREACH_MOD(I_OnPostOper,OnPostOper(this, oper->name, opername));
 +      FOREACH_MOD(OnPostOper, (this, oper->name, opername));
  }
  
  void OperInfo::init()
  {
 -      AllowedOperCommands.clear();
 -      AllowedPrivs.clear();
 +      AllowedOperCommands.Clear();
 +      AllowedPrivs.Clear();
        AllowedUserModes.reset();
        AllowedChanModes.reset();
        AllowedUserModes['o' - 'A'] = true; // Call me paranoid if you want.
        for(std::vector<reference<ConfigTag> >::iterator iter = class_blocks.begin(); iter != class_blocks.end(); ++iter)
        {
                ConfigTag* tag = *iter;
 -              std::string mycmd, mypriv;
 -              /* Process commands */
 -              irc::spacesepstream CommandList(tag->getString("commands"));
 -              while (CommandList.GetToken(mycmd))
 -              {
 -                      AllowedOperCommands.insert(mycmd);
 -              }
  
 -              irc::spacesepstream PrivList(tag->getString("privs"));
 -              while (PrivList.GetToken(mypriv))
 -              {
 -                      AllowedPrivs.insert(mypriv);
 -              }
 +              AllowedOperCommands.AddList(tag->getString("commands"));
 +              AllowedPrivs.AddList(tag->getString("privs"));
  
                std::string modes = tag->getString("usermodes");
                for (std::string::const_iterator c = modes.begin(); c != modes.end(); ++c)
  
  void User::UnOper()
  {
 -      if (!IS_OPER(this))
 +      if (!this->IsOper())
                return;
  
        /*
  
  
        /* Remove all oper only modes from the user when the deoper - Bug #466*/
 -      std::string moderemove("-");
 -
 -      for (unsigned char letter = 'A'; letter <= 'z'; letter++)
 +      Modes::ChangeList changelist;
 +      const ModeParser::ModeHandlerMap& usermodes = ServerInstance->Modes->GetModes(MODETYPE_USER);
 +      for (ModeParser::ModeHandlerMap::const_iterator i = usermodes.begin(); i != usermodes.end(); ++i)
        {
 -              ModeHandler* mh = ServerInstance->Modes->FindMode(letter, MODETYPE_USER);
 -              if (mh && mh->NeedsOper())
 -                      moderemove += letter;
 +              ModeHandler* mh = i->second;
 +              if (mh->NeedsOper())
 +                      changelist.push_remove(mh);
        }
  
 +      ServerInstance->Modes->Process(this, NULL, this, changelist);
  
 -      std::vector<std::string> parameters;
 -      parameters.push_back(this->nick);
 -      parameters.push_back(moderemove);
 +      // Remove the user from the oper list
 +      stdalgo::vector::swaperase(ServerInstance->Users->all_opers, this);
  
 -      ServerInstance->Parser->CallHandler("MODE", parameters, this);
 -
 -      /* remove the user from the oper list. Will remove multiple entries as a safeguard against bug #404 */
 -      ServerInstance->Users->all_opers.remove(this);
 -
 -      this->modes[UM_OPERATOR] = 0;
 -      FOREACH_MOD(I_OnPostDeoper, OnPostDeoper(this));
 -}
 -
 -/* adds or updates an entry in the whowas list */
 -void User::AddToWhoWas()
 -{
 -      Module* whowas = ServerInstance->Modules->Find("cmd_whowas.so");
 -      if (whowas)
 -      {
 -              WhowasRequest req(NULL, whowas, WhowasRequest::WHOWAS_ADD);
 -              req.user = this;
 -              req.Send();
 -      }
 +      ModeHandler* opermh = ServerInstance->Modes->FindMode('o', MODETYPE_USER);
 +      this->SetMode(opermh, false);
 +      FOREACH_MOD(OnPostDeoper, (this));
  }
  
  /*
   * Check class restrictions
   */
 -void LocalUser::CheckClass()
 +void LocalUser::CheckClass(bool clone_count)
  {
        ConnectClass* a = this->MyClass;
  
                ServerInstance->Users->QuitUser(this, a->config->getString("reason", "Unauthorised connection"));
                return;
        }
 -      else if ((a->GetMaxLocal()) && (ServerInstance->Users->LocalCloneCount(this) > a->GetMaxLocal()))
 +      else if (clone_count)
        {
 -              ServerInstance->Users->QuitUser(this, "No more connections allowed from your host via this connect class (local)");
 -              if (a->maxconnwarn)
 -                      ServerInstance->SNO->WriteToSnoMask('a', "WARNING: maximum LOCAL connections (%ld) exceeded for IP %s", a->GetMaxLocal(), this->GetIPString());
 -              return;
 -      }
 -      else if ((a->GetMaxGlobal()) && (ServerInstance->Users->GlobalCloneCount(this) > a->GetMaxGlobal()))
 -      {
 -              ServerInstance->Users->QuitUser(this, "No more connections allowed from your host via this connect class (global)");
 -              if (a->maxconnwarn)
 -                      ServerInstance->SNO->WriteToSnoMask('a', "WARNING: maximum GLOBAL connections (%ld) exceeded for IP %s", a->GetMaxGlobal(), this->GetIPString());
 -              return;
 +              const UserManager::CloneCounts& clonecounts = ServerInstance->Users->GetCloneCounts(this);
 +              if ((a->GetMaxLocal()) && (clonecounts.local > a->GetMaxLocal()))
 +              {
 +                      ServerInstance->Users->QuitUser(this, "No more connections allowed from your host via this connect class (local)");
 +                      if (a->maxconnwarn)
 +                              ServerInstance->SNO->WriteToSnoMask('a', "WARNING: maximum LOCAL connections (%ld) exceeded for IP %s", a->GetMaxLocal(), this->GetIPString().c_str());
 +                      return;
 +              }
 +              else if ((a->GetMaxGlobal()) && (clonecounts.global > a->GetMaxGlobal()))
 +              {
 +                      ServerInstance->Users->QuitUser(this, "No more connections allowed from your host via this connect class (global)");
 +                      if (a->maxconnwarn)
 +                              ServerInstance->SNO->WriteToSnoMask('a', "WARNING: maximum GLOBAL connections (%ld) exceeded for IP %s", a->GetMaxGlobal(), this->GetIPString().c_str());
 +                      return;
 +              }
        }
  
 -      this->nping = ServerInstance->Time() + a->GetPingTime() + ServerInstance->Config->dns_timeout;
 +      this->nping = ServerInstance->Time() + a->GetPingTime();
  }
  
 -bool User::CheckLines(bool doZline)
 +bool LocalUser::CheckLines(bool doZline)
  {
        const char* check[] = { "G" , "K", (doZline) ? "Z" : NULL, NULL };
  
  
  void LocalUser::FullConnect()
  {
 -      ServerInstance->stats->statsConnects++;
 +      ServerInstance->stats.Connects++;
        this->idle_lastmsg = ServerInstance->Time();
  
        /*
        if (quitting)
                return;
  
 -      if (ServerInstance->Config->WelcomeNotice)
 -              this->WriteServ("NOTICE Auth :Welcome to \002%s\002!",ServerInstance->Config->Network.c_str());
 -      this->WriteNumeric(RPL_WELCOME, "%s :Welcome to the %s IRC Network %s",this->nick.c_str(), ServerInstance->Config->Network.c_str(), GetFullRealHost().c_str());
 -      this->WriteNumeric(RPL_YOURHOSTIS, "%s :Your host is %s, running version %s",this->nick.c_str(),ServerInstance->Config->ServerName.c_str(),BRANCH);
 -      this->WriteNumeric(RPL_SERVERCREATED, "%s :This server was created %s %s", this->nick.c_str(), __TIME__, __DATE__);
 -
 -      std::string umlist = ServerInstance->Modes->UserModeList();
 -      std::string cmlist = ServerInstance->Modes->ChannelModeList();
 -      std::string pmlist = ServerInstance->Modes->ParaModeList();
 -      this->WriteNumeric(RPL_SERVERVERSION, "%s %s %s %s %s %s", this->nick.c_str(), ServerInstance->Config->ServerName.c_str(), BRANCH, umlist.c_str(), cmlist.c_str(), pmlist.c_str());
 -
 -      ServerInstance->Config->Send005(this);
 -      this->WriteNumeric(RPL_YOURUUID, "%s %s :your unique ID", this->nick.c_str(), this->uuid.c_str());
 -
 -      /* Now registered */
 -      if (ServerInstance->Users->unregistered_count)
 -              ServerInstance->Users->unregistered_count--;
 -
 -      /* Trigger MOTD and LUSERS output, give modules a chance too */
 -      ModResult MOD_RESULT;
 -      std::string command("MOTD");
 -      std::vector<std::string> parameters;
 -      FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, parameters, this, true, command));
 -      if (!MOD_RESULT)
 -              ServerInstance->Parser->CallHandler(command, parameters, this);
 -
 -      MOD_RESULT = MOD_RES_PASSTHRU;
 -      command = "LUSERS";
 -      FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, parameters, this, true, command));
 -      if (!MOD_RESULT)
 -              ServerInstance->Parser->CallHandler(command, parameters, this);
 -
 -      if (ServerInstance->Config->RawLog)
 -              WriteServ("PRIVMSG %s :*** Raw I/O logging is enabled on this server. All messages, passwords, and commands are being recorded.", nick.c_str());
 -
        /*
         * We don't set REG_ALL until triggering OnUserConnect, so some module events don't spew out stuff
         * for a user that doesn't exist yet.
         */
 -      FOREACH_MOD(I_OnUserConnect,OnUserConnect(this));
 +      FOREACH_MOD(OnUserConnect, (this));
  
 +      /* Now registered */
 +      if (ServerInstance->Users->unregistered_count)
 +              ServerInstance->Users->unregistered_count--;
        this->registered = REG_ALL;
  
 -      FOREACH_MOD(I_OnPostConnect,OnPostConnect(this));
 +      FOREACH_MOD(OnPostConnect, (this));
  
        ServerInstance->SNO->WriteToSnoMask('c',"Client connecting on port %d (class %s): %s (%s) [%s]",
 -              this->GetServerPort(), this->MyClass->name.c_str(), GetFullRealHost().c_str(), this->GetIPString(), this->fullname.c_str());
 -      ServerInstance->Logs->Log("BANCACHE", DEBUG, "BanCache: Adding NEGATIVE hit for %s", this->GetIPString());
 -      ServerInstance->BanCache->AddHit(this->GetIPString(), "", "");
 +              this->GetServerPort(), this->MyClass->name.c_str(), GetFullRealHost().c_str(), this->GetIPString().c_str(), this->GetRealName().c_str());
 +      ServerInstance->Logs->Log("BANCACHE", LOG_DEBUG, "BanCache: Adding NEGATIVE hit for " + this->GetIPString());
 +      ServerInstance->BanCache.AddHit(this->GetIPString(), "", "");
        // reset the flood penalty (which could have been raised due to things like auto +x)
        CommandFloodPenalty = 0;
  }
@@@ -588,25 -845,72 +588,25 @@@ void User::InvalidateCache(
        cached_fullrealhost.clear();
  }
  
 -bool User::ChangeNick(const std::string& newnick, bool force)
 +bool User::ChangeNick(const std::string& newnick, time_t newts)
  {
        if (quitting)
        {
 -              ServerInstance->Logs->Log("USERS", DEFAULT, "ERROR: Attempted to change nick of a quitting user: " + this->nick);
 -              return false;
 -      }
 -
 -      ModResult MOD_RESULT;
 -
 -      if (force)
 -              ServerInstance->NICKForced.set(this, 1);
 -      FIRST_MOD_RESULT(OnUserPreNick, MOD_RESULT, (this, newnick));
 -      ServerInstance->NICKForced.set(this, 0);
 -
 -      if (MOD_RESULT == MOD_RES_DENY)
 -      {
 -              ServerInstance->stats->statsCollisions++;
 +              ServerInstance->Logs->Log("USERS", LOG_DEFAULT, "ERROR: Attempted to change nick of a quitting user: " + this->nick);
                return false;
        }
  
 -      if (assign(newnick) == assign(nick))
 +      User* const InUse = ServerInstance->FindNickOnly(newnick);
 +      if (InUse == this)
        {
 -              // case change, don't need to check Q:lines and such
 +              // case change, don't need to check campers
                // and, if it's identical including case, we can leave right now
 +              // We also don't update the nick TS if it's a case change, either
                if (newnick == nick)
                        return true;
        }
        else
        {
 -              /*
 -               * Don't check Q:Lines if it's a server-enforced change, just on the off-chance some fucking *moron*
 -               * tries to Q:Line SIDs, also, this means we just get our way period, as it really should be.
 -               * Thanks Kein for finding this. -- w00t
 -               *
 -               * Also don't check Q:Lines for remote nickchanges, they should have our Q:Lines anyway to enforce themselves.
 -               *              -- w00t
 -               */
 -              if (IS_LOCAL(this) && !force)
 -              {
 -                      XLine* mq = ServerInstance->XLines->MatchesLine("Q",newnick);
 -                      if (mq)
 -                      {
 -                              if (this->registered == REG_ALL)
 -                              {
 -                                      ServerInstance->SNO->WriteGlobalSno('a', "Q-Lined nickname %s from %s: %s",
 -                                              newnick.c_str(), GetFullRealHost().c_str(), mq->reason.c_str());
 -                              }
 -                              this->WriteNumeric(432, "%s %s :Invalid nickname: %s",this->nick.c_str(), newnick.c_str(), mq->reason.c_str());
 -                              return false;
 -                      }
 -
 -                      if (ServerInstance->Config->RestrictBannedUsers)
 -                      {
 -                              for (UCListIter i = this->chans.begin(); i != this->chans.end(); i++)
 -                              {
 -                                      Channel *chan = *i;
 -                                      if (chan->GetPrefixValue(this) < VOICE_VALUE && chan->IsBanned(this))
 -                                      {
 -                                              this->WriteNumeric(404, "%s %s :Cannot send to channel (you're banned)", this->nick.c_str(), chan->name.c_str());
 -                                              return false;
 -                                      }
 -                              }
 -                      }
 -              }
 -
                /*
                 * Uh oh.. if the nickname is in use, and it's not in use by the person using it (doh) --
                 * then we have a potential collide. Check whether someone else is camping on the nick
                 * If the guy using the nick is already using it, tell the incoming nick change to gtfo,
                 * because the nick is already (rightfully) in use. -- w00t
                 */
 -              User* InUse = ServerInstance->FindNickOnly(newnick);
 -              if (InUse && (InUse != this))
 +              if (InUse)
                {
                        if (InUse->registered != REG_ALL)
                        {
                                /* force the camper to their UUID, and ask them to re-send a NICK. */
 -                              InUse->WriteTo(InUse, "NICK %s", InUse->uuid.c_str());
 -                              InUse->WriteNumeric(433, "%s %s :Nickname overruled.", InUse->nick.c_str(), InUse->nick.c_str());
 -
 -                              ServerInstance->Users->clientlist->erase(InUse->nick);
 -                              (*(ServerInstance->Users->clientlist))[InUse->uuid] = InUse;
 -
 -                              InUse->nick = InUse->uuid;
 -                              InUse->InvalidateCache();
 -                              InUse->registered &= ~REG_NICK;
 +                              LocalUser* const localuser = static_cast<LocalUser*>(InUse);
 +                              localuser->OverruleNick();
                        }
                        else
                        {
                                /* No camping, tell the incoming user  to stop trying to change nick ;p */
 -                              this->WriteNumeric(433, "%s %s :Nickname is already in use.", this->registered >= REG_NICK ? this->nick.c_str() : "*", newnick.c_str());
 +                              this->WriteNumeric(ERR_NICKNAMEINUSE, newnick, "Nickname is already in use.");
                                return false;
                        }
                }
 +
 +              age = newts ? newts : ServerInstance->Time();
        }
  
        if (this->registered == REG_ALL)
 -              this->WriteCommon("NICK %s",newnick.c_str());
 -      std::string oldnick = nick;
 +      {
 +              ClientProtocol::Messages::Nick nickmsg(this, newnick);
 +              ClientProtocol::Event nickevent(ServerInstance->GetRFCEvents().nick, nickmsg);
 +              this->WriteCommonRaw(nickevent, true);
 +      }
 +      const std::string oldnick = nick;
        nick = newnick;
  
        InvalidateCache();
 -      ServerInstance->Users->clientlist->erase(oldnick);
 -      (*(ServerInstance->Users->clientlist))[newnick] = this;
 +      ServerInstance->Users->clientlist.erase(oldnick);
 +      ServerInstance->Users->clientlist[newnick] = this;
  
        if (registered == REG_ALL)
 -              FOREACH_MOD(I_OnUserPostNick,OnUserPostNick(this,oldnick));
 +              FOREACH_MOD(OnUserPostNick, (this,oldnick));
  
        return true;
  }
  
 -int LocalUser::GetServerPort()
 +void LocalUser::OverruleNick()
  {
 -      switch (this->server_sa.sa.sa_family)
        {
 -              case AF_INET6:
 -                      return htons(this->server_sa.in6.sin6_port);
 -              case AF_INET:
 -                      return htons(this->server_sa.in4.sin_port);
 +              ClientProtocol::Messages::Nick nickmsg(this, this->uuid);
 +              this->Send(ServerInstance->GetRFCEvents().nick, nickmsg);
        }
 -      return 0;
 +      this->WriteNumeric(ERR_NICKNAMEINUSE, this->nick, "Nickname overruled.");
 +
 +      // Clear the bit before calling ChangeNick() to make it NOT run the OnUserPostNick() hook
 +      this->registered &= ~REG_NICK;
 +      this->ChangeNick(this->uuid);
 +}
 +
 +int LocalUser::GetServerPort()
 +{
 +      return this->server_sa.port();
  }
  
 -const char* User::GetIPString()
 +const std::string& User::GetIPString()
  {
 -      int port;
        if (cachedip.empty())
        {
 -              irc::sockets::satoap(client_sa, cachedip, port);
 +              cachedip = client_sa.addr();
                /* IP addresses starting with a : on irc are a Bad Thing (tm) */
 -              if (cachedip.c_str()[0] == ':')
 +              if (cachedip[0] == ':')
                        cachedip.insert(cachedip.begin(),1,'0');
        }
  
 -      return cachedip.c_str();
 +      return cachedip;
 +}
 +
 +const std::string& User::GetHost(bool uncloak) const
 +{
 +      return uncloak ? GetRealHost() : GetDisplayedHost();
 +}
 +
 +const std::string& User::GetDisplayedHost() const
 +{
 +      return displayhost.empty() ? realhost : displayhost;
 +}
 +
 +const std::string& User::GetRealHost() const
 +{
 +      return realhost;
 +}
 +
 +const std::string& User::GetRealName() const
 +{
 +      return realname;
  }
  
  irc::sockets::cidr_mask User::GetCIDRMask()
  {
 -      int range = 0;
 -      switch (client_sa.sa.sa_family)
 +      unsigned char range = 0;
 +      switch (client_sa.family())
        {
                case AF_INET6:
                        range = ServerInstance->Config->c_ipv6_range;
        return irc::sockets::cidr_mask(client_sa, range);
  }
  
 -bool User::SetClientIP(const char* sip, bool recheck_eline)
 +bool User::SetClientIP(const std::string& address, bool recheck_eline)
  {
--      this->InvalidateCache();
-       return irc::sockets::aptosa(address, 0, client_sa);
 -      return irc::sockets::aptosa(sip, 0, client_sa);
++      irc::sockets::sockaddrs sa;
++      if (!irc::sockets::aptosa(address, client_sa.port(), sa))
++              return false;
++
++      User::SetClientIP(sa, recheck_eline);
++      return true;
  }
  
  void User::SetClientIP(const irc::sockets::sockaddrs& sa, bool recheck_eline)
  {
--      this->InvalidateCache();
++      const std::string oldip(GetIPString());
        memcpy(&client_sa, &sa, sizeof(irc::sockets::sockaddrs));
++      this->InvalidateCache();
++
++      // If the users hostname was their IP then update it.
++      if (GetRealHost() == oldip)
++              ChangeRealHost(GetIPString(), false);
++      if (GetDisplayedHost() == oldip)
++              ChangeDisplayedHost(GetIPString());
  }
  
 -bool LocalUser::SetClientIP(const char* sip, bool recheck_eline)
 +bool LocalUser::SetClientIP(const std::string& address, bool recheck_eline)
  {
        irc::sockets::sockaddrs sa;
-       if (!irc::sockets::aptosa(address, 0, sa))
 -      if (!irc::sockets::aptosa(sip, 0, sa))
--              // Invalid
++      if (!irc::sockets::aptosa(address, client_sa.port(), sa))
                return false;
  
        LocalUser::SetClientIP(sa, recheck_eline);
@@@ -751,178 -1032,308 +761,178 @@@ void LocalUser::SetClientIP(const irc::
                if (recheck_eline)
                        this->exempt = (ServerInstance->XLines->MatchesLine("E", this) != NULL);
  
 -              FOREACH_MOD(I_OnSetUserIP,OnSetUserIP(this));
 +              FOREACH_MOD(OnSetUserIP, (this));
        }
  }
  
 -static std::string wide_newline("\r\n");
 -
 -void User::Write(const std::string& text)
 -{
 -}
 -
 -void User::Write(const char *text, ...)
 -{
 -}
 -
 -void LocalUser::Write(const std::string& text)
 +void LocalUser::Write(const ClientProtocol::SerializedMessage& text)
  {
 -      if (!ServerInstance->SE->BoundsCheckFd(&eh))
 +      if (!SocketEngine::BoundsCheckFd(&eh))
                return;
  
 -      if (text.length() > MAXBUF - 2)
 +      if (ServerInstance->Config->RawLog)
        {
 -              // this should happen rarely or never. Crop the string at 512 and try again.
 -              std::string try_again = text.substr(0, MAXBUF - 2);
 -              Write(try_again);
 -              return;
 -      }
 +              if (text.empty())
 +                      return;
  
 -      ServerInstance->Logs->Log("USEROUTPUT", RAWIO, "C[%s] O %s", uuid.c_str(), text.c_str());
 +              std::string::size_type nlpos = text.find_first_of("\r\n", 0, 2);
 +              if (nlpos == std::string::npos)
 +                      nlpos = text.length(); // TODO is this ok, test it
 +
 +              ServerInstance->Logs->Log("USEROUTPUT", LOG_RAWIO, "C[%s] O %.*s", uuid.c_str(), (int) nlpos, text.c_str());
 +      }
  
        eh.AddWriteBuf(text);
 -      eh.AddWriteBuf(wide_newline);
  
 -      ServerInstance->stats->statsSent += text.length() + 2;
 -      this->bytes_out += text.length() + 2;
 +      const size_t bytessent = text.length() + 2;
 +      ServerInstance->stats.Sent += bytessent;
 +      this->bytes_out += bytessent;
        this->cmds_out++;
  }
  
 -/** Write()
 - */
 -void LocalUser::Write(const char *text, ...)
 +void LocalUser::Send(ClientProtocol::Event& protoev)
  {
 -      va_list argsPtr;
 -      char textbuffer[MAXBUF];
 -
 -      va_start(argsPtr, text);
 -      vsnprintf(textbuffer, MAXBUF, text, argsPtr);
 -      va_end(argsPtr);
 -
 -      this->Write(std::string(textbuffer));
 -}
 +      if (!serializer)
 +              return;
  
 -void User::WriteServ(const std::string& text)
 -{
 -      this->Write(":%s %s",ServerInstance->Config->ServerName.c_str(),text.c_str());
 +      // In the most common case a static LocalUser field, sendmsglist, is passed to the event to be
 +      // populated. The list is cleared before returning.
 +      // To handle re-enters, if sendmsglist is non-empty upon entering the method then a temporary
 +      // list is used instead of the static one.
 +      if (sendmsglist.empty())
 +      {
 +              Send(protoev, sendmsglist);
 +              sendmsglist.clear();
 +      }
 +      else
 +      {
 +              ClientProtocol::MessageList msglist;
 +              Send(protoev, msglist);
 +      }
  }
  
 -/** WriteServ()
 - *  Same as Write(), except `text' is prefixed with `:server.name '.
 - */
 -void User::WriteServ(const char* text, ...)
 +void LocalUser::Send(ClientProtocol::Event& protoev, ClientProtocol::MessageList& msglist)
  {
 -      va_list argsPtr;
 -      char textbuffer[MAXBUF];
 -
 -      va_start(argsPtr, text);
 -      vsnprintf(textbuffer, MAXBUF, text, argsPtr);
 -      va_end(argsPtr);
 -
 -      this->WriteServ(std::string(textbuffer));
 +      // Modules can personalize the messages sent per user for the event
 +      protoev.GetMessagesForUser(this, msglist);
 +      for (ClientProtocol::MessageList::const_iterator i = msglist.begin(); i != msglist.end(); ++i)
 +      {
 +              ClientProtocol::Message& curr = **i;
 +              ModResult res;
 +              FIRST_MOD_RESULT(OnUserWrite, res, (this, curr));
 +              if (res != MOD_RES_DENY)
 +                      Write(serializer->SerializeForUser(this, curr));
 +      }
  }
  
 -
 -void User::WriteNumeric(unsigned int numeric, const char* text, ...)
 +void User::WriteNumeric(const Numeric::Numeric& numeric)
  {
 -      va_list argsPtr;
 -      char textbuffer[MAXBUF];
 -
 -      va_start(argsPtr, text);
 -      vsnprintf(textbuffer, MAXBUF, text, argsPtr);
 -      va_end(argsPtr);
 -
 -      this->WriteNumeric(numeric, std::string(textbuffer));
 -}
 +      LocalUser* const localuser = IS_LOCAL(this);
 +      if (!localuser)
 +              return;
  
 -void User::WriteNumeric(unsigned int numeric, const std::string &text)
 -{
 -      char textbuffer[MAXBUF];
        ModResult MOD_RESULT;
  
 -      FIRST_MOD_RESULT(OnNumeric, MOD_RESULT, (this, numeric, text));
 +      FIRST_MOD_RESULT(OnNumeric, MOD_RESULT, (this, numeric));
  
        if (MOD_RESULT == MOD_RES_DENY)
                return;
  
 -      snprintf(textbuffer,MAXBUF,":%s %03u %s",ServerInstance->Config->ServerName.c_str(), numeric, text.c_str());
 -      this->Write(std::string(textbuffer));
 -}
 -
 -void User::WriteFrom(User *user, const std::string &text)
 -{
 -      char tb[MAXBUF];
 -
 -      snprintf(tb,MAXBUF,":%s %s",user->GetFullHost().c_str(),text.c_str());
 -
 -      this->Write(std::string(tb));
 -}
 -
 -
 -/* write text from an originating user to originating user */
 -
 -void User::WriteFrom(User *user, const char* text, ...)
 -{
 -      va_list argsPtr;
 -      char textbuffer[MAXBUF];
 -
 -      va_start(argsPtr, text);
 -      vsnprintf(textbuffer, MAXBUF, text, argsPtr);
 -      va_end(argsPtr);
 -
 -      this->WriteFrom(user, std::string(textbuffer));
 +      ClientProtocol::Messages::Numeric numericmsg(numeric, localuser);
 +      localuser->Send(ServerInstance->GetRFCEvents().numeric, numericmsg);
  }
  
 -
 -/* write text to an destination user from a source user (e.g. user privmsg) */
 -
 -void User::WriteTo(User *dest, const char *data, ...)
 +void User::WriteRemoteNotice(const std::string& text)
  {
 -      char textbuffer[MAXBUF];
 -      va_list argsPtr;
 -
 -      va_start(argsPtr, data);
 -      vsnprintf(textbuffer, MAXBUF, data, argsPtr);
 -      va_end(argsPtr);
 -
 -      this->WriteTo(dest, std::string(textbuffer));
 +      ServerInstance->PI->SendUserNotice(this, text);
  }
  
 -void User::WriteTo(User *dest, const std::string &data)
 +void LocalUser::WriteRemoteNotice(const std::string& text)
  {
 -      dest->WriteFrom(this, data);
 +      WriteNotice(text);
  }
  
 -void User::WriteCommon(const char* text, ...)
 +namespace
  {
 -      char textbuffer[MAXBUF];
 -      va_list argsPtr;
 -
 -      if (this->registered != REG_ALL || quitting)
 -              return;
 -
 -      int len = snprintf(textbuffer,MAXBUF,":%s ",this->GetFullHost().c_str());
 +      class WriteCommonRawHandler : public User::ForEachNeighborHandler
 +      {
 +              ClientProtocol::Event& ev;
  
 -      va_start(argsPtr, text);
 -      vsnprintf(textbuffer + len, MAXBUF - len, text, argsPtr);
 -      va_end(argsPtr);
 +              void Execute(LocalUser* user) CXX11_OVERRIDE
 +              {
 +                      user->Send(ev);
 +              }
  
 -      this->WriteCommonRaw(std::string(textbuffer), true);
 +       public:
 +              WriteCommonRawHandler(ClientProtocol::Event& protoev)
 +                      : ev(protoev)
 +              {
 +              }
 +      };
  }
  
 -void User::WriteCommonExcept(const char* text, ...)
 +void User::WriteCommonRaw(ClientProtocol::Event& protoev, bool include_self)
  {
 -      char textbuffer[MAXBUF];
 -      va_list argsPtr;
 -
 -      if (this->registered != REG_ALL || quitting)
 -              return;
 -
 -      int len = snprintf(textbuffer,MAXBUF,":%s ",this->GetFullHost().c_str());
 -
 -      va_start(argsPtr, text);
 -      vsnprintf(textbuffer + len, MAXBUF - len, text, argsPtr);
 -      va_end(argsPtr);
 -
 -      this->WriteCommonRaw(std::string(textbuffer), false);
 +      WriteCommonRawHandler handler(protoev);
 +      ForEachNeighbor(handler, include_self);
  }
  
 -void User::WriteCommonRaw(const std::string &line, bool include_self)
 +void User::ForEachNeighbor(ForEachNeighborHandler& handler, bool include_self)
  {
 -      if (this->registered != REG_ALL || quitting)
 -              return;
 -
 -      LocalUser::already_sent_id++;
 -
 -      UserChanList include_c(chans);
 -      std::map<User*,bool> exceptions;
 +      // The basic logic for visiting the neighbors of a user is to iterate the channel list of the user
 +      // and visit all users on those channels. Because two users may share more than one common channel,
 +      // we must skip users that we have already visited.
 +      // To do this, we make use of a global counter and an integral 'already_sent' field in LocalUser.
 +      // The global counter is incremented every time we do something for each neighbor of a user. Then,
 +      // before visiting a member we examine user->already_sent. If it's equal to the current counter, we
 +      // skip the member. Otherwise, we set it to the current counter and visit the member.
  
 +      // Ask modules to build a list of exceptions.
 +      // Mods may also exclude entire channels by erasing them from include_chans.
 +      IncludeChanList include_chans(chans.begin(), chans.end());
 +      std::map<User*, bool> exceptions;
        exceptions[this] = include_self;
 +      FOREACH_MOD(OnBuildNeighborList, (this, include_chans, exceptions));
  
 -      FOREACH_MOD(I_OnBuildNeighborList,OnBuildNeighborList(this, include_c, exceptions));
 +      // Get next id, guaranteed to differ from the already_sent field of all users
 +      const already_sent_t newid = ServerInstance->Users.NextAlreadySentId();
  
 -      for (std::map<User*,bool>::iterator i = exceptions.begin(); i != exceptions.end(); ++i)
 +      // Handle exceptions first
 +      for (std::map<User*, bool>::const_iterator i = exceptions.begin(); i != exceptions.end(); ++i)
        {
 -              LocalUser* u = IS_LOCAL(i->first);
 -              if (u && !u->quitting)
 +              LocalUser* curr = IS_LOCAL(i->first);
 +              if (curr)
                {
 -                      u->already_sent = LocalUser::already_sent_id;
 -                      if (i->second)
 -                              u->Write(line);
 +                      // Mark as visited to ensure we won't visit again if there is a common channel
 +                      curr->already_sent = newid;
 +                      // Always treat quitting users as excluded
 +                      if ((i->second) && (!curr->quitting))
 +                              handler.Execute(curr);
                }
        }
 -      for (UCListIter v = include_c.begin(); v != include_c.end(); ++v)
 -      {
 -              Channel* c = *v;
 -              const UserMembList* ulist = c->GetUsers();
 -              for (UserMembList::const_iterator i = ulist->begin(); i != ulist->end(); i++)
 -              {
 -                      LocalUser* u = IS_LOCAL(i->first);
 -                      if (u && !u->quitting && u->already_sent != LocalUser::already_sent_id)
 -                      {
 -                              u->already_sent = LocalUser::already_sent_id;
 -                              u->Write(line);
 -                      }
 -              }
 -      }
 -}
  
 -void User::WriteCommonQuit(const std::string &normal_text, const std::string &oper_text)
 -{
 -      char tb1[MAXBUF];
 -      char tb2[MAXBUF];
 -
 -      if (this->registered != REG_ALL)
 -              return;
 -
 -      already_sent_t uniq_id = ++LocalUser::already_sent_id;
 -
 -      snprintf(tb1,MAXBUF,":%s QUIT :%s",this->GetFullHost().c_str(),normal_text.c_str());
 -      snprintf(tb2,MAXBUF,":%s QUIT :%s",this->GetFullHost().c_str(),oper_text.c_str());
 -      std::string out1 = tb1;
 -      std::string out2 = tb2;
 -
 -      UserChanList include_c(chans);
 -      std::map<User*,bool> exceptions;
 -
 -      FOREACH_MOD(I_OnBuildNeighborList,OnBuildNeighborList(this, include_c, exceptions));
 -
 -      for (std::map<User*,bool>::iterator i = exceptions.begin(); i != exceptions.end(); ++i)
 +      // Now consider the real neighbors
 +      for (IncludeChanList::const_iterator i = include_chans.begin(); i != include_chans.end(); ++i)
        {
 -              LocalUser* u = IS_LOCAL(i->first);
 -              if (u && !u->quitting)
 +              Channel* chan = (*i)->chan;
 +              const Channel::MemberMap& userlist = chan->GetUsers();
 +              for (Channel::MemberMap::const_iterator j = userlist.begin(); j != userlist.end(); ++j)
                {
 -                      u->already_sent = uniq_id;
 -                      if (i->second)
 -                              u->Write(IS_OPER(u) ? out2 : out1);
 -              }
 -      }
 -      for (UCListIter v = include_c.begin(); v != include_c.end(); ++v)
 -      {
 -              const UserMembList* ulist = (*v)->GetUsers();
 -              for (UserMembList::const_iterator i = ulist->begin(); i != ulist->end(); i++)
 -              {
 -                      LocalUser* u = IS_LOCAL(i->first);
 -                      if (u && !u->quitting && (u->already_sent != uniq_id))
 +                      LocalUser* curr = IS_LOCAL(j->first);
 +                      // User not yet visited?
 +                      if ((curr) && (curr->already_sent != newid))
                        {
 -                              u->already_sent = uniq_id;
 -                              u->Write(IS_OPER(u) ? out2 : out1);
 +                              // Mark as visited and execute function
 +                              curr->already_sent = newid;
 +                              handler.Execute(curr);
                        }
                }
        }
  }
  
 -void LocalUser::SendText(const std::string& line)
 +void User::WriteRemoteNumeric(const Numeric::Numeric& numeric)
  {
 -      Write(line);
 -}
 -
 -void RemoteUser::SendText(const std::string& line)
 -{
 -      ServerInstance->PI->PushToClient(this, line);
 -}
 -
 -void FakeUser::SendText(const std::string& line)
 -{
 -}
 -
 -void User::SendText(const char *text, ...)
 -{
 -      va_list argsPtr;
 -      char line[MAXBUF];
 -
 -      va_start(argsPtr, text);
 -      vsnprintf(line, MAXBUF, text, argsPtr);
 -      va_end(argsPtr);
 -
 -      SendText(std::string(line));
 -}
 -
 -void User::SendText(const std::string &LinePrefix, std::stringstream &TextStream)
 -{
 -      std::string line;
 -      std::string Word;
 -      while (TextStream >> Word)
 -      {
 -              size_t lineLength = LinePrefix.length() + line.length() + Word.length() + 13;
 -              if (lineLength > MAXBUF)
 -              {
 -                      SendText(LinePrefix + line);
 -                      line.clear();
 -              }
 -              line += " " + Word;
 -      }
 -      SendText(LinePrefix + line);
 +      WriteNumeric(numeric);
  }
  
  /* return 0 or 1 depending if users u and u2 share one or more common channels
   */
  bool User::SharesChannelWith(User *other)
  {
 -      if ((!other) || (this->registered != REG_ALL) || (other->registered != REG_ALL))
 -              return false;
 -
        /* Outer loop */
 -      for (UCListIter i = this->chans.begin(); i != this->chans.end(); i++)
 +      for (User::ChanList::iterator i = this->chans.begin(); i != this->chans.end(); ++i)
        {
                /* Eliminate the inner loop (which used to be ~equal in size to the outer loop)
                 * by replacing it with a map::find which *should* be more efficient
                 */
 -              if ((*i)->HasUser(other))
 +              if ((*i)->chan->HasUser(other))
                        return true;
        }
        return false;
  }
  
 -bool User::ChangeName(const char* gecos)
 +bool User::ChangeRealName(const std::string& real)
  {
 -      if (!this->fullname.compare(gecos))
 +      if (!this->realname.compare(real))
                return true;
  
        if (IS_LOCAL(this))
        {
                ModResult MOD_RESULT;
 -              FIRST_MOD_RESULT(OnChangeLocalUserGECOS, MOD_RESULT, (IS_LOCAL(this),gecos));
 +              FIRST_MOD_RESULT(OnPreChangeRealName, MOD_RESULT, (IS_LOCAL(this), real));
                if (MOD_RESULT == MOD_RES_DENY)
                        return false;
 -              FOREACH_MOD(I_OnChangeName,OnChangeName(this,gecos));
 +              FOREACH_MOD(OnChangeRealName, (this, real));
        }
 -      this->fullname.assign(gecos, 0, ServerInstance->Config->Limits.MaxGecos);
 +      this->realname.assign(real, 0, ServerInstance->Config->Limits.MaxReal);
  
        return true;
  }
  
 -void User::DoHostCycle(const std::string &quitline)
 -{
 -      char buffer[MAXBUF];
 -
 -      if (!ServerInstance->Config->CycleHosts)
 -              return;
 -
 -      already_sent_t silent_id = ++LocalUser::already_sent_id;
 -      already_sent_t seen_id = ++LocalUser::already_sent_id;
 -
 -      UserChanList include_c(chans);
 -      std::map<User*,bool> exceptions;
 -
 -      FOREACH_MOD(I_OnBuildNeighborList,OnBuildNeighborList(this, include_c, exceptions));
 -
 -      // Users shouldn't see themselves quitting when host cycling
 -      exceptions.erase(this);
 -      for (std::map<User*,bool>::iterator i = exceptions.begin(); i != exceptions.end(); ++i)
 -      {
 -              LocalUser* u = IS_LOCAL(i->first);
 -              if (u && !u->quitting)
 -              {
 -                      if (i->second)
 -                      {
 -                              u->already_sent = seen_id;
 -                              u->Write(quitline);
 -                      }
 -                      else
 -                      {
 -                              u->already_sent = silent_id;
 -                      }
 -              }
 -      }
 -      for (UCListIter v = include_c.begin(); v != include_c.end(); ++v)
 -      {
 -              Channel* c = *v;
 -              snprintf(buffer, MAXBUF, ":%s JOIN %s", GetFullHost().c_str(), c->name.c_str());
 -              std::string joinline(buffer);
 -              Membership* memb = c->GetUser(this);
 -              std::string modeline = memb->modes;
 -              if (modeline.length() > 0)
 -              {
 -                      for(unsigned int i=0; i < memb->modes.length(); i++)
 -                              modeline.append(" ").append(nick);
 -                      snprintf(buffer, MAXBUF, ":%s MODE %s +%s",
 -                              ServerInstance->Config->CycleHostsFromUser ? GetFullHost().c_str() : ServerInstance->Config->ServerName.c_str(),
 -                              c->name.c_str(), modeline.c_str());
 -                      modeline = buffer;
 -              }
 -
 -              const UserMembList *ulist = c->GetUsers();
 -              for (UserMembList::const_iterator i = ulist->begin(); i != ulist->end(); i++)
 -              {
 -                      LocalUser* u = IS_LOCAL(i->first);
 -                      if (u == NULL || u == this)
 -                              continue;
 -                      if (u->already_sent == silent_id)
 -                              continue;
 -
 -                      if (u->already_sent != seen_id)
 -                      {
 -                              u->Write(quitline);
 -                              u->already_sent = seen_id;
 -                      }
 -                      u->Write(joinline);
 -                      if (modeline.length() > 0)
 -                              u->Write(modeline);
 -              }
 -      }
 -}
 -
 -bool User::ChangeDisplayedHost(const char* shost)
 +bool User::ChangeDisplayedHost(const std::string& shost)
  {
 -      if (dhost == shost)
 +      if (GetDisplayedHost() == shost)
                return true;
  
 -      if (IS_LOCAL(this))
 +      LocalUser* luser = IS_LOCAL(this);
 +      if (luser)
        {
                ModResult MOD_RESULT;
 -              FIRST_MOD_RESULT(OnChangeLocalUserHost, MOD_RESULT, (IS_LOCAL(this),shost));
 +              FIRST_MOD_RESULT(OnPreChangeHost, MOD_RESULT, (luser, shost));
                if (MOD_RESULT == MOD_RES_DENY)
                        return false;
        }
  
 -      FOREACH_MOD(I_OnChangeHost, OnChangeHost(this,shost));
 -
 -      std::string quitstr = ":" + GetFullHost() + " QUIT :Changing host";
 +      FOREACH_MOD(OnChangeHost, (this,shost));
  
 -      /* Fix by Om: User::dhost is 65 long, this was truncating some long hosts */
 -      this->dhost.assign(shost, 0, 64);
 +      if (realhost == shost)
 +              this->displayhost.clear();
 +      else
 +              this->displayhost.assign(shost, 0, ServerInstance->Config->Limits.MaxHost);
  
        this->InvalidateCache();
  
 -      this->DoHostCycle(quitstr);
 -
        if (IS_LOCAL(this))
 -              this->WriteNumeric(RPL_YOURDISPLAYEDHOST, "%s %s :is now your displayed host",this->nick.c_str(),this->dhost.c_str());
 +              this->WriteNumeric(RPL_YOURDISPLAYEDHOST, this->GetDisplayedHost(), "is now your displayed host");
  
        return true;
  }
  
 -bool User::ChangeIdent(const char* newident)
 +void User::ChangeRealHost(const std::string& host, bool resetdisplay)
  {
 -      if (this->ident == newident)
 -              return true;
 -
 -      FOREACH_MOD(I_OnChangeIdent, OnChangeIdent(this,newident));
 -
 -      std::string quitstr = ":" + GetFullHost() + " QUIT :Changing ident";
 -
 -      this->ident.assign(newident, 0, ServerInstance->Config->Limits.IdentMax);
 +      // If the real host is the new host and we are not resetting the
 +      // display host then we have nothing to do.
 +      const bool changehost = (realhost != host);
 +      if (!changehost && !resetdisplay)
 +              return;
 +      
 +      // If the displayhost is not set and we are not resetting it then
 +      // we need to copy it to the displayhost field.
 +      if (displayhost.empty() && !resetdisplay)
 +              displayhost = realhost;
 +
 +      // If the displayhost is the new host or we are resetting it then
 +      // we clear its contents to save memory.
 +      else if (displayhost == host || resetdisplay)
 +              displayhost.clear();
 +
 +      // If we are just resetting the display host then we don't need to
 +      // do anything else.
 +      if (!changehost)
 +              return;
  
 +      realhost = host;
        this->InvalidateCache();
 -
 -      this->DoHostCycle(quitstr);
 -
 -      return true;
  }
  
 -void User::SendAll(const char* command, const char* text, ...)
 +bool User::ChangeIdent(const std::string& newident)
  {
 -      char textbuffer[MAXBUF];
 -      char formatbuffer[MAXBUF];
 -      va_list argsPtr;
 -
 -      va_start(argsPtr, text);
 -      vsnprintf(textbuffer, MAXBUF, text, argsPtr);
 -      va_end(argsPtr);
 -
 -      snprintf(formatbuffer,MAXBUF,":%s %s $* :%s", this->GetFullHost().c_str(), command, textbuffer);
 -      std::string fmt = formatbuffer;
 -
 -      for (LocalUserList::const_iterator i = ServerInstance->Users->local_users.begin(); i != ServerInstance->Users->local_users.end(); i++)
 -      {
 -              if ((*i)->registered == REG_ALL)
 -                      (*i)->Write(fmt);
 -      }
 -}
 -
 -
 -std::string User::ChannelList(User* source, bool spy)
 -{
 -      std::string list;
 -
 -      for (UCListIter i = this->chans.begin(); i != this->chans.end(); i++)
 -      {
 -              Channel* c = *i;
 -              /* If the target is the sender, neither +p nor +s is set, or
 -               * the channel contains the user, it is not a spy channel
 -               */
 -              if (spy != (source == this || !(c->IsModeSet('p') || c->IsModeSet('s')) || c->HasUser(source)))
 -                      list.append(c->GetPrefixChar(this)).append(c->name).append(" ");
 -      }
 -
 -      return list;
 -}
 -
 -void User::SplitChanList(User* dest, const std::string &cl)
 -{
 -      std::string line;
 -      std::ostringstream prefix;
 -      std::string::size_type start, pos;
 -
 -      prefix << this->nick << " " << dest->nick << " :";
 -      line = prefix.str();
 -      int namelen = ServerInstance->Config->ServerName.length() + 6;
 +      if (this->ident == newident)
 +              return true;
  
 -      for (start = 0; (pos = cl.find(' ', start)) != std::string::npos; start = pos+1)
 -      {
 -              if (line.length() + namelen + pos - start > 510)
 -              {
 -                      ServerInstance->SendWhoisLine(this, dest, 319, "%s", line.c_str());
 -                      line = prefix.str();
 -              }
 +      FOREACH_MOD(OnChangeIdent, (this,newident));
  
 -              line.append(cl.substr(start, pos - start + 1));
 -      }
 +      this->ident.assign(newident, 0, ServerInstance->Config->Limits.IdentMax);
 +      this->InvalidateCache();
  
 -      if (line.length() != prefix.str().length())
 -      {
 -              ServerInstance->SendWhoisLine(this, dest, 319, "%s", line.c_str());
 -      }
 +      return true;
  }
  
  /*
@@@ -1049,27 -1578,27 +1059,27 @@@ void LocalUser::SetClass(const std::str
  {
        ConnectClass *found = NULL;
  
 -      ServerInstance->Logs->Log("CONNECTCLASS", DEBUG, "Setting connect class for UID %s", this->uuid.c_str());
 +      ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Setting connect class for UID %s", this->uuid.c_str());
  
        if (!explicit_name.empty())
        {
 -              for (ClassVector::iterator i = ServerInstance->Config->Classes.begin(); i != ServerInstance->Config->Classes.end(); i++)
 +              for (ServerConfig::ClassVector::const_iterator i = ServerInstance->Config->Classes.begin(); i != ServerInstance->Config->Classes.end(); ++i)
                {
                        ConnectClass* c = *i;
  
                        if (explicit_name == c->name)
                        {
 -                              ServerInstance->Logs->Log("CONNECTCLASS", DEBUG, "Explicitly set to %s", explicit_name.c_str());
 +                              ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Explicitly set to %s", explicit_name.c_str());
                                found = c;
                        }
                }
        }
        else
        {
 -              for (ClassVector::iterator i = ServerInstance->Config->Classes.begin(); i != ServerInstance->Config->Classes.end(); i++)
 +              for (ServerConfig::ClassVector::const_iterator i = ServerInstance->Config->Classes.begin(); i != ServerInstance->Config->Classes.end(); ++i)
                {
                        ConnectClass* c = *i;
 -                      ServerInstance->Logs->Log("CONNECTCLASS", DEBUG, "Checking %s", c->GetName().c_str());
 +                      ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Checking %s", c->GetName().c_str());
  
                        ModResult MOD_RESULT;
                        FIRST_MOD_RESULT(OnSetConnectClass, MOD_RESULT, (this,c));
                                continue;
                        if (MOD_RESULT == MOD_RES_ALLOW)
                        {
 -                              ServerInstance->Logs->Log("CONNECTCLASS", DEBUG, "Class forced by module to %s", c->GetName().c_str());
 +                              ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Class forced by module to %s", c->GetName().c_str());
                                found = c;
                                break;
                        }
  
                        /* check if host matches.. */
                        if (!InspIRCd::MatchCIDR(this->GetIPString(), c->GetHost(), NULL) &&
 -                          !InspIRCd::MatchCIDR(this->host, c->GetHost(), NULL))
 +                          !InspIRCd::MatchCIDR(this->GetRealHost(), c->GetHost(), NULL))
                        {
 -                              ServerInstance->Logs->Log("CONNECTCLASS", DEBUG, "No host match (for %s)", c->GetHost().c_str());
 +                              ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "No host match (for %s)", c->GetHost().c_str());
                                continue;
                        }
  
                         */
                        if (c->limit && (c->GetReferenceCount() >= c->limit))
                        {
 -                              ServerInstance->Logs->Log("CONNECTCLASS", DEBUG, "OOPS: Connect class limit (%lu) hit, denying", c->limit);
 +                              ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "OOPS: Connect class limit (%lu) hit, denying", c->limit);
                                continue;
                        }
  
                        /* if it requires a port ... */
 -                      int port = c->config->getInt("port");
 -                      if (port)
 +                      if (!c->ports.empty())
                        {
 -                              ServerInstance->Logs->Log("CONNECTCLASS", DEBUG, "Requires port (%d)", port);
 -
                                /* and our port doesn't match, fail. */
 -                              if (this->GetServerPort() != port)
 +                              if (!c->ports.count(this->GetServerPort()))
 +                              {
 +                                      ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Requires a different port, skipping");
                                        continue;
 +                              }
                        }
  
                        if (regdone && !c->config->getString("password").empty())
                        {
 -                              if (ServerInstance->PassCompare(this, c->config->getString("password"), password, c->config->getString("hash")))
 +                              if (!ServerInstance->PassCompare(this, c->config->getString("password"), password, c->config->getString("hash")))
                                {
 -                                      ServerInstance->Logs->Log("CONNECTCLASS", DEBUG, "Bad password, skipping");
 +                                      ServerInstance->Logs->Log("CONNECTCLASS", LOG_DEBUG, "Bad password, skipping");
                                        continue;
                                }
                        }
        }
  }
  
 -/* looks up a users password for their connection class (<ALLOW>/<DENY> tags)
 - * NOTE: If the <ALLOW> or <DENY> tag specifies an ip, and this user resolves,
 - * then their ip will be taken as 'priority' anyway, so for example,
 - * <connect allow="127.0.0.1"> will match joe!bloggs@localhost
 - */
 -ConnectClass* LocalUser::GetClass()
 -{
 -      return MyClass;
 -}
 -
 -ConnectClass* User::GetClass()
 -{
 -      return NULL;
 -}
 -
  void User::PurgeEmptyChannels()
  {
        // firstly decrement the count on each channel
 -      for (UCListIter f = this->chans.begin(); f != this->chans.end(); f++)
 +      for (User::ChanList::iterator i = this->chans.begin(); i != this->chans.end(); )
        {
 -              Channel* c = *f;
 +              Channel* c = (*i)->chan;
 +              ++i;
                c->DelUser(this);
        }
  
        this->UnOper();
  }
  
 +void User::WriteNotice(const std::string& text)
 +{
 +      LocalUser* const localuser = IS_LOCAL(this);
 +      if (!localuser)
 +              return;
 +
 +      ClientProtocol::Messages::Privmsg msg(ClientProtocol::Messages::Privmsg::nocopy, ServerInstance->FakeClient, localuser, text, MSG_NOTICE);
 +      localuser->Send(ServerInstance->GetRFCEvents().privmsg, msg);
 +}
 +
  const std::string& FakeUser::GetFullHost()
  {
 -      if (!ServerInstance->Config->HideWhoisServer.empty())
 -              return ServerInstance->Config->HideWhoisServer;
 -      return server;
 +      if (!ServerInstance->Config->HideServer.empty())
 +              return ServerInstance->Config->HideServer;
 +      return server->GetName();
  }
  
  const std::string& FakeUser::GetFullRealHost()
  {
 -      if (!ServerInstance->Config->HideWhoisServer.empty())
 -              return ServerInstance->Config->HideWhoisServer;
 -      return server;
 +      if (!ServerInstance->Config->HideServer.empty())
 +              return ServerInstance->Config->HideServer;
 +      return server->GetName();
  }
  
  ConnectClass::ConnectClass(ConfigTag* tag, char t, const std::string& mask)
        : config(tag), type(t), fakelag(true), name("unnamed"), registration_timeout(0), host(mask),
        pingtime(0), softsendqmax(0), hardsendqmax(0), recvqmax(0),
 -      penaltythreshold(0), commandrate(0), maxlocal(0), maxglobal(0), maxconnwarn(true), maxchans(0), limit(0)
 +      penaltythreshold(0), commandrate(0), maxlocal(0), maxglobal(0), maxconnwarn(true), maxchans(ServerInstance->Config->MaxChans),
 +      limit(0), resolvehostnames(true)
  {
  }
  
  ConnectClass::ConnectClass(ConfigTag* tag, char t, const std::string& mask, const ConnectClass& parent)
 -      : config(tag), type(t), fakelag(parent.fakelag), name("unnamed"),
 -      registration_timeout(parent.registration_timeout), host(mask), pingtime(parent.pingtime),
 -      softsendqmax(parent.softsendqmax), hardsendqmax(parent.hardsendqmax), recvqmax(parent.recvqmax),
 -      penaltythreshold(parent.penaltythreshold), commandrate(parent.commandrate),
 -      maxlocal(parent.maxlocal), maxglobal(parent.maxglobal), maxconnwarn(parent.maxconnwarn), maxchans(parent.maxchans),
 -      limit(parent.limit)
  {
 +      Update(&parent);
 +      name = "unnamed";
 +      type = t;
 +      host = mask;
 +
 +      // Connect classes can inherit from each other but this is problematic for modules which can't use
 +      // ConnectClass::Update so we build a hybrid tag containing all of the values set on this class as
 +      // well as the parent class.
 +      ConfigItems* items = NULL;
 +      config = ConfigTag::create(tag->tag, tag->src_name, tag->src_line, items);
 +
 +      const ConfigItems& parentkeys = parent.config->getItems();
 +      for (ConfigItems::const_iterator piter = parentkeys.begin(); piter != parentkeys.end(); ++piter)
 +      {
 +              // The class name and parent name are not inherited
 +              if (stdalgo::string::equalsci(piter->first, "name") || stdalgo::string::equalsci(piter->first, "parent"))
 +                      continue;
 +
 +              // Store the item in the config tag. If this item also
 +              // exists in the child it will be overwritten.
 +              (*items)[piter->first] = piter->second;
 +      }
 +
 +      const ConfigItems& childkeys = tag->getItems();
 +      for (ConfigItems::const_iterator citer = childkeys.begin(); citer != childkeys.end(); ++citer)
 +      {
 +              // This will overwrite the parent value if present.
 +              (*items)[citer->first] = citer->second;
 +      }
  }
  
  void ConnectClass::Update(const ConnectClass* src)
        maxconnwarn = src->maxconnwarn;
        maxchans = src->maxchans;
        limit = src->limit;
 +      resolvehostnames = src->resolvehostnames;
 +      ports = src->ports;
  }