Index: include/licq_icqd.h
===================================================================
--- include/licq_icqd.h	(revision 6356)
+++ include/licq_icqd.h	(working copy)
@@ -140,6 +140,36 @@
 
 typedef std::list<CConversation *> ConversationList;
 
+/**
+ * Internal template class for storing and processing received contact list.
+ */
+class CUserProperties
+{
+public:
+  CUserProperties();
+  CUserProperties(const CUserProperties& object);
+  ~CUserProperties();
+
+private:
+  char* newAlias;
+  char* newCellular;
+  bool awaitingAuth;
+
+  unsigned short normalSid;
+  unsigned short groupId;
+
+  unsigned short visibleSid;
+  unsigned short invisibleSid;
+  bool inIgnoreList;
+
+  TLVList tlvs;
+
+friend class CICQDaemon;
+};
+
+typedef std::map<std::string, CUserProperties> ContactUserList;
+typedef ContactUserList::iterator ContactUserListIter;
+
 //=====CICQDaemon===============================================================
 enum EDaemonStatus {STATUS_ONLINE, STATUS_OFFLINE_MANUAL, STATUS_OFFLINE_FORCED };
 
@@ -752,6 +782,8 @@
   static pthread_mutex_t mutex_reverseconnect;
   static pthread_cond_t  cond_reverseconnect_done;
 
+  ContactUserList receivedUserList;
+
   ConversationList m_lConversations;
   pthread_mutex_t mutex_conversations;
 
@@ -838,6 +870,8 @@
   void ProcessListFam(CBuffer &, unsigned short);
   void ProcessAuthFam(CBuffer &, unsigned short);
 
+  void ProcessUserList();
+
   void ProcessSystemMessage(CBuffer &packet, unsigned long checkUin, unsigned short newCommand, time_t timeSent);
   void ProcessMetaCommand(CBuffer &packet, unsigned short nMetaCommand, ICQEvent *e);
   bool ProcessTcpPacket(TCPSocket *);
Index: src/icqd.cpp
===================================================================
--- src/icqd.cpp	(revision 6356)
+++ src/icqd.cpp	(working copy)
@@ -167,6 +167,8 @@
 
   fifo_fs = NULL;
 
+  receivedUserList.clear();
+
   // Begin parsing the config file
   snprintf(m_szConfigFile, MAX_FILENAME_LEN, "%s/%s", BASE_DIR, "licq.conf");
   m_szConfigFile[MAX_FILENAME_LEN - 1] = '\0';
@@ -2843,3 +2845,60 @@
 
   return (!*pcEnd);
 }
+
+CUserProperties::CUserProperties()
+  : newAlias(NULL),
+    newCellular(NULL),
+    awaitingAuth(false),
+    normalSid(0),
+    groupId(0),
+    visibleSid(0),
+    invisibleSid(0),
+    inIgnoreList(false)
+{
+  tlvs.clear();
+}
+
+CUserProperties::CUserProperties(const CUserProperties& object)
+{
+#define COPY(var) \
+  if (object.var == NULL) \
+    var = NULL; \
+  else \
+  { \
+    size_t len = strlen(object.var); \
+    var = new char[++len]; \
+    memcpy(var, object.var, len); \
+  }
+
+  COPY(newAlias);
+  COPY(newCellular);
+#undef COPY
+
+  awaitingAuth = object.awaitingAuth;
+  normalSid = object.normalSid;
+  groupId = object.groupId;
+  visibleSid = object.visibleSid;
+  invisibleSid = object.invisibleSid;
+  inIgnoreList = object.inIgnoreList;
+
+  tlvs.clear();
+  tlvs = object.tlvs;
+}
+
+CUserProperties::~CUserProperties()
+{
+  if (newAlias != NULL)
+  {
+    delete[] newAlias;
+    newAlias = NULL;
+  }
+
+  if (newCellular != NULL)
+  {
+    delete[] newCellular;
+    newCellular = NULL;
+  }
+
+  tlvs.clear();
+}
Index: src/icqd-srv.cpp
===================================================================
--- src/icqd-srv.cpp	(revision 6356)
+++ src/icqd-srv.cpp	(working copy)
@@ -4131,66 +4131,24 @@
 
     case ICQ_SNACxLIST_ROSTxREPLY:
     {
-      static unsigned short nCount;
+      static unsigned short nCount = 0;
       static bool sCheckExport = false;
-      unsigned short nPacketCount;
-      unsigned long nTime;
 
-      if (nFlags & 0x0001)
-      {
-        if (!hasServerEvent(nSubSequence))
-          gLog.Warn(tr("%sContact list without request.\n"), L_SRVxSTR);
-        else
-          gLog.Info(tr("%sReceived contact list.\n"), L_SRVxSTR);
-      }
-      else
-      {
-        // This is the last packet so mark it as done
-        ICQEvent *e = DoneServerEvent(nSubSequence, EVENT_SUCCESS);
-
-        if (e == NULL)
-        {
-          // Try acking it, even if we didn't expect it
-          //gLog.Warn(tr("%sContact list without request.\n"), L_SRVxSTR);
-          gLog.Info(tr("%sActivate server contact list.\n"), L_SRVxSTR);
-          CSrvPacketTcp *p = new CPU_GenericFamily(ICQ_SNACxFAM_LIST, ICQ_SNACxLIST_ROSTxACK);
-          SendEvent_Server(p);
-          //This packet had the user list in it, but we didn't ask for it.
-          //Nonetheless, it appears we won't get a second chance for this packet, so process it
-          //break;
-        }
-
-        /* This isn't used anymore. At least with SSI Version 0.
-        if (e->SNAC() == MAKESNAC(ICQ_SNACxFAM_LIST, ICQ_SNACxLIST_REQUESTxRIGHTS) ||
-            e->SNAC() == MAKESNAC(ICQ_SNACxFAM_LIST, ICQ_SNACxLIST_REQUESTxROST))
-        {
-          packet.UnpackUnsignedLong();
-          packet.UnpackUnsignedLong();
-        }
-        */
-
-        gLog.Info(tr("%sReceived end of contact list.\n"), L_SRVxSTR);
-      }
-
       m_bOnlineNotifies = true;
 
       packet.UnpackChar();  // SSI Version
-      nPacketCount = packet.UnpackUnsignedShortBE();
+      unsigned short nPacketCount = packet.UnpackUnsignedShortBE();
       nCount += nPacketCount;
 
-      for (unsigned short i = 0; i < nPacketCount; i++)
+      while (nPacketCount-- != 0)
       {
-        char *szId;
-        unsigned short nTag, nID, nType, nByteLen;
-
         // Can't use UnpackUserString because this may be a group name
-        szId = packet.UnpackStringBE();
-        nTag = packet.UnpackUnsignedShortBE();
-        nID = packet.UnpackUnsignedShortBE();
-        nType = packet.UnpackUnsignedShortBE();
-        nByteLen = packet.UnpackUnsignedShortBE();
+        char* szId = packet.UnpackStringBE();
+        unsigned short nTag = packet.UnpackUnsignedShortBE();
+        unsigned short nID = packet.UnpackUnsignedShortBE();
+        unsigned short nType = packet.UnpackUnsignedShortBE();
 
-        char *szUnicodeName = gTranslator.FromUnicode(szId);
+        unsigned short nByteLen = packet.UnpackUnsignedShortBE();
 
         if (nByteLen)
         {
@@ -4210,97 +4168,44 @@
           case ICQ_ROSTxINVISIBLE:
           case ICQ_ROSTxIGNORE:
           {
-            if (!UseServerContactList()) break; 
-            
-            char *szNewName = packet.UnpackStringTLV(0x0131);
-            char *szSMSNumber = packet.UnpackStringTLV(0x013A);
-            bool bAwaitingAuth = packet.hasTLV(0x0066);
+            if (!UseServerContactList())
+              break;
 
-            bool isOnList = true;
-            if (szId && !gUserManager.IsOnList(szId, LICQ_PPID))
-            {
-              isOnList = false;
-              AddUserToList(szId, LICQ_PPID, false); // Don't notify server
+#define COPYTLV(type, var) \
+            if (packet.hasTLV(type)) \
+            { \
+              if (receivedUserList[szId].var != NULL) \
+                delete[] receivedUserList[szId].var; \
+              receivedUserList[szId].var = packet.UnpackStringTLV(type); \
             }
 
-            ICQUser *u = gUserManager.FetchUser(szId, LICQ_PPID, LOCK_W);
-            if (u)
-            {
-              // For now, just save all the TLVs. We should change this to have awaiting auth check
-              // for the 0x0066 TLV, SMS number if it has the 0x013A TLV, etc
-              TLVList tlvList = packet.getTLVList();
-              TLVListIter iter;
-              for (iter = tlvList.begin(); iter != tlvList.end(); ++iter)
-              {
-                TLVPtr tlv = iter->second;
-                u->AddTLV(tlv);
-              }
+            COPYTLV(0x0131, newAlias);
+            COPYTLV(0x013A, newCellular);
+#undef COPYTLV
+            if (packet.hasTLV(0x0066))
+              receivedUserList[szId].awaitingAuth = true;
 
-              u->SetGSID(nTag);
+            if (nTag != 0)
+              receivedUserList[szId].groupId = nTag;
 
-              if (szNewName)
-                u->SetAlias(szNewName);
+            if (nType == ICQ_ROSTxIGNORE)
+              receivedUserList[szId].inIgnoreList = true;
 
-              if (szSMSNumber)
+            if (nID != 0)
+            {
+              if (nType == ICQ_ROSTxVISIBLE)
               {
-                char *szUnicodeSMS = gTranslator.FromUnicode(szSMSNumber);
-                if (szUnicodeSMS)
-                {
-                  u->SetCellularNumber(szUnicodeSMS);
-                  delete [] szUnicodeSMS;
-                }
+                receivedUserList[szId].visibleSid = nID;
               }
-
-              u->SetAwaitingAuth(bAwaitingAuth);
-
-              if (nType == ICQ_ROSTxINVISIBLE)
+              else if (nType == ICQ_ROSTxINVISIBLE)
               {
-                u->SetInvisibleList(true);
-                u->SetInvisibleSID(nID);
+                receivedUserList[szId].invisibleSid = nID;
               }
-              else if (nType == ICQ_ROSTxVISIBLE)
-              {
-                u->SetVisibleList(true);
-                u->SetVisibleSID(nID);
-              }
               else
               {
-                u->SetSID(nID);
-
-                if (nType == ICQ_ROSTxNORMAL)
-                {
-                  // Save the group that they are in
-                  u->AddToGroup(GROUPS_USER, gUserManager.GetGroupFromID(nTag));
-                }
+                receivedUserList[szId].normalSid = nID;
               }
-
-              u->SetIgnoreList(nType == ICQ_ROSTxIGNORE);
-
-              if (!isOnList)
-              {
-                // They aren't a new user if we added them to a server list
-                u->SetNewUser(false);
-              }
-
-              // Save GSID, SID and group memberships
-              u->SaveLicqInfo();
-
-              PushPluginSignal(new CICQSignal(SIGNAL_UPDATExUSER, USER_GENERAL,
-                u->IdString(), u->PPID()));
-              gUserManager.DropUser(u);
             }
-
-            if (!isOnList)
-            {
-              gLog.Info(tr("%sAdded %s (%s) to list from server.\n"), L_SRVxSTR,
-                (szNewName ? szNewName : szId), szId);
-            }
-
-            if (szNewName)
-              delete [] szNewName;
-            if (szSMSNumber)
-              delete [] szSMSNumber;
-
             break;
           }
 
@@ -4312,6 +4217,8 @@
             {
               // Rename the group if we have it already or else add it
               unsigned short nGroup = gUserManager.GetGroupFromID(nTag);
+              char* szUnicodeName = gTranslator.FromUnicode(szId);
+
               if (nGroup == 0)
               {
                 if (!gUserManager.AddGroup(szUnicodeName, nTag))
@@ -4322,6 +4229,9 @@
                 gUserManager.RenameGroup(nGroup, szUnicodeName, false);
               }
               
+              if (szUnicodeName)
+                delete[] szUnicodeName;
+
               // This is bad, i don't think we want to call this at all..
               // it will add users to different groups that they werent even
               // assigned to
@@ -4353,9 +4263,6 @@
           }
         }  // switch (nType)
 
-        if (szUnicodeName)
-          delete [] szUnicodeName;
-
         if (szId)
           delete[] szId;
       } // for count
@@ -4368,17 +4275,33 @@
       }
       
       // Update local info about contact list
-      nTime = packet.UnpackUnsignedLongBE();
-      ICQOwner *o = gUserManager.FetchOwner(LOCK_W);
+      unsigned long nTime = packet.UnpackUnsignedLongBE();
+      ICQOwner* o = gUserManager.FetchOwner(LICQ_PPID, LOCK_W);
       o->SetSSTime(nTime);
       o->SetSSCount(nCount);
       gUserManager.DropOwner();
 
-      gLog.Info(tr("%sActivate server contact list.\n"), L_SRVxSTR);
-      CSrvPacketTcp *p = new CPU_GenericFamily(ICQ_SNACxFAM_LIST, ICQ_SNACxLIST_ROSTxACK);
-      SendEvent_Server(p);
+      if (nFlags & 0x0001)
+      {
+        if (!hasServerEvent(nSubSequence))
+          gLog.Warn(tr("%sContact list without request.\n"), L_SRVxSTR);
+        else
+          gLog.Info(tr("%sReceived contact list.\n"), L_SRVxSTR);
+      }
+      else
+      {
+        // This is the last packet so mark it as done
+        DoneServerEvent(nSubSequence, EVENT_SUCCESS);
 
-      
+        gLog.Info(tr("%sReceived end of contact list.\n"), L_SRVxSTR);
+
+        ProcessUserList();
+
+        gLog.Info(tr("%sActivating server contact list.\n"), L_SRVxSTR);
+        CSrvPacketTcp *p = new CPU_GenericFamily(ICQ_SNACxFAM_LIST, ICQ_SNACxLIST_ROSTxACK);
+        SendEvent_Server(p);
+      }
+
       break;
     } // case rost reply
 
@@ -6388,6 +6311,88 @@
   }
 }
 
+void CICQDaemon::ProcessUserList()
+{
+  if (receivedUserList.empty())
+    return;
+
+  ContactUserListIter iter;
+
+  for (iter = receivedUserList.begin(); iter != receivedUserList.end(); iter++)
+  {
+    const char* id = iter->first.c_str();
+    CUserProperties data = iter->second;
+
+    if (id == NULL || id[0] == '\0')
+    {
+      gLog.Warn(tr("%sEmpty User ID was received in the contact list.\n"),
+          L_SRVxSTR);
+      continue;
+    }
+
+    bool isOnList = gUserManager.IsOnList(id, LICQ_PPID);
+
+    if (!isOnList)
+    {
+      AddUserToList(id, LICQ_PPID, false, false); // Don't notify server
+      gLog.Info(tr("%sAdded %s (%s) to list from server.\n"),
+          L_SRVxSTR, (data.newAlias ? data.newAlias : id), id);
+    }
+
+    ICQUser* u = gUserManager.FetchUser(id, LICQ_PPID, LOCK_W);
+    if (u == NULL)
+      continue;
+
+    if (data.newAlias != NULL)
+      u->SetAlias(data.newAlias);
+
+    u->SetSID(data.normalSid);
+    u->SetGSID(data.groupId);
+    u->SetVisibleSID(data.visibleSid);
+    u->SetVisibleList(data.visibleSid != 0);
+    u->SetInvisibleSID(data.invisibleSid);
+    u->SetInvisibleList(data.invisibleSid != 0);
+    u->SetIgnoreList(data.inIgnoreList);
+
+    u->AddToGroup(GROUPS_USER, gUserManager.GetGroupFromID(data.groupId));
+
+    u->SetAwaitingAuth(data.awaitingAuth);
+
+    if (!isOnList)
+    {
+      // They aren't a new user if we added them to a server list
+      u->SetNewUser(false);
+    }
+
+    if (data.newCellular != NULL)
+    {
+      char* tmp = gTranslator.FromUnicode(data.newCellular);
+      if (tmp != NULL)
+      {
+        u->SetCellularNumber(tmp);
+        delete[] tmp;
+      }
+    }
+
+    // For now, just save all the TLVs. We should change this to have awaiting auth check
+    // for the 0x0066 TLV, SMS number if it has the 0x013A TLV, etc
+    TLVListIter tlvIter;
+    for (tlvIter = data.tlvs.begin(); tlvIter != data.tlvs.end(); tlvIter++)
+    {
+      TLVPtr tlv = tlvIter->second;
+      u->AddTLV(tlv);
+    }
+
+    // Save GSID, SID and group memberships
+    u->SaveLicqInfo();
+    gUserManager.DropUser(u);
+
+    PushPluginSignal(new CICQSignal(SIGNAL_UPDATExUSER, USER_GENERAL, id, LICQ_PPID));
+  }
+
+  receivedUserList.clear();
+}
+
 //--------ProcessDataChannel---------------------------------------------------
 
 void CICQDaemon::ProcessDataChannel(CBuffer &packet)
