From 48540940b6c28bb4378abfeb500ec45a625b37b6 Mon Sep 17 00:00:00 2001 From: Vadim Dashevskiy Date: Tue, 15 May 2012 10:38:20 +0000 Subject: initial commit git-svn-id: http://svn.miranda-ng.org/main/trunk@2 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/JabberG/jabber_thread.cpp | 2193 +++++++++++++++++++++++++++++++++++ 1 file changed, 2193 insertions(+) create mode 100644 protocols/JabberG/jabber_thread.cpp (limited to 'protocols/JabberG/jabber_thread.cpp') diff --git a/protocols/JabberG/jabber_thread.cpp b/protocols/JabberG/jabber_thread.cpp new file mode 100644 index 0000000000..68712157ab --- /dev/null +++ b/protocols/JabberG/jabber_thread.cpp @@ -0,0 +1,2193 @@ +/* + +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. + +Revision : $Revision: 14176 $ +Last change on : $Date: 2012-03-11 16:05:02 +0200 (Вс, 11 мар 2012) $ +Last change by : $Author: borkra $ + +*/ + +#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 + +//extern int bSecureIM; +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 IDOK: + param->saveOnlinePassword = IsDlgButtonChecked( hwndDlg, IDC_SAVEPASSWORD ); + param->pro->JSetByte( NULL, "SaveSessionPassword", param->saveOnlinePassword ); + + GetDlgItemText( hwndDlg, IDC_PASSWORD, param->onlinePassword, SIZEOF( param->onlinePassword )); + // 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 + SetStatus(ID_STATUS_OFFLINE); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// + +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'; + JFreeVariant( &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 ); + JFreeVariant( &dbv ); + } + + if ( *rtrim(info->username) == '\0' ) { + DWORD dwSize = SIZEOF( info->username ); + if ( GetUserName( info->username, &dwSize )) + JSetStringT( NULL, "LoginName", info->username ); + else + info->username[0] = 0; + } + + if ( *rtrim(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 ); + JFreeVariant( &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 ); + JFreeVariant( &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 + int socket = JCallService( MS_NETLIB_GETSOCKET, ( WPARAM ) info->s, 0 ); + if ( info->type==JABBER_SESSION_NORMAL && socket!=INVALID_SOCKET ) { + struct sockaddr_in saddr; + int len; + + len = sizeof( saddr ); + getsockname( socket, ( struct sockaddr * ) &saddr, &len ); + m_dwJabberLocalIP = saddr.sin_addr.S_un.S_addr; + Log( "Local IP = %s", inet_ntoa( saddr.sin_addr )); + } + + if ( info->useSSL ) { + Log( "Intializing SSL connection" ); + if (!JCallService( 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 = JCallService( 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; + #if defined( _UNICODE ) + str = mir_utf8decodeW( buffer ); + #else + str = buffer; + #endif + + 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; + } + #if defined( _UNICODE ) + mir_free(str); + #endif + } + else { + #if defined( _UNICODE ) + if ( root ) str[ bytesParsed ] = 0; + bytesParsed = ( root ) ? mir_utf8lenW( str ) : 0; + mir_free(str); + #else + bytesParsed = ( root ) ? bytesParsed : 0; + #endif + } + + 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 ) JCallService( MS_DB_CONTACT_FINDFIRST, 0, 0 ); + while ( hContact != NULL ) { + if ( !lstrcmpA(( char* )JCallService( MS_PROTO_GETCONTACTBASEPROTO, ( WPARAM ) hContact, 0 ), m_szModuleName )) + { + SetContactOfflineStatus( hContact ); + MenuHideSrmmIcon( hContact ); + } + + hContact = ( HANDLE ) JCallService( MS_DB_CONTACT_FINDNEXT, ( WPARAM ) hContact, 0 ); + } + + 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 + JFreeVariant( &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 (!JCallService( MS_NETLIB_STARTSSL, ( WPARAM )info->s, ( LPARAM )&ssl)) { + Log( "SSL initialization failed" ); + if (info->type == JABBER_SESSION_REGISTER) { + info->send( "" ); + info->shutdown(); + } + else + SetStatus(ID_STATUS_OFFLINE); + } + 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)JCallService( 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 = JCallService( 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 ))) + JCallService( MS_PROTO_CONTACTISTYPING, ( WPARAM )hContact, 60 ); + + // chatstates paused event + if ( hContact && xmlGetChildByTag( node, "paused", "xmlns", _T( JABBER_FEAT_CHATSTATES ))) + JCallService( MS_PROTO_CONTACTISTYPING, ( WPARAM )hContact, PROTOTYPE_CONTACTTYPING_OFF ); + + // chatstates inactive event + if ( hContact && xmlGetChildByTag( node, "inactive", "xmlns", _T( JABBER_FEAT_CHATSTATES ))) + JCallService( 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 ); + } + + // XEP-0203 delay support + if ( n = xmlGetChildByTag( node, "delay", "xmlns", _T("urn:xmpp:delay") ) ) { + const TCHAR* ptszTimeStamp = xmlGetAttrValue( n, _T("stamp")); + if ( ptszTimeStamp != NULL ) { + // skip '-' chars + TCHAR* szStamp = mir_tstrdup( ptszTimeStamp ); + int si = 0, sj = 0; + while (1) { + if ( szStamp[si] == _T('-') ) + si++; + else + if ( !( szStamp[sj++] = szStamp[si++] )) + break; + }; + msgTime = JabberIsoToUnixTime( szStamp ); + mir_free( szStamp ); + } + } + + // 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 ) + JCallService( MS_PROTO_CONTACTISTYPING, ( WPARAM ) hContact, 60 ); + + // Maybe a cancel to the previous composing + HXML child = xmlGetChild( xNode ,0); + if ( hContact && ( !child || ( child && idNode != NULL ))) + JCallService( 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 ) + DBWriteContactSettingTString( 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 ) + JCallService( 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("")); + + #if defined( _UNICODE ) + char* buf = mir_utf8encodeW( szMessage ); + #else + char* buf = mir_utf8encode( szMessage ); + #endif + + if ( item != NULL ) { + if ( resourceStatus ) resourceStatus->bMessageSessionActive = TRUE; + if ( hContact != NULL ) + JCallService( 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 ); + + CCSDATA ccs; + ccs.hContact = hContact; + ccs.wParam = 0; + ccs.szProtoService = PSR_MESSAGE; + ccs.lParam = ( LPARAM )&recv; + JCallService( MS_PROTO_CHAINRECV, 0, ( LPARAM )&ccs ); + + 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 ) { + replaceStr( r->szCapsNode, szNode ); + replaceStr( r->szCapsVer, szVer ); + replaceStr( 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 ) + DBWriteContactSettingTString( hContact, "CList", "StatusMsg", item->itemResource.statusMessage ); + else + DBDeleteContactSetting( 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 ) + DBWriteContactSettingTString( hContact, "CList", "StatusMsg", item->resource[nSelectedResource].statusMessage ); + else + DBDeleteContactSetting( 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, JCallService(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 ) JFreeVariant( &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 ) JFreeVariant( &dbv ); + } else { + removedAvatar = true; + } + } } } } + if ( !hasAvatar && removedAvatar ) { + Log( "Has no avatar" ); + JDeleteSetting( hContact, "AvatarHash" ); + DBVARIANT dbv = {0}; + if ( !JGetStringT( hContact, "AvatarSaved", &dbv ) ) { + JFreeVariant( &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 && DBGetContactSettingByte( 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 ) ) + replaceStr( item->itemResource.statusMessage, xmlGetText( statusNode ) ); + else + replaceStr( 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(). + DBDeleteContactSetting( 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; + + replaceStr( r->software, NULL ); + replaceStr( r->version, NULL ); + replaceStr( 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) { + #if defined( _UNICODE ) + WCHAR c = *p; + #else + WCHAR c = *( BYTE* )p; + #endif + 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; + + #if defined( _UNICODE ) + char* utfStr = mir_utf8encodeT( str ); + int result = send( utfStr, (int)strlen( utfStr )); + mir_free( utfStr ); + #else + int result = send( str, (int)strlen( str )); + #endif + 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; +} -- cgit v1.2.3