/* Jabber Protocol Plugin for Miranda IM Copyright (C) 2002-04 Santithorn Bunchua Copyright (C) 2005-12 George Hazan Copyright (C) 2007 Maxim Mluhov 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "jabber.h" #include // requires Windows Platform SDK #include "jabber_list.h" #include "jabber_iq.h" #include "jabber_secur.h" #include "jabber_caps.h" #include "jabber_privacy.h" #include "jabber_rc.h" #include "jabber_proto.h" #ifndef DNS_TYPE_SRV #define DNS_TYPE_SRV 0x0021 #endif // identification number for various actions // for JABBER_REGISTER thread int iqIdRegGetReg; int iqIdRegSetReg; // XML Console #define JCPF_IN 0x01UL #define JCPF_OUT 0x02UL #define JCPF_ERROR 0x04UL static VOID CALLBACK JabberDummyApcFunc(DWORD_PTR) { return; } struct JabberPasswordDlgParam { CJabberProto* pro; BOOL saveOnlinePassword; WORD dlgResult; TCHAR onlinePassword[128]; HANDLE hEventPasswdDlg; TCHAR* ptszJid; }; static INT_PTR CALLBACK JabberPasswordDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) { JabberPasswordDlgParam* param = (JabberPasswordDlgParam*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); switch (msg) { case WM_INITDIALOG: TranslateDialogDefault(hwndDlg); { param = (JabberPasswordDlgParam*)lParam; SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); TCHAR text[512]; mir_sntprintf(text, SIZEOF(text), _T("%s %s"), TranslateT("Enter password for"), (TCHAR*)param->ptszJid); SetDlgItemText(hwndDlg, IDC_JID, text); int bSavePassword = param->pro->JGetByte(NULL, "SaveSessionPassword", 0); CheckDlgButton(hwndDlg, IDC_SAVEPASSWORD, (bSavePassword) ? BST_CHECKED : BST_UNCHECKED); } return TRUE; case WM_COMMAND: switch (LOWORD(wParam)) { case IDC_SAVE_PERM: EnableWindow(GetDlgItem(hwndDlg,IDC_SAVEPASSWORD),!IsDlgButtonChecked(hwndDlg, IDC_SAVE_PERM)); break; case IDOK: param->saveOnlinePassword = IsDlgButtonChecked(hwndDlg, IDC_SAVEPASSWORD); param->pro->JSetByte(NULL, "SaveSessionPassword", param->saveOnlinePassword); GetDlgItemText(hwndDlg, IDC_PASSWORD, param->onlinePassword, SIZEOF(param->onlinePassword)); { BOOL savePassword = IsDlgButtonChecked(hwndDlg, IDC_SAVE_PERM); param->pro->JSetByte(NULL, "SavePassword", savePassword); if (savePassword) { param->pro->JSetStringCrypt(NULL,"LoginPassword",param->onlinePassword); param->saveOnlinePassword = TRUE; } } // Fall through case IDCANCEL: param->dlgResult = LOWORD(wParam); SetEvent(param->hEventPasswdDlg); DestroyWindow(hwndDlg); return TRUE; } break; } return FALSE; } static VOID CALLBACK JabberPasswordCreateDialogApcProc(void* param) { CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_PASSWORD), NULL, JabberPasswordDlgProc, (LPARAM)param); } static VOID CALLBACK JabberOfflineChatWindows(void* param) { CJabberProto* ppro = (CJabberProto*)param; GCDEST gcd = { ppro->m_szModuleName, NULL, GC_EVENT_CONTROL }; GCEVENT gce = { 0 }; gce.cbSize = sizeof(GCEVENT); gce.pDest = &gcd; CallService(MS_GC_EVENT, SESSION_TERMINATE, (LPARAM)&gce); } ///////////////////////////////////////////////////////////////////////////////////////// // Jabber keep-alive thread void CJabberProto::OnPingReply(HXML, CJabberIqInfo* pInfo) { if ( !pInfo) return; if (pInfo->GetIqType() == JABBER_IQ_TYPE_FAIL) { // disconnect because of timeout m_ThreadInfo->send(""); m_ThreadInfo->shutdown(); } } ///////////////////////////////////////////////////////////////////////////////////////// typedef DNS_STATUS (WINAPI *DNSQUERYA)(IN PCSTR pszName, IN WORD wType, IN DWORD Options, IN PIP4_ARRAY aipServers OPTIONAL, IN OUT PDNS_RECORDA *ppQueryResults OPTIONAL, IN OUT PVOID *pReserved OPTIONAL); typedef void (WINAPI *DNSFREELIST)(IN OUT PDNS_RECORDA pRecordList, IN DNS_FREE_TYPE FreeType); static int CompareDNS(const DNS_SRV_DATAA* dns1, const DNS_SRV_DATAA* dns2) { return (int)dns1->wPriority - (int)dns2->wPriority; } void ThreadData::xmpp_client_query(void) { if (inet_addr(server) != INADDR_NONE) return; HMODULE hDnsapi = LoadLibraryA("dnsapi.dll"); if (hDnsapi == NULL) return; DNSQUERYA pDnsQuery = (DNSQUERYA)GetProcAddress(hDnsapi, "DnsQuery_A"); DNSFREELIST pDnsRecordListFree = (DNSFREELIST)GetProcAddress(hDnsapi, "DnsRecordListFree"); if (pDnsQuery == NULL) { //dnsapi.dll is not the needed dnsapi ;) FreeLibrary(hDnsapi); return; } char temp[256]; mir_snprintf(temp, SIZEOF(temp), "_xmpp-client._tcp.%s", server); DNS_RECORDA *results = NULL; DNS_STATUS status = pDnsQuery(temp, DNS_TYPE_SRV, DNS_QUERY_STANDARD, NULL, &results, NULL); if (SUCCEEDED(status) && results) { LIST dnsList(5, CompareDNS); for (DNS_RECORDA *rec = results; rec; rec = rec->pNext) { if (rec->Data.Srv.pNameTarget && rec->wType == DNS_TYPE_SRV) dnsList.insert(&rec->Data.Srv); } for (int i = 0; i < dnsList.getCount(); i++) { WORD dnsPort = port == 0 || port == 5222 ? dnsList[i]->wPort : port; char* dnsHost = dnsList[i]->pNameTarget; proto->Log("%s%s resolved to %s:%d", "_xmpp-client._tcp.", server, dnsHost, dnsPort); s = proto->WsConnect(dnsHost, dnsPort); if (s) { mir_snprintf(manualHost, SIZEOF(manualHost), "%s", dnsHost); port = dnsPort; break; } } dnsList.destroy(); pDnsRecordListFree(results, DnsFreeRecordList); } else proto->Log("%s not resolved", temp); FreeLibrary(hDnsapi); } void CJabberProto::xmlStreamInitialize(char *szWhich) { Log("Stream will be initialized %s", szWhich); if (m_szXmlStreamToBeInitialized) free(m_szXmlStreamToBeInitialized); m_szXmlStreamToBeInitialized = _strdup(szWhich); } void CJabberProto::xmlStreamInitializeNow(ThreadData* info) { Log("Stream is initializing %s", m_szXmlStreamToBeInitialized ? m_szXmlStreamToBeInitialized : "after connect"); if (m_szXmlStreamToBeInitialized) { free(m_szXmlStreamToBeInitialized); m_szXmlStreamToBeInitialized = NULL; } HXML n = xi.createNode(_T("xml"), NULL, 1) << XATTR(_T("version"), _T("1.0")) << XATTR(_T("encoding"), _T("UTF-8")); HXML stream = n << XCHILDNS(_T("stream:stream"), _T("jabber:client")) << XATTR(_T("to"), _A2T(info->server)) << XATTR(_T("xmlns:stream"), _T("http://etherx.jabber.org/streams")); if (m_tszSelectedLang) xmlAddAttr(stream, _T("xml:lang"), m_tszSelectedLang); if ( !m_options.Disable3920auth) xmlAddAttr(stream, _T("version"), _T("1.0")); LPTSTR xmlQuery = xi.toString(n, NULL); char* buf = mir_utf8encodeT(xmlQuery); int bufLen = (int)strlen(buf); if (bufLen > 2) { strdel(buf + bufLen - 2, 1); bufLen--; } info->send(buf, bufLen); mir_free(buf); xi.freeMem(xmlQuery); xi.destroyNode(n); } void CJabberProto::ServerThread(ThreadData* info) { DBVARIANT dbv; char* buffer; int datalen; int oldStatus; Log("Thread started: type=%d", info->type); info->resolveID = -1; info->auth = NULL; if (m_options.ManualConnect == TRUE) { if ( !DBGetContactSettingString(NULL, m_szModuleName, "ManualHost", &dbv)) { strncpy(info->manualHost, dbv.pszVal, SIZEOF(info->manualHost)); info->manualHost[SIZEOF(info->manualHost)-1] = '\0'; db_free(&dbv); } info->port = JGetWord(NULL, "ManualPort", JABBER_DEFAULT_PORT); } else info->port = JGetWord(NULL, "Port", JABBER_DEFAULT_PORT); info->useSSL = m_options.UseSSL; if (info->type == JABBER_SESSION_NORMAL) { // Normal server connection, we will fetch all connection parameters // e.g. username, password, etc. from the database. if (m_ThreadInfo != NULL) { // Will not start another connection thread if a thread is already running. // Make APC call to the main thread. This will immediately wake the thread up // in case it is asleep in the reconnect loop so that it will immediately // reconnect. QueueUserAPC(JabberDummyApcFunc, m_ThreadInfo->hThread, 0); Log("Thread ended, another normal thread is running"); LBL_Exit: delete info; return; } m_ThreadInfo = info; if (m_szStreamId) mir_free(m_szStreamId); m_szStreamId = NULL; if ( !JGetStringT(NULL, "LoginName", &dbv)) { _tcsncpy(info->username, dbv.ptszVal, SIZEOF(info->username)-1); db_free(&dbv); } if (*trtrim(info->username) == '\0') { DWORD dwSize = SIZEOF(info->username); if (GetUserName(info->username, &dwSize)) JSetStringT(NULL, "LoginName", info->username); else info->username[0] = 0; } if (*trtrim(info->username) == '\0') { Log("Thread ended, login name is not configured"); JSendBroadcast(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_BADUSERID); LBL_FatalError: m_ThreadInfo = NULL; oldStatus = m_iStatus; m_iDesiredStatus = m_iStatus = ID_STATUS_OFFLINE; JSendBroadcast(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); goto LBL_Exit; } if ( !DBGetContactSettingString(NULL, m_szModuleName, "LoginServer", &dbv)) { strncpy(info->server, dbv.pszVal, SIZEOF(info->server)-1); db_free(&dbv); } else { JSendBroadcast(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_NONETWORK); Log("Thread ended, login server is not configured"); goto LBL_FatalError; } if (m_options.HostNameAsResource == FALSE) { if ( !JGetStringT(NULL, "Resource", &dbv)) { _tcsncpy(info->resource, dbv.ptszVal, SIZEOF(info->resource) - 1); db_free(&dbv); } else _tcscpy(info->resource, _T("Miranda")); } else { DWORD dwCompNameLen = SIZEOF(info->resource) - 1; if ( !GetComputerName(info->resource, &dwCompNameLen)) _tcscpy(info->resource, _T("Miranda")); } TCHAR jidStr[512]; mir_sntprintf(jidStr, SIZEOF(jidStr), _T("%s@") _T(TCHAR_STR_PARAM) _T("/%s"), info->username, info->server, info->resource); _tcsncpy(info->fullJID, jidStr, SIZEOF(info->fullJID)-1); if (m_options.SavePassword == FALSE) { if (*m_savedPassword) { _tcsncpy(info->password, m_savedPassword, SIZEOF(info->password)); info->password[ SIZEOF(info->password)-1] = '\0'; } else { mir_sntprintf(jidStr, SIZEOF(jidStr), _T("%s@") _T(TCHAR_STR_PARAM), info->username, info->server); JabberPasswordDlgParam param; param.pro = this; param.ptszJid = jidStr; param.hEventPasswdDlg = CreateEvent(NULL, FALSE, FALSE, NULL); CallFunctionAsync(JabberPasswordCreateDialogApcProc, ¶m); WaitForSingleObject(param.hEventPasswdDlg, INFINITE); CloseHandle(param.hEventPasswdDlg); if (param.dlgResult == IDCANCEL) { JSendBroadcast(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_BADUSERID); Log("Thread ended, password request dialog was canceled"); goto LBL_FatalError; } if (param.saveOnlinePassword) lstrcpy(m_savedPassword, param.onlinePassword); else *m_savedPassword = 0; _tcsncpy(info->password, param.onlinePassword, SIZEOF(info->password)); info->password[ SIZEOF(info->password)-1] = '\0'; } } else { TCHAR *passw = JGetStringCrypt(NULL, "LoginPassword"); if (passw == NULL) { JSendBroadcast(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_BADUSERID); Log("Thread ended, password is not configured"); goto LBL_FatalError; } _tcsncpy(info->password, passw, SIZEOF(info->password)); info->password[SIZEOF(info->password)-1] = '\0'; mir_free(passw); } } else if (info->type == JABBER_SESSION_REGISTER) { // Register new user connection, all connection parameters are already filled-in. // Multiple thread allowed, although not possible :) // thinking again.. multiple thread should not be allowed info->reg_done = FALSE; SendMessage(info->reg_hwndDlg, WM_JABBER_REGDLG_UPDATE, 25, (LPARAM)TranslateT("Connecting...")); iqIdRegGetReg = -1; iqIdRegSetReg = -1; } else { Log("Thread ended, invalid session type"); goto LBL_FatalError; } int jabberNetworkBufferSize = 2048; if ((buffer=(char*)mir_alloc(jabberNetworkBufferSize + 1)) == NULL) { // +1 is for '\0' when debug logging this buffer Log("Cannot allocate network buffer, thread ended"); if (info->type == JABBER_SESSION_NORMAL) { JSendBroadcast(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_NONETWORK); } else if (info->type == JABBER_SESSION_REGISTER) { SendMessage(info->reg_hwndDlg, WM_JABBER_REGDLG_UPDATE, 100, (LPARAM)TranslateT("Error: Not enough memory")); } Log("Thread ended, network buffer cannot be allocated"); goto LBL_FatalError; } if (info->manualHost[0] == 0) { info->xmpp_client_query(); if (info->s == NULL) { strncpy(info->manualHost, info->server, SIZEOF(info->manualHost)); info->s = WsConnect(info->manualHost, info->port); } } else info->s = WsConnect(info->manualHost, info->port); Log("Thread type=%d server='%s' port='%d'", info->type, info->manualHost, info->port); if (info->s == NULL) { Log("Connection failed (%d)", WSAGetLastError()); if (info->type == JABBER_SESSION_NORMAL) { if (m_ThreadInfo == info) { JSendBroadcast(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_NONETWORK); } } else if (info->type == JABBER_SESSION_REGISTER) SendMessage(info->reg_hwndDlg, WM_JABBER_REGDLG_UPDATE, 100, (LPARAM)TranslateT("Error: Cannot connect to the server")); Log("Thread ended, connection failed"); mir_free(buffer); goto LBL_FatalError; } // Determine local IP if (info->useSSL) { Log("Intializing SSL connection"); if ( !CallService(MS_NETLIB_STARTSSL, (WPARAM)info->s, 0)) { Log("SSL intialization failed"); if (info->type == JABBER_SESSION_NORMAL) { JSendBroadcast(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_NONETWORK); } else if (info->type == JABBER_SESSION_REGISTER) { SendMessage(info->reg_hwndDlg, WM_JABBER_REGDLG_UPDATE, 100, (LPARAM)TranslateT("Error: Cannot connect to the server")); } mir_free(buffer); info->close(); Log("Thread ended, SSL connection failed"); goto LBL_FatalError; } } // User may change status to OFFLINE while we are connecting above if (m_iDesiredStatus != ID_STATUS_OFFLINE || info->type == JABBER_SESSION_REGISTER) { if (info->type == JABBER_SESSION_NORMAL) { m_bJabberConnected = TRUE; size_t len = _tcslen(info->username) + strlen(info->server)+1; m_szJabberJID = (TCHAR*)mir_alloc(sizeof(TCHAR)*(len+1)); mir_sntprintf(m_szJabberJID, len+1, _T("%s@") _T(TCHAR_STR_PARAM), info->username, info->server); m_bSendKeepAlive = m_options.KeepAlive != 0; JSetStringT(NULL, "jid", m_szJabberJID); // store jid in database } xmlStreamInitializeNow(info); const TCHAR *tag = _T("stream:stream"); Log("Entering main recv loop"); datalen = 0; // cache values DWORD dwConnectionKeepAliveInterval = m_options.ConnectionKeepAliveInterval; for (;;) { if ( !info->useZlib || info->zRecvReady) { DWORD dwIdle = GetTickCount() - m_lastTicks; if (dwIdle >= dwConnectionKeepAliveInterval) dwIdle = dwConnectionKeepAliveInterval - 10; // now! NETLIBSELECT nls = {0}; nls.cbSize = sizeof(NETLIBSELECT); nls.dwTimeout = dwConnectionKeepAliveInterval - dwIdle; nls.hReadConns[0] = info->s; int nSelRes = CallService(MS_NETLIB_SELECT, 0, (LPARAM)&nls); if (nSelRes == -1) // error break; else if (nSelRes == 0 && m_bSendKeepAlive) { if (m_ThreadInfo->jabberServerCaps & JABBER_CAPS_PING) { CJabberIqInfo* pInfo = m_iqManager.AddHandler(&CJabberProto::OnPingReply, JABBER_IQ_TYPE_GET, NULL, 0, -1, this); pInfo->SetTimeout(m_options.ConnectionKeepAliveTimeout); info->send( XmlNodeIq(pInfo) << XATTR(_T("from"), m_ThreadInfo->fullJID) << XCHILDNS(_T("ping"), _T(JABBER_FEAT_PING))); } else info->send(" \t "); continue; } } int recvResult = info->recv(buffer + datalen, jabberNetworkBufferSize - datalen); Log("recvResult = %d", recvResult); if (recvResult <= 0) break; datalen += recvResult; recvRest: buffer[datalen] = '\0'; TCHAR* str; str = mir_utf8decodeW(buffer); int bytesParsed = 0; XmlNode root(str, &bytesParsed, tag); if (root && tag) { char *p = strstr(buffer, "stream:stream"); if (p) p = strchr(p, '>'); if (p) bytesParsed = p - buffer + 1; else { root = XmlNode(); bytesParsed = 0; } mir_free(str); } else { if (root) str[ bytesParsed ] = 0; bytesParsed = (root) ? mir_utf8lenW(str) : 0; mir_free(str); } Log("bytesParsed = %d", bytesParsed); if (root) tag = NULL; if (xmlGetName(root) == NULL) { for (int i=0; ; i++) { HXML n = xmlGetChild(root , i); if ( !n) break; OnProcessProtocol(n, info); } } else OnProcessProtocol(root, info); if (bytesParsed > 0) { if (bytesParsed < datalen) memmove(buffer, buffer+bytesParsed, datalen-bytesParsed); datalen -= bytesParsed; } else if (datalen >= jabberNetworkBufferSize) { //jabberNetworkBufferSize += 65536; jabberNetworkBufferSize *= 2; Log("Increasing network buffer size to %d", jabberNetworkBufferSize); if ((buffer=(char*)mir_realloc(buffer, jabberNetworkBufferSize + 1)) == NULL) { Log("Cannot reallocate more network buffer, go offline now"); break; } } else Log("Unknown state: bytesParsed=%d, datalen=%d, jabberNetworkBufferSize=%d", bytesParsed, datalen, jabberNetworkBufferSize); if (m_szXmlStreamToBeInitialized) { xmlStreamInitializeNow(info); tag = _T("stream:stream"); } if (root && datalen) goto recvRest; } if (info->type == JABBER_SESSION_NORMAL) { m_iqManager.ExpireAll(info); m_bJabberOnline = FALSE; m_bJabberConnected = FALSE; info->zlibUninit(); EnableMenuItems(FALSE); RebuildInfoFrame(); if (m_hwndJabberChangePassword) { //DestroyWindow(hwndJabberChangePassword); // Since this is a different thread, simulate the click on the cancel button instead SendMessage(m_hwndJabberChangePassword, WM_COMMAND, MAKEWORD(IDCANCEL, 0), 0); } if (jabberChatDllPresent) CallFunctionAsync(JabberOfflineChatWindows, this); ListRemoveList(LIST_CHATROOM); ListRemoveList(LIST_BOOKMARK); //UI_SAFE_NOTIFY(m_pDlgJabberJoinGroupchat, WM_JABBER_CHECK_ONLINE); UI_SAFE_NOTIFY_HWND(m_hwndJabberAddBookmark, WM_JABBER_CHECK_ONLINE); //UI_SAFE_NOTIFY(m_pDlgBookmarks, WM_JABBER_CHECK_ONLINE); WindowNotify(WM_JABBER_CHECK_ONLINE); // Set status to offline oldStatus = m_iStatus; m_iDesiredStatus = m_iStatus = ID_STATUS_OFFLINE; JSendBroadcast(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); // Set all contacts to offline HANDLE hContact = (HANDLE)db_find_first(); while (hContact != NULL) { if ( !lstrcmpA(GetContactProto(hContact), m_szModuleName)) { SetContactOfflineStatus(hContact); MenuHideSrmmIcon(hContact); } hContact = db_find_next(hContact); } mir_free(m_szJabberJID); m_szJabberJID = NULL; m_tmJabberLoggedInTime = 0; ListWipe(); WindowNotify(WM_JABBER_REFRESH_VCARD); } else if (info->type==JABBER_SESSION_REGISTER && !info->reg_done) SendMessage(info->reg_hwndDlg, WM_JABBER_REGDLG_UPDATE, 100, (LPARAM)TranslateT("Error: Connection lost")); } else if (info->type == JABBER_SESSION_NORMAL) { oldStatus = m_iStatus; m_iDesiredStatus = m_iStatus = ID_STATUS_OFFLINE; JSendBroadcast(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); } Log("Thread ended: type=%d server='%s'", info->type, info->server); if (info->type==JABBER_SESSION_NORMAL && m_ThreadInfo==info) { if (m_szStreamId) mir_free(m_szStreamId); m_szStreamId = NULL; m_ThreadInfo = NULL; } info->close(); mir_free(buffer); Log("Exiting ServerThread"); goto LBL_Exit; } void CJabberProto::PerformRegistration(ThreadData* info) { iqIdRegGetReg = SerialNext(); info->send( XmlNodeIq(_T("get"), iqIdRegGetReg, NULL) << XQUERY(_T(JABBER_FEAT_REGISTER))); SendMessage(info->reg_hwndDlg, WM_JABBER_REGDLG_UPDATE, 50, (LPARAM)TranslateT("Requesting registration instruction...")); } void CJabberProto::PerformIqAuth(ThreadData* info) { if (info->type == JABBER_SESSION_NORMAL) { int iqId = SerialNext(); IqAdd(iqId, IQ_PROC_NONE, &CJabberProto::OnIqResultGetAuth); info->send( XmlNodeIq(_T("get"), iqId) << XQUERY(_T("jabber:iq:auth")) << XCHILD(_T("username"), info->username)); } else if (info->type == JABBER_SESSION_REGISTER) PerformRegistration(info); } ///////////////////////////////////////////////////////////////////////////////////////// void CJabberProto::OnProcessStreamOpening(HXML node, ThreadData *info) { if (lstrcmp(xmlGetName(node), _T("stream:stream"))) return; if (info->type == JABBER_SESSION_NORMAL) { const TCHAR *sid = xmlGetAttrValue(node, _T("id")); if (sid != NULL) { char* pszSid = mir_t2a(sid); replaceStr(info->proto->m_szStreamId, pszSid); mir_free(pszSid); } } // old server - disable SASL then if (xmlGetAttrValue(node, _T("version")) == NULL) info->proto->m_options.Disable3920auth = TRUE; if (info->proto->m_options.Disable3920auth) info->proto->PerformIqAuth(info); } void CJabberProto::PerformAuthentication(ThreadData* info) { TJabberAuth* auth = NULL; char* request = NULL; if (info->auth) { delete info->auth; info->auth = NULL; } if (m_AuthMechs.isSpnegoAvailable) { m_AuthMechs.isSpnegoAvailable = false; auth = new TNtlmAuth(info, "GSS-SPNEGO"); if ( !auth->isValid()) { delete auth; auth = NULL; } } if (auth == NULL && m_AuthMechs.isNtlmAvailable) { m_AuthMechs.isNtlmAvailable = false; auth = new TNtlmAuth(info, "NTLM"); if ( !auth->isValid()) { delete auth; auth = NULL; } } if (auth == NULL && m_AuthMechs.isKerberosAvailable) { m_AuthMechs.isKerberosAvailable = false; auth = new TNtlmAuth(info, "GSSAPI", m_AuthMechs.m_gssapiHostName); if ( !auth->isValid()) { delete auth; auth = NULL; } else { request = auth->getInitialRequest(); if ( !request) { delete auth; auth = NULL; } } } if (auth == NULL && m_AuthMechs.isScramAvailable) { m_AuthMechs.isScramAvailable = false; auth = new TScramAuth(info); } if (auth == NULL && m_AuthMechs.isMd5Available) { m_AuthMechs.isMd5Available = false; auth = new TMD5Auth(info); } if (auth == NULL && m_AuthMechs.isPlainAvailable) { m_AuthMechs.isPlainAvailable = false; auth = new TPlainAuth(info, false); } if (auth == NULL && m_AuthMechs.isPlainOldAvailable) { m_AuthMechs.isPlainOldAvailable = false; auth = new TPlainAuth(info, true); } if (auth == NULL) { if (m_AuthMechs.isAuthAvailable) { // no known mechanisms but iq_auth is available m_AuthMechs.isAuthAvailable = false; PerformIqAuth(info); return; } TCHAR text[1024]; mir_sntprintf(text, SIZEOF(text), _T("%s %s@")_T(TCHAR_STR_PARAM)_T("."), TranslateT("Authentication failed for"), info->username, info->server); MsgPopup(NULL, text, TranslateT("Jabber Authentication")); JSendBroadcast(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_WRONGPASSWORD); info->send(""); m_ThreadInfo = NULL; return; } info->auth = auth; if ( !request) request = auth->getInitialRequest(); info->send(XmlNode(_T("auth"), _A2T(request)) << XATTR(_T("xmlns"), _T("urn:ietf:params:xml:ns:xmpp-sasl")) << XATTR(_T("mechanism"), _A2T(auth->getName()))); mir_free(request); } ///////////////////////////////////////////////////////////////////////////////////////// void CJabberProto::OnProcessFeatures(HXML node, ThreadData* info) { bool isRegisterAvailable = false; bool areMechanismsDefined = false; for (int i=0; ;i++) { HXML n = xmlGetChild(node ,i); if ( !n) break; if ( !_tcscmp(xmlGetName(n), _T("starttls"))) { if ( !info->useSSL && m_options.UseTLS) { Log("Requesting TLS"); info->send(XmlNode(xmlGetName(n)) << XATTR(_T("xmlns"), _T("urn:ietf:params:xml:ns:xmpp-tls"))); return; } } if ( !_tcscmp(xmlGetName(n), _T("compression")) && m_options.EnableZlib == TRUE) { Log("Server compression available"); for (int k=0; ; k++) { HXML c = xmlGetChild(n ,k); if ( !c) break; if ( !_tcscmp(xmlGetName(c), _T("method"))) { if ( !_tcscmp(xmlGetText(c), _T("zlib")) && info->zlibInit() == TRUE) { Log("Requesting Zlib compression"); info->send(XmlNode(_T("compress")) << XATTR(_T("xmlns"), _T("http://jabber.org/protocol/compress")) << XCHILD(_T("method"), _T("zlib"))); return; } } } } if ( !_tcscmp(xmlGetName(n), _T("mechanisms"))) { m_AuthMechs.isPlainAvailable = false; m_AuthMechs.isPlainOldAvailable = false; m_AuthMechs.isMd5Available = false; m_AuthMechs.isScramAvailable = false; m_AuthMechs.isNtlmAvailable = false; m_AuthMechs.isSpnegoAvailable = false; m_AuthMechs.isKerberosAvailable = false; mir_free(m_AuthMechs.m_gssapiHostName); m_AuthMechs.m_gssapiHostName = NULL; areMechanismsDefined = true; //JabberLog("%d mechanisms\n",n->numChild); for (int k=0; ; k++) { HXML c = xmlGetChild(n ,k); if ( !c) break; if ( !_tcscmp(xmlGetName(c), _T("mechanism"))) { //JabberLog("Mechanism: %s",xmlGetText(c)); if ( !_tcscmp(xmlGetText(c), _T("PLAIN"))) m_AuthMechs.isPlainOldAvailable = m_AuthMechs.isPlainAvailable = true; else if ( !_tcscmp(xmlGetText(c), _T("DIGEST-MD5"))) m_AuthMechs.isMd5Available = true; else if ( !_tcscmp(xmlGetText(c), _T("SCRAM-SHA-1"))) m_AuthMechs.isScramAvailable = true; else if ( !_tcscmp(xmlGetText(c), _T("NTLM"))) m_AuthMechs.isNtlmAvailable = true; else if ( !_tcscmp(xmlGetText(c), _T("GSS-SPNEGO"))) m_AuthMechs.isSpnegoAvailable = true; else if ( !_tcscmp(xmlGetText(c), _T("GSSAPI"))) m_AuthMechs.isKerberosAvailable = true; } else if ( !_tcscmp(xmlGetName(c), _T("hostname"))) { const TCHAR *mech = xmlGetAttrValue(c, _T("mechanism")); if (mech && _tcsicmp(mech, _T("GSSAPI")) == 0) { m_AuthMechs.m_gssapiHostName = mir_tstrdup(xmlGetText(c)); } } } } else if ( !_tcscmp(xmlGetName(n), _T("register"))) isRegisterAvailable = true; else if ( !_tcscmp(xmlGetName(n), _T("auth" ))) m_AuthMechs.isAuthAvailable = true; else if ( !_tcscmp(xmlGetName(n), _T("session" ))) m_AuthMechs.isSessionAvailable = true; } if (areMechanismsDefined) { if (info->type == JABBER_SESSION_NORMAL) PerformAuthentication(info); else if (info->type == JABBER_SESSION_REGISTER) PerformRegistration(info); else info->send(""); return; } // mechanisms are not defined. if (info->auth) { //We are already logged-in info->send( XmlNodeIq(m_iqManager.AddHandler(&CJabberProto::OnIqResultBind, JABBER_IQ_TYPE_SET)) << XCHILDNS(_T("bind"), _T("urn:ietf:params:xml:ns:xmpp-bind")) << XCHILD(_T("resource"), info->resource)); if (m_AuthMechs.isSessionAvailable) info->bIsSessionAvailable = TRUE; return; } //mechanisms not available and we are not logged in PerformIqAuth(info); } void CJabberProto::OnProcessFailure(HXML node, ThreadData* info) { const TCHAR *type; //failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" if ((type = xmlGetAttrValue(node, _T("xmlns"))) == NULL) return; if ( !_tcscmp(type, _T("urn:ietf:params:xml:ns:xmpp-sasl"))) { PerformAuthentication(info); } } void CJabberProto::OnProcessError(HXML node, ThreadData* info) { TCHAR *buff; int i; int pos; bool skipMsg = false; //failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" if ( !xmlGetChild(node ,0)) return; buff = (TCHAR *)mir_alloc(1024*sizeof(TCHAR)); pos=0; for (i=0; ; i++) { HXML n = xmlGetChild(node , i); if ( !n) break; const TCHAR *name = xmlGetName(n); const TCHAR *desc = xmlGetText(n); if (desc) pos += mir_sntprintf(buff+pos, 1024-pos, _T("%s: %s\r\n"), name, desc); else pos += mir_sntprintf(buff+pos, 1024-pos, _T("%s\r\n"), name); if ( !_tcscmp(name, _T("conflict"))) JSendBroadcast(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_OTHERLOCATION); else if ( !_tcscmp(name, _T("see-other-host"))) { skipMsg = true; } } if ( !skipMsg) MsgPopup(NULL, buff, TranslateT("Jabber Error")); mir_free(buff); info->send(""); } void CJabberProto::OnProcessSuccess(HXML node, ThreadData* info) { const TCHAR *type; // int iqId; // RECVED: auth->validateLogin(xmlGetText(node))) { info->send(""); return; } Log("Success: Logged-in."); if ( DBGetContactSettingString(NULL, m_szModuleName, "Nick", &dbv)) JSetStringT(NULL, "Nick", info->username); else db_free(&dbv); xmlStreamInitialize("after successful sasl"); } else Log("Success: unknown action "TCHAR_STR_PARAM".",type); } void CJabberProto::OnProcessChallenge(HXML node, ThreadData* info) { if (info->auth == NULL) { Log("No previous auth have been made, exiting..."); return; } if (lstrcmp(xmlGetAttrValue(node, _T("xmlns")), _T("urn:ietf:params:xml:ns:xmpp-sasl"))) return; char* challenge = info->auth->getChallenge(xmlGetText(node)); info->send(XmlNode(_T("response"), _A2T(challenge)) << XATTR(_T("xmlns"), _T("urn:ietf:params:xml:ns:xmpp-sasl"))); mir_free(challenge); } void CJabberProto::OnProcessProtocol(HXML node, ThreadData* info) { OnConsoleProcessXml(node, JCPF_IN); if ( !lstrcmp(xmlGetName(node), _T("proceed"))) OnProcessProceed(node, info); else if ( !lstrcmp(xmlGetName(node), _T("compressed"))) OnProcessCompressed(node, info); else if ( !lstrcmp(xmlGetName(node), _T("stream:features"))) OnProcessFeatures(node, info); else if ( !lstrcmp(xmlGetName(node), _T("stream:stream"))) OnProcessStreamOpening(node, info); else if ( !lstrcmp(xmlGetName(node), _T("success"))) OnProcessSuccess(node, info); else if ( !lstrcmp(xmlGetName(node), _T("failure"))) OnProcessFailure(node, info); else if ( !lstrcmp(xmlGetName(node), _T("stream:error"))) OnProcessError(node, info); else if ( !lstrcmp(xmlGetName(node), _T("challenge"))) OnProcessChallenge(node, info); else if (info->type == JABBER_SESSION_NORMAL) { if ( !lstrcmp(xmlGetName(node), _T("message"))) OnProcessMessage(node, info); else if ( !lstrcmp(xmlGetName(node), _T("presence"))) OnProcessPresence(node, info); else if ( !lstrcmp(xmlGetName(node), _T("iq"))) OnProcessIq(node); else Log("Invalid top-level tag (only and allowed)"); } else if (info->type == JABBER_SESSION_REGISTER) { if ( !lstrcmp(xmlGetName(node), _T("iq"))) OnProcessRegIq(node, info); else Log("Invalid top-level tag (only allowed)"); } } void CJabberProto::OnProcessProceed(HXML node, ThreadData* info) { const TCHAR *type; if ((type = xmlGetAttrValue(node, _T("xmlns"))) != NULL && !lstrcmp(type, _T("error"))) return; if ( !lstrcmp(type, _T("urn:ietf:params:xml:ns:xmpp-tls"))) { Log("Starting TLS..."); char* gtlk = strstr(info->manualHost, "google.com"); bool isHosted = gtlk && !gtlk[10] && stricmp(info->server, "gmail.com") && stricmp(info->server, "googlemail.com"); NETLIBSSL ssl = {0}; ssl.cbSize = sizeof(ssl); ssl.host = isHosted ? info->manualHost : info->server; if ( !CallService(MS_NETLIB_STARTSSL, (WPARAM)info->s, (LPARAM)&ssl)) { Log("SSL initialization failed"); info->send(""); info->shutdown(); } else xmlStreamInitialize("after successful StartTLS"); } } void CJabberProto::OnProcessCompressed(HXML node, ThreadData* info) { const TCHAR *type; Log("Compression confirmed"); if ((type = xmlGetAttrValue(node, _T("xmlns"))) != NULL && !lstrcmp(type, _T("error"))) return; if (lstrcmp(type, _T("http://jabber.org/protocol/compress"))) return; Log("Starting Zlib stream compression..."); info->useZlib = TRUE; info->zRecvData = (char*)mir_alloc(ZLIB_CHUNK_SIZE); xmlStreamInitialize("after successful Zlib init"); } void CJabberProto::OnProcessPubsubEvent(HXML node) { const TCHAR *from = xmlGetAttrValue(node, _T("from")); if ( !from) return; HXML eventNode = xmlGetChildByTag(node, "event", "xmlns", _T(JABBER_FEAT_PUBSUB_EVENT)); if ( !eventNode) return; m_pepServices.ProcessEvent(from, eventNode); HANDLE hContact = HContactFromJID(from); if ( !hContact) return; HXML itemsNode; if (m_options.EnableUserTune && (itemsNode = xmlGetChildByTag(eventNode, "items", "node", _T(JABBER_FEAT_USER_TUNE)))) { // node retract? if (xmlGetChild(itemsNode , "retract")) { SetContactTune(hContact, NULL, NULL, NULL, NULL, NULL); return; } HXML tuneNode = XPath(itemsNode, _T("item/tune[@xmlns='") _T(JABBER_FEAT_USER_TUNE) _T("']")); if ( !tuneNode) return; const TCHAR *szArtist = XPathT(tuneNode, "artist"); const TCHAR *szLength = XPathT(tuneNode, "length"); const TCHAR *szSource = XPathT(tuneNode, "source"); const TCHAR *szTitle = XPathT(tuneNode, "title"); const TCHAR *szTrack = XPathT(tuneNode, "track"); TCHAR szLengthInTime[32]; szLengthInTime[0] = _T('\0'); if (szLength) { int nLength = _ttoi(szLength); mir_sntprintf(szLengthInTime, SIZEOF(szLengthInTime), _T("%02d:%02d:%02d"), nLength / 3600, (nLength / 60) % 60, nLength % 60); } SetContactTune(hContact, szArtist, szLength ? szLengthInTime : NULL, szSource, szTitle, szTrack); } } // returns 0, if error or no events DWORD JabberGetLastContactMessageTime(HANDLE hContact) { // TODO: time cache can improve performance HANDLE hDbEvent = (HANDLE)CallService(MS_DB_EVENT_FINDLAST, (WPARAM)hContact, 0); if ( !hDbEvent) return 0; DWORD dwTime = 0; DBEVENTINFO dbei = { 0 }; dbei.cbSize = sizeof(dbei); dbei.cbBlob = CallService(MS_DB_EVENT_GETBLOBSIZE, (WPARAM)hDbEvent, 0); if (dbei.cbBlob != -1) { dbei.pBlob = (PBYTE)mir_alloc(dbei.cbBlob + 1); int nGetTextResult = CallService(MS_DB_EVENT_GET, (WPARAM)hDbEvent, (LPARAM)&dbei); if ( !nGetTextResult) dwTime = dbei.timestamp; mir_free(dbei.pBlob); } return dwTime; } HANDLE CJabberProto::CreateTemporaryContact(const TCHAR *szJid, JABBER_LIST_ITEM* chatItem) { HANDLE hContact = NULL; if (chatItem) { const TCHAR *p = _tcschr(szJid, '/'); if (p != NULL && p[1] != '\0') p++; else p = szJid; hContact = DBCreateContact(szJid, p, TRUE, FALSE); for (int i=0; i < chatItem->resourceCount; i++) { if ( !lstrcmp(chatItem->resource[i].resourceName, p)) { JSetWord(hContact, "Status", chatItem->resource[i].status); break; } } } else { TCHAR *nick = JabberNickFromJID(szJid); hContact = DBCreateContact(szJid, nick, TRUE, TRUE); mir_free(nick); } return hContact; } void CJabberProto::OnProcessMessage(HXML node, ThreadData* info) { HXML subjectNode, xNode, inviteNode, idNode, n; LPCTSTR from, type, idStr, fromResource; HANDLE hContact; if ( !xmlGetName(node) || _tcscmp(xmlGetName(node), _T("message"))) return; type = xmlGetAttrValue(node, _T("type")); if ((from = xmlGetAttrValue(node, _T("from"))) == NULL) return; idStr = xmlGetAttrValue(node, _T("id")); JABBER_RESOURCE_STATUS *resourceStatus = ResourceInfoFromJID(from); // Message receipts delivery request. Reply here, before a call to HandleMessagePermanent() to make sure message receipts are handled for external plugins too. if ((!type || _tcsicmp(type, _T("error"))) && xmlGetChildByTag(node, "request", "xmlns", _T(JABBER_FEAT_MESSAGE_RECEIPTS))) { info->send( XmlNode(_T("message")) << XATTR(_T("to"), from) << XATTR(_T("id"), idStr) << XCHILDNS(_T("received"), _T(JABBER_FEAT_MESSAGE_RECEIPTS)) << XATTR(_T("id"), idStr)); if (resourceStatus) resourceStatus->jcbManualDiscoveredCaps |= JABBER_CAPS_MESSAGE_RECEIPTS; } if (m_messageManager.HandleMessagePermanent(node, info)) return; hContact = HContactFromJID(from); JABBER_LIST_ITEM *chatItem = ListGetItemPtr(LIST_CHATROOM, from); if (chatItem) { HXML xCaptcha = xmlGetChild(node, "captcha"); if (xCaptcha) if (ProcessCaptcha(xCaptcha, node, info)) return; } const TCHAR *szMessage = NULL; HXML bodyNode = xmlGetChildByTag(node , "body", "xml:lang", m_tszSelectedLang); if (bodyNode == NULL) bodyNode = xmlGetChild(node , "body"); if (bodyNode != NULL && xmlGetText(bodyNode)) szMessage = xmlGetText(bodyNode); if ((subjectNode = xmlGetChild(node , "subject")) && xmlGetText(subjectNode) && xmlGetText(subjectNode)[0] != _T('\0')) { size_t cbLen = (szMessage ? _tcslen(szMessage) : 0) + _tcslen(xmlGetText(subjectNode)) + 128; TCHAR* szTmp = (TCHAR *)alloca(sizeof(TCHAR) * cbLen); szTmp[0] = _T('\0'); if (szMessage) _tcscat(szTmp, _T("Subject: ")); _tcscat(szTmp, xmlGetText(subjectNode)); if (szMessage) { _tcscat(szTmp, _T("\r\n")); _tcscat(szTmp, szMessage); } szMessage = szTmp; } if (szMessage && (n = xmlGetChildByTag(node, "addresses", "xmlns", _T(JABBER_FEAT_EXT_ADDRESSING)))) { HXML addressNode = xmlGetChildByTag(n, "address", "type", _T("ofrom")); if (addressNode) { const TCHAR *szJid = xmlGetAttrValue(addressNode, _T("jid")); if (szJid) { size_t cbLen = _tcslen(szMessage) + 1000; TCHAR* p = (TCHAR*)alloca(sizeof(TCHAR) * cbLen); mir_sntprintf(p, cbLen, TranslateT("Message redirected from: %s\r\n%s"), from, szMessage); szMessage = p; from = szJid; // rewrite hContact hContact = HContactFromJID(from); } } } // If message is from a stranger (not in roster), item is NULL JABBER_LIST_ITEM* item = ListGetItemPtr(LIST_ROSTER, from); if ( !item) item = ListGetItemPtr(LIST_VCARD_TEMP, from); time_t msgTime = 0; BOOL isChatRoomInvitation = FALSE; const TCHAR *inviteRoomJid = NULL; const TCHAR *inviteFromJid = NULL; const TCHAR *inviteReason = NULL; const TCHAR *invitePassword = NULL; BOOL delivered = FALSE; // check chatstates availability if (resourceStatus && xmlGetChildByTag(node, "active", "xmlns", _T(JABBER_FEAT_CHATSTATES))) resourceStatus->jcbManualDiscoveredCaps |= JABBER_CAPS_CHATSTATES; // chatstates composing event if (hContact && xmlGetChildByTag(node, "composing", "xmlns", _T(JABBER_FEAT_CHATSTATES))) CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hContact, 60); // chatstates paused event if (hContact && xmlGetChildByTag(node, "paused", "xmlns", _T(JABBER_FEAT_CHATSTATES))) CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hContact, PROTOTYPE_CONTACTTYPING_OFF); // chatstates inactive event if (hContact && xmlGetChildByTag(node, "inactive", "xmlns", _T(JABBER_FEAT_CHATSTATES))) CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hContact, PROTOTYPE_CONTACTTYPING_OFF); // message receipts delivery notification if (n = xmlGetChildByTag(node, "received", "xmlns", _T(JABBER_FEAT_MESSAGE_RECEIPTS))) { int nPacketId = JabberGetPacketID(n); if (nPacketId == -1) nPacketId = JabberGetPacketID(node); if (nPacketId != -1) JSendBroadcast(hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)nPacketId, 0); } JabberReadXep203delay(node, msgTime); // XEP-0224 support (Attention/Nudge) if (xmlGetChildByTag(node, "attention", "xmlns", _T(JABBER_FEAT_ATTENTION)) || xmlGetChildByTag(node, "attention", "xmlns", _T(JABBER_FEAT_ATTENTION_0))) { if ( !hContact) hContact = CreateTemporaryContact(from, chatItem); if (hContact) NotifyEventHooks(m_hEventNudge, (WPARAM)hContact, 0); } // chatstates gone event if (hContact && xmlGetChildByTag(node, "gone", "xmlns", _T(JABBER_FEAT_CHATSTATES)) && m_options.LogChatstates) { DBEVENTINFO dbei; BYTE bEventType = JABBER_DB_EVENT_CHATSTATES_GONE; // gone event dbei.cbSize = sizeof(dbei); dbei.pBlob = &bEventType; dbei.cbBlob = 1; dbei.eventType = JABBER_DB_EVENT_TYPE_CHATSTATES; dbei.flags = DBEF_READ; dbei.timestamp = time(NULL); dbei.szModule = m_szModuleName; CallService(MS_DB_EVENT_ADD, (WPARAM)hContact, (LPARAM)&dbei); } if ((n = xmlGetChildByTag(node, "confirm", "xmlns", _T(JABBER_FEAT_HTTP_AUTH))) && m_options.AcceptHttpAuth) { const TCHAR *szId = xmlGetAttrValue(n, _T("id")); const TCHAR *szMethod = xmlGetAttrValue(n, _T("method")); const TCHAR *szUrl = xmlGetAttrValue(n, _T("url")); if ( !szId || !szMethod || !szUrl) return; CJabberHttpAuthParams *pParams = (CJabberHttpAuthParams *)mir_alloc(sizeof(CJabberHttpAuthParams)); if ( !pParams) return; ZeroMemory(pParams, sizeof(CJabberHttpAuthParams)); pParams->m_nType = CJabberHttpAuthParams::MSG; pParams->m_szFrom = mir_tstrdup(from); HXML pThreadNode = xmlGetChild(node , "thread"); if (pThreadNode && xmlGetText(pThreadNode) && xmlGetText(pThreadNode)[0]) pParams->m_szThreadId = mir_tstrdup(xmlGetText(pThreadNode)); pParams->m_szId = mir_tstrdup(szId); pParams->m_szMethod = mir_tstrdup(szMethod); pParams->m_szUrl = mir_tstrdup(szUrl); AddClistHttpAuthEvent(pParams); return; } for (int i = 0; (xNode = xmlGetChild(node, i)) != NULL; i++) { xNode = xmlGetNthChild(node, _T("x"), i + 1); if (xNode == NULL) { xNode = xmlGetNthChild(node, _T("user:x"), i + 1); if (xNode == NULL) continue; } const TCHAR *ptszXmlns = xmlGetAttrValue(xNode, _T("xmlns")); if (ptszXmlns == NULL) ptszXmlns = xmlGetAttrValue(xNode, _T("xmlns:user")); if (ptszXmlns == NULL) continue; if ( !_tcscmp(ptszXmlns, _T(JABBER_FEAT_MIRANDA_NOTES))) { if (OnIncomingNote(from, xmlGetChild(xNode, "note"))) return; } else if ( !_tcscmp(ptszXmlns, _T("jabber:x:encrypted"))) { if (xmlGetText(xNode) == NULL) return; TCHAR* prolog = _T("-----BEGIN PGP MESSAGE-----\r\n\r\n"); TCHAR* epilog = _T("\r\n-----END PGP MESSAGE-----\r\n"); TCHAR* tempstring = (TCHAR*)alloca(sizeof(TCHAR) * (_tcslen(prolog) + _tcslen(xmlGetText(xNode)) + _tcslen(epilog) + 3)); _tcsncpy(tempstring, prolog, _tcslen(prolog) + 1); _tcsncpy(tempstring + _tcslen(prolog), xmlGetText(xNode), _tcslen(xmlGetText(xNode)) + 1); _tcsncpy(tempstring + _tcslen(prolog) + _tcslen(xmlGetText(xNode)), epilog, _tcslen(epilog) + 1); szMessage = tempstring; } else if ( !_tcscmp(ptszXmlns, _T(JABBER_FEAT_DELAY)) && msgTime == 0) { const TCHAR *ptszTimeStamp = xmlGetAttrValue(xNode, _T("stamp")); if (ptszTimeStamp != NULL) msgTime = JabberIsoToUnixTime(ptszTimeStamp); } else if ( !_tcscmp(ptszXmlns, _T(JABBER_FEAT_MESSAGE_EVENTS))) { // set events support only if we discovered caps and if events not already set JabberCapsBits jcbCaps = GetResourceCapabilites(from, TRUE); if (jcbCaps & JABBER_RESOURCE_CAPS_ERROR) jcbCaps = JABBER_RESOURCE_CAPS_NONE; // FIXME: disabled due to expired XEP-0022 and problems with bombus delivery checks // if (jcbCaps && resourceStatus && (!(jcbCaps & JABBER_CAPS_MESSAGE_EVENTS))) // resourceStatus->jcbManualDiscoveredCaps |= (JABBER_CAPS_MESSAGE_EVENTS | JABBER_CAPS_MESSAGE_EVENTS_NO_DELIVERY); if (bodyNode == NULL) { idNode = xmlGetChild(xNode , "id"); if (xmlGetChild(xNode , "delivered") != NULL || xmlGetChild(xNode , "offline") != NULL) { int id = -1; if (idNode != NULL && xmlGetText(idNode) != NULL) if ( !_tcsncmp(xmlGetText(idNode), _T(JABBER_IQID), strlen(JABBER_IQID))) id = _ttoi((xmlGetText(idNode))+strlen(JABBER_IQID)); if (id != -1) JSendBroadcast(hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)id, 0); } if (hContact && xmlGetChild(xNode , "composing") != NULL) CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hContact, 60); // Maybe a cancel to the previous composing HXML child = xmlGetChild(xNode ,0); if (hContact && (!child || (child && idNode != NULL))) CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hContact, PROTOTYPE_CONTACTTYPING_OFF); } else { // Check whether any event is requested if ( !delivered && (n = xmlGetChild(xNode , "delivered")) != NULL) { delivered = TRUE; XmlNode m(_T("message")); m << XATTR(_T("to"), from); HXML x = m << XCHILDNS(_T("x"), _T(JABBER_FEAT_MESSAGE_EVENTS)); x << XCHILD(_T("delivered")); x << XCHILD(_T("id"), idStr); info->send(m); } if (item != NULL && xmlGetChild(xNode , "composing") != NULL) { if (item->messageEventIdStr) mir_free(item->messageEventIdStr); item->messageEventIdStr = (idStr==NULL)?NULL:mir_tstrdup(idStr); } } } else if ( !_tcscmp(ptszXmlns, _T(JABBER_FEAT_OOB2))) { HXML urlNode; if (((urlNode = xmlGetChild(xNode , "url")) != NULL) && xmlGetText(urlNode) && xmlGetText(urlNode)[0] != _T('\0')) { size_t cbLen = (szMessage ? _tcslen(szMessage) : 0) + _tcslen(xmlGetText(urlNode)) + 32; TCHAR* szTmp = (TCHAR *)alloca(sizeof(TCHAR) * cbLen); _tcscpy(szTmp, xmlGetText(urlNode)); if (szMessage) { _tcscat(szTmp, _T("\r\n")); _tcscat(szTmp, szMessage); } szMessage = szTmp; } } else if ( !_tcscmp(ptszXmlns, _T(JABBER_FEAT_MUC_USER))) { inviteNode = xmlGetChild(xNode , _T("invite")); if (inviteNode == NULL) inviteNode = xmlGetChild(xNode , _T("user:invite")); if (inviteNode != NULL) { inviteFromJid = xmlGetAttrValue(inviteNode, _T("from")); n = xmlGetChild(inviteNode , _T("reason")); if (n == NULL) n = xmlGetChild(inviteNode , _T("user:reason")); if (n != NULL) inviteReason = xmlGetText(n); } inviteRoomJid = from; if ( !inviteReason) inviteReason = szMessage; isChatRoomInvitation = TRUE; if ((n = xmlGetChild(xNode , "password")) != NULL) invitePassword = xmlGetText(n); } else if ( !_tcscmp(ptszXmlns, _T(JABBER_FEAT_ROSTER_EXCHANGE)) && item != NULL && (item->subscription == SUB_BOTH || item->subscription == SUB_TO)) { TCHAR chkJID[JABBER_MAX_JID_LEN] = _T("@"); JabberStripJid(from, chkJID + 1, SIZEOF(chkJID) - 1); for (int i = 1; ; i++) { HXML iNode = xmlGetNthChild(xNode , _T("item"), i); if (iNode == NULL) break; const TCHAR *action = xmlGetAttrValue(iNode, _T("action")); const TCHAR *jid = xmlGetAttrValue(iNode, _T("jid")); const TCHAR *nick = xmlGetAttrValue(iNode, _T("name")); const TCHAR *group = xmlGetText(xmlGetChild(iNode, _T("group"))); if (action && jid && _tcsstr(jid, chkJID)) { if ( !_tcscmp(action, _T("add"))) { HANDLE hContact = DBCreateContact(jid, nick, FALSE, FALSE); if (group) db_set_ts(hContact, "CList", "Group", group); } else if ( !_tcscmp(action, _T("modify"))) { // HANDLE hContact = HContactFromJID(jid); } else if ( !_tcscmp(action, _T("delete"))) { HANDLE hContact = HContactFromJID(jid); if (hContact) CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0); } } } } else if ( !isChatRoomInvitation && !_tcscmp(ptszXmlns, _T("jabber:x:conference"))) { inviteRoomJid = xmlGetAttrValue(xNode, _T("jid")); inviteFromJid = from; if (inviteReason == NULL) inviteReason = xmlGetText(xNode); if ( !inviteReason) inviteReason = szMessage; isChatRoomInvitation = TRUE; } } if (isChatRoomInvitation) { if (inviteRoomJid != NULL) { if (m_options.IgnoreMUCInvites) { // FIXME: temporary disabled due to MUC inconsistence on server side /* XmlNode m("message"); xmlAddAttr(m, "to", from); XmlNode xNode = xmlAddChild(m, "x"); xmlAddAttr(xNode, "xmlns", JABBER_FEAT_MUC_USER); XmlNode declineNode = xmlAddChild(xNode, "decline"); xmlAddAttr(declineNode, "from", inviteRoomJid); XmlNode reasonNode = xmlAddChild(declineNode, "reason", "The user has chosen to not accept chat invites"); info->send(m); */ } else GroupchatProcessInvite(inviteRoomJid, inviteFromJid, inviteReason, invitePassword); } return; } if (szMessage) { if ((szMessage = JabberUnixToDosT(szMessage)) == NULL) szMessage = mir_tstrdup(_T("")); char* buf = mir_utf8encodeW(szMessage); if (item != NULL) { if (resourceStatus) resourceStatus->bMessageSessionActive = TRUE; if (hContact != NULL) CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hContact, PROTOTYPE_CONTACTTYPING_OFF); // no we will monitor last resource in all modes if (/*item->resourceMode==RSMODE_LASTSEEN &&*/ (fromResource = _tcschr(from, '/'))!=NULL) { fromResource++; if (*fromResource != '\0') { for (int i=0; iresourceCount; i++) { if ( !lstrcmp(item->resource[i].resourceName, fromResource)) { int nLastSeenResource = item->lastSeenResource; item->lastSeenResource = i; if ((item->resourceMode==RSMODE_LASTSEEN) && (i != nLastSeenResource)) UpdateMirVer(item); break; } } } } } // Create a temporary contact if (hContact == NULL) hContact = CreateTemporaryContact(from, chatItem); time_t now = time(NULL); if ( !msgTime) msgTime = now; if (m_options.FixIncorrectTimestamps && (msgTime > now || (msgTime < (time_t)JabberGetLastContactMessageTime(hContact)))) msgTime = now; PROTORECVEVENT recv; recv.flags = PREF_UTF; recv.timestamp = (DWORD)msgTime; recv.szMessage = buf; EnterCriticalSection(&m_csLastResourceMap); recv.lParam = (LPARAM)AddToLastResourceMap(from); LeaveCriticalSection(&m_csLastResourceMap); ProtoChainRecvMsg(hContact, &recv); mir_free((void*)szMessage); mir_free(buf); } } // XEP-0115: Entity Capabilities void CJabberProto::OnProcessPresenceCapabilites(HXML node) { const TCHAR *from; if ((from = xmlGetAttrValue(node, _T("from"))) == NULL) return; Log("presence: for jid " TCHAR_STR_PARAM, from); JABBER_RESOURCE_STATUS *r = ResourceInfoFromJID(from); if (r == NULL) return; HXML n; // check XEP-0115 support, and old style: if ((n = xmlGetChildByTag(node, "c", "xmlns", _T(JABBER_FEAT_ENTITY_CAPS))) != NULL || (n = xmlGetChildByTag(node, "caps:c", "xmlns:caps", _T(JABBER_FEAT_ENTITY_CAPS))) != NULL || (n = xmlGetChild(node, "c")) != NULL) { const TCHAR *szNode = xmlGetAttrValue(n, _T("node")); const TCHAR *szVer = xmlGetAttrValue(n, _T("ver")); const TCHAR *szExt = xmlGetAttrValue(n, _T("ext")); if (szNode && szVer) { replaceStrT(r->szCapsNode, szNode); replaceStrT(r->szCapsVer, szVer); replaceStrT(r->szCapsExt, szExt); HANDLE hContact = HContactFromJID(from); if (hContact) UpdateMirVer(hContact, r); } } // update user's caps // JabberCapsBits jcbCaps = GetResourceCapabilites(from, TRUE); } void CJabberProto::UpdateJidDbSettings(const TCHAR *jid) { JABBER_LIST_ITEM *item = ListGetItemPtr(LIST_ROSTER, jid); if ( !item) return; HANDLE hContact = HContactFromJID(jid); if ( !hContact) return; int status = ID_STATUS_OFFLINE; if ( !item->resourceCount) { // set offline only if jid has resources if (_tcschr(jid, '/')==NULL) status = item->itemResource.status; if (item->itemResource.statusMessage) db_set_ts(hContact, "CList", "StatusMsg", item->itemResource.statusMessage); else db_unset(hContact, "CList", "StatusMsg"); } // Determine status to show for the contact based on the remaining resources int nSelectedResource = -1, i = 0; int nMaxPriority = -999; // -128...+127 valid range for (i = 0; i < item->resourceCount; i++) { if (item->resource[i].priority > nMaxPriority) { nMaxPriority = item->resource[i].priority; status = item->resource[i].status; nSelectedResource = i; } else if (item->resource[i].priority == nMaxPriority) { if ((status = JabberCombineStatus(status, item->resource[i].status)) == item->resource[i].status) nSelectedResource = i; } } item->itemResource.status = status; if (nSelectedResource != -1) { Log("JabberUpdateJidDbSettings: updating jid " TCHAR_STR_PARAM " to rc " TCHAR_STR_PARAM, item->jid, item->resource[nSelectedResource].resourceName); if (item->resource[nSelectedResource].statusMessage) db_set_ts(hContact, "CList", "StatusMsg", item->resource[nSelectedResource].statusMessage); else db_unset(hContact, "CList", "StatusMsg"); UpdateMirVer(hContact, &item->resource[nSelectedResource]); } else JDeleteSetting(hContact, DBSETTING_DISPLAY_UID); if (_tcschr(jid, '@') != NULL || m_options.ShowTransport == TRUE) if (JGetWord(hContact, "Status", ID_STATUS_OFFLINE) != status) JSetWord(hContact, "Status", (WORD)status); if (status == ID_STATUS_OFFLINE) { // remove x-status icon JDeleteSetting(hContact, DBSETTING_XSTATUSID); JDeleteSetting(hContact, DBSETTING_XSTATUSNAME); JDeleteSetting(hContact, DBSETTING_XSTATUSMSG); //JabberUpdateContactExtraIcon(hContact); } MenuUpdateSrmmIcon(item); } void CJabberProto::OnProcessPresence(HXML node, ThreadData* info) { HANDLE hContact; HXML showNode, statusNode, priorityNode; JABBER_LIST_ITEM *item; LPCTSTR from, show, p; TCHAR *nick; if ( !node || !xmlGetName(node) ||_tcscmp(xmlGetName(node), _T("presence"))) return; if ((from = xmlGetAttrValue(node, _T("from"))) == NULL) return; if (m_presenceManager.HandlePresencePermanent(node, info)) return; if (ListExist(LIST_CHATROOM, from)) { GroupchatProcessPresence(node); return; } BOOL bSelfPresence = FALSE; TCHAR szBareFrom[ JABBER_MAX_JID_LEN ]; JabberStripJid(from, szBareFrom, SIZEOF(szBareFrom)); TCHAR szBareOurJid[ JABBER_MAX_JID_LEN ]; JabberStripJid(info->fullJID, szBareOurJid, SIZEOF(szBareOurJid)); if ( !_tcsicmp(szBareFrom, szBareOurJid)) bSelfPresence = TRUE; LPCTSTR type = xmlGetAttrValue(node, _T("type")); if (type == NULL || !_tcscmp(type, _T("available"))) { if ((nick = JabberNickFromJID(from)) == NULL) return; if ((hContact = HContactFromJID(from)) == NULL) { if ( !_tcsicmp(info->fullJID, from) || (!bSelfPresence && !ListExist(LIST_ROSTER, from))) { Log("SKIP Receive presence online from "TCHAR_STR_PARAM" (who is not in my roster and not in list - skiping)", from); mir_free(nick); return; } hContact = DBCreateContact(from, nick, TRUE, TRUE); } if ( !ListExist(LIST_ROSTER, from)) { Log("Receive presence online from "TCHAR_STR_PARAM" (who is not in my roster)", from); ListAdd(LIST_ROSTER, from); } DBCheckIsTransportedContact(from, hContact); int status = ID_STATUS_ONLINE; if ((showNode = xmlGetChild(node , "show")) != NULL) { if ((show = xmlGetText(showNode)) != NULL) { if ( !_tcscmp(show, _T("away"))) status = ID_STATUS_AWAY; else if ( !_tcscmp(show, _T("xa"))) status = ID_STATUS_NA; else if ( !_tcscmp(show, _T("dnd"))) status = ID_STATUS_DND; else if ( !_tcscmp(show, _T("chat"))) status = ID_STATUS_FREECHAT; } } char priority = 0; if ((priorityNode = xmlGetChild(node , "priority")) != NULL && xmlGetText(priorityNode) != NULL) priority = (char)_ttoi(xmlGetText(priorityNode)); if ((statusNode = xmlGetChild(node , "status")) != NULL && xmlGetText(statusNode) != NULL) p = xmlGetText(statusNode); else p = NULL; ListAddResource(LIST_ROSTER, from, status, p, priority); // XEP-0115: Entity Capabilities OnProcessPresenceCapabilites(node); UpdateJidDbSettings(from); if (_tcschr(from, '@')==NULL) { UI_SAFE_NOTIFY(m_pDlgServiceDiscovery, WM_JABBER_TRANSPORT_REFRESH); } Log(TCHAR_STR_PARAM " (" TCHAR_STR_PARAM ") online, set contact status to %s", nick, from, CallService(MS_CLIST_GETSTATUSMODEDESCRIPTION,(WPARAM)status,0)); mir_free(nick); HXML xNode; if (m_options.EnableAvatars) { BOOL hasAvatar = false; BOOL removedAvatar = false; Log("Avatar enabled"); for (int i = 1; (xNode=xmlGetNthChild(node, _T("x"), i)) != NULL; i++) { if ( !lstrcmp(xmlGetAttrValue(xNode, _T("xmlns")), _T("jabber:x:avatar"))) { if ((xNode = xmlGetChild(xNode , "hash")) != NULL && xmlGetText(xNode) != NULL) { JDeleteSetting(hContact,"AvatarXVcard"); Log("AvatarXVcard deleted"); JSetStringT(hContact, "AvatarHash", xmlGetText(xNode)); hasAvatar = true; DBVARIANT dbv; int result = JGetStringT(hContact, "AvatarSaved", &dbv); if (result || lstrcmp(dbv.ptszVal, xmlGetText(xNode))) { Log("Avatar was changed"); JSendBroadcast(hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, NULL, NULL); } else Log("Not broadcasting avatar changed"); if ( !result) db_free(&dbv); } else { removedAvatar = true; } } } if ( !hasAvatar) { //no jabber:x:avatar. try vcard-temp:x:update Log("Not hasXAvatar"); for (int i = 1; (xNode=xmlGetNthChild(node, _T("x"), i)) != NULL; i++) { if ( !lstrcmp(xmlGetAttrValue(xNode, _T("xmlns")), _T("vcard-temp:x:update"))) { if ((xNode = xmlGetChild(xNode , "photo")) != NULL) { LPCTSTR txt = xmlGetText(xNode); if (txt != NULL && txt[0] != 0) { JSetByte(hContact, "AvatarXVcard", 1); Log("AvatarXVcard set"); JSetStringT(hContact, "AvatarHash", txt); hasAvatar = true; DBVARIANT dbv; int result = JGetStringT(hContact, "AvatarSaved", &dbv); if (result || lstrcmp(dbv.ptszVal, txt)) { Log("Avatar was changed. Using vcard-temp:x:update"); JSendBroadcast(hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, NULL, NULL); } else Log("Not broadcasting avatar changed"); if ( !result) db_free(&dbv); } else { removedAvatar = true; } } } } } if ( !hasAvatar && removedAvatar) { Log("Has no avatar"); JDeleteSetting(hContact, "AvatarHash"); DBVARIANT dbv = {0}; if ( !JGetStringT(hContact, "AvatarSaved", &dbv)) { db_free(&dbv); JDeleteSetting(hContact, "AvatarSaved"); JSendBroadcast(hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, NULL, NULL); } } } return; } if ( !_tcscmp(type, _T("unavailable"))) { hContact = HContactFromJID(from); if ((item = ListGetItemPtr(LIST_ROSTER, from)) != NULL) { ListRemoveResource(LIST_ROSTER, from); hContact = HContactFromJID(from); if (hContact && db_get_b(hContact, "CList", "NotOnList", 0) == 1) { // remove selfcontact, if where is no more another resources if (item->resourceCount == 1 && ResourceInfoFromJID(info->fullJID)) ListRemoveResource(LIST_ROSTER, info->fullJID); } // set status only if no more available resources if ( !item->resourceCount) { item->itemResource.status = ID_STATUS_OFFLINE; if (((statusNode = xmlGetChild(node , "status")) != NULL) && xmlGetText(statusNode)) replaceStrT(item->itemResource.statusMessage, xmlGetText(statusNode)); else replaceStrT(item->itemResource.statusMessage, NULL); } } else Log("SKIP Receive presence offline from " TCHAR_STR_PARAM " (who is not in my roster)", from); UpdateJidDbSettings(from); if (_tcschr(from, '@')==NULL) { UI_SAFE_NOTIFY(m_pDlgServiceDiscovery, WM_JABBER_TRANSPORT_REFRESH); } DBCheckIsTransportedContact(from, hContact); return; } if ( !_tcscmp(type, _T("subscribe"))) { if (hContact = HContactFromJID(from)) AddDbPresenceEvent(hContact, JABBER_DB_EVENT_PRESENCE_SUBSCRIBE); // automatically send authorization allowed to agent/transport if (_tcschr(from, '@') == NULL || m_options.AutoAcceptAuthorization) { ListAdd(LIST_ROSTER, from); info->send(XmlNode(_T("presence")) << XATTR(_T("to"), from) << XATTR(_T("type"), _T("subscribed"))); if (m_options.AutoAdd == TRUE) { if ((item = ListGetItemPtr(LIST_ROSTER, from)) == NULL || (item->subscription != SUB_BOTH && item->subscription != SUB_TO)) { Log("Try adding contact automatically jid = " TCHAR_STR_PARAM, from); if ((hContact=AddToListByJID(from, 0)) != NULL) { // Trigger actual add by removing the "NotOnList" added by AddToListByJID() // See AddToListByJID() and JabberDbSettingChanged(). db_unset(hContact, "CList", "NotOnList"); } } } RebuildInfoFrame(); } else { HXML n = xmlGetChild(node , "nick"); nick = (n == NULL) ? JabberNickFromJID(from) : mir_tstrdup(xmlGetText(n)); if (nick != NULL) { Log(TCHAR_STR_PARAM " (" TCHAR_STR_PARAM ") requests authorization", nick, from); DBAddAuthRequest(from, nick); mir_free(nick); } } return; } if ( !_tcscmp(type, _T("unsubscribe"))) { if (hContact = HContactFromJID(from)) AddDbPresenceEvent(hContact, JABBER_DB_EVENT_PRESENCE_UNSUBSCRIBE); } if ( !_tcscmp(type, _T("unsubscribed"))) { if (hContact = HContactFromJID(from)) AddDbPresenceEvent(hContact, JABBER_DB_EVENT_PRESENCE_UNSUBSCRIBED); } if ( !_tcscmp(type, _T("error"))) { if (hContact = HContactFromJID(from)) { AddDbPresenceEvent(hContact, JABBER_DB_EVENT_PRESENCE_ERROR); } } if ( !_tcscmp(type, _T("subscribed"))) { if (hContact = HContactFromJID(from)) AddDbPresenceEvent(hContact, JABBER_DB_EVENT_PRESENCE_SUBSCRIBED); if ((item=ListGetItemPtr(LIST_ROSTER, from)) != NULL) { if (item->subscription == SUB_FROM) item->subscription = SUB_BOTH; else if (item->subscription == SUB_NONE) { item->subscription = SUB_TO; if (_tcschr(from, '@')==NULL) { UI_SAFE_NOTIFY(m_pDlgServiceDiscovery, WM_JABBER_TRANSPORT_REFRESH); } } UpdateSubscriptionInfo(hContact, item); } } } void CJabberProto::OnIqResultVersion(HXML /*node*/, CJabberIqInfo *pInfo) { JABBER_RESOURCE_STATUS *r = ResourceInfoFromJID(pInfo->GetFrom()); if (r == NULL) return; r->dwVersionRequestTime = -1; replaceStrT(r->software, NULL); replaceStrT(r->version, NULL); replaceStrT(r->system, NULL); HXML queryNode = pInfo->GetChildNode(); if (pInfo->GetIqType() == JABBER_IQ_TYPE_RESULT && queryNode) { HXML n; if ((n = xmlGetChild(queryNode , "name"))!=NULL && xmlGetText(n)) r->software = mir_tstrdup(xmlGetText(n)); if ((n = xmlGetChild(queryNode , "version"))!=NULL && xmlGetText(n)) r->version = mir_tstrdup(xmlGetText(n)); if ((n = xmlGetChild(queryNode , "os"))!=NULL && xmlGetText(n)) r->system = mir_tstrdup(xmlGetText(n)); } GetResourceCapabilites(pInfo->GetFrom(), TRUE); if (pInfo->GetHContact()) UpdateMirVer(pInfo->GetHContact(), r); JabberUserInfoUpdate(pInfo->GetHContact()); } BOOL CJabberProto::OnProcessJingle(HXML node) { LPCTSTR type; HXML child = xmlGetChildByTag(node, _T("jingle"), _T("xmlns"), _T(JABBER_FEAT_JINGLE)); if (child) { if ((type=xmlGetAttrValue(node, _T("type"))) == NULL) return FALSE; if ((!_tcscmp(type, _T("get")) || !_tcscmp(type, _T("set")))) { LPCTSTR szAction = xmlGetAttrValue(child, _T("action")); LPCTSTR idStr = xmlGetAttrValue(node, _T("id")); LPCTSTR from = xmlGetAttrValue(node, _T("from")); if (szAction && !_tcscmp(szAction, _T("session-initiate"))) { // if this is a Jingle 'session-initiate' and noone processed it yet, reply with "unsupported-applications" m_ThreadInfo->send( XmlNodeIq(_T("result"), idStr, from)); XmlNodeIq iq(_T("set"), SerialNext(), from); HXML jingleNode = iq << XCHILDNS(_T("jingle"), _T(JABBER_FEAT_JINGLE)); jingleNode << XATTR(_T("action"), _T("session-terminate")); LPCTSTR szInitiator = xmlGetAttrValue(child, _T("initiator")); if (szInitiator) jingleNode << XATTR(_T("initiator"), szInitiator); LPCTSTR szSid = xmlGetAttrValue(child, _T("sid")); if (szSid) jingleNode << XATTR(_T("sid"), szSid); jingleNode << XCHILD(_T("reason")) << XCHILD(_T("unsupported-applications")); m_ThreadInfo->send(iq); return TRUE; } else { // if it's something else than 'session-initiate' and noone processed it yet, reply with "unknown-session" XmlNodeIq iq(_T("error"), idStr, from); HXML errNode = iq << XCHILD(_T("error")); errNode << XATTR(_T("type"), _T("cancel")); errNode << XCHILDNS(_T("item-not-found"), _T("urn:ietf:params:xml:ns:xmpp-stanzas")); errNode << XCHILDNS(_T("unknown-session"), _T("urn:xmpp:jingle:errors:1")); m_ThreadInfo->send(iq); return TRUE; } } } return FALSE; } void CJabberProto::OnProcessIq(HXML node) { HXML queryNode; const TCHAR *type, *xmlns; JABBER_IQ_PFUNC pfunc; if ( !xmlGetName(node) || _tcscmp(xmlGetName(node), _T("iq"))) return; if ((type=xmlGetAttrValue(node, _T("type"))) == NULL) return; int id = JabberGetPacketID(node); const TCHAR *idStr = xmlGetAttrValue(node, _T("id")); queryNode = xmlGetChild(node , "query"); xmlns = xmlGetAttrValue(queryNode, _T("xmlns")); // new match by id if (m_iqManager.HandleIq(id, node)) return; // new iq handler engine if (m_iqManager.HandleIqPermanent(node)) return; // Jingle support if (OnProcessJingle(node)) return; ///////////////////////////////////////////////////////////////////////// // OLD MATCH BY ID ///////////////////////////////////////////////////////////////////////// if ((!_tcscmp(type, _T("result")) || !_tcscmp(type, _T("error"))) && ((pfunc=JabberIqFetchFunc(id)) != NULL)) { Log("Handling iq request for id=%d", id); (this->*pfunc)(node); return; } // RECVED: ... else if ( !_tcscmp(type, _T("error"))) { Log("XXX on entry"); // Check for file transfer deny by comparing idStr with ft->iqId LISTFOREACH(i, this, LIST_FILE) { JABBER_LIST_ITEM *item = ListGetItemPtrFromIndex(i); if (item->ft != NULL && item->ft->state == FT_CONNECTING && !_tcscmp(idStr, item->ft->iqId)) { Log("Denying file sending request"); item->ft->state = FT_DENIED; if (item->ft->hFileEvent != NULL) SetEvent(item->ft->hFileEvent); // Simulate the termination of file server connection } } } else if ((!_tcscmp(type, _T("get")) || !_tcscmp(type, _T("set")))) { XmlNodeIq iq(_T("error"), idStr, xmlGetAttrValue(node, _T("from"))); HXML pFirstChild = xmlGetChild(node , 0); if (pFirstChild) xmlAddChild(iq, pFirstChild); iq << XCHILD(_T("error")) << XATTR(_T("type"), _T("cancel")) << XCHILDNS(_T("service-unavailable"), _T("urn:ietf:params:xml:ns:xmpp-stanzas")); m_ThreadInfo->send(iq); } } void CJabberProto::OnProcessRegIq(HXML node, ThreadData* info) { HXML errorNode; const TCHAR *type; if ( !xmlGetName(node) || _tcscmp(xmlGetName(node), _T("iq"))) return; if ((type=xmlGetAttrValue(node, _T("type"))) == NULL) return; int id = JabberGetPacketID(node); if ( !_tcscmp(type, _T("result"))) { // RECVED: result of the request for registration mechanism // ACTION: send account registration information if (id == iqIdRegGetReg) { iqIdRegSetReg = SerialNext(); XmlNodeIq iq(_T("set"), iqIdRegSetReg); HXML query = iq << XQUERY(_T(JABBER_FEAT_REGISTER)); query << XCHILD(_T("password"), info->password); query << XCHILD(_T("username"), info->username); info->send(iq); SendMessage(info->reg_hwndDlg, WM_JABBER_REGDLG_UPDATE, 75, (LPARAM)TranslateT("Sending registration information...")); } // RECVED: result of the registration process // ACTION: account registration successful else if (id == iqIdRegSetReg) { info->send(""); SendMessage(info->reg_hwndDlg, WM_JABBER_REGDLG_UPDATE, 100, (LPARAM)TranslateT("Registration successful")); info->reg_done = TRUE; } } else if ( !_tcscmp(type, _T("error"))) { errorNode = xmlGetChild(node , "error"); TCHAR* str = JabberErrorMsg(errorNode); SendMessage(info->reg_hwndDlg, WM_JABBER_REGDLG_UPDATE, 100, (LPARAM)str); mir_free(str); info->reg_done = TRUE; info->send(""); } } ///////////////////////////////////////////////////////////////////////////////////////// // ThreadData constructor & destructor ThreadData::ThreadData(CJabberProto* aproto, JABBER_SESSION_TYPE parType) { memset(this, 0, sizeof(*this)); type = parType; proto = aproto; iomutex = CreateMutex(NULL, FALSE, NULL); } ThreadData::~ThreadData() { if (auth) delete auth; mir_free(zRecvData); CloseHandle(iomutex); CloseHandle(hThread); } void ThreadData::close(void) { if (s) { Netlib_CloseHandle(s); s = NULL; } } void ThreadData::shutdown(void) { if (s) Netlib_Shutdown(s); } int ThreadData::recvws(char* buf, size_t len, int flags) { if (this == NULL) return 0; return proto->WsRecv(s, buf, (int)len, flags); } int ThreadData::recv(char* buf, size_t len) { if (useZlib) return zlibRecv(buf, (long)len); return recvws(buf, len, MSG_DUMPASTEXT); } int ThreadData::sendws(char* buffer, size_t bufsize, int flags) { return proto->WsSend(s, buffer, (int)bufsize, flags); } int ThreadData::send(char* buffer, int bufsize) { if (this == NULL) return 0; int result; WaitForSingleObject(iomutex, 6000); if (useZlib) result = zlibSend(buffer, bufsize); else result = sendws(buffer, bufsize, MSG_DUMPASTEXT); ReleaseMutex(iomutex); return result; } // Caution: DO NOT use ->send() to send binary (non-string) data int ThreadData::send(HXML node) { if (this == NULL) return 0; while (HXML parent = xi.getParent(node)) node = parent; if (proto->m_sendManager.HandleSendPermanent(node, this)) return 0; proto->OnConsoleProcessXml(node, JCPF_OUT); TCHAR* str = xi.toString(node, NULL); // strip forbidden control characters from outgoing XML stream TCHAR *q = str; for (TCHAR *p = str; *p; ++p) { WCHAR c = *p; if (c < 0x9 || c > 0x9 && c < 0xA || c > 0xA && c < 0xD || c > 0xD && c < 0x20 || c > 0xD7FF && c < 0xE000 || c > 0xFFFD) continue; *q++ = *p; } *q = 0; char* utfStr = mir_utf8encodeT(str); int result = send(utfStr, (int)strlen(utfStr)); mir_free(utfStr); xi.freeMem(str); return result; } int ThreadData::send(const char* fmt, ...) { if (this == NULL) return 0; va_list vararg; va_start(vararg, fmt); int size = 512; char* str = (char*)mir_alloc(size); while (_vsnprintf(str, size, fmt, vararg) == -1) { size += 512; str = (char*)mir_realloc(str, size); } va_end(vararg); int result = send(str, (int)strlen(str)); mir_free(str); return result; }