From 48540940b6c28bb4378abfeb500ec45a625b37b6 Mon Sep 17 00:00:00 2001 From: Vadim Dashevskiy Date: Tue, 15 May 2012 10:38:20 +0000 Subject: initial commit git-svn-id: http://svn.miranda-ng.org/main/trunk@2 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/IcqOscarJ/fam_15icqserver.cpp | 1208 +++++++++++++++++++++++++++++++ 1 file changed, 1208 insertions(+) create mode 100644 protocols/IcqOscarJ/fam_15icqserver.cpp (limited to 'protocols/IcqOscarJ/fam_15icqserver.cpp') diff --git a/protocols/IcqOscarJ/fam_15icqserver.cpp b/protocols/IcqOscarJ/fam_15icqserver.cpp new file mode 100644 index 0000000000..6df21796c4 --- /dev/null +++ b/protocols/IcqOscarJ/fam_15icqserver.cpp @@ -0,0 +1,1208 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright 2001-2002 Jon Keating, Richard Hughes +// Copyright 2002-2004 Martin berg, Sam Kothari, Robert Rainwater +// Copyright 2004-2010 Joe Kucera +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// +// File name : $URL: http://miranda.googlecode.com/svn/trunk/miranda/protocols/IcqOscarJ/fam_15icqserver.cpp $ +// Revision : $Revision: 13027 $ +// Last change on : $Date: 2010-10-24 16:23:07 +0200 (Вс, 24 окт 2010) $ +// Last change by : $Author: borkra $ +// +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- + +#include "icqoscar.h" + +void CIcqProto::handleIcqExtensionsFam(BYTE *pBuffer, WORD wBufferLength, snac_header* pSnacHeader) +{ + switch (pSnacHeader->wSubtype) { + + case ICQ_META_ERROR: + handleExtensionError(pBuffer, wBufferLength); + break; + + case ICQ_META_SRV_REPLY: + handleExtensionServerInfo(pBuffer, wBufferLength, pSnacHeader->wFlags); + break; + + default: + NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_EXTENSIONS_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + } +} + + +void CIcqProto::handleExtensionError(BYTE *buf, WORD wPackLen) +{ + WORD wErrorCode; + + if (wPackLen < 2) + wErrorCode = 0; + + if (wPackLen >= 2 && wPackLen <= 6) + unpackWord(&buf, &wErrorCode); + else + { // TODO: cookies need to be handled and freed here on error + oscar_tlv_chain *chain = NULL; + + unpackWord(&buf, &wErrorCode); + wPackLen -= 2; + chain = readIntoTLVChain(&buf, wPackLen, 0); + if (chain) + { + oscar_tlv* pTLV; + + pTLV = chain->getTLV(0x21, 1); // get meta error data + if (pTLV && pTLV->wLen >= 8) + { + BYTE *pBuffer = pTLV->pData; + WORD wData; + pBuffer += 6; + unpackLEWord(&pBuffer, &wData); // get request type + switch (wData) + { + case CLI_META_INFO_REQ: + if (pTLV->wLen >= 12) + { + WORD wSubType; + WORD wCookie; + + unpackWord(&pBuffer, &wCookie); + unpackLEWord(&pBuffer, &wSubType); + // more sofisticated detection, send ack + if (wSubType == META_REQUEST_FULL_INFO) + { + HANDLE hContact; + cookie_fam15_data *pCookieData = NULL; + int foundCookie; + + foundCookie = FindCookie(wCookie, &hContact, (void**)&pCookieData); + if (foundCookie && pCookieData) + { + BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0); + + ReleaseCookie(wCookie); // we do not leak cookie and memory + } + + NetLog_Server("Full info request error 0x%02x received", wErrorCode); + } + else if (wSubType == META_SET_PASSWORD_REQ) + { // failed to change user password, report to UI + BroadcastAck(NULL, ACKTYPE_SETINFO, ACKRESULT_FAILED, (HANDLE)wCookie, 0); + + NetLog_Server("Meta change password request failed, error 0x%02x", wErrorCode); + } + else + NetLog_Server("Meta request error 0x%02x received", wErrorCode); + } + else + NetLog_Server("Meta request error 0x%02x received", wErrorCode); + + break; + + default: + NetLog_Server("Unknown request 0x%02x error 0x%02x received", wData, wErrorCode); + } + disposeChain(&chain); + return; + } + disposeChain(&chain); + } + } + LogFamilyError(ICQ_EXTENSIONS_FAMILY, wErrorCode); +} + + +void CIcqProto::handleExtensionServerInfo(BYTE *buf, WORD wPackLen, WORD wFlags) +{ + oscar_tlv_chain *chain; + oscar_tlv *dataTlv; + + // The entire packet is encapsulated in a TLV type 1 + chain = readIntoTLVChain(&buf, wPackLen, 0); + if (chain == NULL) + { + NetLog_Server("Error: Broken snac 15/3 %d", 1); + return; + } + + dataTlv = chain->getTLV(0x0001, 1); + if (dataTlv == NULL) + { + disposeChain(&chain); + NetLog_Server("Error: Broken snac 15/3 %d", 2); + return; + } + BYTE *databuf = dataTlv->pData; + wPackLen -= 4; + + _ASSERTE(dataTlv->wLen == wPackLen); + _ASSERTE(wPackLen >= 10); + + if ((dataTlv->wLen == wPackLen) && (wPackLen >= 10)) + { + WORD wBytesRemaining; + WORD wRequestType; + WORD wCookie; + DWORD dwMyUin; + + unpackLEWord(&databuf, &wBytesRemaining); + unpackLEDWord(&databuf, &dwMyUin); + unpackLEWord(&databuf, &wRequestType); + unpackWord(&databuf, &wCookie); + + _ASSERTE(wBytesRemaining == (wPackLen - 2)); + if (wBytesRemaining == (wPackLen - 2)) + { + wPackLen -= 10; + switch (wRequestType) + { + case SRV_META_INFO_REPLY: // SRV_META request replies + handleExtensionMetaResponse(databuf, wPackLen, wCookie, wFlags); + break; + + default: + NetLog_Server("Warning: Ignoring Meta response - Unknown type %d", wRequestType); + break; + } + } + } + else + NetLog_Server("Error: Broken snac 15/3 %d", 3); + + if (chain) + disposeChain(&chain); +} + + +void CIcqProto::handleExtensionMetaResponse(BYTE *databuf, WORD wPacketLen, WORD wCookie, WORD wFlags) +{ + WORD wReplySubtype; + BYTE bResultCode; + + _ASSERTE(wPacketLen >= 3); + if (wPacketLen >= 3) + { + // Reply subtype + unpackLEWord(&databuf, &wReplySubtype); + wPacketLen -= 2; + + // Success byte + unpackByte(&databuf, &bResultCode); + wPacketLen -= 1; + + switch (wReplySubtype) + { + case META_SET_PASSWORD_ACK: + parseUserInfoUpdateAck(databuf, wPacketLen, wCookie, wReplySubtype, bResultCode); + break; + + case SRV_RANDOM_FOUND: + case SRV_USER_FOUND: + case SRV_LAST_USER_FOUND: + parseSearchReplies(databuf, wPacketLen, wCookie, wReplySubtype, bResultCode); + break; + + case META_PROCESSING_ERROR: // Meta processing error server reply + // Todo: We only use this as an SMS ack, that will have to change + { + // Terminate buffer + char *pszInfo = (char *)_alloca(wPacketLen + 1); + if (wPacketLen > 0) + memcpy(pszInfo, databuf, wPacketLen); + pszInfo[wPacketLen] = 0; + + BroadcastAck(NULL, ICQACKTYPE_SMS, ACKRESULT_FAILED, (HANDLE)wCookie, (LPARAM)pszInfo); + FreeCookie(wCookie); + break; + } + break; + + case META_SMS_DELIVERY_RECEIPT: + // Todo: This overlaps with META_SET_AFFINFO_ACK. + // Todo: Check what happens if result != A + if (wPacketLen > 8) + { + WORD wNetworkNameLen; + WORD wAckLen; + char *pszInfo; + + + databuf += 6; // Some unknowns + wPacketLen -= 6; + + unpackWord(&databuf, &wNetworkNameLen); + if (wPacketLen >= (wNetworkNameLen + 2)) + { + databuf += wNetworkNameLen; + wPacketLen -= wNetworkNameLen; + + unpackWord(&databuf, &wAckLen); + if (pszInfo = (char *)_alloca(wAckLen + 1)) + { + // Terminate buffer + if (wAckLen > 0) + memcpy(pszInfo, databuf, wAckLen); + pszInfo[wAckLen] = 0; + + BroadcastAck(NULL, ICQACKTYPE_SMS, ACKRESULT_SENTREQUEST, (HANDLE)wCookie, (LPARAM)pszInfo); + FreeCookie(wCookie); + + // Parsing success + break; + } + } + } + + // Parsing failure + NetLog_Server("Error: Failure parsing META_SMS_DELIVERY_RECEIPT"); + break; + + case META_DIRECTORY_DATA: + case META_DIRECTORY_RESPONSE: + if (bResultCode == 0x0A) + handleDirectoryQueryResponse(databuf, wPacketLen, wCookie, wReplySubtype, wFlags); + else + NetLog_Server("Error: Directory request failed, code %u", bResultCode); + break; + + case META_DIRECTORY_UPDATE_ACK: + if (bResultCode == 0x0A) + handleDirectoryUpdateResponse(databuf, wPacketLen, wCookie, wReplySubtype); + else + NetLog_Server("Error: Directory request failed, code %u", bResultCode); + break; + + default: + NetLog_Server("Warning: Ignored 15/03 replysubtype x%x", wReplySubtype); + // _ASSERTE(0); + break; + } + + // Success + return; + } + + // Failure + NetLog_Server("Warning: Broken 15/03 ExtensionMetaResponse"); +} + + +void CIcqProto::ReleaseSearchCookie(DWORD dwCookie, cookie_search *pCookie) +{ + if (pCookie) + { + FreeCookie(dwCookie); + if (pCookie->dwMainId) + { + if (pCookie->dwStatus) + { + SAFE_FREE((void**)&pCookie); + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0); + } + else + pCookie->dwStatus = 1; + } + else + { + SAFE_FREE((void**)&pCookie); + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0); + } + } + else + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0); +} + + +void CIcqProto::parseSearchReplies(unsigned char *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype, BYTE bResultCode) +{ + BYTE bParsingOK = FALSE; // For debugging purposes only + BOOL bLastUser = FALSE; + cookie_search *pCookie; + + if (!FindCookie(wCookie, NULL, (void**)&pCookie)) + { + NetLog_Server("Warning: Received unexpected search reply"); + pCookie = NULL; + } + + switch (wReplySubtype) + { + + case SRV_LAST_USER_FOUND: // Search: last user found reply + bLastUser = TRUE; + + case SRV_USER_FOUND: // Search: user found reply + if (bLastUser) + NetLog_Server("SNAC(0x15,0x3): Last search reply"); + else + NetLog_Server("SNAC(0x15,0x3): Search reply"); + + if (bResultCode == 0xA) + { + ICQSEARCHRESULT sr = {0}; + DWORD dwUin; + char szUin[UINMAXLEN]; + WORD wLen; + + sr.hdr.cbSize = sizeof(sr); + + // Remaining bytes + if (wPacketLen < 2) + break; + unpackLEWord(&databuf, &wLen); + wPacketLen -= 2; + + _ASSERTE(wLen <= wPacketLen); + if (wLen > wPacketLen) + break; + + // Uin + if (wPacketLen < 4) + break; + unpackLEDWord(&databuf, &dwUin); // Uin + wPacketLen -= 4; + sr.uin = dwUin; + _itoa(dwUin, szUin, 10); + sr.hdr.id = (FNAMECHAR*)szUin; + + // Nick + if (wPacketLen < 2) + break; + unpackLEWord(&databuf, &wLen); + wPacketLen -= 2; + if (wLen > 0) + { + if (wPacketLen < wLen || (databuf[wLen-1] != 0)) + break; + sr.hdr.nick = (FNAMECHAR*)databuf; + databuf += wLen; + } + else + { + sr.hdr.nick = NULL; + } + + // First name + if (wPacketLen < 2) + break; + unpackLEWord(&databuf, &wLen); + wPacketLen -= 2; + if (wLen > 0) + { + if (wPacketLen < wLen || (databuf[wLen-1] != 0)) + break; + sr.hdr.firstName = (FNAMECHAR*)databuf; + databuf += wLen; + } + else + { + sr.hdr.firstName = NULL; + } + + // Last name + if (wPacketLen < 2) + break; + unpackLEWord(&databuf, &wLen); + wPacketLen -= 2; + if (wLen > 0) + { + if (wPacketLen < wLen || (databuf[wLen-1] != 0)) + break; + sr.hdr.lastName = (FNAMECHAR*)databuf; + databuf += wLen; + } + else + { + sr.hdr.lastName = NULL; + } + + // E-mail name + if (wPacketLen < 2) + break; + unpackLEWord(&databuf, &wLen); + wPacketLen -= 2; + if (wLen > 0) + { + if (wPacketLen < wLen || (databuf[wLen-1] != 0)) + break; + sr.hdr.email = (FNAMECHAR*)databuf; + databuf += wLen; + } + else + { + sr.hdr.email = NULL; + } + + // Authentication needed flag + if (wPacketLen < 1) + break; + unpackByte(&databuf, &sr.auth); + + // Finally, broadcast the result + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)wCookie, (LPARAM)&sr); + + // Broadcast "Last result" ack if this was the last user found + if (wReplySubtype == SRV_LAST_USER_FOUND) + { + if (wPacketLen>=10) + { + DWORD dwLeft; + + databuf += 5; + unpackLEDWord(&databuf, &dwLeft); + if (dwLeft) + NetLog_Server("Warning: %d search results omitted", dwLeft); + } + ReleaseSearchCookie(wCookie, pCookie); + } + bParsingOK = TRUE; + } + else + { + // Failed search + NetLog_Server("SNAC(0x15,0x3): Search error %u", bResultCode); + + ReleaseSearchCookie(wCookie, pCookie); + + bParsingOK = TRUE; + } + break; + + case SRV_RANDOM_FOUND: // Random search server reply + default: + if (pCookie) + ReleaseCookie(wCookie); + break; + } + + // For debugging purposes only + if (!bParsingOK) + { + NetLog_Server("Warning: Parsing error in 15/03 search reply type x%x", wReplySubtype); + _ASSERTE(!bParsingOK); + } +} + + +void CIcqProto::parseUserInfoUpdateAck(unsigned char *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype, BYTE bResultCode) +{ + switch (wReplySubtype) { + case META_SET_PASSWORD_ACK: // Set user password server ack + + if (bResultCode == 0xA) + BroadcastAck(NULL, ACKTYPE_SETINFO, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); + else + BroadcastAck(NULL, ACKTYPE_SETINFO, ACKRESULT_FAILED, (HANDLE)wCookie, 0); + + FreeCookie(wCookie); + break; + + default: + NetLog_Server("Warning: Ignored 15/03 user info update ack type x%x", wReplySubtype); + break; + } +} + + +UserInfoRecordItem rEmail[] = { + {0x64, DBVT_ASCIIZ, "e-mail%u"} +}; + +UserInfoRecordItem rAddress[] = { + {0x64, DBVT_UTF8, "Street"}, + {0x6E, DBVT_UTF8, "City"}, + {0x78, DBVT_UTF8, "State"}, + {0x82, DBVT_UTF8, "ZIP"}, + {0x8C, DBVT_WORD, "Country"} +}; + +UserInfoRecordItem rOriginAddress[] = { + {0x64, DBVT_UTF8, "OriginStreet"}, + {0x6E, DBVT_UTF8, "OriginCity"}, + {0x78, DBVT_UTF8, "OriginState"}, + {0x8C, DBVT_WORD, "OriginCountry"} +}; + +UserInfoRecordItem rCompany[] = { + {0x64, DBVT_UTF8, "CompanyPosition"}, + {0x6E, DBVT_UTF8, "Company"}, + {0x7D, DBVT_UTF8, "CompanyDepartment"}, + {0x78, DBVT_ASCIIZ, "CompanyHomepage"}, + {0x82, DBVT_WORD, "CompanyIndustry"}, + {0xAA, DBVT_UTF8, "CompanyStreet"}, + {0xB4, DBVT_UTF8, "CompanyCity"}, + {0xBE, DBVT_UTF8, "CompanyState"}, + {0xC8, DBVT_UTF8, "CompanyZIP"}, + {0xD2, DBVT_WORD, "CompanyCountry"} +}; + +UserInfoRecordItem rEducation[] = { + {0x64, DBVT_WORD, "StudyLevel"}, + {0x6E, DBVT_UTF8, "StudyInstitute"}, + {0x78, DBVT_UTF8, "StudyDegree"}, + {0x8C, DBVT_WORD, "StudyYear"} +}; + +UserInfoRecordItem rInterest[] = { + {0x64, DBVT_UTF8, "Interest%uText"}, + {0x6E, DBVT_WORD, "Interest%uCat"} +}; + + +int CIcqProto::parseUserInfoRecord(HANDLE hContact, oscar_tlv *pData, UserInfoRecordItem pRecordDef[], int nRecordDef, int nMaxRecords) +{ + int nRecords = 0; + + if (pData && pData->wLen >= 2) + { + BYTE *pRecords = pData->pData; + WORD wRecordCount; + unpackWord(&pRecords, &wRecordCount); + oscar_tlv_record_list *cData = readIntoTLVRecordList(&pRecords, pData->wLen - 2, nMaxRecords > wRecordCount ? wRecordCount : nMaxRecords); + oscar_tlv_record_list *cDataItem = cData; + while (cDataItem) + { + oscar_tlv_chain *cItem = cDataItem->item; + + for (int i = 0; i < nRecordDef; i++) + { + char szItemKey[MAX_PATH]; + + null_snprintf(szItemKey, MAX_PATH, pRecordDef[i].szDbSetting, nRecords); + + switch (pRecordDef[i].dbType) + { + case DBVT_ASCIIZ: + writeDbInfoSettingTLVString(hContact, szItemKey, cItem, pRecordDef[i].wTLV); + break; + + case DBVT_UTF8: + writeDbInfoSettingTLVStringUtf(hContact, szItemKey, cItem, pRecordDef[i].wTLV); + break; + + case DBVT_WORD: + writeDbInfoSettingTLVWord(hContact, szItemKey, cItem, pRecordDef[i].wTLV); + break; + } + } + nRecords++; + + cDataItem = cDataItem->next; + } + // release memory + disposeRecordList(&cData); + } + // remove old data from database + if (!nRecords || nMaxRecords > 1) + for (int i = nRecords; i <= nMaxRecords; i++) + for (int j = 0; j < nRecordDef; j++) + { + char szItemKey[MAX_PATH]; + + null_snprintf(szItemKey, MAX_PATH, pRecordDef[j].szDbSetting, i); + + deleteSetting(hContact, szItemKey); + } + + return nRecords; +} + + +void CIcqProto::handleDirectoryQueryResponse(BYTE *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype, WORD wFlags) +{ + WORD wBytesRemaining = 0; + snac_header requestSnac = {0}; + BYTE requestResult; + +#ifdef _DEBUG + NetLog_Server("Received directory query response"); +#endif + if (wPacketLen >= 2) + unpackLEWord(&databuf, &wBytesRemaining); + wPacketLen -= 2; + _ASSERTE(wPacketLen == wBytesRemaining); + + if (!unpackSnacHeader(&requestSnac, &databuf, &wPacketLen) || !requestSnac.bValid) + { + NetLog_Server("Error: Failed to parse directory response"); + return; + } + + cookie_directory_data *pCookieData; + HANDLE hContact; + // check request cookie + if (!FindCookie(wCookie, &hContact, (void**)&pCookieData) || !pCookieData) + { + NetLog_Server("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 + { + NetLog_Server("Error: Malformed directory response"); + if (!bMoreDataFollows) + ReleaseCookie(wCookie); + return; + } + if (requestResult != 1 && requestResult != 4) + { + NetLog_Server("Error: Directory request failed, status %u", requestResult); + + if (!bMoreDataFollows) + { + if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER) + BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0); + else if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH) + BroadcastAck(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) + NetLog_Server("Warning: Data in error message present!"); + + if (wPacketLen <= 0x16) + { // sanity check + NetLog_Server("Error: Malformed directory response"); + + if (!bMoreDataFollows) + { + if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER) + BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0); + else if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH) + BroadcastAck(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) + NetLog_Server("Directory Search: %d contacts found (%u pages)", dwItemCount, wPageCount); + + if (wPacketLen <= 2) + { // sanity check, block expected + NetLog_Server("Error: Malformed directory response"); + + if (!bMoreDataFollows) + { + if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER) + BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0); + else if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH) + BroadcastAck(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) + { + NetLog_Server("Directory Search: No contacts found"); + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); + ReleaseCookie(wCookie); + return; + } + + _ASSERTE(wData == 1 && wPacketLen == wLen); + if (wData != 1 || wPacketLen != wLen) + { + NetLog_Server("Error: Malformed directory response (missing data)"); + + if (!bMoreDataFollows) + { + if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER) + BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0); + else if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH) + BroadcastAck(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) + { + NetLog_Server("Error: Received unrecognized data from the directory"); + break; + } + + if (IsStringUIN(szUid)) + dwUin = atoi(szUid); + + if (hContact != HContactFromUID(dwUin, szUid, NULL)) + { + NetLog_Server("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: + NetLog_Server("Error: Unknown cookie type %x for directory response!", pCookieData->bRequestType); + } + disposeChain(&pDirectoryData); + } + else + NetLog_Server("Error: Failed parsing directory response"); + + // Release Memory + if (!bMoreDataFollows) + ReleaseCookie(wCookie); +} + + +static int calcAgeFromBirthDate(double dDate) +{ + if (dDate > 0) + { // date is stored as double with unit equal to a day, incrementing since 1/1/1900 0:00 GMT + SYSTEMTIME sDate = {0}; + if (VariantTimeToSystemTime(dDate + 2, &sDate)) + { + SYSTEMTIME sToday = {0}; + + GetLocalTime(&sToday); + + int nAge = sToday.wYear - sDate.wYear; + + if (sToday.wMonth < sDate.wMonth || (sToday.wMonth == sDate.wMonth && sToday.wDay < sDate.wDay)) + nAge--; + + return nAge; + } + } + return 0; +} + + +void CIcqProto::parseDirectoryUserDetailsData(HANDLE 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) + { + NetLog_Server("Error: Received unrecognized data from the directory"); + return; + } + + if (IsStringUIN(szUid)) + dwUin = atoi(szUid); + + hContact = HContactFromUID(dwUin, szUid, NULL); + if (hContact == INVALID_HANDLE_VALUE) + { + NetLog_Server("Error: Received details for unknown contact \"%s\"", szUid); + SAFE_FREE(&szUid); + return; + } +#ifdef _DEBUG + else + NetLog_Server("Received user info for %s from directory", szUid); +#endif + SAFE_FREE(&szUid); + } +#ifdef _DEBUG + else + { + char *szUid = cDetails->getString(0x32, 1); + + if (!hContact) + NetLog_Server("Received owner user info from directory"); + else + NetLog_Server("Received user info for %s from directory", szUid); + SAFE_FREE(&szUid); + } +#endif + + pTLV = cDetails->getTLV(0x50, 1); + if (pTLV && pTLV->wLen > 0) + writeDbInfoSettingTLVString(hContact, "e-mail", cDetails, 0x50); // Verified e-mail + else + writeDbInfoSettingTLVString(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); + writeDbInfoSettingTLVString(hContact, "Phone", cPhone, 0x64); + cPhone = cPhones->getRecordByTLV(0x6E, 2); + writeDbInfoSettingTLVString(hContact, "CompanyPhone", cPhone, 0x64); + cPhone = cPhones->getRecordByTLV(0x6E, 3); + writeDbInfoSettingTLVString(hContact, "Cellular", cPhone, 0x64); + cPhone = cPhones->getRecordByTLV(0x6E, 4); + writeDbInfoSettingTLVString(hContact, "Fax", cPhone, 0x64); + cPhone = cPhones->getRecordByTLV(0x6E, 5); + writeDbInfoSettingTLVString(hContact, "CompanyFax", cPhone, 0x64); + + disposeRecordList(&cPhones); + } + else + { // Remove old data when phones not available + deleteSetting(hContact, "Phone"); + deleteSetting(hContact, "CompanyPhone"); + deleteSetting(hContact, "Cellular"); + deleteSetting(hContact, "Fax"); + deleteSetting(hContact, "CompanyFax"); + } + } + else + { // Remove old data when phones not available + deleteSetting(hContact, "Phone"); + deleteSetting(hContact, "CompanyPhone"); + deleteSetting(hContact, "Cellular"); + deleteSetting(hContact, "Fax"); + deleteSetting(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: + setSettingByte(hContact, "Gender", 'F'); + break; + case 2: + setSettingByte(hContact, "Gender", 'M'); + break; + default: + deleteSetting(hContact, "Gender"); + } + + writeDbInfoSettingTLVString(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) + { + setSettingByte(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) + setSettingWord(hContact, "Age", nAge); + else + deleteSetting(hContact, "Age"); + } + else // we do not need to calculate age for owner + deleteSetting(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 + setSettingDword(hContact, DBSETTING_METAINFO_SAVED, time(NULL)); + } + + if (wReplySubType == META_DIRECTORY_RESPONSE) + if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER) + BroadcastAck(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::parseDirectorySearchData(oscar_tlv_chain *cDetails, DWORD dwCookie, cookie_directory_data *pCookieData, WORD wReplySubType) +{ + ICQSEARCHRESULT isr = {0}; + char *szUid = cDetails->getString(0x32, 1); // User ID + +#ifdef _DEBUG + NetLog_Server("Directory Search: Found user %s", szUid); +#endif + isr.hdr.cbSize = sizeof(ICQSEARCHRESULT); + isr.hdr.flags = PSR_TCHAR; + isr.hdr.id = ansi_to_tchar(szUid); + + if (IsStringUIN(szUid)) + isr.uin = atoi(szUid); + else + isr.uin = 0; + + SAFE_FREE(&szUid); + + oscar_tlv *pTLV = cDetails->getTLV(0x50, 1); + char *szData = NULL; + + if (pTLV && pTLV->wLen > 0) + szData = cDetails->getString(0x50, 1); // Verified e-mail + else + szData = cDetails->getString(0x55, 1); // Pending e-mail + if (strlennull(szData)) + isr.hdr.email = ansi_to_tchar(szData); + SAFE_FREE(&szData); + + szData = cDetails->getString(0x64, 1); // First Name + if (strlennull(szData)) + isr.hdr.firstName = utf8_to_tchar(szData); + SAFE_FREE(&szData); + + szData = cDetails->getString(0x6E, 1); // Last Name + if (strlennull(szData)) + isr.hdr.lastName = utf8_to_tchar(szData); + SAFE_FREE(&szData); + + szData = cDetails->getString(0x78, 1); // Nick + if (strlennull(szData)) + isr.hdr.nick = utf8_to_tchar(szData); + SAFE_FREE(&szData); + + switch (cDetails->getNumber(0x82, 1)) // Gender + { + case 1: + isr.gender = 'F'; + break; + case 2: + isr.gender = 'M'; + break; + } + + pTLV = cDetails->getTLV(0x96, 1); + if (pTLV && pTLV->wLen >= 4) + { + BYTE *buf = pTLV->pData; + oscar_tlv_chain *chain = readIntoTLVChain(&buf, pTLV->wLen, 0); + if (chain) + isr.country = chain->getDWord(0x8C, 1); // Home Country + disposeChain(&chain); + } + + isr.auth = !cDetails->getByte(0x19A, 1); // Require Authorization + isr.maritalStatus = cDetails->getNumber(0x12C, 1); // Marital Status + + // calculate Age if Birthdate is available + isr.age = calcAgeFromBirthDate(cDetails->getDouble(0x1A4, 1)); + + // Finally, broadcast the result + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)dwCookie, (LPARAM)&isr); + + // Release memory + SAFE_FREE(&isr.hdr.id); + SAFE_FREE(&isr.hdr.nick); + SAFE_FREE(&isr.hdr.firstName); + SAFE_FREE(&isr.hdr.lastName); + SAFE_FREE(&isr.hdr.email); + + // Search is over, broadcast final ack + if (wReplySubType == META_DIRECTORY_RESPONSE) + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0); +} + + +void CIcqProto::handleDirectoryUpdateResponse(BYTE *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype) +{ + WORD wBytesRemaining = 0; + snac_header requestSnac = {0}; + BYTE requestResult; + +#ifdef _DEBUG + NetLog_Server("Received directory update response"); +#endif + if (wPacketLen >= 2) + unpackLEWord(&databuf, &wBytesRemaining); + wPacketLen -= 2; + _ASSERTE(wPacketLen == wBytesRemaining); + + if (!unpackSnacHeader(&requestSnac, &databuf, &wPacketLen) || !requestSnac.bValid) + { + NetLog_Server("Error: Failed to parse directory response"); + return; + } + + cookie_directory_data *pCookieData; + HANDLE hContact; + // check request cookie + if (!FindCookie(wCookie, &hContact, (void**)&pCookieData) || !pCookieData) + { + NetLog_Server("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 ?? + + if (wPacketLen >= 3) + unpackByte(&databuf, &requestResult); + else + { + NetLog_Server("Error: Malformed directory response"); + ReleaseCookie(wCookie); + return; + } + if (requestResult != 1 && requestResult != 4) + { + NetLog_Server("Error: Directory request failed, status %u", requestResult); + + if (pCookieData->bRequestType == DIRECTORYREQUEST_UPDATEOWNER) + BroadcastAck(NULL, ACKTYPE_SETINFO, ACKRESULT_FAILED, (HANDLE)wCookie, 0); + + ReleaseCookie(wCookie); + return; + } + WORD wLen; + + unpackWord(&databuf, &wLen); + wPacketLen -= 3; + if (wLen) + NetLog_Server("Warning: Data in error message present!"); + + if (pCookieData->bRequestType == DIRECTORYREQUEST_UPDATEOWNER) + BroadcastAck(NULL, ACKTYPE_SETINFO, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); + if (wPacketLen == 0x18) + { + DWORD64 qwMetaTime; + BYTE pbEmptyMetaToken[0x10] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + + unpackQWord(&databuf, &qwMetaTime); + setSettingBlob(NULL, DBSETTING_METAINFO_TIME, (BYTE*)&qwMetaTime, 8); + + if (memcmp(databuf, pbEmptyMetaToken, 0x10)) + setSettingBlob(NULL, DBSETTING_METAINFO_TOKEN, databuf, 0x10); + } + ReleaseCookie(wCookie); +} -- cgit v1.2.3