]> git.netwichtig.de Git - user/henk/code/inspircd.git/blobdiff - src/users.cpp
Fix interaction of m_permchannels post-cull and channel empty-by-quit that causes...
[user/henk/code/inspircd.git] / src / users.cpp
index b6335038620bf865328413b2a06164ff3ddc2bc3..fbb2309c3097524fa2a08ceaa2492b86d596335f 100644 (file)
@@ -11,8 +11,6 @@
  * ---------------------------------------------------
  */
 
-/* $Core */
-
 #include "inspircd.h"
 #include <stdarg.h>
 #include "socketengine.h"
 #include "bancache.h"
 #include "commands/cmd_whowas.h"
 
-/* XXX: Used for speeding up WriteCommon operations */
-unsigned long uniq_id = 1;
-
-static unsigned long* already_sent = NULL;
-
-LocalIntExt User::NICKForced("NICKForced", NULL);
-LocalStringExt User::OperQuit("OperQuit", NULL);
-
-void InitializeAlreadySent(SocketEngine* SE)
+typedef unsigned int uniq_id_t;
+class sent
 {
-       already_sent = new unsigned long[SE->GetMaxFds()];
-       memset(already_sent, 0, SE->GetMaxFds() * sizeof(unsigned long));
-}
+       uniq_id_t uniq_id;
+       uniq_id_t* array;
+       void init()
+       {
+               if (!array)
+                       array = new uniq_id_t[ServerInstance->SE->GetMaxFds()];
+               memset(array, 0, ServerInstance->SE->GetMaxFds() * sizeof(uniq_id_t));
+               uniq_id++;
+       }
+ public:
+       sent() : uniq_id(static_cast<uniq_id_t>(-1)), array(NULL) {}
+       inline uniq_id_t operator++()
+       {
+               if (++uniq_id == 0)
+                       init();
+               return uniq_id;
+       }
+       inline uniq_id_t& operator[](int i)
+       {
+               return array[i];
+       }
+       ~sent()
+       {
+               delete[] array;
+       }
+};
+
+static sent already_sent;
 
 std::string User::ProcessNoticeMasks(const char *sm)
 {
@@ -109,7 +125,7 @@ void User::StartDNSLookup()
                UserResolver *res_reverse;
 
                QueryType resolvtype = this->client_sa.sa.sa_family == AF_INET6 ? DNS_QUERY_PTR6 : DNS_QUERY_PTR4;
-               res_reverse = new UserResolver(ServerInstance, this, sip, resolvtype, cached);
+               res_reverse = new UserResolver(this, sip, resolvtype, cached);
 
                ServerInstance->AddResolver(res_reverse, cached);
        }
@@ -205,65 +221,37 @@ void User::DecrementModes()
        }
 }
 
-User::User(InspIRCd* Instance, const std::string &uid)
+User::User(const std::string &uid)
 {
-       server = Instance->FindServerNamePtr(Instance->Config->ServerName);
+       server = ServerInstance->Config->ServerName;
        age = ServerInstance->Time();
        Penalty = 0;
        lastping = signon = idle_lastmsg = nping = registered = 0;
        bytes_in = bytes_out = cmds_in = cmds_out = 0;
-       quietquit = quitting = exempt = haspassed = dns_done = false;
+       quietquit = quitting = exempt = dns_done = false;
        fd = -1;
        server_sa.sa.sa_family = AF_UNSPEC;
        client_sa.sa.sa_family = AF_UNSPEC;
-       MyClass = NULL;
        AllowedPrivs = AllowedOperCommands = NULL;
 
        if (uid.empty())
-               uuid.assign(Instance->GetUID(), 0, UUID_LENGTH - 1);
+               uuid = ServerInstance->GetUID();
        else
-               uuid.assign(uid, 0, UUID_LENGTH - 1);
+               uuid = uid;
 
        ServerInstance->Logs->Log("USERS", DEBUG,"New UUID for user: %s (%s)", uuid.c_str(), uid.empty() ? "allocated new" : "used remote");
 
-       user_hash::iterator finduuid = Instance->Users->uuidlist->find(uuid);
-       if (finduuid == Instance->Users->uuidlist->end())
-               (*Instance->Users->uuidlist)[uuid] = this;
+       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");
 }
 
 User::~User()
 {
-       ServerInstance->Logs->Log("USERS", DEBUG, "User destructor for %s", uuid.c_str());
-       /* NULL for remote users :) */
-       if (this->MyClass)
-       {
-               this->MyClass->RefCount--;
-               ServerInstance->Logs->Log("USERS", DEBUG, "User destructor -- connect refcount now: %lu", this->MyClass->RefCount);
-               if (MyClass->RefCount == 0)
-                       delete MyClass;
-       }
-
-       if (this->AllowedOperCommands)
-       {
-               delete AllowedOperCommands;
-               AllowedOperCommands = NULL;
-       }
-
-       if (this->AllowedPrivs)
-       {
-               delete AllowedPrivs;
-               AllowedPrivs = NULL;
-       }
-
-       this->InvalidateCache();
-       this->DecrementModes();
-
-       if (client_sa.sa.sa_family != AF_UNSPEC)
-               ServerInstance->Users->RemoveCloneCounts(this);
-
-       ServerInstance->Users->uuidlist->erase(uuid);
+       if (uuid.length())
+               ServerInstance->Logs->Log("USERS", ERROR, "User destructor for %s called without cull", uuid.c_str());
 }
 
 const std::string& User::MakeHost()
@@ -518,14 +506,20 @@ void User::OnDataReady()
        if (quitting)
                return;
 
-       if (MyClass && !HasPrivPermission("users/flood/increased-buffers") && recvq.length() > MyClass->GetRecvqMax())
+       if (MyClass && recvq.length() > MyClass->GetRecvqMax() && !HasPrivPermission("users/flood/increased-buffers"))
        {
                ServerInstance->Users->QuitUser(this, "RecvQ exceeded");
                ServerInstance->SNO->WriteToSnoMask('a', "User %s RecvQ of %lu exceeds connect class maximum of %lu",
                        nick.c_str(), (unsigned long)recvq.length(), MyClass->GetRecvqMax());
        }
-
-       while (this->Penalty < 10)
+       unsigned long sendqmax = ULONG_MAX;
+       if (MyClass && !HasPrivPermission("users/flood/increased-buffers"))
+               sendqmax = MyClass->GetSendqSoftMax();
+       int penaltymax = MyClass->GetPenaltyThreshold();
+       if (penaltymax == 0 || HasPrivPermission("users/flood/no-fakelag"))
+               penaltymax = INT_MAX;
+
+       while (Penalty < penaltymax && getSendQSize() < sendqmax)
        {
                std::string line;
                line.reserve(MAXBUF);
@@ -558,7 +552,12 @@ eol_found:
                this->cmds_in++;
 
                ServerInstance->Parser->ProcessBuffer(line, this);
+               if (quitting)
+                       return;
        }
+       // Add pseudo-penalty so that we continue processing after sendq recedes
+       if (Penalty == 0 && getSendQSize() >= sendqmax)
+               Penalty++;
 }
 
 void User::AddWriteBuf(const std::string &data)
@@ -566,16 +565,15 @@ void User::AddWriteBuf(const std::string &data)
        // Don't bother sending text to remote users!
        if (IS_REMOTE(this))
                return;
-       if (!quitting && MyClass && getSendQSize() + data.length() > MyClass->GetSendqMax() && !HasPrivPermission("users/flood/increased-buffers"))
+       if (!quitting && MyClass && getSendQSize() + data.length() > MyClass->GetSendqHardMax() && !HasPrivPermission("users/flood/increased-buffers"))
        {
                /*
-                * Fix by brain - Set the error text BEFORE calling, because
-                * if we dont it'll recursively  call here over and over again trying
-                * to repeatedly add the text to the sendq!
+                * Quit the user FIRST, because otherwise we could recurse
+                * here and hit the same limit.
                 */
                ServerInstance->Users->QuitUser(this, "SendQ exceeded");
-               ServerInstance->SNO->WriteToSnoMask('a', "User %s SendQ of %lu exceeds connect class maximum of %lu",
-                       nick.c_str(), (unsigned long)getSendQSize() + data.length(), MyClass->GetSendqMax());
+               ServerInstance->SNO->WriteToSnoMask('a', "User %s SendQ exceeds connect class maximum of %lu",
+                       nick.c_str(), MyClass->GetSendqHardMax());
                return;
        }
 
@@ -590,10 +588,16 @@ void User::OnError(BufferedSocketError)
        ServerInstance->Users->QuitUser(this, getError());
 }
 
-void User::cull()
+CullResult User::cull()
 {
        if (!quitting)
                ServerInstance->Users->QuitUser(this, "Culled without QuitUser");
+       if (uuid.empty())
+       {
+               ServerInstance->Logs->Log("USERS", DEBUG, "User culled twice? UUID empty");
+               return Extensible::cull();
+       }
+       PurgeEmptyChannels();
        if (IS_LOCAL(this))
        {
                if (fd != INT_MAX)
@@ -605,6 +609,28 @@ void User::cull()
                else
                        ServerInstance->Logs->Log("USERS", DEBUG, "Failed to remove user from vector");
        }
+
+       if (this->AllowedOperCommands)
+       {
+               delete AllowedOperCommands;
+               AllowedOperCommands = NULL;
+       }
+
+       if (this->AllowedPrivs)
+       {
+               delete AllowedPrivs;
+               AllowedPrivs = NULL;
+       }
+
+       this->InvalidateCache();
+       this->DecrementModes();
+
+       if (client_sa.sa.sa_family != AF_UNSPEC)
+               ServerInstance->Users->RemoveCloneCounts(this);
+
+       ServerInstance->Users->uuidlist->erase(uuid);
+       uuid.clear();
+       return Extensible::cull();
 }
 
 void User::Oper(const std::string &opertype, const std::string &opername)
@@ -630,7 +656,7 @@ void User::Oper(const std::string &opertype, const std::string &opername)
         * For multi-network servers, we may not have the opertypes of the remote server, but we still want to mark the user as an oper of that type.
         * -- w00t
         */
-       opertype_t::iterator iter_opertype = ServerInstance->Config->opertypes.find(this->oper.c_str());
+       TagIndex::iterator iter_opertype = ServerInstance->Config->opertypes.find(this->oper.c_str());
        if (iter_opertype != ServerInstance->Config->opertypes.end())
        {
                if (AllowedOperCommands)
@@ -648,26 +674,26 @@ void User::Oper(const std::string &opertype, const std::string &opername)
                this->AllowedUserModes['o' - 'A'] = true; // Call me paranoid if you want.
 
                std::string myclass, mycmd, mypriv;
-               irc::spacesepstream Classes(iter_opertype->second.c_str());
+               irc::spacesepstream Classes(iter_opertype->second->getString("classes"));
                while (Classes.GetToken(myclass))
                {
-                       operclass_t::iterator iter_operclass = ServerInstance->Config->operclass.find(myclass.c_str());
+                       TagIndex::iterator iter_operclass = ServerInstance->Config->operclass.find(myclass.c_str());
                        if (iter_operclass != ServerInstance->Config->operclass.end())
                        {
                                /* Process commands */
-                               irc::spacesepstream CommandList(iter_operclass->second.commandlist);
+                               irc::spacesepstream CommandList(iter_operclass->second->getString("commands"));
                                while (CommandList.GetToken(mycmd))
                                {
                                        this->AllowedOperCommands->insert(mycmd);
                                }
 
-                               irc::spacesepstream PrivList(iter_operclass->second.privs);
+                               irc::spacesepstream PrivList(iter_operclass->second->getString("privs"));
                                while (PrivList.GetToken(mypriv))
                                {
                                        this->AllowedPrivs->insert(mypriv);
                                }
 
-                               for (unsigned char* c = (unsigned char*)iter_operclass->second.umodelist.c_str(); *c; ++c)
+                               for (unsigned char* c = (unsigned char*)iter_operclass->second->getString("usermodes").c_str(); *c; ++c)
                                {
                                        if (*c == '*')
                                        {
@@ -679,7 +705,7 @@ void User::Oper(const std::string &opertype, const std::string &opername)
                                        }
                                }
 
-                               for (unsigned char* c = (unsigned char*)iter_operclass->second.cmodelist.c_str(); *c; ++c)
+                               for (unsigned char* c = (unsigned char*)iter_operclass->second->getString("chanmodes").c_str(); *c; ++c)
                                {
                                        if (*c == '*')
                                        {
@@ -824,20 +850,23 @@ void User::FullConnect()
        /* Check the password, if one is required by the user's connect class.
         * This CANNOT be in CheckClass(), because that is called prior to PASS as well!
         */
-       if (this->MyClass && !this->MyClass->GetPass().empty() && !this->haspassed)
+       if (MyClass && !MyClass->pass.empty())
        {
-               ServerInstance->Users->QuitUser(this, "Invalid password");
-               return;
+               if (ServerInstance->PassCompare(this, MyClass->pass.c_str(), password.c_str(), MyClass->hash.c_str()))
+               {
+                       ServerInstance->Users->QuitUser(this, "Invalid password");
+                       return;
+               }
        }
 
        if (this->CheckLines())
                return;
 
-       this->WriteServ("NOTICE Auth :Welcome to \002%s\002!",ServerInstance->Config->Network);
-       this->WriteNumeric(RPL_WELCOME, "%s :Welcome to the %s IRC Network %s!%s@%s",this->nick.c_str(), ServerInstance->Config->Network, this->nick.c_str(), this->ident.c_str(), this->host.c_str());
-       this->WriteNumeric(RPL_YOURHOSTIS, "%s :Your host is %s, running version InspIRCd-2.0",this->nick.c_str(),ServerInstance->Config->ServerName);
+       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!%s@%s",this->nick.c_str(), ServerInstance->Config->Network.c_str(), this->nick.c_str(), this->ident.c_str(), this->host.c_str());
+       this->WriteNumeric(RPL_YOURHOSTIS, "%s :Your host is %s, running version InspIRCd-2.0",this->nick.c_str(),ServerInstance->Config->ServerName.c_str());
        this->WriteNumeric(RPL_SERVERCREATED, "%s :This server was created %s %s", this->nick.c_str(), __TIME__, __DATE__);
-       this->WriteNumeric(RPL_SERVERVERSION, "%s %s InspIRCd-2.0 %s %s %s", this->nick.c_str(), ServerInstance->Config->ServerName, ServerInstance->Modes->UserModeList().c_str(), ServerInstance->Modes->ChannelModeList().c_str(), ServerInstance->Modes->ParaModeList().c_str());
+       this->WriteNumeric(RPL_SERVERVERSION, "%s %s InspIRCd-2.0 %s %s %s", this->nick.c_str(), ServerInstance->Config->ServerName.c_str(), ServerInstance->Modes->UserModeList().c_str(), ServerInstance->Modes->ChannelModeList().c_str(), ServerInstance->Modes->ParaModeList().c_str());
 
        ServerInstance->Config->Send005(this);
        this->WriteNumeric(RPL_YOURUUID, "%s %s :your unique ID", this->nick.c_str(), this->uuid.c_str());
@@ -853,7 +882,7 @@ void User::FullConnect()
        ModResult MOD_RESULT;
        std::string command("LUSERS");
        std::vector<std::string> parameters;
-       FIRST_MOD_RESULT(ServerInstance, OnPreCommand, MOD_RESULT, (command, parameters, this, true, "LUSERS"));
+       FIRST_MOD_RESULT(OnPreCommand, MOD_RESULT, (command, parameters, this, true, "LUSERS"));
        if (!MOD_RESULT)
                ServerInstance->CallCommandHandler(command, parameters, this);
 
@@ -909,9 +938,9 @@ bool User::ForceNickChange(const char* newnick)
 
        this->InvalidateCache();
 
-       NICKForced.set(this, 1);
-       FIRST_MOD_RESULT(ServerInstance, OnUserPreNick, MOD_RESULT, (this, newnick));
-       NICKForced.set(this, 0);
+       ServerInstance->NICKForced.set(this, 1);
+       FIRST_MOD_RESULT(OnUserPreNick, MOD_RESULT, (this, newnick));
+       ServerInstance->NICKForced.set(this, 0);
 
        if (MOD_RESULT == MOD_RES_DENY)
        {
@@ -925,9 +954,9 @@ bool User::ForceNickChange(const char* newnick)
        {
                std::vector<std::string> parameters;
                parameters.push_back(newnick);
-               NICKForced.set(this, 1);
+               ServerInstance->NICKForced.set(this, 1);
                bool result = (ServerInstance->Parser->CallHandler("NICK", parameters, this) == CMD_SUCCESS);
-               NICKForced.set(this, 0);
+               ServerInstance->NICKForced.set(this, 0);
                return result;
        }
 
@@ -1043,14 +1072,6 @@ const char* User::GetCIDRMask(int range)
        return ""; // unused, but oh well
 }
 
-std::string User::GetServerIP()
-{
-       int port;
-       std::string ip;
-       irc::sockets::satoap(&server_sa, ip, port);
-       return ip;
-}
-
 const char* User::GetIPString()
 {
        int port;
@@ -1112,7 +1133,7 @@ void User::Write(const char *text, ...)
 
 void User::WriteServ(const std::string& text)
 {
-       this->Write(":%s %s",ServerInstance->Config->ServerName,text.c_str());
+       this->Write(":%s %s",ServerInstance->Config->ServerName.c_str(),text.c_str());
 }
 
 /** WriteServ()
@@ -1148,12 +1169,12 @@ void User::WriteNumeric(unsigned int numeric, const std::string &text)
        char textbuffer[MAXBUF];
        ModResult MOD_RESULT;
 
-       FIRST_MOD_RESULT(ServerInstance, OnNumeric, MOD_RESULT, (this, numeric, text));
+       FIRST_MOD_RESULT(OnNumeric, MOD_RESULT, (this, numeric, text));
 
        if (MOD_RESULT == MOD_RES_DENY)
                return;
 
-       snprintf(textbuffer,MAXBUF,":%s %03u %s",ServerInstance->Config->ServerName, numeric, text.c_str());
+       snprintf(textbuffer,MAXBUF,":%s %03u %s",ServerInstance->Config->ServerName.c_str(), numeric, text.c_str());
        this->Write(std::string(textbuffer));
 }
 
@@ -1201,175 +1222,167 @@ void User::WriteTo(User *dest, const std::string &data)
        dest->WriteFrom(this, data);
 }
 
-
 void User::WriteCommon(const char* text, ...)
 {
        char textbuffer[MAXBUF];
        va_list argsPtr;
 
-       if (this->registered != REG_ALL)
+       if (this->registered != REG_ALL || quitting)
                return;
 
+       int len = snprintf(textbuffer,MAXBUF,":%s ",this->GetFullHost().c_str());
+
        va_start(argsPtr, text);
-       vsnprintf(textbuffer, MAXBUF, text, argsPtr);
+       vsnprintf(textbuffer + len, MAXBUF - len, text, argsPtr);
        va_end(argsPtr);
 
-       this->WriteCommon(std::string(textbuffer));
-}
-
-void User::WriteCommon(const std::string &text)
-{
-       bool sent_to_at_least_one = false;
-       char tb[MAXBUF];
-
-       if (this->registered != REG_ALL)
-               return;
-
-       uniq_id++;
-
-       if (!already_sent)
-               InitializeAlreadySent(ServerInstance->SE);
-
-       /* We dont want to be doing this n times, just once */
-       snprintf(tb,MAXBUF,":%s %s",this->GetFullHost().c_str(),text.c_str());
-       std::string out = tb;
-
-       for (UCListIter v = this->chans.begin(); v != this->chans.end(); v++)
-       {
-               const UserMembList* ulist = (*v)->GetUsers();
-               for (UserMembList::const_iterator i = ulist->begin(); i != ulist->end(); i++)
-               {
-                       if ((IS_LOCAL(i->first)) && (already_sent[i->first->fd] != uniq_id))
-                       {
-                               already_sent[i->first->fd] = uniq_id;
-                               i->first->Write(out);
-                               sent_to_at_least_one = true;
-                       }
-               }
-       }
-
-       /*
-        * if the user was not in any channels, no users will receive the text. Make sure the user
-        * receives their OWN message for WriteCommon
-        */
-       if (!sent_to_at_least_one)
-       {
-               this->Write(std::string(tb));
-       }
+       this->WriteCommonRaw(std::string(textbuffer), true);
 }
 
-
-/* write a formatted string to all users who share at least one common
- * channel, NOT including the source user e.g. for use in QUIT
- */
-
 void User::WriteCommonExcept(const char* text, ...)
 {
        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, MAXBUF, text, argsPtr);
+       vsnprintf(textbuffer + len, MAXBUF - len, text, argsPtr);
        va_end(argsPtr);
 
-       this->WriteCommonExcept(std::string(textbuffer));
+       this->WriteCommonRaw(std::string(textbuffer), false);
 }
 
-void User::WriteCommonQuit(const std::string &normal_text, const std::string &oper_text)
+void User::WriteCommonRaw(const std::string &line, bool include_self)
 {
-       char tb1[MAXBUF];
-       char tb2[MAXBUF];
-
-       if (this->registered != REG_ALL)
+       if (this->registered != REG_ALL || quitting)
                return;
 
-       uniq_id++;
+       uniq_id_t uniq_id = ++already_sent;
 
-       if (!already_sent)
-               InitializeAlreadySent(ServerInstance->SE);
+       UserChanList include_c(chans);
+       std::map<User*,bool> exceptions;
 
-       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;
+       exceptions[this] = include_self;
+
+       FOREACH_MOD(I_OnBuildNeighborList,OnBuildNeighborList(this, include_c, exceptions));
 
-       for (UCListIter v = this->chans.begin(); v != this->chans.end(); v++)
+       for (std::map<User*,bool>::iterator i = exceptions.begin(); i != exceptions.end(); ++i)
        {
-               const UserMembList* ulist = (*v)->GetUsers();
+               User* u = i->first;
+               if (IS_LOCAL(u) && !u->quitting)
+               {
+                       already_sent[u->fd] = uniq_id;
+                       if (i->second)
+                               u->Write(line);
+               }
+       }
+       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++)
                {
-                       if (this != i->first)
+                       User* u = i->first;
+                       if (IS_LOCAL(u) && !u->quitting && already_sent[u->fd] != uniq_id)
                        {
-                               if ((IS_LOCAL(i->first)) && (already_sent[i->first->fd] != uniq_id))
-                               {
-                                       already_sent[i->first->fd] = uniq_id;
-                                       i->first->Write(IS_OPER(i->first) ? out2 : out1);
-                               }
+                               already_sent[u->fd] = uniq_id;
+                               u->Write(line);
                        }
                }
        }
 }
 
-void User::WriteCommonExcept(const std::string &text)
+void User::WriteCommonQuit(const std::string &normal_text, const std::string &oper_text)
 {
        char tb1[MAXBUF];
-       std::string out1;
+       char tb2[MAXBUF];
 
        if (this->registered != REG_ALL)
                return;
 
-       uniq_id++;
+       uniq_id_t uniq_id = ++already_sent;
+
+       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;
 
-       if (!already_sent)
-               InitializeAlreadySent(ServerInstance->SE);
+       UserChanList include_c(chans);
+       std::map<User*,bool> exceptions;
 
-       snprintf(tb1,MAXBUF,":%s %s",this->GetFullHost().c_str(),text.c_str());
-       out1 = tb1;
+       FOREACH_MOD(I_OnBuildNeighborList,OnBuildNeighborList(this, include_c, exceptions));
 
-       for (UCListIter v = this->chans.begin(); v != this->chans.end(); v++)
+       for (std::map<User*,bool>::iterator i = exceptions.begin(); i != exceptions.end(); ++i)
+       {
+               User* u = i->first;
+               if (IS_LOCAL(u) && !u->quitting)
+               {
+                       already_sent[u->fd] = 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++)
                {
-                       if (this != i->first)
+                       User* u = i->first;
+                       if (IS_LOCAL(u) && !u->quitting && (already_sent[u->fd] != uniq_id))
                        {
-                               if ((IS_LOCAL(i->first)) && (already_sent[i->first->fd] != uniq_id))
-                               {
-                                       already_sent[i->first->fd] = uniq_id;
-                                       i->first->Write(out1);
-                               }
+                               already_sent[u->fd] = uniq_id;
+                               u->Write(IS_OPER(u) ? out2 : out1);
                        }
                }
        }
-
 }
 
-void User::WriteWallOps(const std::string &text)
+void User::SendText(const std::string& line)
 {
-       std::string wallop("WALLOPS :");
-       wallop.append(text);
-
-       for (std::vector<User*>::const_iterator i = ServerInstance->Users->local_users.begin(); i != ServerInstance->Users->local_users.end(); i++)
-       {
-               User* t = *i;
-               if (t->IsModeSet('w'))
-                       this->WriteTo(t,wallop);
-       }
+       if (IS_LOCAL(this))
+               Write(line);
+       else if (!IS_SERVER(this))
+               ServerInstance->PI->PushToClient(this, line);
 }
 
-void User::WriteWallOps(const char* text, ...)
+void User::SendText(const char *text, ...)
 {
-       if (!IS_LOCAL(this))
-               return;
-
-       char textbuffer[MAXBUF];
        va_list argsPtr;
+       char line[MAXBUF];
 
        va_start(argsPtr, text);
-       vsnprintf(textbuffer, MAXBUF, text, argsPtr);
+       vsnprintf(line, MAXBUF, text, argsPtr);
        va_end(argsPtr);
 
-       this->WriteWallOps(std::string(textbuffer));
+       SendText(std::string(line));
+}
+
+void User::SendText(const std::string &LinePrefix, std::stringstream &TextStream)
+{
+       char line[MAXBUF];
+       int start_pos = LinePrefix.length();
+       int pos = start_pos;
+       memcpy(line, LinePrefix.data(), pos);
+       std::string Word;
+       while (TextStream >> Word)
+       {
+               int len = Word.length();
+               if (pos + len + 12 > MAXBUF)
+               {
+                       line[pos] = '\0';
+                       SendText(std::string(line));
+                       pos = start_pos;
+               }
+               line[pos] = ' ';
+               memcpy(line + pos + 1, Word.data(), len);
+               pos += len + 1;
+       }
+       line[pos] = '\0';
+       SendText(std::string(line));
 }
 
 /* return 0 or 1 depending if users u and u2 share one or more common channels
@@ -1409,7 +1422,7 @@ bool User::ChangeName(const char* gecos)
        if (IS_LOCAL(this))
        {
                ModResult MOD_RESULT;
-               FIRST_MOD_RESULT(ServerInstance, OnChangeLocalUserGECOS, MOD_RESULT, (this,gecos));
+               FIRST_MOD_RESULT(OnChangeLocalUserGECOS, MOD_RESULT, (this,gecos));
                if (MOD_RESULT == MOD_RES_DENY)
                        return false;
                FOREACH_MOD(I_OnChangeName,OnChangeName(this,gecos));
@@ -1423,20 +1436,34 @@ void User::DoHostCycle(const std::string &quitline)
 {
        char buffer[MAXBUF];
 
-       ModResult result = MOD_RES_PASSTHRU;
-       FIRST_MOD_RESULT(ServerInstance, OnHostCycle, result, (this));
-
-       if (result == MOD_RES_DENY)
-               return;
-       if (result == MOD_RES_PASSTHRU && !ServerInstance->Config->CycleHosts)
+       if (!ServerInstance->Config->CycleHosts)
                return;
 
-       uniq_id++;
+       uniq_id_t silent_id = ++already_sent;
+       uniq_id_t seen_id = ++already_sent;
+
+       UserChanList include_c(chans);
+       std::map<User*,bool> exceptions;
 
-       if (!already_sent)
-               InitializeAlreadySent(ServerInstance->SE);
+       FOREACH_MOD(I_OnBuildNeighborList,OnBuildNeighborList(this, include_c, exceptions));
 
-       for (UCListIter v = this->chans.begin(); v != this->chans.end(); v++)
+       for (std::map<User*,bool>::iterator i = exceptions.begin(); i != exceptions.end(); ++i)
+       {
+               User* u = i->first;
+               if (IS_LOCAL(u) && !u->quitting)
+               {
+                       if (i->second)
+                       {
+                               already_sent[u->fd] = seen_id;
+                               u->Write(quitline);
+                       }
+                       else
+                       {
+                               already_sent[u->fd] = 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());
@@ -1454,11 +1481,13 @@ void User::DoHostCycle(const std::string &quitline)
                        User* u = i->first;
                        if (u == this || !IS_LOCAL(u))
                                continue;
+                       if (already_sent[u->fd] == silent_id)
+                               continue;
 
-                       if (already_sent[i->first->fd] != uniq_id)
+                       if (already_sent[u->fd] != seen_id)
                        {
                                u->Write(quitline);
-                               already_sent[i->first->fd] = uniq_id;
+                               already_sent[i->first->fd] = seen_id;
                        }
                        u->Write(joinline);
                        if (modeline.length() > 0)
@@ -1475,7 +1504,7 @@ bool User::ChangeDisplayedHost(const char* shost)
        if (IS_LOCAL(this))
        {
                ModResult MOD_RESULT;
-               FIRST_MOD_RESULT(ServerInstance, OnChangeLocalUserHost, MOD_RESULT, (this,shost));
+               FIRST_MOD_RESULT(OnChangeLocalUserHost, MOD_RESULT, (this,shost));
                if (MOD_RESULT == MOD_RES_DENY)
                        return false;
        }
@@ -1560,7 +1589,7 @@ void User::SplitChanList(User* dest, const std::string &cl)
 
        prefix << this->nick << " " << dest->nick << " :";
        line = prefix.str();
-       int namelen = strlen(ServerInstance->Config->ServerName) + 6;
+       int namelen = ServerInstance->Config->ServerName.length() + 6;
 
        for (start = 0; (pos = cl.find(' ', start)) != std::string::npos; start = pos+1)
        {
@@ -1645,7 +1674,7 @@ ConnectClass* User::SetClass(const std::string &explicit_name)
                         * deny change if change will take class over the limit check it HERE, not after we found a matching class,
                         * because we should attempt to find another class if this one doesn't match us. -- w00t
                         */
-                       if (c->limit && (c->RefCount >= c->limit))
+                       if (c->limit && (c->GetReferenceCount() >= c->limit))
                        {
                                ServerInstance->Logs->Log("CONNECTCLASS", DEBUG, "OOPS: Connect class limit (%lu) hit, denying", c->limit);
                                continue;
@@ -1675,20 +1704,7 @@ ConnectClass* User::SetClass(const std::string &explicit_name)
         */
        if (found)
        {
-               /* only fiddle with refcounts if they are already in a class .. */
-               if (this->MyClass)
-               {
-                       if (found == this->MyClass) // no point changing this shit :P
-                               return this->MyClass;
-                       this->MyClass->RefCount--;
-                       ServerInstance->Logs->Log("USERS", DEBUG, "Untying user from connect class -- refcount: %lu", this->MyClass->RefCount);
-                       if (MyClass->RefCount == 0)
-                               delete MyClass;
-               }
-
-               this->MyClass = found;
-               this->MyClass->RefCount++;
-               ServerInstance->Logs->Log("USERS", DEBUG, "User tied to new class -- connect refcount now: %lu", this->MyClass->RefCount);
+               MyClass = found;
        }
 
        return this->MyClass;
@@ -1706,42 +1722,11 @@ ConnectClass* User::GetClass()
 
 void User::PurgeEmptyChannels()
 {
-       std::vector<Channel*> to_delete;
-
        // firstly decrement the count on each channel
        for (UCListIter f = this->chans.begin(); f != this->chans.end(); f++)
        {
                Channel* c = *f;
-               c->RemoveAllPrefixes(this);
-               if (c->DelUser(this) == 0)
-               {
-                       /* No users left in here, mark it for deletion */
-                       try
-                       {
-                               to_delete.push_back(c);
-                       }
-                       catch (...)
-                       {
-                               ServerInstance->Logs->Log("USERS", DEBUG,"Exception in User::PurgeEmptyChannels to_delete.push_back()");
-                       }
-               }
-       }
-
-       for (std::vector<Channel*>::iterator n = to_delete.begin(); n != to_delete.end(); n++)
-       {
-               Channel* thischan = *n;
-               chan_hash::iterator i2 = ServerInstance->chanlist->find(thischan->name);
-               if (i2 != ServerInstance->chanlist->end())
-               {
-                       ModResult MOD_RESULT;
-                       FIRST_MOD_RESULT(ServerInstance, OnChannelPreDelete, MOD_RESULT, (i2->second));
-                       if (MOD_RESULT == MOD_RES_DENY)
-                               continue; // delete halted by module
-                       FOREACH_MOD(I_OnChannelDelete,OnChannelDelete(i2->second));
-                       delete i2->second;
-                       ServerInstance->chanlist->erase(i2);
-                       this->chans.erase(*n);
-               }
+               c->DelUser(this);
        }
 
        this->UnOper();
@@ -1754,7 +1739,7 @@ void User::ShowMOTD()
                this->WriteNumeric(ERR_NOMOTD, "%s :Message of the day file is missing.",this->nick.c_str());
                return;
        }
-       this->WriteNumeric(RPL_MOTDSTART, "%s :%s message of the day", this->nick.c_str(), ServerInstance->Config->ServerName);
+       this->WriteNumeric(RPL_MOTDSTART, "%s :%s message of the day", this->nick.c_str(), ServerInstance->Config->ServerName.c_str());
 
        for (file_cache::iterator i = ServerInstance->Config->MOTD.begin(); i != ServerInstance->Config->MOTD.end(); i++)
                this->WriteNumeric(RPL_MOTD, "%s :- %s",this->nick.c_str(),i->c_str());
@@ -1770,7 +1755,7 @@ void User::ShowRULES()
                return;
        }
 
-       this->WriteNumeric(RPL_RULESTART, "%s :- %s Server Rules -",this->nick.c_str(),ServerInstance->Config->ServerName);
+       this->WriteNumeric(RPL_RULESTART, "%s :- %s Server Rules -",this->nick.c_str(),ServerInstance->Config->ServerName.c_str());
 
        for (file_cache::iterator i = ServerInstance->Config->RULES.begin(); i != ServerInstance->Config->RULES.end(); i++)
                this->WriteNumeric(RPL_RULES, "%s :- %s",this->nick.c_str(),i->c_str());
@@ -1783,38 +1768,41 @@ void User::IncreasePenalty(int increase)
        this->Penalty += increase;
 }
 
-void User::DecreasePenalty(int decrease)
-{
-       this->Penalty -= decrease;
-}
-
 void FakeUser::SetFakeServer(std::string name)
 {
        this->nick = name;
-       this->server = nick.c_str();
+       this->server = name;
 }
 
 const std::string FakeUser::GetFullHost()
 {
-       if (*ServerInstance->Config->HideWhoisServer)
+       if (!ServerInstance->Config->HideWhoisServer.empty())
                return ServerInstance->Config->HideWhoisServer;
        return nick;
 }
 
 const std::string FakeUser::GetFullRealHost()
 {
-       if (*ServerInstance->Config->HideWhoisServer)
+       if (!ServerInstance->Config->HideWhoisServer.empty())
                return ServerInstance->Config->HideWhoisServer;
        return nick;
 }
 
-ConnectClass::ConnectClass(char t, const std::string& mask)
-       : type(t), name("unnamed"), registration_timeout(0), host(mask), pingtime(0), pass(""), hash(""), sendqmax(0), recvqmax(0), maxlocal(0), maxglobal(0), maxchans(0), port(0), limit(0), RefCount(1)
+ConnectClass::ConnectClass(ConfigTag* tag, char t, const std::string& mask)
+       : config(tag), type(t), name("unnamed"), registration_timeout(0), host(mask),
+       pingtime(0), pass(""), hash(""), softsendqmax(0), hardsendqmax(0),
+       recvqmax(0), penaltythreshold(0), maxlocal(0), maxglobal(0), maxchans(0), port(0), limit(0)
 {
 }
 
-ConnectClass::ConnectClass(char t, const std::string& mask, const ConnectClass& parent)
-       : type(t), name("unnamed"), registration_timeout(parent.registration_timeout), host(mask), pingtime(parent.pingtime), pass(parent.pass), hash(parent.hash), sendqmax(parent.sendqmax), recvqmax(parent.recvqmax), maxlocal(parent.maxlocal), maxglobal(parent.maxglobal), maxchans(parent.maxchans), port(parent.port), limit(parent.limit), RefCount(1)
+ConnectClass::ConnectClass(ConfigTag* tag, char t, const std::string& mask, const ConnectClass& parent)
+       : config(tag), type(t), name("unnamed"),
+       registration_timeout(parent.registration_timeout), host(mask),
+       pingtime(parent.pingtime), pass(parent.pass), hash(parent.hash),
+       softsendqmax(parent.softsendqmax), hardsendqmax(parent.hardsendqmax),
+       recvqmax(parent.recvqmax), penaltythreshold(parent.penaltythreshold), maxlocal(parent.maxlocal),
+       maxglobal(parent.maxglobal), maxchans(parent.maxchans),
+       port(parent.port), limit(parent.limit)
 {
 }
 
@@ -1826,8 +1814,10 @@ void ConnectClass::Update(const ConnectClass* src)
        pingtime = src->pingtime;
        pass = src->pass;
        hash = src->hash;
-       sendqmax = src->sendqmax;
+       softsendqmax = src->softsendqmax;
+       hardsendqmax = src->hardsendqmax;
        recvqmax = src->recvqmax;
+       penaltythreshold = src->penaltythreshold;
        maxlocal = src->maxlocal;
        maxglobal = src->maxglobal;
        limit = src->limit;