Exemple #1
0
void CIcqProto::ReleaseLookupCookie(DWORD dwCookie, cookie_search *pCookie)
{
    FreeCookie(dwCookie);
    SAFE_FREE(&pCookie->szObject);

    if (pCookie->dwMainId && !pCookie->dwStatus) // we need to wait for main search
        pCookie->dwStatus = 1;
    else { // finish everything
        if (pCookie->dwMainId)
            ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)pCookie->dwMainId, 0);
        else // we are single
            ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0);

        SAFE_FREE((void**)&pCookie);
    }
}
void CIcqProto::icq_CancelFileTransfer(MCONTACT hContact, filetransfer* ft)
{
	DWORD dwCookie;

	if (FindCookieByData(ft, &dwCookie, NULL))
		FreeCookie(dwCookie);      /* this bit stops a send that's waiting for acceptance */

	if (IsValidFileTransfer(ft)) { // Transfer still out there, end it
		NetLib_CloseConnection(&ft->hConnection, FALSE);

		ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0);

		if (!FindFileTransferDC(ft)) { // Release orphan structure only
			SafeReleaseFileTransfer((void**)&ft);
		}
	}
}
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)
			ProtoBroadcastAck(NULL, ACKTYPE_SETINFO, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0);
		else
			ProtoBroadcastAck(NULL, ACKTYPE_SETINFO, ACKRESULT_FAILED, (HANDLE)wCookie, 0);

		FreeCookie(wCookie);
		break;

	default:
		debugLogA("Warning: Ignored 15/03 user info update ack type x%x", wReplySubtype);
		break;
	}
}
void CIcqProto::ReleaseSearchCookie(DWORD dwCookie, cookie_search *pCookie)
{
	if (pCookie)
	{
		FreeCookie(dwCookie);
		if (pCookie->dwMainId)
		{
			if (pCookie->dwStatus)
			{
				SAFE_FREE((void**)&pCookie);
				ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0);
			}
			else
				pCookie->dwStatus = 1;
		}
		else
		{
			SAFE_FREE((void**)&pCookie);
			ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0);
		}
	}
	else
		ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0);
}
void CIcqProto::handleFileAck(PBYTE buf, WORD wLen, DWORD dwUin, DWORD dwCookie, WORD wStatus, char* pszText)
{
	char* pszFileName = NULL;
	DWORD dwFileSize;
	HANDLE hCookieContact;
	WORD wPort;
	WORD wFilenameLength;
	filetransfer* ft;


	// Find the filetransfer that belongs to this response
	if (!FindCookie(dwCookie, &hCookieContact, (void**)&ft))
	{
		NetLog_Direct("Error: Received unexpected file transfer request response");
		return;
	}

	FreeCookie(dwCookie);

	if (hCookieContact != HContactFromUIN(dwUin, NULL))
	{
		NetLog_Direct("Error: UINs do not match in file transfer request response");
		return;
	}

	// If status != 0, a request has been denied
	if (wStatus != 0)
	{
		NetLog_Direct("File transfer denied by %u.", dwUin);
		ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_DENIED, (HANDLE)ft, 0);

		FreeCookie(dwCookie);

		return;
	}

	if (wLen < 6) 
	{ // sanity check
		NetLog_Direct("Ignoring malformed file transfer request response");
		return;
	}

	// Port to connect to
	unpackWord(&buf, &wPort);
	ft->dwRemotePort = wPort;
	wLen -= 2;

	// Unknown
	buf += 2;
	wLen -= 2;

	// Filename
	unpackLEWord(&buf, &wFilenameLength);
	if (wFilenameLength > 0)
	{
		if (wFilenameLength > wLen - 2)
			wFilenameLength = wLen - 2;
		pszFileName = (char*)_alloca(wFilenameLength+1);
		unpackString(&buf, pszFileName, wFilenameLength);
		pszFileName[wFilenameLength] = '\0';
	}
	wLen = wLen - 2 - wFilenameLength;

	if (wLen >= 4)
	{ // Total filesize
		unpackLEDWord(&buf, &dwFileSize);
		wLen -= 4;
	}
	else
		dwFileSize = 0;

	NetLog_Direct("File transfer ack from %u, port %u, name %s, size %u", dwUin, ft->dwRemotePort, pszFileName, dwFileSize);

	ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTING, (HANDLE)ft, 0);

	OpenDirectConnection(ft->hContact, DIRECTCONN_FILE, ft);
}
Exemple #6
0
void CIcqProto::handleServiceFam(BYTE *pBuffer, size_t wBufferLength, snac_header *pSnacHeader, serverthread_info *info)
{
	icq_packet packet;

	switch (pSnacHeader->wSubtype) {

	case ICQ_SERVER_READY:
		debugLogA("Server is ready and is requesting my Family versions");
		debugLogA("Sending my Families");

		// This packet is a response to SRV_FAMILIES SNAC(1,3).
		// This tells the server which SNAC families and their corresponding
		// versions which the client understands. This also seems to identify
		// the client as an ICQ vice AIM client to the server.
		// Miranda mimics the behaviour of ICQ 6
		serverPacketInit(&packet, 54);
		packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_FAMILIES);
		packDWord(&packet, 0x00220001);
		packDWord(&packet, 0x00010004);
		packDWord(&packet, 0x00130004);
		packDWord(&packet, 0x00020001);
		packDWord(&packet, 0x00030001);
		packDWord(&packet, 0x00150001);
		packDWord(&packet, 0x00040001);
		packDWord(&packet, 0x00060001);
		packDWord(&packet, 0x00090001);
		packDWord(&packet, 0x000a0001);
		packDWord(&packet, 0x000b0001);
		sendServPacket(&packet);
		break;

	case ICQ_SERVER_FAMILIES2:
		/* This is a reply to CLI_FAMILIES and it tells the client which families and their versions that this server understands.
		* We send a rate request packet */
		debugLogA("Server told me his Family versions");
		debugLogA("Requesting Rate Information");

		serverPacketInit(&packet, 10);
		packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_REQ_RATE_INFO);
		sendServPacket(&packet);
		break;

	case ICQ_SERVER_RATE_INFO:
		debugLogA("Server sent Rate Info");

		/* init rates management */
		m_rates = new rates(this, pBuffer, wBufferLength);

		/* ack rate levels */
		debugLogA("Sending Rate Info Ack");

		m_rates->initAckPacket(&packet);
		sendServPacket(&packet);

		/* CLI_REQINFO - This command requests from the server certain information about the client that is stored on the server. */
		debugLogA("Sending CLI_REQINFO");

		serverPacketInit(&packet, 10);
		packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_REQINFO);
		sendServPacket(&packet);

		if (m_bSsiEnabled) {
			cookie_servlist_action* ack;
			DWORD dwCookie;

			DWORD dwLastUpdate = getDword("SrvLastUpdate", 0);
			WORD wRecordCount = getWord("SrvRecordCount", 0);

			// CLI_REQLISTS - we want to use SSI
			debugLogA("Requesting roster rights");

			serverPacketInit(&packet, 16);
			packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_REQLISTS);
			packTLVWord(&packet, 0x0B, 0x000F); // mimic ICQ 6
			sendServPacket(&packet);

			if (!wRecordCount) { // CLI_REQROSTER
				// we do not have any data - request full list
				debugLogA("Requesting full roster");

				serverPacketInit(&packet, 10);
				ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
				if (ack) { // we try to use standalone cookie if available
					ack->dwAction = SSA_CHECK_ROSTER; // loading list
					dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_CLI_REQUEST, 0, ack);
				}
				else // if not use that old fake
					dwCookie = ICQ_LISTS_CLI_REQUEST << 0x10;

				packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_REQUEST, 0, dwCookie);
				sendServPacket(&packet);
			}
			else { // CLI_CHECKROSTER
				debugLogA("Requesting roster check");

				serverPacketInit(&packet, 16);
				ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
				if (ack)  { // TODO: rewrite - use get list service for empty list
					// we try to use standalone cookie if available
					ack->dwAction = SSA_CHECK_ROSTER; // loading list
					dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_CLI_CHECK, 0, ack);
				}
				else // if not use that old fake
					dwCookie = ICQ_LISTS_CLI_CHECK << 0x10;

				packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_CHECK, 0, dwCookie);
				// check if it was not changed elsewhere (force reload, set that setting to zero)
				if (IsServerGroupsDefined()) {
					packDWord(&packet, dwLastUpdate);  // last saved time
					packWord(&packet, wRecordCount);   // number of records saved
				}
				else { // we need to get groups info into DB, force receive list
					packDWord(&packet, 0);  // last saved time
					packWord(&packet, 0);   // number of records saved
				}
				sendServPacket(&packet);
			}
		}

		// CLI_REQLOCATION
		debugLogA("Requesting Location rights");

		serverPacketInit(&packet, 10);
		packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_CLI_REQ_RIGHTS);
		sendServPacket(&packet);

		// CLI_REQBUDDY
		debugLogA("Requesting Client-side contactlist rights");

		serverPacketInit(&packet, 16);
		packFNACHeader(&packet, ICQ_BUDDY_FAMILY, ICQ_USER_CLI_REQBUDDY);
		// Query flags: 1 = Enable Avatars
		//              2 = Enable offline status message notification
		//              4 = Enable Avatars for offline contacts
		//              8 = Use reject for not authorized contacts
		packTLVWord(&packet, 0x05, 0x0007);
		sendServPacket(&packet);

		// CLI_REQICBM
		debugLogA("Sending CLI_REQICBM");

		serverPacketInit(&packet, 10);
		packFNACHeader(&packet, ICQ_MSG_FAMILY, ICQ_MSG_CLI_REQICBM);
		sendServPacket(&packet);

		// CLI_REQBOS
		debugLogA("Sending CLI_REQBOS");

		serverPacketInit(&packet, 10);
		packFNACHeader(&packet, ICQ_BOS_FAMILY, ICQ_PRIVACY_REQ_RIGHTS);
		sendServPacket(&packet);
		break;

	case ICQ_SERVER_PAUSE:
		debugLogA("Server is going down in a few seconds... (Flags: %u)", pSnacHeader->wFlags);
		// This is the list of groups that we want to have on the next server
		serverPacketInit(&packet, 30);
		packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_PAUSE_ACK);
		packWord(&packet, ICQ_SERVICE_FAMILY);
		packWord(&packet, ICQ_LISTS_FAMILY);
		packWord(&packet, ICQ_LOCATION_FAMILY);
		packWord(&packet, ICQ_BUDDY_FAMILY);
		packWord(&packet, ICQ_EXTENSIONS_FAMILY);
		packWord(&packet, ICQ_MSG_FAMILY);
		packWord(&packet, 0x06);
		packWord(&packet, ICQ_BOS_FAMILY);
		packWord(&packet, ICQ_LOOKUP_FAMILY);
		packWord(&packet, ICQ_STATS_FAMILY);
		sendServPacket(&packet);

		debugLogA("Sent server pause ack");
		break;

	case ICQ_SERVER_MIGRATIONREQ:
		debugLogA("Server migration requested (Flags: %u)", pSnacHeader->wFlags);

		pBuffer += 2; // Unknown, seen: 0
		wBufferLength -= 2;
		{
			oscar_tlv_chain *chain = readIntoTLVChain(&pBuffer, wBufferLength, 0);

			if (info->cookieDataLen > 0)
				SAFE_FREE((void**)&info->cookieData);

			info->newServer = chain->getString(0x05, 1);
			info->newServerSSL = chain->getNumber(0x8E, 1);
			info->cookieData = (BYTE*)chain->getString(0x06, 1);
			info->cookieDataLen = chain->getLength(0x06, 1);

			disposeChain(&chain);

			if (!info->newServer || !info->cookieData) {
				icq_LogMessage(LOG_FATAL, LPGEN("A server migration has failed because the server returned invalid data. You must reconnect manually."));
				SAFE_FREE(&info->newServer);
				SAFE_FREE((void**)&info->cookieData);
				info->cookieDataLen = 0;
				info->isNewServerReady = false;
				return;
			}

			debugLogA("Migration has started. New server will be %s", info->newServer);

			m_iDesiredStatus = m_iStatus;
			SetCurrentStatus(ID_STATUS_CONNECTING); // revert to connecting state

			info->isNewServerReady = info->isMigrating = true;
		}
		break;

	case ICQ_SERVER_NAME_INFO: // This is the reply to CLI_REQINFO
		debugLogA("Received self info");
		{
			BYTE bUinLen;
			unpackByte(&pBuffer, &bUinLen);
			pBuffer += bUinLen;
			pBuffer += 4;      /* warning level & user class */
			wBufferLength -= 5 + bUinLen;

			// This is during the login sequence
			if (pSnacHeader->dwRef == ICQ_CLIENT_REQINFO << 0x10) {
				// TLV(x01) User type?
				// TLV(x0C) Empty CLI2CLI Direct connection info
				// TLV(x0A) External IP
				// TLV(x0F) Number of seconds that user has been online
				// TLV(x03) The online since time.
				// TLV(x0A) External IP again
				// TLV(x22) Unknown
				// TLV(x1E) Unknown: empty.
				// TLV(x05) Member of ICQ since.
				// TLV(x14) Unknown
				oscar_tlv_chain *chain = readIntoTLVChain(&pBuffer, wBufferLength, 0);

				// Save external IP
				DWORD dwValue = chain->getDWord(0x0A, 1);
				setDword("IP", dwValue);

				// Save member since timestamp
				dwValue = chain->getDWord(0x05, 1);
				if (dwValue) setDword("MemberTS", dwValue);

				dwValue = chain->getDWord(0x03, 1);
				setDword("LogonTS", dwValue ? dwValue : time(NULL));

				disposeChain(&chain);

				// If we are in SSI mode, this is sent after the list is acked instead
				// to make sure that we don't set status before seing the visibility code
				if (!m_bSsiEnabled || info->isMigrating)
					handleServUINSettings(wListenPort, info);
			}
		}
		break;

	case ICQ_SERVER_RATE_CHANGE:
		if (wBufferLength >= 2) {
			WORD wStatus, wClass;
			DWORD dwLevel;
			// We now have global rate management, although controlled are only some
			// areas. This should not arrive in most cases. If it does, update our
			// local rate levels & issue broadcast.
			unpackWord(&pBuffer, &wStatus);
			unpackWord(&pBuffer, &wClass);
			pBuffer += 20;
			unpackDWord(&pBuffer, &dwLevel);
			{
				mir_cslock l(m_ratesMutex);
				m_rates->updateLevel(wClass, dwLevel);
			}

			if (wStatus == 2 || wStatus == 3) {
				// this is only the simplest solution, needs rate management to every section
				ProtoBroadcastAck(NULL, ICQACKTYPE_RATEWARNING, ACKRESULT_STATUS, (HANDLE)wClass, wStatus);
				if (wStatus == 2)
					debugLogA("Rates #%u: Alert", wClass);
				else
					debugLogA("Rates #%u: Limit", wClass);
			}
			else if (wStatus == 4) {
				ProtoBroadcastAck(NULL, ICQACKTYPE_RATEWARNING, ACKRESULT_STATUS, (HANDLE)wClass, wStatus);
				debugLogA("Rates #%u: Clear", wClass);
			}
		}

		break;

	case ICQ_SERVER_REDIRECT_SERVICE: // reply to family request, got new connection point
	{
		oscar_tlv_chain *pChain = NULL;
		cookie_family_request *pCookieData;

		if (!(pChain = readIntoTLVChain(&pBuffer, wBufferLength, 0))) {
			debugLogA("Received Broken Redirect Service SNAC(1,5).");
			break;
		}

		// pick request data
		WORD wFamily = pChain->getWord(0x0D, 1);
		if ((!FindCookie(pSnacHeader->dwRef, NULL, (void**)&pCookieData)) || (pCookieData->wFamily != wFamily)) {
			disposeChain(&pChain);
			debugLogA("Received unexpected SNAC(1,5), skipping.");
			break;
		}

		FreeCookie(pSnacHeader->dwRef);

		// new family entry point received
		char *pServer = pChain->getString(0x05, 1);
		BYTE bServerSSL = pChain->getNumber(0x8E, 1);
		char *pCookie = pChain->getString(0x06, 1);
		WORD wCookieLen = pChain->getLength(0x06, 1);

		if (!pServer || !pCookie) {
			debugLogA("Server returned invalid data, family unavailable.");

			SAFE_FREE(&pServer);
			SAFE_FREE(&pCookie);
			SAFE_FREE((void**)&pCookieData);
			disposeChain(&pChain);
			break;
		}

		// Get new family server ip and port
		WORD wPort = info->wServerPort; // get default port
		parseServerAddress(pServer, &wPort);

		// establish connection
		NETLIBOPENCONNECTION nloc = { 0 };
		if (m_bGatewayMode)
			nloc.flags |= NLOCF_HTTPGATEWAY;
		nloc.szHost = pServer;
		nloc.wPort = wPort;

		HANDLE hConnection = NetLib_OpenConnection(m_hNetlibUser, wFamily == ICQ_AVATAR_FAMILY ? "Avatar " : NULL, &nloc);

		if (hConnection == NULL)
			debugLogA("Unable to connect to ICQ new family server.");
		// we want the handler to be called even if the connecting failed
		else if (bServerSSL) { /* Start SSL session if requested */
			debugLogA("(%p) Starting SSL negotiation", CallService(MS_NETLIB_GETSOCKET, (WPARAM)hConnection, 0));

			if (!CallService(MS_NETLIB_STARTSSL, (WPARAM)hConnection, 0)) {
				debugLogA("Unable to connect to ICQ new family server, SSL could not be negotiated");
				NetLib_CloseConnection(&hConnection, FALSE);
			}
		}

		(this->*pCookieData->familyHandler)(hConnection, pCookie, wCookieLen);

		// Free allocated memory
		// NOTE: "cookie" will get freed when we have connected to the avatar server.
		disposeChain(&pChain);
		SAFE_FREE(&pServer);
		SAFE_FREE((void**)&pCookieData);
		break;
	}

	case ICQ_SERVER_EXTSTATUS: // our session data
		debugLogA("Received owner session data.");

		while (wBufferLength > 4) { // loop thru all items
			WORD itemType = pBuffer[0] * 0x10 | pBuffer[1];
			BYTE itemFlags = pBuffer[2];
			size_t itemLen = pBuffer[3];

			if (itemType == AVATAR_HASH_PHOTO) { /// TODO: handle photo item
				// skip photo item
				debugLogA("Photo item recognized");
			}
			else if ((itemType == AVATAR_HASH_STATIC || itemType == AVATAR_HASH_FLASH) && (itemLen >= 0x10)) {
				debugLogA("Avatar item recognized");

				if (m_bAvatarsEnabled && !info->bMyAvatarInited) { // signal the server after login
					// this refreshes avatar state - it used to work automatically, but now it does not
					if (getByte("ForceOurAvatar", 0)) { // keep our avatar
						TCHAR *file = GetOwnAvatarFileName();
						SetMyAvatar(0, (LPARAM)file);
						SAFE_FREE(&file);
					}
					else { // only change avatar hash to the same one
						BYTE hash[0x14];
						memcpy(hash, pBuffer, 0x14);
						hash[2] = 1; // update image status
						updateServAvatarHash(hash, 0x14);
					}
					info->bMyAvatarInited = true;
					break;
				}
				// process owner avatar hash changed notification
				handleAvatarOwnerHash(itemFlags, pBuffer, itemLen + 4);
			}
			else if (itemType == 0x02) {
				debugLogA("Status message item recognized");
			}
			else if (itemType == 0x0E) {
				debugLogA("Status mood item recognized");
			}

			// move to next item
			if (wBufferLength >= itemLen + 4) {
				wBufferLength -= itemLen + 4;
				pBuffer += itemLen + 4;
			}
			else {
				pBuffer += wBufferLength;
				wBufferLength = 0;
			}
		}
		break;

	case ICQ_ERROR: // Something went wrong, probably the request for avatar family failed
	{
		WORD wError;
		if (wBufferLength >= 2)
			unpackWord(&pBuffer, &wError);
		else
			wError = 0;

		LogFamilyError(ICQ_SERVICE_FAMILY, wError);
	}
		break;

		// Stuff we don't care about
	case ICQ_SERVER_MOTD:
		debugLogA("Server message of the day");
		break;

	default:
		debugLogA("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_SERVICE_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef);
		break;
	}
}
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;

				ProtoBroadcastAck(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;

						ProtoBroadcastAck(NULL, ICQACKTYPE_SMS, ACKRESULT_SENTREQUEST, (HANDLE)wCookie, (LPARAM)pszInfo);
						FreeCookie(wCookie);

						// Parsing success
						break; 
					}
				}
			}

			// Parsing failure
			debugLogA("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
				debugLogA("Error: Directory request failed, code %u", bResultCode);
			break;

		case META_DIRECTORY_UPDATE_ACK:
			if (bResultCode == 0x0A)
				handleDirectoryUpdateResponse(databuf, wPacketLen, wCookie, wReplySubtype);
			else
				debugLogA("Error: Directory request failed, code %u", bResultCode);
			break;

		case META_BASIC_USERINFO:
		case META_WORK_USERINFO:
		case META_MORE_USERINFO:
		case META_NOTES_USERINFO:
		case META_EMAIL_USERINFO:
		case META_INTERESTS_USERINFO:
		case META_AFFILATIONS_USERINFO:
		case META_SHORT_USERINFO:
		case META_HPAGECAT_USERINFO:
			debugLogA("Warning: Ignored 15/03 (legacy user info) replysubtype x%x", wReplySubtype);
			break;

		default:
			debugLogA("Warning: Ignored 15/03 replysubtype x%x", wReplySubtype);
			//      _ASSERTE(0);
			break;
		}

		// Success
		return;
	}

	// Failure
	debugLogA("Warning: Broken 15/03 ExtensionMetaResponse");
}