void CIcqProto::handleLocationUserInfoReply(BYTE* buf, WORD wLen, DWORD dwCookie) { HANDLE hContact; DWORD dwUIN; uid_str szUID; WORD wTLVCount; WORD wWarningLevel; HANDLE hCookieContact; WORD status; cookie_message_data *pCookieData; // Unpack the sender's user ID if (!unpackUID(&buf, &wLen, &dwUIN, &szUID)) return; // Syntax check if (wLen < 4) return; // Warning level? unpackWord(&buf, &wWarningLevel); wLen -= 2; // TLV count unpackWord(&buf, &wTLVCount); wLen -= 2; // Determine contact hContact = HContactFromUID(dwUIN, szUID, NULL); // Ignore away status if the user is not already on our list if (hContact == INVALID_HANDLE_VALUE) { #ifdef _DEBUG NetLog_Server("Ignoring away reply (%s)", strUID(dwUIN, szUID)); #endif return; } if (!FindCookie(dwCookie, &hCookieContact, (void**)&pCookieData)) { NetLog_Server("Error: Received unexpected away reply from %s", strUID(dwUIN, szUID)); return; } if (hContact != hCookieContact) { NetLog_Server("Error: Away reply Contact does not match Cookie Contact(0x%x != 0x%x)", hContact, hCookieContact); ReleaseCookie(dwCookie); // This could be a bad idea, but I think it is safe return; } switch (GetCookieType(dwCookie)) { case CKT_FAMILYSPECIAL: { ReleaseCookie(dwCookie); // Read user info TLVs { oscar_tlv_chain* pChain; BYTE *tmp; char *szMsg = NULL; // Syntax check if (wLen < 4) return; tmp = buf; // Get general chain if (!(pChain = readIntoTLVChain(&buf, wLen, wTLVCount))) return; disposeChain(&pChain); wLen -= (buf - tmp); // Get extra chain if (pChain = readIntoTLVChain(&buf, wLen, 2)) { oscar_tlv *pTLV; char *szEncoding = NULL; // Get Profile encoding TLV pTLV = pChain->getTLV(0x01, 1); if (pTLV && (pTLV->wLen >= 1)) { szEncoding = (char*)_alloca(pTLV->wLen + 1); memcpy(szEncoding, pTLV->pData, pTLV->wLen); szEncoding[pTLV->wLen] = '\0'; } // Get Profile info TLV pTLV = pChain->getTLV(0x02, 1); if (pTLV && (pTLV->wLen >= 1)) { szMsg = (char*)SAFE_MALLOC(pTLV->wLen + 2); memcpy(szMsg, pTLV->pData, pTLV->wLen); szMsg[pTLV->wLen] = '\0'; szMsg[pTLV->wLen + 1] = '\0'; szMsg = AimApplyEncoding(szMsg, szEncoding); szMsg = EliminateHtml(szMsg, pTLV->wLen); } // Free TLV chain disposeChain(&pChain); } setSettingString(hContact, "About", szMsg); BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, (HANDLE)1 ,0); SAFE_FREE((void**)&szMsg); } break; } default: // away message { status = AwayMsgTypeToStatus(pCookieData->nAckType); if (status == ID_STATUS_OFFLINE) { NetLog_Server("SNAC(2.6) Ignoring unknown status message from %s", strUID(dwUIN, szUID)); ReleaseCookie(dwCookie); return; } ReleaseCookie(dwCookie); // Read user info TLVs { oscar_tlv_chain* pChain; oscar_tlv* pTLV; BYTE *tmp; char *szMsg = NULL; CCSDATA ccs; PROTORECVEVENT pre; // Syntax check if (wLen < 4) return; tmp = buf; // Get general chain if (!(pChain = readIntoTLVChain(&buf, wLen, wTLVCount))) return; disposeChain(&pChain); wLen -= (buf - tmp); // Get extra chain if (pChain = readIntoTLVChain(&buf, wLen, 2)) { char* szEncoding = NULL; // Get Away encoding TLV pTLV = pChain->getTLV(0x03, 1); if (pTLV && (pTLV->wLen >= 1)) { szEncoding = (char*)_alloca(pTLV->wLen + 1); memcpy(szEncoding, pTLV->pData, pTLV->wLen); szEncoding[pTLV->wLen] = '\0'; } // Get Away info TLV pTLV = pChain->getTLV(0x04, 1); if (pTLV && (pTLV->wLen >= 1)) { szMsg = (char*)SAFE_MALLOC(pTLV->wLen + 2); memcpy(szMsg, pTLV->pData, pTLV->wLen); szMsg[pTLV->wLen] = '\0'; szMsg[pTLV->wLen + 1] = '\0'; szMsg = AimApplyEncoding(szMsg, szEncoding); szMsg = EliminateHtml(szMsg, pTLV->wLen); } // Free TLV chain disposeChain(&pChain); } ccs.szProtoService = PSR_AWAYMSG; ccs.hContact = hContact; ccs.wParam = status; ccs.lParam = (LPARAM)⪯ pre.flags = 0; pre.szMessage = szMsg?szMsg:(char *)""; pre.timestamp = time(NULL); pre.lParam = dwCookie; CallService(MS_PROTO_CHAINRECV,0,(LPARAM)&ccs); SAFE_FREE((void**)&szMsg); } break; } } }
void CIcqProto::handleUserOffline(BYTE *buf, size_t wLen) { DWORD dwUIN; uid_str szUID; do { oscar_tlv_chain *pChain = NULL; // Unpack the sender's user ID if (!unpackUID(&buf, &wLen, &dwUIN, &szUID)) return; // Warning level? buf += 2; // TLV Count WORD wTLVCount; unpackWord(&buf, &wTLVCount); wLen -= 4; // Skip the TLV chain DWORD dwAwaySince = 0; while (wTLVCount && wLen >= 4) { WORD wTLVType; WORD wTLVLen; unpackWord(&buf, &wTLVType); unpackWord(&buf, &wTLVLen); wLen -= 4; // stop parsing overflowed packet if (wTLVLen > wLen) { disposeChain(&pChain); return; } if (wTLVType == 0x1D) { // read only TLV with Session data into chain BYTE *pTLV = buf - 4; disposeChain(&pChain); pChain = readIntoTLVChain(&pTLV, wLen + 4, 1); } else if (wTLVType == 0x29 && wTLVLen == sizeof(DWORD)) { // get Away Since value BYTE *pData = buf; unpackDWord(&pData, &dwAwaySince); } buf += wTLVLen; wLen -= wTLVLen; wTLVCount--; } // Determine contact MCONTACT hContact = HContactFromUID(dwUIN, szUID, NULL); // Skip contacts that are not already on our list or are already offline if (hContact != INVALID_CONTACT_ID) { WORD wOldStatus = getContactStatus(hContact); // Process Avatar Hash oscar_tlv *pAvatarTLV = pChain ? pChain->getTLV(0x1D, 1) : NULL; if (pAvatarTLV) handleAvatarContactHash(dwUIN, szUID, hContact, pAvatarTLV->pData, pAvatarTLV->wLen); else handleAvatarContactHash(dwUIN, szUID, hContact, NULL, 0); // Process Status Note (offline status note) parseStatusNote(dwUIN, szUID, hContact, pChain); // Update status times setDword(hContact, "IdleTS", 0); setDword(hContact, "AwayTS", dwAwaySince); // Clear custom status & mood char tmp = NULL; handleXStatusCaps(dwUIN, szUID, hContact, (BYTE*)&tmp, 0, &tmp, 0); if (wOldStatus != ID_STATUS_OFFLINE) { debugLogA("%s went offline.", strUID(dwUIN, szUID)); setWord(hContact, "Status", ID_STATUS_OFFLINE); // close Direct Connections to that user CloseContactDirectConns(hContact); // Reset DC status setByte(hContact, "DCStatus", 0); } else debugLogA("%s is offline.", strUID(dwUIN, szUID)); } // Release memory disposeChain(&pChain); } while (wLen >= 1); }
void CIcqProto::parseDirectoryUserDetailsData(MCONTACT hContact, oscar_tlv_chain *cDetails, DWORD dwCookie, cookie_directory_data *pCookieData, WORD wReplySubType) { oscar_tlv *pTLV; WORD wRecordCount; if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOMULTI && !hContact) { DWORD dwUin = 0; char *szUid = cDetails->getString(0x32, 1); if (!szUid) { debugLogA("Error: Received unrecognized data from the directory"); return; } if (IsStringUIN(szUid)) dwUin = atoi(szUid); hContact = HContactFromUID(dwUin, szUid, NULL); if (hContact == INVALID_CONTACT_ID) { debugLogA("Error: Received details for unknown contact \"%s\"", szUid); SAFE_FREE(&szUid); return; } #ifdef _DEBUG else debugLogA("Received user info for %s from directory", szUid); #endif SAFE_FREE(&szUid); } #ifdef _DEBUG else { char *szUid = cDetails->getString(0x32, 1); if (!hContact) debugLogA("Received owner user info from directory"); else debugLogA("Received user info for %s from directory", szUid); SAFE_FREE(&szUid); } #endif pTLV = cDetails->getTLV(0x50, 1); if (pTLV && pTLV->wLen > 0) writeDbInfoSettingTLVStringUtf(hContact, "e-mail", cDetails, 0x50); // Verified e-mail else writeDbInfoSettingTLVStringUtf(hContact, "e-mail", cDetails, 0x55); // Pending e-mail writeDbInfoSettingTLVStringUtf(hContact, "FirstName", cDetails, 0x64); writeDbInfoSettingTLVStringUtf(hContact, "LastName", cDetails, 0x6E); writeDbInfoSettingTLVStringUtf(hContact, "Nick", cDetails, 0x78); // Home Address parseUserInfoRecord(hContact, cDetails->getTLV(0x96, 1), rAddress, SIZEOF(rAddress), 1); // Origin Address parseUserInfoRecord(hContact, cDetails->getTLV(0xA0, 1), rOriginAddress, SIZEOF(rOriginAddress), 1); // Phones pTLV = cDetails->getTLV(0xC8, 1); if (pTLV && pTLV->wLen >= 2) { BYTE *pRecords = pTLV->pData; unpackWord(&pRecords, &wRecordCount); oscar_tlv_record_list *cPhones = readIntoTLVRecordList(&pRecords, pTLV->wLen - 2, wRecordCount); if (cPhones) { oscar_tlv_chain *cPhone; cPhone = cPhones->getRecordByTLV(0x6E, 1); writeDbInfoSettingTLVStringUtf(hContact, "Phone", cPhone, 0x64); cPhone = cPhones->getRecordByTLV(0x6E, 2); writeDbInfoSettingTLVStringUtf(hContact, "CompanyPhone", cPhone, 0x64); cPhone = cPhones->getRecordByTLV(0x6E, 3); writeDbInfoSettingTLVStringUtf(hContact, "Cellular", cPhone, 0x64); cPhone = cPhones->getRecordByTLV(0x6E, 4); writeDbInfoSettingTLVStringUtf(hContact, "Fax", cPhone, 0x64); cPhone = cPhones->getRecordByTLV(0x6E, 5); writeDbInfoSettingTLVStringUtf(hContact, "CompanyFax", cPhone, 0x64); disposeRecordList(&cPhones); } else { // Remove old data when phones not available delSetting(hContact, "Phone"); delSetting(hContact, "CompanyPhone"); delSetting(hContact, "Cellular"); delSetting(hContact, "Fax"); delSetting(hContact, "CompanyFax"); } } else { // Remove old data when phones not available delSetting(hContact, "Phone"); delSetting(hContact, "CompanyPhone"); delSetting(hContact, "Cellular"); delSetting(hContact, "Fax"); delSetting(hContact, "CompanyFax"); } // Emails parseUserInfoRecord(hContact, cDetails->getTLV(0x8C, 1), rEmail, SIZEOF(rEmail), 4); writeDbInfoSettingTLVByte(hContact, "Timezone", cDetails, 0x17C); // Company parseUserInfoRecord(hContact, cDetails->getTLV(0x118, 1), rCompany, SIZEOF(rCompany), 1); // Education parseUserInfoRecord(hContact, cDetails->getTLV(0x10E, 1), rEducation, SIZEOF(rEducation), 1); switch (cDetails->getNumber(0x82, 1)) { case 1: setByte(hContact, "Gender", 'F'); break; case 2: setByte(hContact, "Gender", 'M'); break; default: delSetting(hContact, "Gender"); } writeDbInfoSettingTLVStringUtf(hContact, "Homepage", cDetails, 0xFA); writeDbInfoSettingTLVDate(hContact, "BirthYear", "BirthMonth", "BirthDay", cDetails, 0x1A4); writeDbInfoSettingTLVByte(hContact, "Language1", cDetails, 0xAA); writeDbInfoSettingTLVByte(hContact, "Language2", cDetails, 0xB4); writeDbInfoSettingTLVByte(hContact, "Language3", cDetails, 0xBE); writeDbInfoSettingTLVByte(hContact, "MaritalStatus", cDetails, 0x12C); // Interests parseUserInfoRecord(hContact, cDetails->getTLV(0x122, 1), rInterest, SIZEOF(rInterest), 4); writeDbInfoSettingTLVStringUtf(hContact, "About", cDetails, 0x186); // if (hContact) // writeDbInfoSettingTLVStringUtf(hContact, DBSETTING_STATUS_NOTE, cDetails, 0x226); // else if (!hContact) { // Owner contact needs special processing, in the database is current status note for the client // We just received the last status note set on directory, if it differs call SetStatusNote() to // ensure the directory will be updated (it should be in process anyway) char *szClientStatusNote = getSettingStringUtf(hContact, DBSETTING_STATUS_NOTE, NULL); char *szDirectoryStatusNote = cDetails->getString(0x226, 1); if (strcmpnull(szClientStatusNote, szDirectoryStatusNote)) SetStatusNote(szClientStatusNote, 1000, TRUE); // Release memory SAFE_FREE(&szDirectoryStatusNote); SAFE_FREE(&szClientStatusNote); } writeDbInfoSettingTLVByte(hContact, "PrivacyLevel", cDetails, 0x1F9); if (!hContact) { setByte(hContact, "Auth", !cDetails->getByte(0x19A, 1)); writeDbInfoSettingTLVByte(hContact, "WebAware", cDetails, 0x212); writeDbInfoSettingTLVByte(hContact, "AllowSpam", cDetails, 0x1EA); } writeDbInfoSettingTLVWord(hContact, "InfoCP", cDetails, 0x1C2); if (hContact) { // Handle deprecated setting (Age & Birthdate are not separate fields anymore) int nAge = calcAgeFromBirthDate(cDetails->getDouble(0x1A4, 1)); if (nAge) setWord(hContact, "Age", nAge); else delSetting(hContact, "Age"); } else // we do not need to calculate age for owner delSetting(hContact, "Age"); { // Save user info last update time and privacy token double dInfoTime; BYTE pbEmptyMetaToken[0x10] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; int bHasMetaToken = FALSE; // Check if the details arrived with privacy token! if ((pTLV = cDetails->getTLV(0x3C, 1)) && pTLV->wLen == 0x10 && memcmp(pTLV->pData, pbEmptyMetaToken, 0x10)) bHasMetaToken = TRUE; // !Important, we need to save the MDir server-item time - it can be newer than the one from the directory if ((dInfoTime = getSettingDouble(hContact, DBSETTING_METAINFO_TIME, 0)) > 0) setSettingDouble(hContact, DBSETTING_METAINFO_SAVED, dInfoTime); else if (bHasMetaToken || !hContact) writeDbInfoSettingTLVDouble(hContact, DBSETTING_METAINFO_SAVED, cDetails, 0x1CC); else setDword(hContact, DBSETTING_METAINFO_SAVED, time(NULL)); } if (wReplySubType == META_DIRECTORY_RESPONSE) if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER) ProtoBroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, (HANDLE)1 ,0); // Remove user from info update queue. Removing is fast so we always call this // even if it is likely that the user is not queued at all. if (hContact) icq_DequeueUser(getContactUin(hContact)); }
void CIcqProto::handleUserOnline(BYTE *buf, size_t wLen, serverthread_info*) { DWORD dwPort = 0; DWORD dwRealIP = 0; DWORD dwUIN; uid_str szUID; DWORD dwDirectConnCookie = 0; DWORD dwWebPort = 0; DWORD dwFT1 = 0, dwFT2 = 0, dwFT3 = 0; const char *szClient = NULL; BYTE bClientId = 0; WORD wVersion = 0; WORD wTLVCount; WORD wWarningLevel; WORD wStatusFlags; WORD wStatus = 0, wOldStatus = 0; BYTE nTCPFlag = 0; char szStrBuf[MAX_PATH]; // Unpack the sender's user ID if (!unpackUID(&buf, &wLen, &dwUIN, &szUID)) return; // Syntax check if (wLen < 4) return; // Warning level? unpackWord(&buf, &wWarningLevel); wLen -= 2; // TLV count unpackWord(&buf, &wTLVCount); wLen -= 2; // Ignore status notification if the user is not already on our list MCONTACT hContact = HContactFromUID(dwUIN, szUID, NULL); if (hContact == INVALID_CONTACT_ID) { debugLogA("Ignoring user online (%s)", strUID(dwUIN, szUID)); return; } // Read user info TLVs oscar_tlv_chain *pChain; oscar_tlv *pTLV; // Syntax check if (wLen < 4) return; // Get chain if (!(pChain = readIntoTLVChain(&buf, wLen, wTLVCount))) return; // Get Class word WORD wClass = pChain->getWord(0x01, 1); int nIsICQ = wClass & CLASS_ICQ; if (dwUIN) { // Get DC info TLV pTLV = pChain->getTLV(0x0C, 1); if (pTLV && (pTLV->wLen >= 15)) { BYTE *pBuffer = pTLV->pData; nIsICQ = TRUE; unpackDWord(&pBuffer, &dwRealIP); unpackDWord(&pBuffer, &dwPort); unpackByte(&pBuffer, &nTCPFlag); unpackWord(&pBuffer, &wVersion); unpackDWord(&pBuffer, &dwDirectConnCookie); unpackDWord(&pBuffer, &dwWebPort); // Web front port pBuffer += 4; // Client features // Get faked time signatures, used to identify clients if (pTLV->wLen >= 0x23) { unpackDWord(&pBuffer, &dwFT1); unpackDWord(&pBuffer, &dwFT2); unpackDWord(&pBuffer, &dwFT3); } } // Get Status info TLV pTLV = pChain->getTLV(0x06, 1); if (pTLV && (pTLV->wLen >= 4)) { BYTE *pBuffer = pTLV->pData; unpackWord(&pBuffer, &wStatusFlags); unpackWord(&pBuffer, &wStatus); } else if (!nIsICQ) { // Connected thru AIM client, guess by user class if (wClass & CLASS_AWAY) wStatus = ID_STATUS_AWAY; else if (wClass & CLASS_WIRELESS) wStatus = ID_STATUS_ONTHEPHONE; else wStatus = ID_STATUS_ONLINE; wStatusFlags = 0; } else { // Huh? No status TLV? Lets guess then... wStatusFlags = 0; wStatus = ICQ_STATUS_ONLINE; } } else { nIsICQ = FALSE; if (wClass & CLASS_AWAY) wStatus = ID_STATUS_AWAY; else if (wClass & CLASS_WIRELESS) wStatus = ID_STATUS_ONTHEPHONE; else wStatus = ID_STATUS_ONLINE; wStatusFlags = 0; } debugLogA("Flags are %x", wStatusFlags); debugLogA("Status is %x", wStatus); // Get IP TLV DWORD dwIP = pChain->getDWord(0x0A, 1); // Get Online Since TLV DWORD dwOnlineSince = pChain->getDWord(0x03, 1); // Get Away Since TLV DWORD dwAwaySince = pChain->getDWord(0x29, 1); // Get Member Since TLV DWORD dwMemberSince = pChain->getDWord(0x05, 1); // Get Idle timer TLV WORD wIdleTimer = pChain->getWord(0x04, 1); time_t tIdleTS = 0; if (wIdleTimer) { time(&tIdleTS); tIdleTS -= (wIdleTimer*60); } if (wIdleTimer) debugLogA("Idle timer is %u.", wIdleTimer); debugLogA("Online since %s", time2text(dwOnlineSince)); if (dwAwaySince) debugLogA("Status was set on %s", time2text(dwAwaySince)); // Check client capabilities if (hContact != NULL) { wOldStatus = getContactStatus(hContact); // Collect all Capability info from TLV chain BYTE *capBuf = NULL; WORD capLen = 0; // Get Location Capability Info TLVs oscar_tlv *pFullTLV = pChain->getTLV(0x0D, 1); oscar_tlv *pShortTLV = pChain->getTLV(0x19, 1); if (pFullTLV && (pFullTLV->wLen >= BINARY_CAP_SIZE)) capLen += pFullTLV->wLen; if (pShortTLV && (pShortTLV->wLen >= 2)) capLen += (pShortTLV->wLen * 8); capBuf = (BYTE*)_alloca(capLen + BINARY_CAP_SIZE); if (capLen) { BYTE *pCapability = capBuf; capLen = 0; // we need to recount that if (pFullTLV && (pFullTLV->wLen >= BINARY_CAP_SIZE)) { // copy classic Capabilities BYTE *cData = pFullTLV->pData; int cLen = pFullTLV->wLen; while (cLen) { // be impervious to duplicates (AOL sends them sometimes) if (!capLen || !MatchCapability(capBuf, capLen, (capstr*)cData, BINARY_CAP_SIZE)) { // not present, add memcpy(pCapability, cData, BINARY_CAP_SIZE); capLen += BINARY_CAP_SIZE; pCapability += BINARY_CAP_SIZE; } cData += BINARY_CAP_SIZE; cLen -= BINARY_CAP_SIZE; } } if (pShortTLV && (pShortTLV->wLen >= 2)) { // copy short Capabilities capstr tmp; BYTE *cData = pShortTLV->pData; int cLen = pShortTLV->wLen; memcpy(tmp, capShortCaps, BINARY_CAP_SIZE); while (cLen) { // be impervious to duplicates (AOL sends them sometimes) tmp[2] = cData[0]; tmp[3] = cData[1]; if (!capLen || !MatchCapability(capBuf, capLen, &tmp, BINARY_CAP_SIZE)) { // not present, add memcpy(pCapability, tmp, BINARY_CAP_SIZE); capLen += BINARY_CAP_SIZE; pCapability += BINARY_CAP_SIZE; } cData += 2; cLen -= 2; } } debugLogA("Detected %d capability items.", capLen / BINARY_CAP_SIZE); } if (capLen) { // Update the contact's capabilies if present in packet SetCapabilitiesFromBuffer(hContact, capBuf, capLen, wOldStatus == ID_STATUS_OFFLINE); char *szCurrentClient = wOldStatus == ID_STATUS_OFFLINE ? NULL : getSettingStringUtf(hContact, "MirVer", NULL); szClient = detectUserClient(hContact, nIsICQ, wClass, dwOnlineSince, szCurrentClient, wVersion, dwFT1, dwFT2, dwFT3, dwDirectConnCookie, dwWebPort, capBuf, capLen, &bClientId, szStrBuf); // Check if the client changed, if not do not change if (szCurrentClient && !strcmpnull(szCurrentClient, szClient)) szClient = (const char*)-1; SAFE_FREE(&szCurrentClient); } else if (wOldStatus == ID_STATUS_OFFLINE) { // Remove the contact's capabilities if coming from offline ClearAllContactCapabilities(hContact); // no capability debugLogA("No capability info TLVs"); szClient = detectUserClient(hContact, nIsICQ, wClass, dwOnlineSince, NULL, wVersion, dwFT1, dwFT2, dwFT3, dwDirectConnCookie, dwWebPort, NULL, capLen, &bClientId, szStrBuf); } else // Capabilities not present in update packet, do not touch szClient = (const char*)-1; // we don't want to client be overwritten // handle Xtraz status char *moodData = NULL; WORD moodSize = 0; unpackSessionDataItem(pChain, 0x0E, (BYTE**)&moodData, &moodSize, NULL); if (capLen || wOldStatus == ID_STATUS_OFFLINE) handleXStatusCaps(dwUIN, szUID, hContact, capBuf, capLen, moodData, moodSize); else handleXStatusCaps(dwUIN, szUID, hContact, NULL, 0, moodData, moodSize); // Determine support for extended status messages if (pChain->getWord(0x08, 1) == 0x0A06) SetContactCapabilities(hContact, CAPF_STATUS_MESSAGES); else if (wOldStatus == ID_STATUS_OFFLINE) ClearContactCapabilities(hContact, CAPF_STATUS_MESSAGES); if (wOldStatus == ID_STATUS_OFFLINE) { if (CheckContactCapabilities(hContact, CAPF_SRV_RELAY)) debugLogA("Supports advanced messages"); else debugLogA("Does NOT support advanced messages"); } if (!nIsICQ) { // AIM clients does not advertise these, but do support them SetContactCapabilities(hContact, CAPF_UTF | CAPF_TYPING); // Server relayed messages are only supported by ICQ clients ClearContactCapabilities(hContact, CAPF_SRV_RELAY); if (dwUIN && wOldStatus == ID_STATUS_OFFLINE) debugLogA("Logged in with AIM client"); } if (nIsICQ && wVersion < 8) { ClearContactCapabilities(hContact, CAPF_SRV_RELAY); if (wOldStatus == ID_STATUS_OFFLINE) debugLogA("Forcing simple messages due to compability issues"); } // Process Avatar Hash pTLV = pChain->getTLV(0x1D, 1); if (pTLV) handleAvatarContactHash(dwUIN, szUID, hContact, pTLV->pData, pTLV->wLen); else handleAvatarContactHash(dwUIN, szUID, hContact, NULL, 0); // Process Status Note parseStatusNote(dwUIN, szUID, hContact, pChain); } // Free TLV chain disposeChain(&pChain); // Save contacts details in database if (hContact != NULL) { setDword(hContact, "LogonTS", dwOnlineSince); setDword(hContact, "AwayTS", dwAwaySince); setDword(hContact, "IdleTS", tIdleTS); if (dwMemberSince) setDword(hContact, "MemberTS", dwMemberSince); if (nIsICQ) { // on AIM these are not used setDword(hContact, "DirectCookie", dwDirectConnCookie); setByte(hContact, "DCType", (BYTE)nTCPFlag); setWord(hContact, "UserPort", (WORD)(dwPort & 0xffff)); setWord(hContact, "Version", wVersion); } else { delSetting(hContact, "DirectCookie"); delSetting(hContact, "DCType"); delSetting(hContact, "UserPort"); delSetting(hContact, "Version"); } // if no detection, set uknown if (!szClient) szClient = (nIsICQ ? "Unknown" : "Unknown AIM"); if (szClient != (char*)-1) { db_set_utf(hContact, m_szModuleName, "MirVer", szClient); setByte(hContact, "ClientID", bClientId); } if (wOldStatus == ID_STATUS_OFFLINE) { setDword(hContact, "IP", dwIP); setDword(hContact, "RealIP", dwRealIP); } else { // if not first notification only write significant information if (dwIP) setDword(hContact, "IP", dwIP); if (dwRealIP) setDword(hContact, "RealIP", dwRealIP); } setWord(hContact, "Status", (WORD)IcqStatusToMiranda(wStatus)); // Update info? if (dwUIN) { // check if the local copy of user details is up-to-date if (IsMetaInfoChanged(hContact)) icq_QueueUser(hContact); } } LPCTSTR ptszStatus = pcli->pfnGetStatusModeDescription(IcqStatusToMiranda(wStatus), 0); if (wOldStatus != IcqStatusToMiranda(wStatus)) { // And a small log notice... if status was changed if (nIsICQ) debugLogA("%u changed status to %S (v%d).", dwUIN, ptszStatus, wVersion); else debugLogA("%s changed status to %S.", strUID(dwUIN, szUID), ptszStatus); } if (szClient == cliSpamBot) { if (getByte("KillSpambots", DEFAULT_KILLSPAM_ENABLED) && db_get_b(hContact, "CList", "NotOnList", 0)) { // kill spammer icq_DequeueUser(dwUIN); icq_sendRemoveContact(dwUIN, NULL); AddToSpammerList(dwUIN); if (getByte("PopupsSpamEnabled", DEFAULT_SPAM_POPUPS_ENABLED)) ShowPopupMsg(hContact, LPGEN("Spambot Detected"), LPGEN("Contact deleted & further events blocked."), POPTYPE_SPAM); CallService(MS_DB_CONTACT_DELETE, hContact, 0); debugLogA("Contact %u deleted", dwUIN); } } }
void CIcqProto::handleDirectoryQueryResponse(BYTE *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype, WORD wFlags) { WORD wBytesRemaining = 0; snac_header requestSnac = {0}; BYTE requestResult; #ifdef _DEBUG debugLogA("Received directory query response"); #endif if (wPacketLen >= 2) unpackLEWord(&databuf, &wBytesRemaining); wPacketLen -= 2; _ASSERTE(wPacketLen == wBytesRemaining); if (!unpackSnacHeader(&requestSnac, &databuf, &wPacketLen) || !requestSnac.bValid) { debugLogA("Error: Failed to parse directory response"); return; } cookie_directory_data *pCookieData; MCONTACT hContact; // check request cookie if (!FindCookie(wCookie, &hContact, (void**)&pCookieData) || !pCookieData) { debugLogA("Warning: Ignoring unrequested directory reply type (x%x, x%x)", requestSnac.wFamily, requestSnac.wSubtype); return; } /// FIXME: we should really check the snac contents according to cookie data here ?? // Check if this is the last packet for this request BOOL bMoreDataFollows = wFlags&0x0001 && requestSnac.wFlags&0x0001; if (wPacketLen >= 3) unpackByte(&databuf, &requestResult); else { debugLogA("Error: Malformed directory response"); if (!bMoreDataFollows) ReleaseCookie(wCookie); return; } if (requestResult != 1 && requestResult != 4) { debugLogA("Error: Directory request failed, status %u", requestResult); if (!bMoreDataFollows) { if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER) ProtoBroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0); else if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH) ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); // should report error here, but Find/Add module does not support that ReleaseCookie(wCookie); } return; } WORD wLen; unpackWord(&databuf, &wLen); wPacketLen -= 3; if (wLen) debugLogA("Warning: Data in error message present!"); if (wPacketLen <= 0x16) { // sanity check debugLogA("Error: Malformed directory response"); if (!bMoreDataFollows) { if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER) ProtoBroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0); else if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH) ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); // should report error here, but Find/Add module does not support that ReleaseCookie(wCookie); } return; } databuf += 0x10; // unknown stuff wPacketLen -= 0x10; DWORD dwItemCount; WORD wPageCount; /// FIXME: check itemcount, pagecount against the cookie data ??? unpackDWord(&databuf, &dwItemCount); unpackWord(&databuf, &wPageCount); wPacketLen -= 6; if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH && !bMoreDataFollows) debugLogA("Directory Search: %d contacts found (%u pages)", dwItemCount, wPageCount); if (wPacketLen <= 2) { // sanity check, block expected debugLogA("Error: Malformed directory response"); if (!bMoreDataFollows) { if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER) ProtoBroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0); else if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH) ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); // should report error here, but Find/Add module does not support that ReleaseCookie(wCookie); } return; } WORD wData; unpackWord(&databuf, &wData); // This probably the count of items following (a block) wPacketLen -= 2; if (wPacketLen >= 2 && wData >= 1) { unpackWord(&databuf, &wLen); // This is the size of the first item wPacketLen -= 2; } if (wData == 0 && pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH) { debugLogA("Directory Search: No contacts found"); ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); ReleaseCookie(wCookie); return; } _ASSERTE(wData == 1 && wPacketLen == wLen); if (wData != 1 || wPacketLen != wLen) { debugLogA("Error: Malformed directory response (missing data)"); if (!bMoreDataFollows) { if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER) ProtoBroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0); else if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH) ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); // should report error here, but Find/Add module does not support that ReleaseCookie(wCookie); } return; } oscar_tlv_chain *pDirectoryData = readIntoTLVChain(&databuf, wLen, -1); if (pDirectoryData) { switch (pCookieData->bRequestType) { case DIRECTORYREQUEST_INFOOWNER: parseDirectoryUserDetailsData(NULL, pDirectoryData, wCookie, pCookieData, wReplySubtype); break; case DIRECTORYREQUEST_INFOUSER: { DWORD dwUin = 0; char *szUid = pDirectoryData->getString(0x32, 1); if (!szUid) { debugLogA("Error: Received unrecognized data from the directory"); break; } if (IsStringUIN(szUid)) dwUin = atoi(szUid); if (hContact != HContactFromUID(dwUin, szUid, NULL)) { debugLogA("Error: Received data does not match cookie contact, ignoring."); SAFE_FREE(&szUid); break; } else SAFE_FREE(&szUid); } case DIRECTORYREQUEST_INFOMULTI: parseDirectoryUserDetailsData(hContact, pDirectoryData, wCookie, pCookieData, wReplySubtype); break; case DIRECTORYREQUEST_SEARCH: parseDirectorySearchData(pDirectoryData, wCookie, pCookieData, wReplySubtype); break; default: debugLogA("Error: Unknown cookie type %x for directory response!", pCookieData->bRequestType); } disposeChain(&pDirectoryData); } else debugLogA("Error: Failed parsing directory response"); // Release Memory if (!bMoreDataFollows) ReleaseCookie(wCookie); }