/* Copyright (c) 2015-23 Miranda NG team (https://miranda-ng.org) 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 version 2 of the License. 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 . */ #include "stdafx.h" void CSkypeProto::CheckConvert() { m_szSkypename = getMStringA(SKYPE_SETTINGS_ID); if (m_szSkypename.IsEmpty()) { m_szSkypename = getMStringA(SKYPE_SETTINGS_LOGIN); if (!m_szSkypename.IsEmpty()) { // old settings format, need to update all settings m_szSkypename.Insert(0, "8:"); setString(SKYPE_SETTINGS_ID, m_szSkypename); for (auto &hContact : AccContacts()) { CMStringA id(ptrA(getUStringA(hContact, "Skypename"))); if (!id.IsEmpty()) setString(hContact, SKYPE_SETTINGS_ID, (isChatRoom(hContact)) ? "19:" + id : "8:" + id); ptrW wszNick(getWStringA(hContact, "Nick")); if (wszNick == nullptr) setUString(hContact, "Nick", id); delSetting(hContact, "Skypename"); } } } } void CSkypeProto::Login() { CheckConvert(); // login m_iStatus = ID_STATUS_CONNECTING; StartQueue(); int tokenExpires = getDword("TokenExpiresIn"); pass_ptrA szPassword(getStringA(SKYPE_SETTINGS_PASSWORD)); if (m_szSkypename.IsEmpty() || szPassword == NULL) { ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGIN_ERROR_UNKNOWN); return; } m_bHistorySynced = m_bThreadsTerminated = false; if ((tokenExpires - 1800) > time(0)) OnLoginSuccess(); else PushRequest(new OAuthRequest()); } void CSkypeProto::OnLoginOAuth(NETLIBHTTPREQUEST *response, AsyncHttpRequest*) { if (!IsStatusConnecting(m_iStatus)) return; if (response == nullptr || response->pData == nullptr) { ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGIN_ERROR_UNKNOWN); SetStatus(ID_STATUS_OFFLINE); return; } JSONNode json = JSONNode::parse(response->pData); if (!json) { ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGIN_ERROR_UNKNOWN); SetStatus(ID_STATUS_OFFLINE); return; } if (response->resultCode != 200) { int error = 0; if (json["status"]) { const JSONNode &status = json["status"]; if (status["code"]) { switch (status["code"].as_int()) { case 40002: ShowNotification(L"Skype", TranslateT("Authentication failed. Invalid username."), NULL, 1); error = LOGINERR_BADUSERID; break; case 40120: ShowNotification(L"Skype", TranslateT("Authentication failed. Bad username or password."), NULL, 1); error = LOGINERR_WRONGPASSWORD; break; case 40121: ShowNotification(L"Skype", TranslateT("Too many failed authentication attempts with given username or IP."), NULL, 1); error = LOGIN_ERROR_TOOMANY_REQUESTS; break; default: ShowNotification(L"Skype", status["text"] ? status["text"].as_mstring() : TranslateT("Authentication failed. Unknown error."), NULL, 1); error = LOGIN_ERROR_UNKNOWN; } } } ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, error); SetStatus(ID_STATUS_OFFLINE); return; } if (!json["skypetoken"] || !json["expiresIn"]) { ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGIN_ERROR_UNKNOWN); SetStatus(ID_STATUS_OFFLINE); return; } setString("TokenSecret", json["skypetoken"].as_string().c_str()); setDword("TokenExpiresIn", time(NULL) + json["expiresIn"].as_int()); OnLoginSuccess(); } void CSkypeProto::OnLoginSuccess() { if (!IsStatusConnecting(m_iStatus)) return; m_bThreadsTerminated = false; ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_SUCCESS, NULL, 0); int oldStatus = m_iStatus; m_iStatus = m_iDesiredStatus; ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); m_szApiToken = getStringA("TokenSecret"); m_impl.m_heartBeat.StartSafe(600 * 1000); PushRequest(new CreateEndpointRequest(this)); } void CSkypeProto::OnEndpointCreated(NETLIBHTTPREQUEST *response, AsyncHttpRequest*) { if (IsStatusConnecting(m_iStatus)) m_iStatus++; if (response == nullptr) { debugLogA(__FUNCTION__ ": failed to get create endpoint"); ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGIN_ERROR_UNKNOWN); SetStatus(ID_STATUS_OFFLINE); return; } switch (response->resultCode) { case 200: case 201: // okay, endpoint created break; case 301: case 302: // redirect to the closest data center if (auto *hdr = Netlib_GetHeader(response, "Location")) { CMStringA szUrl(hdr+8); int iEnd = szUrl.Find('/'); g_plugin.szDefaultServer = (iEnd != -1) ? szUrl.Left(iEnd) : szUrl; } PushRequest(new CreateEndpointRequest(this)); return; case 401: // unauthorized if (auto *szStatus = Netlib_GetHeader(response, "StatusText")) if (strstr(szStatus, "SkypeTokenExpired")) delSetting("TokenSecret"); delSetting("TokenExpiresIn"); PushRequest(new LoginOAuthRequest(m_szSkypename, pass_ptrA(getStringA(SKYPE_SETTINGS_PASSWORD)))); return; default: delSetting("TokenExpiresIn"); ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGIN_ERROR_UNKNOWN); SetStatus(ID_STATUS_OFFLINE); return; } // Succeeded, decode the answer if (auto *hdr = Netlib_GetHeader(response, "Set-RegistrationToken")) { CMStringA szValue = hdr; int iStart = 0; while (true) { CMStringA szToken = szValue.Tokenize(";", iStart).Trim(); if (iStart == -1) break; int iStart2 = 0; CMStringA name = szToken.Tokenize("=", iStart2); CMStringA val = szToken.Mid(iStart2); if (name == "registrationToken") m_szToken = val.Detach(); else if (name == "endpointId") m_szId = val.Detach(); } } RefreshStatuses(); PushRequest(new CreateSubscriptionsRequest()); } void CSkypeProto::OnEndpointDeleted(NETLIBHTTPREQUEST *, AsyncHttpRequest *) { m_szId = nullptr; m_szToken = nullptr; } void CSkypeProto::OnSubscriptionsCreated(NETLIBHTTPREQUEST *response, AsyncHttpRequest*) { if (!IsStatusConnecting(m_iStatus)) return; if (response == nullptr) { debugLogA(__FUNCTION__ ": failed to create subscription"); ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGIN_ERROR_UNKNOWN); SetStatus(ID_STATUS_OFFLINE); return; } SendPresence(); } void CSkypeProto::SendPresence() { ptrA epname; if (!bUseHostnameAsPlace && wstrPlace && *wstrPlace) epname = mir_utf8encodeW(wstrPlace); else { wchar_t compName[MAX_COMPUTERNAME_LENGTH + 1]; DWORD size = _countof(compName); GetComputerName(compName, &size); epname = mir_utf8encodeW(compName); } PushRequest(new SendCapabilitiesRequest(epname, this)); } void CSkypeProto::OnCapabilitiesSended(NETLIBHTTPREQUEST *response, AsyncHttpRequest*) { if (!IsStatusConnecting(m_iStatus)) return; if (response == nullptr || response->pData == nullptr) { ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGIN_ERROR_UNKNOWN); SetStatus(ID_STATUS_OFFLINE); return; } PushRequest(new SetStatusRequest(MirandaToSkypeStatus(m_iDesiredStatus))); LIST skypenames(1); for (auto &hContact : AccContacts()) if (!isChatRoom(hContact)) skypenames.insert(getId(hContact).Detach()); PushRequest(new CreateContactsSubscriptionRequest(skypenames)); FreeList(skypenames); skypenames.destroy(); m_hPollingEvent.Set(); PushRequest(new LoadChatsRequest()); PushRequest(new GetContactListRequest(this, nullptr)); PushRequest(new GetAvatarRequest(ptrA(getStringA("AvatarUrl")), 0)); if (bAutoHistorySync) PushRequest(new SyncHistoryFirstRequest(100)); JSONNode root = JSONNode::parse(response->pData); if (root) setString("SelfEndpointName", UrlToSkypeId(root["selfLink"].as_string().c_str())); PushRequest(new GetProfileRequest(this, 0)); } void CSkypeProto::OnStatusChanged(NETLIBHTTPREQUEST *response, AsyncHttpRequest*) { if (response == nullptr || response->pData == nullptr) { debugLogA(__FUNCTION__ ": failed to change status"); ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGIN_ERROR_UNKNOWN); SetStatus(ID_STATUS_OFFLINE); return; } JSONNode json = JSONNode::parse(response->pData); if (!json) { debugLogA(__FUNCTION__ ": failed to change status"); ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGIN_ERROR_UNKNOWN); SetStatus(ID_STATUS_OFFLINE); return; } const JSONNode &nStatus = json["status"]; if (!nStatus) { debugLogA(__FUNCTION__ ": result contains no valid status to switch to"); return; } int iNewStatus = SkypeToMirandaStatus(nStatus.as_string().c_str()); if (iNewStatus == ID_STATUS_OFFLINE) { debugLogA(__FUNCTION__ ": failed to change status"); ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGIN_ERROR_UNKNOWN); SetStatus(ID_STATUS_OFFLINE); return; } int oldStatus = m_iStatus; m_iStatus = m_iDesiredStatus = iNewStatus; ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); }