/*
Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.

Copyright (c) 2012-2013 Miranda NG Team
Copyright (c) 2007-2012 Boris Krasnovskiy.

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, see <http://www.gnu.org/licenses/>.
*/

#include "msn_global.h"
#include "msn_proto.h"

static const char abReqHdr[] =
	"SOAPAction: http://www.msn.com/webservices/AddressBook/%s\r\n";


ezxml_t CMsnProto::abSoapHdr(const char* service, const char* scenario, ezxml_t& tbdy, char*& httphdr)
{
	ezxml_t xmlp = ezxml_new("soap:Envelope");
	ezxml_set_attr(xmlp, "xmlns:soap", "http://schemas.xmlsoap.org/soap/envelope/");
	ezxml_set_attr(xmlp, "xmlns:xsi",  "http://www.w3.org/2001/XMLSchema-instance");
	ezxml_set_attr(xmlp, "xmlns:xsd",  "http://www.w3.org/2001/XMLSchema");
	ezxml_set_attr(xmlp, "xmlns:soapenc", "http://schemas.xmlsoap.org/soap/encoding/");

	ezxml_t hdr = ezxml_add_child(xmlp, "soap:Header", 0);
	ezxml_t apphdr = ezxml_add_child(hdr, "ABApplicationHeader", 0);
	ezxml_set_attr(apphdr, "xmlns", "http://www.msn.com/webservices/AddressBook");
	ezxml_t node = ezxml_add_child(apphdr, "ApplicationId", 0);
	ezxml_set_txt(node, msnAppID);
	node = ezxml_add_child(apphdr, "IsMigration", 0);
	ezxml_set_txt(node, abchMigrated ? "false" : "true");
	node = ezxml_add_child(apphdr, "PartnerScenario", 0);
	ezxml_set_txt(node, scenario);

	char *cacheKey = strstr(service, "Member") ? sharingCacheKey : abCacheKey;
	if (cacheKey)
	{
		node = ezxml_add_child(apphdr, "CacheKey", 0);
		ezxml_set_txt(node, cacheKey);
	}

	ezxml_t authhdr = ezxml_add_child(hdr, "ABAuthHeader", 0);
	ezxml_set_attr(authhdr, "xmlns", "http://www.msn.com/webservices/AddressBook");
	node = ezxml_add_child(authhdr, "ManagedGroupRequest", 0);
	ezxml_set_txt(node, "false");
	node = ezxml_add_child(authhdr, "TicketToken", 0);
	if (authContactToken) ezxml_set_txt(node, authContactToken);

	ezxml_t bdy = ezxml_add_child(xmlp, "soap:Body", 0);

	tbdy = ezxml_add_child(bdy, service, 0);
	ezxml_set_attr(tbdy, "xmlns", "http://www.msn.com/webservices/AddressBook");

	if (strstr(service, "Member") == NULL && strcmp(service, "ABAdd") != 0 && strcmp(service, "ABFindContactsPaged"))
	{
		ezxml_t node = ezxml_add_child(tbdy, "abId", 0);
		ezxml_set_txt(node, "00000000-0000-0000-0000-000000000000");
	}

	size_t hdrsz = strlen(service) + sizeof(abReqHdr) + 20;
	httphdr = (char*)mir_alloc(hdrsz);

	mir_snprintf(httphdr, hdrsz, abReqHdr, service);

	return xmlp;
}


ezxml_t CMsnProto::getSoapResponse(ezxml_t bdy, const char* service)
{
	char resp1[40], resp2[40];
	mir_snprintf(resp1, sizeof(resp1), "%sResponse", service);
	mir_snprintf(resp2, sizeof(resp2), "%sResult", service);

	ezxml_t res = ezxml_get(bdy, "soap:Body", 0, resp1, 0, resp2, -1);
	if (res == NULL)
		res = ezxml_get(bdy, "s:Body", 0, resp1, 0, resp2, -1);

	return res;
}

ezxml_t CMsnProto::getSoapFault(ezxml_t bdy, bool err)
{
	ezxml_t flt = ezxml_get(bdy, "soap:Body", 0, "soap:Fault", -1);
	return err ? ezxml_get(flt, "detail", 0, "errorcode", -1) : flt;
}

void CMsnProto::UpdateABHost(const char* service, const char* url)
{
	char hostname[128];
	mir_snprintf(hostname, sizeof(hostname), "ABHost-%s", service);

	if (url)
		setString(NULL, hostname, url);
	else
		deleteSetting(NULL, hostname);
}

void CMsnProto::UpdateABCacheKey(ezxml_t bdy,  bool isSharing)
{
	ezxml_t hdr = ezxml_get(bdy, "soap:Header", 0, "ServiceHeader", -1);
	bool changed = strcmp(ezxml_txt(ezxml_child(hdr, "CacheKeyChanged")), "true") == 0;
	if (changed)
	{
		replaceStr(isSharing ? sharingCacheKey : abCacheKey, ezxml_txt(ezxml_child(hdr, "CacheKey")));
	}
}

char* CMsnProto::GetABHost(const char* service, bool isSharing)
{
	char hostname[128];
	mir_snprintf(hostname, sizeof(hostname), "ABHost-%s", service);

	char* host = (char*)mir_alloc(256);
	if (getStaticString(NULL, hostname, host, 256))
	{
		mir_snprintf(host, 256, "https://byrdr.omega.contacts.msn.com/abservice/%s.asmx",
			isSharing ? "SharingService" : "abservice");
	}

	return host;
}

/*
ezxml_t CMsnProto::PerformSoapReq(const char *service, bool isSharing, char *szData, const char* hdrs, unsigned& status)
{
	unsigned status = 0;
	char *abUrl = NULL, *tResult = NULL;

	for (int k = 4; --k;)
	{
		mir_free(abUrl);
		abUrl = GetABHost(service, true);
		tResult = getSslResult(&abUrl, szData, hdrs, status);
		if (tResult == NULL) UpdateABHost(service, NULL);
		else break;
	}

	mir_free(reqHdr);
	free(szData);

	if (tResult != NULL)
	{
		UpdateABHost(service, abUrl);
		ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
		if (!xmlm || !ezxml_child(xmlm, "soap:Body"))
		{
			mir_free(tResult);
			ezxml_free(xmlm);
			UpdateABHost("service", NULL);
			PerformSoapReq(service, isSharing, szData, hdrs, status);
		}
		else if (status == 500)
		{
			const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
			if (!szErr[0])
			{
				mir_free(tResult);
				ezxml_free(xmlm);
				UpdateABHost("service", NULL);
				PerformSoapReq(service, isSharing, szData, hdrs, status);
			}
		}
	}
	mir_free(abUrl);
}
*/


bool CMsnProto::MSN_ABAdd(bool allowRecurse)
{
	char* reqHdr;
	ezxml_t tbdy, node;
	ezxml_t xmlp = abSoapHdr("ABAdd", "Timer", tbdy, reqHdr);

	ezxml_t abinf = ezxml_add_child(tbdy, "abInfo", 0);
	ezxml_add_child(abinf, "name", 0);
	node = ezxml_add_child(abinf, "ownerPuid", 0);
	ezxml_set_txt(node, "0");
	node = ezxml_add_child(abinf, "ownerEmail", 0);
	ezxml_set_txt(node, MyOptions.szEmail);
	node = ezxml_add_child(abinf, "fDefault", 0);
	ezxml_set_txt(node, "true");

	char* szData = ezxml_toxml(xmlp, true);
	ezxml_free(xmlp);

	unsigned status = 0;
	char *abUrl = NULL, *tResult = NULL;

	for (int k = 4; --k;)
	{
		mir_free(abUrl);
		abUrl = GetABHost("ABAdd", false);
		tResult = getSslResult(&abUrl, szData, reqHdr, status);
		if (tResult == NULL) UpdateABHost("ABAdd", NULL);
		else break;
	}

	mir_free(reqHdr);
	free(szData);

	if (tResult != NULL)
	{
		UpdateABHost("ABAdd", abUrl);

		ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
		if (status == 500)
		{
			const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
			if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
			{
				MSN_GetPassportAuth();
				status = MSN_ABAdd(false) ? 200 : 500;
			}
		}
		ezxml_free(xmlm);
	}

	mir_free(tResult);
	mir_free(abUrl);

	return status == 200;
}


bool CMsnProto::MSN_SharingFindMembership(bool deltas, bool allowRecurse)
{
	char* reqHdr;
	ezxml_t tbdy;
	ezxml_t xmlp = abSoapHdr("FindMembership", "Initial", tbdy, reqHdr);

	ezxml_t svcflt = ezxml_add_child(tbdy, "serviceFilter", 0);
	ezxml_t tps = ezxml_add_child(svcflt, "Types", 0);
	ezxml_t node = ezxml_add_child(tps, "ServiceType", 0);
	ezxml_set_txt(node, "Messenger");
/*
	node = ezxml_add_child(tps, "ServiceType", 0);
	ezxml_set_txt(node, "Invitation");
	node = ezxml_add_child(tps, "ServiceType", 0);
	ezxml_set_txt(node, "SocialNetwork");
	node = ezxml_add_child(tps, "ServiceType", 0);
	ezxml_set_txt(node, "Space");
	node = ezxml_add_child(tps, "ServiceType", 0);
	ezxml_set_txt(node, "Profile");
	node = ezxml_add_child(tps, "ServiceType", 0);
	ezxml_set_txt(node, "Folder");
	node = ezxml_add_child(tps, "ServiceType", 0);
	ezxml_set_txt(node, "OfficeLiveWebNotification");
*/
	const char *szLastChange = NULL;
	if (deltas)
	{
		DBVARIANT dbv;
		if (!getString("SharingLastChange", &dbv) && dbv.pszVal[0])
		{
			szLastChange = NEWSTR_ALLOCA(dbv.pszVal);
			db_free(&dbv);
		}
		deltas &= (szLastChange != NULL);
	}

	node = ezxml_add_child(tbdy, "View", 0);
	ezxml_set_txt(node, "Full");
	node = ezxml_add_child(tbdy, "deltasOnly", 0);
	ezxml_set_txt(node, deltas ? "true" : "false");
	node = ezxml_add_child(tbdy, "lastChange", 0);
	ezxml_set_txt(node, deltas ? szLastChange : "0001-01-01T00:00:00.0000000-08:00");

	char* szData = ezxml_toxml(xmlp, true);

	ezxml_free(xmlp);

	unsigned status = 0;
	char *abUrl = NULL, *tResult = NULL;

	for (int k = 4; --k;)
	{
		mir_free(abUrl);
		abUrl = GetABHost("FindMembership", true);
		tResult = getSslResult(&abUrl, szData, reqHdr, status);
		if (tResult == NULL) UpdateABHost("FindMembership", NULL);
		else break;
	}

	mir_free(reqHdr);
	free(szData);

	if (tResult != NULL)
	{
		ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
		if (status == 200)
		{
			UpdateABCacheKey(xmlm, true);
			ezxml_t body = getSoapResponse(xmlm, "FindMembership");
			ezxml_t svcs = ezxml_get(body, "Services", 0, "Service", -1);

			UpdateABHost("FindMembership", body ? abUrl : NULL);

			while (svcs != NULL)
			{
				const char* szType = ezxml_txt(ezxml_get(svcs, "Info", 0, "Handle", 0, "Type", -1));
				if (_stricmp(szType, "Messenger") == 0) break;
				svcs = ezxml_next(svcs);
			}

			const char* szLastChange = ezxml_txt(ezxml_child(svcs, "LastChange"));
			if (szLastChange[0]) setString("SharingLastChange", szLastChange);

			ezxml_t mems = ezxml_get(svcs, "Memberships", 0, "Membership", -1);

			while (mems != NULL)
			{
				const char* szRole = ezxml_txt(ezxml_child(mems, "MemberRole"));

				int lstId = 0;
				if (strcmp(szRole, "Allow") == 0)			lstId = LIST_AL;
				else if (strcmp(szRole, "Block") == 0)		lstId = LIST_BL;
				else if (strcmp(szRole, "Reverse") == 0)	lstId = LIST_RL;
				else if (strcmp(szRole, "Pending") == 0)	lstId = LIST_PL;

				ezxml_t memb = ezxml_get(mems, "Members", 0, "Member", -1);
				while (memb != NULL)
				{
					bool deleted = strcmp(ezxml_txt(ezxml_child(memb, "Deleted")), "true") == 0;
					const char* szType = ezxml_txt(ezxml_child(memb, "Type"));
					if (strcmp(szType, "Passport") == 0)
					{
						const char* szInvite = NULL;
						const char* szEmail = ezxml_txt(ezxml_child(memb, "PassportName"));
						const char* szNick = ezxml_txt(ezxml_child(memb, "DisplayName")); if (!szNick[0]) szNick = NULL;
						ezxml_t anot = ezxml_get(memb, "Annotations", 0, "Annotation", -1);
						while (anot != NULL)
						{
							if (strcmp(ezxml_txt(ezxml_child(anot, "Name")), "MSN.IM.InviteMessage") == 0)
							{
								szInvite = ezxml_txt(ezxml_child(anot, "Value"));
							}
							anot = ezxml_next(anot);
						}
						if (!deleted) Lists_Add(lstId, NETID_MSN, szEmail, NULL, szNick, szInvite); else Lists_Remove(lstId, szEmail);
					}
					else if (strcmp(szType, "Phone") == 0)
					{
						const char* szEmail = ezxml_txt(ezxml_child(memb, "PhoneNumber"));
						char email[128];
						mir_snprintf(email, sizeof(email), "tel:%s", szEmail);
						if (!deleted) Lists_Add(lstId, NETID_MOB, email); else Lists_Remove(lstId, szEmail);
					}
					else if (strcmp(szType, "Email") == 0)
					{
						const char* szInvite = NULL;
						const char* szEmail = ezxml_txt(ezxml_child(memb, "Email"));
						const char* szNick = ezxml_txt(ezxml_child(memb, "DisplayName")); if (!szNick[0]) szNick = NULL;
						int netId = strstr(szEmail, "@yahoo.com") ? NETID_YAHOO : NETID_LCS;
						ezxml_t anot = ezxml_get(memb, "Annotations", 0, "Annotation", -1);
						while (anot != NULL)
						{
							if (strcmp(ezxml_txt(ezxml_child(anot, "Name")), "MSN.IM.BuddyType") == 0)
							{
								netId = atol(ezxml_txt(ezxml_child(anot, "Value")));
							}
							else if (strcmp(ezxml_txt(ezxml_child(anot, "Name")), "MSN.IM.InviteMessage") == 0)
							{
								szInvite = ezxml_txt(ezxml_child(anot, "Value"));
							}
							anot = ezxml_next(anot);
						}

						if (!deleted) Lists_Add(lstId, netId, szEmail, NULL, szNick, szInvite);  else Lists_Remove(lstId, szEmail);
					}
					memb = ezxml_next(memb);
				}
				mems = ezxml_next(mems);
			}
		}
		else if (status == 500)
		{
			const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
			if (strcmp(szErr, "ABDoesNotExist") == 0)
			{
				MSN_ABAdd();
				status = 200;
			}
			else if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
			{
				MSN_GetPassportAuth();
				status = MSN_SharingFindMembership(deltas, false) ? 200 : 500;
			}
		}
		else
			UpdateABHost("FindMembership", NULL);

		ezxml_free(xmlm);
	}
	mir_free(tResult);
	mir_free(abUrl);

	return status == 200;
}

// AddMember, DeleteMember
bool CMsnProto::MSN_SharingAddDelMember(const char* szEmail, const int listId, const int netId, const char* szMethod, bool allowRecurse)
{
	const char* szRole;
	if (listId & LIST_AL) szRole = "Allow";
	else if (listId & LIST_BL) szRole = "Block";
	else if (listId & LIST_PL) szRole = "Pending";
	else if (listId & LIST_RL) szRole = "Reverse";
	else return false;

	char* reqHdr;
	ezxml_t tbdy;
	ezxml_t xmlp = abSoapHdr(szMethod, "BlockUnblock", tbdy, reqHdr);

	ezxml_t svchnd = ezxml_add_child(tbdy, "serviceHandle", 0);
	ezxml_t node = ezxml_add_child(svchnd, "Id", 0);
	ezxml_set_txt(node, "0");
	node = ezxml_add_child(svchnd, "Type", 0);
	ezxml_set_txt(node, "Messenger");
	node = ezxml_add_child(svchnd, "ForeignId", 0);
//	ezxml_set_txt(node, "");

	const char* szMemberName = "";
	const char* szTypeName = "";
	const char* szAccIdName = "";

	switch (netId)
	{
		case 1:
			szMemberName = "PassportMember";
			szTypeName = "Passport";
			szAccIdName = "PassportName";
			break;

		case 4:
			szMemberName = "PhoneMember";
			szTypeName = "Phone";
			szAccIdName = "PhoneNumber";
			szEmail = strchr(szEmail, ':') + 1;
			break;

		case 2:
		case 32:
			szMemberName = "EmailMember";
			szTypeName = "Email";
			szAccIdName = "Email";
			break;
	}

	ezxml_t memb = ezxml_add_child(tbdy, "memberships", 0);
	memb = ezxml_add_child(memb, "Membership", 0);
	node = ezxml_add_child(memb, "MemberRole", 0);
	ezxml_set_txt(node, szRole);
	memb = ezxml_add_child(memb, "Members", 0);
	memb = ezxml_add_child(memb, "Member", 0);
	ezxml_set_attr(memb, "xsi:type",  szMemberName);
	ezxml_set_attr(memb, "xmlns:xsi",  "http://www.w3.org/2001/XMLSchema-instance");
	node = ezxml_add_child(memb, "Type", 0);
	ezxml_set_txt(node, szTypeName);
	node = ezxml_add_child(memb, "State", 0);
	ezxml_set_txt(node, "Accepted");
	node = ezxml_add_child(memb, szAccIdName, 0);
	ezxml_set_txt(node, szEmail);

	char buf[64];
	if ((netId == NETID_LCS || netId == NETID_YAHOO) && strcmp(szMethod, "DeleteMember") != 0)
	{
		node = ezxml_add_child(memb, "Annotations", 0);
		ezxml_t anot = ezxml_add_child(node, "Annotation", 0);
		node = ezxml_add_child(anot, "Name", 0);
		ezxml_set_txt(node, "MSN.IM.BuddyType");
		node = ezxml_add_child(anot, "Value", 0);

		mir_snprintf(buf, sizeof(buf), "%02d:", netId);
		ezxml_set_txt(node, buf);
	}

	char* szData = ezxml_toxml(xmlp, true);

	ezxml_free(xmlp);

	unsigned status;
	char *abUrl = NULL, *tResult;

	for (int k = 4; --k;)
	{
		mir_free(abUrl);
		abUrl = GetABHost(szMethod, true);
		tResult = getSslResult(&abUrl, szData, reqHdr, status);
		if (tResult == NULL) UpdateABHost(szMethod, NULL);
		else break;
	}

	mir_free(reqHdr);
	free(szData);

	if (tResult != NULL)
	{
		UpdateABHost(szMethod, abUrl);
		ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
		UpdateABCacheKey(xmlm, true);
		if (status == 500)
		{
			const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
			if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
			{
				MSN_GetPassportAuth();
				status = MSN_SharingAddDelMember(szEmail, listId, netId, szMethod, false) ? 200 : 500;
			}
		}
		ezxml_free(xmlm);
	}

	mir_free(tResult);
	mir_free(abUrl);

	return status == 200;
}

bool CMsnProto::MSN_SharingMyProfile(bool allowRecurse)
{
	char* reqHdr;
	ezxml_t tbdy;
	ezxml_t xmlp = abSoapHdr("AddMember", "RoamingSeed", tbdy, reqHdr);

	ezxml_t svchnd = ezxml_add_child(tbdy, "serviceHandle", 0);
	ezxml_t node = ezxml_add_child(svchnd, "Id", 0);
	ezxml_set_txt(node, "0");
	node = ezxml_add_child(svchnd, "Type", 0);
	ezxml_set_txt(node, "Profile");
	node = ezxml_add_child(svchnd, "ForeignId", 0);
	ezxml_set_txt(node, "MyProfile");

	ezxml_t memb = ezxml_add_child(tbdy, "memberships", 0);
	memb = ezxml_add_child(memb, "Membership", 0);
	node = ezxml_add_child(memb, "MemberRole", 0);
	ezxml_set_txt(node, "ProfileExpression");
	memb = ezxml_add_child(memb, "Members", 0);
	memb = ezxml_add_child(memb, "Member", 0);
	ezxml_set_attr(memb, "xsi:type", "RoleMember");
	ezxml_set_attr(memb, "xmlns:xsi",  "http://www.w3.org/2001/XMLSchema-instance");
	node = ezxml_add_child(memb, "Type", 0);
	ezxml_set_txt(node, "Role");
	node = ezxml_add_child(memb, "State", 0);
	ezxml_set_txt(node, "Accepted");
	node = ezxml_add_child(memb, "Id", 0);
	ezxml_set_txt(node, "Allow");

	ezxml_t svcdef = ezxml_add_child(memb, "DefiningService", 0);
	node = ezxml_add_child(svcdef, "Id", 0);
	ezxml_set_txt(node, "0");
	node = ezxml_add_child(svcdef, "Type", 0);
	ezxml_set_txt(node, "Messenger");
	node = ezxml_add_child(svcdef, "ForeignId", 0);

	node = ezxml_add_child(memb, "MaxRoleRecursionDepth", 0);
	ezxml_set_txt(node, "0");

	node = ezxml_add_child(memb, "MaxDegreesSeparationDepth", 0);
	ezxml_set_txt(node, "0");

	char* szData = ezxml_toxml(xmlp, true);

	ezxml_free(xmlp);

	unsigned status = 0;
	char *abUrl = NULL, *tResult = NULL;

	for (int k = 4; --k;)
	{
		mir_free(abUrl);
		abUrl = GetABHost("AddMember", true);
		tResult = getSslResult(&abUrl, szData, reqHdr, status);
		if (tResult == NULL) UpdateABHost("AddMember", NULL);
		else break;
	}

	mir_free(reqHdr);
	free(szData);

	ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
	if (status == 500)
	{
		const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
		if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
		{
			MSN_GetPassportAuth();
			MSN_SharingMyProfile(false);
		}
	}
	ezxml_free(xmlm);

	mir_free(tResult);
	mir_free(abUrl);

	return status == 200;
}


void CMsnProto::SetAbParam(HANDLE hContact, const char *name, const char *par)
{
	if (*par) setStringUtf(hContact, name, (char*)par);
//	else deleteSetting(hContact, "FirstName");
}

//		"ABFindAll", "ABFindByContacts", "ABFindContactsPaged"
bool CMsnProto::MSN_ABFind(const char* szMethod, const char* szGuid, bool deltas, bool allowRecurse)
{
	char* reqHdr;
	ezxml_t tbdy;
	ezxml_t xmlp = abSoapHdr(szMethod, "Initial", tbdy, reqHdr);


	const char *szLastChange = NULL;
	if (deltas)
	{
		DBVARIANT dbv;
		if (!getString("ABFullLastChange", &dbv) && dbv.pszVal[0])
		{
			szLastChange = NEWSTR_ALLOCA(dbv.pszVal);
			db_free(&dbv);
		}
		deltas &= (szLastChange != NULL);
	}
	const char *szDynLastChange = NULL;
	if (deltas)
	{
		DBVARIANT dbv;
		if (!getString("ABFullDynLastChange", &dbv) && dbv.pszVal[0])
		{
			szDynLastChange = NEWSTR_ALLOCA(dbv.pszVal);
			db_free(&dbv);
		}
		deltas &= (szDynLastChange != NULL);
	}

	const char *szGroups, *szContacts, *szLastChangeStr;
	if (strcmp(szMethod, "ABFindContactsPaged"))
	{
		ezxml_t node = ezxml_add_child(tbdy, "abView", 0);
		ezxml_set_txt(node, "Full");
		node = ezxml_add_child(tbdy, "deltasOnly", 0);
		ezxml_set_txt(node, deltas ? "true" : "false");
		node = ezxml_add_child(tbdy, "dynamicItemView", 0);
		ezxml_set_txt(node, "Gleam");
		if (deltas)
		{
			node = ezxml_add_child(tbdy, "lastChange", 0);
			ezxml_set_txt(node, szLastChange);
			node = ezxml_add_child(tbdy, "dynamicItemLastChange", 0);
			ezxml_set_txt(node, szDynLastChange);
		}

		if (szGuid)
		{
			node = ezxml_add_child(tbdy, "contactIds", 0);
			node = ezxml_add_child(node, "guid", 0);
			ezxml_set_txt(node, szGuid);
		}
		szGroups = "groups";
		szContacts = "contacts";
		szLastChangeStr = "LastChange";
	}
	else
	{
		ezxml_t node = ezxml_add_child(tbdy, "abView", 0);
		ezxml_set_txt(node, "MessengerClient8");
		node = ezxml_add_child(tbdy, "extendedContent", 0);
		ezxml_set_txt(node, "AB AllGroups CircleResult");
		ezxml_t filt = ezxml_add_child(tbdy, "filterOptions", 0);

		node = ezxml_add_child(filt, "DeltasOnly", 0);
		ezxml_set_txt(node, deltas ? "true" : "false");
		if (deltas)
		{
			node = ezxml_add_child(filt, "LastChanged", 0);
			ezxml_set_txt(node, szLastChange);
		}
 		node = ezxml_add_child(filt, "ContactFilter", 0);
		node = ezxml_add_child(node, "IncludeHiddenContacts", 0);
		ezxml_set_txt(node, "true");

		szGroups = "Groups";
		szContacts = "Contacts";
		szLastChangeStr = "lastChange";
	}

	char* szData = ezxml_toxml(xmlp, true);
	ezxml_free(xmlp);

	unsigned status = 0;
	char *abUrl = NULL, *tResult = NULL;

	for (int k = 4; --k;)
	{
		mir_free(abUrl);
		abUrl = GetABHost(szMethod, false);
		tResult = getSslResult(&abUrl, szData, reqHdr, status);
		if (tResult == NULL) UpdateABHost(szMethod, NULL);
		else break;
	}

	mir_free(reqHdr);
	free(szData);

	if (tResult != NULL)
	{
		ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
		UpdateABCacheKey(xmlm, false);

		if (status == 200)
		{
			ezxml_t body = getSoapResponse(xmlm, szMethod);
			UpdateABHost(szMethod, body ? abUrl : NULL);

			ezxml_t ab = ezxml_child(body, "Ab");
			if (strcmp(szMethod, "ABFindByContacts"))
			{
				const char* szLastChange = ezxml_txt(ezxml_child(ab, szLastChangeStr));
				if (szLastChange[0]) setString("ABFullLastChange", szLastChange);
				szLastChange = ezxml_txt(ezxml_child(ab, "DynamicItemLastChanged"));
				if (szLastChange[0]) setString("ABFullDynLastChange", szLastChange);
			}

			ezxml_t abinf = ezxml_child(ab, "abInfo");
			mir_snprintf(mycid,  sizeof(mycid), "%s", ezxml_txt(ezxml_child(abinf, "OwnerCID")));
			mir_snprintf(mypuid, sizeof(mycid), "%s", ezxml_txt(ezxml_child(abinf, "ownerPuid")));

			if (MyOptions.ManageServer)
			{
				ezxml_t grp = ezxml_get(body, szGroups, 0, "Group", -1);
				while (grp != NULL)
				{
					const char* szGrpId = ezxml_txt(ezxml_child(grp, "groupId"));
					const char* szGrpName = ezxml_txt(ezxml_get(grp, "groupInfo", 0, "name", -1));
					MSN_AddGroup(szGrpName, szGrpId, true);

					grp = ezxml_next(grp);
				}
			}

			for (ezxml_t cont = ezxml_get(body, szContacts, 0, "Contact", -1); cont != NULL; cont = ezxml_next(cont))
			{
				const char* szContId = ezxml_txt(ezxml_child(cont, "contactId"));

				ezxml_t contInf = ezxml_child(cont, "contactInfo");
				const char* szType = ezxml_txt(ezxml_child(contInf, "contactType"));

				if (strcmp(szType, "Me") != 0)
				{
					char email[128];

					const char* szEmail = ezxml_txt(ezxml_child(contInf, "passportName"));
					const char* szMsgUsr = ezxml_txt(ezxml_child(contInf, "isMessengerUser"));

					int netId = NETID_UNKNOWN;
					if (strcmp(szMsgUsr, "true") == 0) netId = NETID_MSN;

					if (szEmail[0] == '\0')
					{
						ezxml_t eml = ezxml_get(contInf, "emails", 0, "ContactEmail", -1);
						while (eml != NULL)
						{
							szMsgUsr = ezxml_txt(ezxml_child(eml, "isMessengerEnabled"));
							if (strcmp(szMsgUsr, "true") == 0)
							{
								szEmail = ezxml_txt(ezxml_child(eml, "email"));
								const char* szCntType = ezxml_txt(ezxml_child(eml, "contactEmailType"));
								if (strcmp(szCntType, "Messenger2") == 0)
									netId = NETID_YAHOO;
								else if (strcmp(szCntType, "Messenger3") == 0)
									netId = NETID_LCS;
								break;
							}
							eml = ezxml_next(eml);
						}

						if (netId == NETID_UNKNOWN)
						{
							ezxml_t phn = ezxml_get(contInf, "phones", 0, "ContactPhone", -1);
							while (phn != NULL)
							{
								szMsgUsr = ezxml_txt(ezxml_child(phn, "isMessengerEnabled"));
								if (strcmp(szMsgUsr, "true") == 0)
								{
									szEmail = ezxml_txt(ezxml_child(phn, "number"));
									mir_snprintf(email, sizeof(email), "tel:%s", szEmail);
									szEmail = email;
									netId = NETID_MOB;
									break;
								}
								phn = ezxml_next(phn);
							}
						}
					}

					if (netId == NETID_UNKNOWN || szEmail[0] == 0) continue;

					Lists_Add(LIST_FL, netId, szEmail);
					const char *szTmp;

	//				Depricated in WLM 8.1
	//				const char* szNick  = ezxml_txt(ezxml_child(contInf, "displayName"));
	//				if (*szNick == '\0') szNick = szEmail;
					HANDLE hContact = MSN_HContactFromEmail(szEmail, szEmail, true, false);
	//				setStringUtf(hContact, "Nick", (char*)szNick);

					if (MyOptions.ManageServer)
					{
						ezxml_t grpid = ezxml_child(contInf, "groupIds");
						if (!deltas || grpid)
						{
							ezxml_t grps = ezxml_child(grpid, "guid");
							MSN_SyncContactToServerGroup(hContact, szContId, grps);
						}
					}

					const char* szNick = NULL;
					ezxml_t anot = ezxml_get(contInf, "annotations", 0, "Annotation", -1);
					while (anot != NULL)
					{
						if (strcmp(ezxml_txt(ezxml_child(anot, "Name")), "AB.NickName") == 0)
						{
							szNick = ezxml_txt(ezxml_child(anot, "Value"));
							db_set_utf(hContact, "CList", "MyHandle", szNick);
						}
						if (strcmp(ezxml_txt(ezxml_child(anot, "Name")), "AB.JobTitle") == 0)
						{
							szTmp = ezxml_txt(ezxml_child(anot, "Value"));
							SetAbParam(hContact, "CompanyPosition", szTmp);
						}
						anot = ezxml_next(anot);
					}
					if (szNick == NULL)
						db_unset(hContact, "CList", "MyHandle");

					setString(hContact, "ID", szContId);

					switch (netId)
					{
					case NETID_YAHOO:
						setString(hContact, "Transport", "YAHOO");
						break;

					case NETID_LCS:
						setString(hContact, "Transport", "LCS");
						break;

					default:
						deleteSetting(hContact, "Transport");
					}

					szTmp = ezxml_txt(ezxml_child(contInf, "CID"));
					SetAbParam(hContact, "CID", szTmp);

					szTmp  = ezxml_txt(ezxml_child(contInf, "IsNotMobileVisible"));
					setByte(hContact, "MobileAllowed", strcmp(szTmp, "true") != 0);

					szTmp = ezxml_txt(ezxml_child(contInf, "isMobileIMEnabled"));
					setByte(hContact, "MobileEnabled", strcmp(szTmp, "true") == 0);

					szTmp = ezxml_txt(ezxml_child(contInf, "firstName"));
					SetAbParam(hContact, "FirstName", szTmp);

					szTmp = ezxml_txt(ezxml_child(contInf, "lastName"));
					SetAbParam(hContact, "LastName", szTmp);

					szTmp = ezxml_txt(ezxml_child(contInf, "birthdate"));
					char *szPtr;
					if (strtol(szTmp, &szPtr, 10) > 1)
					{
						setWord(hContact, "BirthYear", (WORD)strtol(szTmp, &szPtr, 10));
						setByte(hContact, "BirthMonth", (BYTE)strtol(szPtr+1, &szPtr, 10));
						setByte(hContact, "BirthDay", (BYTE)strtol(szPtr+1, &szPtr, 10));
					}
					else
					{
	//					deleteSetting(hContact, "BirthYear");
	//					deleteSetting(hContact, "BirthMonth");
	//					deleteSetting(hContact, "BirthDay");
					}

					szTmp = ezxml_txt(ezxml_child(contInf, "comment"));
					if (*szTmp) db_set_s(hContact, "UserInfo", "MyNotes", szTmp);
	//				else db_unset(hContact, "UserInfo", "MyNotes");

					ezxml_t loc = ezxml_get(contInf, "locations", 0, "ContactLocation", -1);
					while (loc != NULL)
					{
						const char* szCntType = ezxml_txt(ezxml_child(loc, "contactLocationType"));

						int locid = -1;
						if (strcmp(szCntType, "ContactLocationPersonal") == 0)
							locid = 0;
						else if (strcmp(szCntType, "ContactLocationBusiness") == 0)
							locid = 1;

						if (locid >= 0)
						{
							szTmp = ezxml_txt(ezxml_child(loc, "name"));
							SetAbParam(hContact, "Company", szTmp);
							szTmp = ezxml_txt(ezxml_child(loc, "street"));
							SetAbParam(hContact, locid ? "CompanyStreet" : "Street", szTmp);
							szTmp = ezxml_txt(ezxml_child(loc, "city"));
							SetAbParam(hContact, locid ? "CompanyCity" : "City", szTmp);
							szTmp = ezxml_txt(ezxml_child(loc, "state"));
							SetAbParam(hContact, locid ? "CompanyState" : "State", szTmp);
							szTmp = ezxml_txt(ezxml_child(loc, "country"));
							SetAbParam(hContact, locid ? "CompanyCountry" : "Country", szTmp);
							szTmp = ezxml_txt(ezxml_child(loc, "postalCode"));
							SetAbParam(hContact, locid ? "CompanyZIP" : "ZIP", szTmp);
						}
						loc = ezxml_next(loc);
					}

					ezxml_t web = ezxml_get(contInf, "webSites", 0, "ContactWebSite", -1);
					while (web != NULL)
					{
						const char* szCntType = ezxml_txt(ezxml_child(web, "contactWebSiteType"));
						if (strcmp(szCntType, "ContactWebSiteBusiness") == 0)
						{
							szTmp = ezxml_txt(ezxml_child(web, "webURL"));
							SetAbParam(hContact, "CompanyHomepage", szTmp);
						}
						web = ezxml_next(web);
					}
				}
				else
				{
	//              This depricated in WLM 8.1
	//				if (!getByte("NeverUpdateNickname", 0))
	//				{
	//					const char* szNick  = ezxml_txt(ezxml_child(contInf, "displayName"));
	//					setStringUtf(NULL, "Nick", (char*)szNick);
	//				}
					const char *szTmp;

					szTmp = ezxml_txt(ezxml_child(contInf, "isMobileIMEnabled"));
					setByte("MobileEnabled", strcmp(szTmp, "true") == 0);

					szTmp = ezxml_txt(ezxml_child(contInf, "IsNotMobileVisible"));
					setByte("MobileAllowed", strcmp(szTmp, "true") != 0);

					szTmp = ezxml_txt(ezxml_child(contInf, "firstName"));
					setStringUtf(NULL, "FirstName", szTmp);

					szTmp = ezxml_txt(ezxml_child(contInf, "lastName"));
					setStringUtf(NULL, "LastName", szTmp);

					ezxml_t anot = ezxml_get(contInf, "annotations", 0, "Annotation", -1);
					while (anot != NULL)
					{
						if (strcmp(ezxml_txt(ezxml_child(anot, "Name")), "MSN.IM.BLP") == 0)
							msnOtherContactsBlocked = !atol(ezxml_txt(ezxml_child(anot, "Value")));

						anot = ezxml_next(anot);
					}
				}
			}
			if (!msnLoggedIn && msnNsThread)
			{
				char *szCircleTicket = ezxml_txt(ezxml_get(body, "CircleResult", 0, "CircleTicket", -1));
				ptrA szCircleTicketEnc( mir_base64_encode((PBYTE)szCircleTicket, (unsigned)strlen(szCircleTicket)));
				if (szCircleTicketEnc)
					msnNsThread->sendPacket("USR", "SHA A %s", szCircleTicketEnc);
			}

		}
		else if (status == 500)
		{
			const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
			if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
			{
				MSN_GetPassportAuth();
				status = MSN_ABFind(szMethod, szGuid, deltas, false) ? 200 : 500;
			}
			else if (strcmp(szErr, "FullSyncRequired") == 0 && deltas)
			{
				status = MSN_ABFind(szMethod, szGuid, false, false) ? 200 : 500;
			}
		}
		else
			UpdateABHost(szMethod, NULL);

		ezxml_free(xmlm);
	}
	mir_free(tResult);
	mir_free(abUrl);

	return status == 200;
}


//		"ABGroupContactAdd" : "ABGroupContactDelete", "ABGroupDelete", "ABContactDelete"
bool CMsnProto::MSN_ABAddDelContactGroup(const char* szCntId, const char* szGrpId, const char* szMethod, bool allowRecurse)
{
	char* reqHdr;
	ezxml_t tbdy, node;
	ezxml_t xmlp = abSoapHdr(szMethod, "Timer", tbdy, reqHdr);

	if (szGrpId != NULL)
	{
		node = ezxml_add_child(tbdy, "groupFilter", 0);
		node = ezxml_add_child(node, "groupIds", 0);
		node = ezxml_add_child(node, "guid", 0);
		ezxml_set_txt(node, szGrpId);
	}

	if (szCntId != NULL)
	{
		node = ezxml_add_child(tbdy, "contacts", 0);
		node = ezxml_add_child(node, "Contact", 0);
		node = ezxml_add_child(node, "contactId", 0);
		ezxml_set_txt(node, szCntId);
	}

	char* szData = ezxml_toxml(xmlp, true);
	ezxml_free(xmlp);

	unsigned status = 0;
	char *abUrl = NULL, *tResult = NULL;

	for (int k = 4; --k;)
	{
		mir_free(abUrl);
		abUrl = GetABHost(szMethod, false);
		tResult = getSslResult(&abUrl, szData, reqHdr, status);
		if (tResult == NULL) UpdateABHost(szMethod, NULL);
		else break;
	}

	mir_free(reqHdr);
	free(szData);

	if (tResult != NULL)
	{
		UpdateABHost(szMethod, abUrl);
		ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
		UpdateABCacheKey(xmlm, false);
		if (status == 500)
		{
			const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
			if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
			{
				MSN_GetPassportAuth();
				status = MSN_ABAddDelContactGroup(szCntId, szGrpId, szMethod, false) ? 200 : 500;
			}
		}
		ezxml_free(xmlm);
	}
	mir_free(tResult);
	mir_free(abUrl);

	return status == 200;
}

void CMsnProto::MSN_ABAddGroup(const char* szGrpName, bool allowRecurse)
{
	char* reqHdr;
	ezxml_t tbdy;
	ezxml_t xmlp = abSoapHdr("ABGroupAdd", "GroupSave", tbdy, reqHdr);

	ezxml_t node = ezxml_add_child(tbdy, "groupAddOptions", 0);
	node = ezxml_add_child(node, "fRenameOnMsgrConflict", 0);
	ezxml_set_txt(node, "false");

	node = ezxml_add_child(tbdy, "groupInfo", 0);
	ezxml_t grpi = ezxml_add_child(node, "GroupInfo", 0);
	node = ezxml_add_child(grpi, "name", 0);
	ezxml_set_txt(node, szGrpName);
	node = ezxml_add_child(grpi, "groupType", 0);
	ezxml_set_txt(node, "C8529CE2-6EAD-434d-881F-341E17DB3FF8");
	node = ezxml_add_child(grpi, "fMessenger", 0);
	ezxml_set_txt(node, "false");
	node = ezxml_add_child(grpi, "annotations", 0);
	ezxml_t annt = ezxml_add_child(node, "Annotation", 0);
	node = ezxml_add_child(annt, "Name", 0);
	ezxml_set_txt(node, "MSN.IM.Display");
	node = ezxml_add_child(annt, "Value", 0);
	ezxml_set_txt(node, "1");

	char* szData = ezxml_toxml(xmlp, true);
	ezxml_free(xmlp);

	unsigned status = 0;
	char *abUrl = NULL, *tResult = NULL;

	for (int k = 4; --k;)
	{
		mir_free(abUrl);
		abUrl = GetABHost("ABGroupAdd", false);
		tResult = getSslResult(&abUrl, szData, reqHdr, status);
		if (tResult == NULL) UpdateABHost("ABGroupAdd", NULL);
		else break;
	}

	free(szData);
	mir_free(reqHdr);

	if (tResult != NULL)
	{
		UpdateABHost("ABGroupAdd", abUrl);
		ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
		UpdateABCacheKey(xmlm, false);
		if (status == 200)
		{
			ezxml_t body = getSoapResponse(xmlm, "ABGroupAdd");
			const char* szGrpId = ezxml_txt(ezxml_child(body, "guid"));
			MSN_AddGroup(szGrpName, szGrpId, false);
		}
		else if (status == 500)
		{
			const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
			if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
			{
				MSN_GetPassportAuth();
				MSN_ABAddGroup(szGrpName, false);
			}
		}
		ezxml_free(xmlm);
	}
	mir_free(tResult);
	mir_free(abUrl);
}


void CMsnProto::MSN_ABRenameGroup(const char* szGrpName, const char* szGrpId, bool allowRecurse)
{
	char* reqHdr;
	ezxml_t tbdy;
	ezxml_t xmlp = abSoapHdr("ABGroupUpdate", "Timer", tbdy, reqHdr);

	ezxml_t node = ezxml_add_child(tbdy, "groups", 0);
	ezxml_t grp = ezxml_add_child(node, "Group", 0);

	node = ezxml_add_child(grp, "groupId", 0);
	ezxml_set_txt(node, szGrpId);
	ezxml_t grpi = ezxml_add_child(grp, "groupInfo", 0);
	node = ezxml_add_child(grpi, "name", 0);
	ezxml_set_txt(node, szGrpName);
	node = ezxml_add_child(grp, "propertiesChanged", 0);
	ezxml_set_txt(node, "GroupName");

	char* szData = ezxml_toxml(xmlp, true);
	ezxml_free(xmlp);

	unsigned status = 0;
	char *abUrl = NULL, *tResult = NULL;

	for (int k = 4; --k;)
	{
		mir_free(abUrl);
		abUrl = GetABHost("ABGroupUpdate", false);
		tResult = getSslResult(&abUrl, szData, reqHdr, status);
		if (tResult == NULL) UpdateABHost("ABGroupUpdate", NULL);
		else break;
	}

	mir_free(reqHdr);
	free(szData);

	if (tResult != NULL)
	{
		UpdateABHost("ABGroupUpdate", abUrl);
		ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
		UpdateABCacheKey(xmlm, false);
		if (status == 500)
		{
			const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
			if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
			{
				MSN_GetPassportAuth();
				MSN_ABRenameGroup(szGrpName, szGrpId, false);
			}
		}
		ezxml_free(xmlm);
	}
	mir_free(tResult);
	mir_free(abUrl);
}


bool CMsnProto::MSN_ABAddRemoveContact(const char* szCntId, int netId, bool add, bool allowRecurse)
{
	char* reqHdr;
	ezxml_t tbdy;
	ezxml_t xmlp = abSoapHdr("ABContactUpdate", "Timer", tbdy, reqHdr);

	ezxml_t node = ezxml_add_child(tbdy, "contacts", 0);
	ezxml_t cont = ezxml_add_child(node, "Contact", 0);
	ezxml_set_attr(cont, "xmlns", "http://www.msn.com/webservices/AddressBook");

	node = ezxml_add_child(cont, "contactId", 0);
	ezxml_set_txt(node, szCntId);
	ezxml_t conti = ezxml_add_child(cont, "contactInfo", 0);

	switch (netId)
	{
	case NETID_MSN:
		node = ezxml_add_child(conti, "isMessengerUser", 0);
		ezxml_set_txt(node, add ? "true" : "false");
		node = ezxml_add_child(cont, "propertiesChanged", 0);
		ezxml_set_txt(node, "IsMessengerUser");
		break;

	case NETID_LCS:
	case NETID_YAHOO:
		{
			ezxml_t contp = ezxml_add_child(conti, "emails", 0);
			contp = ezxml_add_child(contp, "ContactEmail", 0);
			node = ezxml_add_child(contp, "contactEmailType", 0);
			ezxml_set_txt(node, netId == NETID_YAHOO ? "Messenger2" : "Messenger3");
			node = ezxml_add_child(contp, "isMessengerEnabled", 0);
			ezxml_set_txt(node, add ? "true" : "false");
			node = ezxml_add_child(contp, "propertiesChanged", 0);
			ezxml_set_txt(node, "IsMessengerEnabled");
			node = ezxml_add_child(cont, "propertiesChanged", 0);
			ezxml_set_txt(node, "ContactEmail");
		}
		break;

	case NETID_MOB:
		{
			ezxml_t contp = ezxml_add_child(conti, "phones", 0);
			contp = ezxml_add_child(contp, "ContactPhone", 0);
			node = ezxml_add_child(contp, "contactPhoneType", 0);
			ezxml_set_txt(node, "ContactPhoneMobile");
			node = ezxml_add_child(contp, "isMessengerEnabled", 0);
			ezxml_set_txt(node, add ? "true" : "false");
			node = ezxml_add_child(contp, "propertiesChanged", 0);
			ezxml_set_txt(node, "IsMessengerEnabled");
			node = ezxml_add_child(cont, "propertiesChanged", 0);
			ezxml_set_txt(node, "ContactPhone");
		}
		break;
	}

	char* szData = ezxml_toxml(xmlp, true);
	ezxml_free(xmlp);

	unsigned status = 0;
	char *abUrl = NULL, *tResult = NULL;

	for (int k = 4; --k;)
	{
		mir_free(abUrl);
		abUrl = GetABHost("ABContactUpdate", false);
		tResult = getSslResult(&abUrl, szData, reqHdr, status);
		if (tResult == NULL) UpdateABHost("ABContactUpdate", NULL);
		else break;
	}

	mir_free(reqHdr);
	free(szData);

	if (tResult != NULL)
	{
		UpdateABHost("ABContactUpdate", abUrl);
		ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
		UpdateABCacheKey(xmlm, false);
		if (status == 500)
		{
			const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
			if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
			{
				MSN_GetPassportAuth();
				if (MSN_ABAddRemoveContact(szCntId, netId, add, false))
					status = 200;
			}
		}
		ezxml_free(xmlm);
	}
	mir_free(tResult);
	mir_free(abUrl);

	return status == 200;
}


bool CMsnProto::MSN_ABUpdateProperty(const char* szCntId, const char* propName, const char* propValue, bool allowRecurse)
{
	char* reqHdr;
	ezxml_t tbdy;
	ezxml_t xmlp = abSoapHdr("ABContactUpdate", "Timer", tbdy, reqHdr);

	ezxml_t node = ezxml_add_child(tbdy, "contacts", 0);
	ezxml_t cont = ezxml_add_child(node, "Contact", 0);
	ezxml_set_attr(cont, "xmlns", "http://www.msn.com/webservices/AddressBook");

	ezxml_t conti = ezxml_add_child(cont, "contactInfo", 0);
	if (szCntId == NULL)
	{
		node = ezxml_add_child(conti, "contactType", 0);
		ezxml_set_txt(node, "Me");
	}
	else
	{
		node = ezxml_add_child(cont, "contactId", 0);
		ezxml_set_txt(node, szCntId);
	}
	node = ezxml_add_child(conti, propName, 0);
	ezxml_set_txt(node, propValue);

	node = ezxml_add_child(cont, "propertiesChanged", 0);
	char* szPrpChg = mir_strdup(propName);
	*szPrpChg = _toupper(*szPrpChg);
	ezxml_set_txt(node, szPrpChg);

	char* szData = ezxml_toxml(xmlp, true);
	ezxml_free(xmlp);
	mir_free(szPrpChg);

	unsigned status = 0;
	char *abUrl = NULL, *tResult = NULL;

	for (int k = 4; --k;)
	{
		mir_free(abUrl);
		abUrl = GetABHost("ABContactUpdate", false);
		tResult = getSslResult(&abUrl, szData, reqHdr, status);
		if (tResult == NULL) UpdateABHost("ABContactUpdate", NULL);
		else break;
	}

	mir_free(reqHdr);
	free(szData);

	if (tResult != NULL)
	{
		UpdateABHost("ABContactUpdate", abUrl);
		ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
		UpdateABCacheKey(xmlm, false);
		if (status == 500)
		{
			const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
			if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
			{
				MSN_GetPassportAuth();
				if (MSN_ABUpdateProperty(szCntId, propName, propValue, false))
					status = 200;
			}
		}
		ezxml_free(xmlm);
	}
	mir_free(tResult);
	mir_free(abUrl);

	return status == 200;
}


void CMsnProto::MSN_ABUpdateAttr(const char* szCntId, const char* szAttr, const char* szValue, bool allowRecurse)
{
	char* reqHdr;
	ezxml_t tbdy;
	ezxml_t xmlp = abSoapHdr("ABContactUpdate", "Timer", tbdy, reqHdr);

	ezxml_t node = ezxml_add_child(tbdy, "contacts", 0);
	ezxml_t cont = ezxml_add_child(node, "Contact", 0);
	ezxml_set_attr(cont, "xmlns", "http://www.msn.com/webservices/AddressBook");
	ezxml_t conti = ezxml_add_child(cont, "contactInfo", 0);
	if (szCntId == NULL)
	{
		node = ezxml_add_child(conti, "contactType", 0);
		ezxml_set_txt(node, "Me");
	}
	else
	{
		node = ezxml_add_child(cont, "contactId", 0);
		ezxml_set_txt(node, szCntId);
	}
	node = ezxml_add_child(conti, "annotations", 0);
	ezxml_t anot = ezxml_add_child(node, "Annotation", 0);
	node = ezxml_add_child(anot, "Name", 0);
	ezxml_set_txt(node, szAttr);
	node = ezxml_add_child(anot, "Value", 0);
	if (szValue != NULL) ezxml_set_txt(node, szValue);

	node = ezxml_add_child(cont, "propertiesChanged", 0);
	ezxml_set_txt(node, "Annotation");

	char* szData = ezxml_toxml(xmlp, true);
	ezxml_free(xmlp);

	unsigned status = 0;
	char *abUrl = NULL, *tResult = NULL;

	for (int k = 4; --k;)
	{
		mir_free(abUrl);
		abUrl = GetABHost("ABContactUpdate", false);
		tResult = getSslResult(&abUrl, szData, reqHdr, status);
		if (tResult == NULL) UpdateABHost("ABContactUpdate", NULL);
		else break;
	}

	mir_free(reqHdr);
	free(szData);

	if (tResult != NULL)
	{
		UpdateABHost("ABContactUpdate", abUrl);
		ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
		UpdateABCacheKey(xmlm, false);
		if (status == 500)
		{
			const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
			if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
			{
				MSN_GetPassportAuth();
				MSN_ABUpdateAttr(szCntId, szAttr, szValue, false);
			}
		}
		ezxml_free(xmlm);
	}
	mir_free(tResult);
	mir_free(abUrl);
}


void CMsnProto::MSN_ABUpdateNick(const char* szNick, const char* szCntId)
{
	if (szCntId != NULL)
		MSN_ABUpdateAttr(szCntId, "AB.NickName", szNick);
	else
		MSN_ABUpdateProperty(szCntId, "displayName", szNick);
}


unsigned CMsnProto::MSN_ABContactAdd(const char* szEmail, const char* szNick, int netId, const char* szInvite, bool search, bool retry, bool allowRecurse)
{
	char* reqHdr;
	ezxml_t tbdy;
	ezxml_t xmlp = abSoapHdr("ABContactAdd", "ContactSave", tbdy, reqHdr);

	ezxml_t conts = ezxml_add_child(tbdy, "contacts", 0);
	ezxml_t node = ezxml_add_child(conts, "Contact", 0);
	ezxml_set_attr(node, "xmlns", "http://www.msn.com/webservices/AddressBook");
	ezxml_t conti = ezxml_add_child(node, "contactInfo", 0);
	ezxml_t contp;

	const char* szEmailNP = strchr(szEmail, ':');
	if (szEmailNP != NULL) netId = NETID_MOB;

	switch (netId)
	{
	case NETID_MSN:
		node = ezxml_add_child(conti, "contactType", 0);
		ezxml_set_txt(node, "LivePending");
		node = ezxml_add_child(conti, "passportName", 0);
		ezxml_set_txt(node, szEmail);
		node = ezxml_add_child(conti, "isMessengerUser", 0);
		ezxml_set_txt(node, "true");

		if (szInvite)
		{
			node = ezxml_add_child(conti, "MessengerMemberInfo", 0);
			node = ezxml_add_child(node, "PendingAnnotations", 0);
			ezxml_t anot = ezxml_add_child(node, "Annotation", 0);
			node = ezxml_add_child(anot, "Name", 0);
			ezxml_set_txt(node, "MSN.IM.InviteMessage");
			node = ezxml_add_child(anot, "Value", 0);
			ezxml_set_txt(node, szInvite);
		}
		break;

	case NETID_MOB:
		++szEmailNP;
		if (szNick == NULL) szNick = szEmailNP;
		node = ezxml_add_child(conti, "phones", 0);
		contp = ezxml_add_child(node, "ContactPhone", 0);
		node = ezxml_add_child(contp, "contactPhoneType", 0);
		ezxml_set_txt(node, "ContactPhoneMobile");
		node = ezxml_add_child(contp, "number", 0);
		ezxml_set_txt(node, szEmailNP);
		node = ezxml_add_child(contp, "isMessengerEnabled", 0);
		ezxml_set_txt(node, "true");
		node = ezxml_add_child(contp, "propertiesChanged", 0);
		ezxml_set_txt(node, "Number IsMessengerEnabled");
		break;

	case NETID_LCS:
	case NETID_YAHOO:
		node = ezxml_add_child(conti, "emails", 0);
		contp = ezxml_add_child(node, "ContactEmail", 0);
		node = ezxml_add_child(contp, "contactEmailType", 0);
		ezxml_set_txt(node, netId == NETID_YAHOO ? "Messenger2" : "Messenger3");
		node = ezxml_add_child(contp, "email", 0);
		ezxml_set_txt(node, szEmail);
		node = ezxml_add_child(contp, "isMessengerEnabled", 0);
		ezxml_set_txt(node, "true");
		node = ezxml_add_child(contp, "Capability", 0);
		ezxml_set_txt(node,  netId == NETID_YAHOO ? "32" : "2");
		node = ezxml_add_child(contp, "propertiesChanged", 0);
		ezxml_set_txt(node, "Email IsMessengerEnabled Capability");
		break;
	}

	if (szNick != NULL)
	{
		node = ezxml_add_child(conti, "annotations", 0);
		ezxml_t annt = ezxml_add_child(node, "Annotation", 0);
		node = ezxml_add_child(annt, "Name", 0);
		ezxml_set_txt(node, "MSN.IM.Display");
		node = ezxml_add_child(annt, "Value", 0);
		ezxml_set_txt(node, szNick);
	}

	node = ezxml_add_child(conts, "options", 0);
	node = ezxml_add_child(node, "EnableAllowListManagement", 0);
	ezxml_set_txt(node, "true");

	char* szData = ezxml_toxml(xmlp, true);
	ezxml_free(xmlp);

	unsigned status = 0;
	char *abUrl = NULL, *tResult = NULL;

	for (int k = 4; --k;)
	{
		mir_free(abUrl);
		abUrl = GetABHost("ABContactAdd", false);
		tResult = getSslResult(&abUrl, szData, reqHdr, status);
		if (tResult == NULL) UpdateABHost("ABContactAdd", NULL);
		else break;
	}

	mir_free(reqHdr);
	free(szData);

	if (tResult != NULL)
	{
		ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
		UpdateABCacheKey(xmlm, false);
		if (status == 200)
		{
			ezxml_t body = getSoapResponse(xmlm, "ABContactAdd");

			const char* szContId = ezxml_txt(ezxml_child(body, "guid"));

			if (search)
				MSN_ABAddDelContactGroup(szContId , NULL, "ABContactDelete");
			else
			{
				MSN_ABAddRemoveContact(szContId, NETID_MSN, true);
				HANDLE hContact = MSN_HContactFromEmail(szEmail, szNick ? szNick : szEmail, true, false);
				setString(hContact, "ID", szContId);
			}
			status = 0;
		}
		else if (status == 500)
		{
			const char* szErr = ezxml_txt(getSoapFault(xmlm, true));

			if (strcmp(szErr, "InvalidPassportUser") == 0)
				status = 1;
			else if (strcmp(szErr, "FederatedQueryFailure") == 0)
				status = 4;
			else if (strcmp(szErr, "EmailDomainIsFederated") == 0)
				status = 2;
			else if (strcmp(szErr, "BadEmailArgument") == 0)
				status = 4;
			else if (strcmp(szErr, "ContactAlreadyExists") == 0)
			{
				status = 3;

				ezxml_t node = getSoapFault(xmlm, false);
				node = ezxml_get(node, "detail", 0, "additionalDetails", 0, "conflictObjectId", -1);
				const char* szContId = ezxml_txt(node);

				if (search)
				{
					if (retry)
					{
						MSN_ABAddDelContactGroup(szContId , NULL, "ABContactDelete");
						status = 0;
					}
				}
				else
				{
					HANDLE hContact = MSN_HContactFromEmail(szEmail, szNick ? szNick : szEmail, true, false);
					setString(hContact, "ID", szContId);
				}
			}
			else if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
			{
				MSN_GetPassportAuth();
				status = MSN_ABContactAdd(szEmail, szNick, netId, NULL, search, retry, false);
			}
			else
			{
				status = MSN_ABContactAdd(szEmail, szNick, netId, NULL, search, false);
			}
		}
		ezxml_free(xmlm);
	}
	mir_free(tResult);
	mir_free(abUrl);

	return status;
}


void CMsnProto::MSN_ABUpdateDynamicItem(bool allowRecurse)
{
	char* reqHdr;
	ezxml_t tbdy;
	ezxml_t xmlp = abSoapHdr("UpdateDynamicItem", "RoamingIdentityChanged", tbdy, reqHdr);

	ezxml_t dynitms = ezxml_add_child(tbdy, "dynamicItems", 0);
	ezxml_t dynitm = ezxml_add_child(dynitms, "DynamicItem", 0);

	ezxml_set_attr(dynitm, "xsi:type", "PassportDynamicItem");
	ezxml_t node = ezxml_add_child(dynitm, "Type", 0);
	ezxml_set_txt(node, "Passport");
	node = ezxml_add_child(dynitm, "PassportName", 0);
	ezxml_set_txt(node, MyOptions.szEmail);

	ezxml_t nots = ezxml_add_child(dynitm, "Notifications", 0);
	ezxml_t notd = ezxml_add_child(nots, "NotificationData", 0);
	ezxml_t strsvc = ezxml_add_child(notd, "StoreService", 0);
	ezxml_t info = ezxml_add_child(strsvc, "Info", 0);

	ezxml_t hnd = ezxml_add_child(info, "Handle", 0);
	node = ezxml_add_child(hnd, "Id", 0);
	ezxml_set_txt(node, "0");
	node = ezxml_add_child(hnd, "Type", 0);
	ezxml_set_txt(node, "Profile");
	node = ezxml_add_child(hnd, "ForeignId", 0);
	ezxml_set_txt(node, "MyProfile");

	node = ezxml_add_child(info, "InverseRequired", 0);
	ezxml_set_txt(node, "false");
	node = ezxml_add_child(info, "IsBot", 0);
	ezxml_set_txt(node, "false");

	node = ezxml_add_child(strsvc, "Changes", 0);
	node = ezxml_add_child(strsvc, "LastChange", 0);
	ezxml_set_txt(node, "0001-01-01T00:00:00");
	node = ezxml_add_child(strsvc, "Deleted", 0);
	ezxml_set_txt(node, "false");

	node = ezxml_add_child(notd, "Status", 0);
	ezxml_set_txt(node, "Exist Access");
	node = ezxml_add_child(notd, "LastChanged", 0);

	time_t timer;
	time(&timer);
	tm *tmst = gmtime(&timer);

	char tmstr[32];
	mir_snprintf(tmstr, sizeof(tmstr), "%04u-%02u-%02uT%02u:%02u:%02uZ",
		tmst->tm_year + 1900, tmst->tm_mon+1, tmst->tm_mday,
		tmst->tm_hour, tmst->tm_min, tmst->tm_sec);

	ezxml_set_txt(node, tmstr);
	node = ezxml_add_child(notd, "Gleam", 0);
	ezxml_set_txt(node, "false");
	node = ezxml_add_child(notd, "InstanceId", 0);
	ezxml_set_txt(node, "0");

	node = ezxml_add_child(dynitm, "Changes", 0);
	ezxml_set_txt(node, "Notifications");

	char* szData = ezxml_toxml(xmlp, true);
	ezxml_free(xmlp);

	unsigned status;
	char *abUrl = NULL, *tResult;

	for (int k = 4; --k;)
	{
		mir_free(abUrl);
		abUrl = GetABHost("UpdateDynamicItem", false);
		tResult = getSslResult(&abUrl, szData, reqHdr, status);
		if (tResult == NULL) UpdateABHost("UpdateDynamicItem", NULL);
		else break;
	}

	mir_free(reqHdr);
	free(szData);

	if (tResult != NULL)
	{
		UpdateABHost("UpdateDynamicItem", abUrl);
		ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
		UpdateABCacheKey(xmlm, false);
		if (status == 500)
		{
			const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
			if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
			{
				MSN_GetPassportAuth();
				MSN_ABUpdateDynamicItem(false);
			}
		}
		ezxml_free(xmlm);
	}
	mir_free(tResult);
	mir_free(abUrl);
}