From 0ecadfc45326fce5fc4ba28b27a0a7ad484e5b84 Mon Sep 17 00:00:00 2001 From: Vadim Dashevskiy Date: Fri, 12 Oct 2012 14:18:20 +0000 Subject: Gadu-Gadu: folders restructurization git-svn-id: http://svn.miranda-ng.org/main/trunk@1888 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/Gadu-Gadu/src/avatar.cpp | 459 +++++ protocols/Gadu-Gadu/src/core.cpp | 1756 +++++++++++++++++ protocols/Gadu-Gadu/src/dialogs.cpp | 1016 ++++++++++ protocols/Gadu-Gadu/src/dynstuff.cpp | 612 ++++++ protocols/Gadu-Gadu/src/dynstuff.h | 70 + protocols/Gadu-Gadu/src/filetransfer.cpp | 977 ++++++++++ protocols/Gadu-Gadu/src/gg.cpp | 523 +++++ protocols/Gadu-Gadu/src/gg.h | 341 ++++ protocols/Gadu-Gadu/src/gg_proto.cpp | 804 ++++++++ protocols/Gadu-Gadu/src/gg_proto.h | 295 +++ protocols/Gadu-Gadu/src/groupchat.cpp | 669 +++++++ protocols/Gadu-Gadu/src/icolib.cpp | 109 ++ protocols/Gadu-Gadu/src/image.cpp | 1191 ++++++++++++ protocols/Gadu-Gadu/src/import.cpp | 651 +++++++ protocols/Gadu-Gadu/src/keepalive.cpp | 92 + protocols/Gadu-Gadu/src/libgadu/COPYING | 504 +++++ protocols/Gadu-Gadu/src/libgadu/common.c | 975 ++++++++++ protocols/Gadu-Gadu/src/libgadu/compat.h | 36 + protocols/Gadu-Gadu/src/libgadu/dcc.c | 1363 +++++++++++++ protocols/Gadu-Gadu/src/libgadu/dcc7.c | 1655 ++++++++++++++++ protocols/Gadu-Gadu/src/libgadu/events.c | 2864 ++++++++++++++++++++++++++++ protocols/Gadu-Gadu/src/libgadu/http.c | 544 ++++++ protocols/Gadu-Gadu/src/libgadu/internal.h | 48 + protocols/Gadu-Gadu/src/libgadu/libgadu.c | 2353 +++++++++++++++++++++++ protocols/Gadu-Gadu/src/libgadu/libgadu.h | 2311 ++++++++++++++++++++++ protocols/Gadu-Gadu/src/libgadu/obsolete.c | 238 +++ protocols/Gadu-Gadu/src/libgadu/protocol.h | 277 +++ protocols/Gadu-Gadu/src/libgadu/pthread.c | 78 + protocols/Gadu-Gadu/src/libgadu/pthread.h | 56 + protocols/Gadu-Gadu/src/libgadu/pubdir.c | 862 +++++++++ protocols/Gadu-Gadu/src/libgadu/pubdir50.c | 557 ++++++ protocols/Gadu-Gadu/src/libgadu/resolver.c | 766 ++++++++ protocols/Gadu-Gadu/src/libgadu/resolver.h | 33 + protocols/Gadu-Gadu/src/libgadu/sha1.c | 308 +++ protocols/Gadu-Gadu/src/libgadu/win32.c | 65 + protocols/Gadu-Gadu/src/libgadu/win32.h | 75 + protocols/Gadu-Gadu/src/links.cpp | 173 ++ protocols/Gadu-Gadu/src/oauth.cpp | 584 ++++++ protocols/Gadu-Gadu/src/ownerinfo.cpp | 78 + protocols/Gadu-Gadu/src/popups.cpp | 174 ++ protocols/Gadu-Gadu/src/resource.h | 147 ++ protocols/Gadu-Gadu/src/services.cpp | 330 ++++ protocols/Gadu-Gadu/src/sessions.cpp | 445 +++++ protocols/Gadu-Gadu/src/token.cpp | 161 ++ protocols/Gadu-Gadu/src/userutils.cpp | 329 ++++ protocols/Gadu-Gadu/src/version.h | 23 + 46 files changed, 27977 insertions(+) create mode 100644 protocols/Gadu-Gadu/src/avatar.cpp create mode 100644 protocols/Gadu-Gadu/src/core.cpp create mode 100644 protocols/Gadu-Gadu/src/dialogs.cpp create mode 100644 protocols/Gadu-Gadu/src/dynstuff.cpp create mode 100644 protocols/Gadu-Gadu/src/dynstuff.h create mode 100644 protocols/Gadu-Gadu/src/filetransfer.cpp create mode 100644 protocols/Gadu-Gadu/src/gg.cpp create mode 100644 protocols/Gadu-Gadu/src/gg.h create mode 100644 protocols/Gadu-Gadu/src/gg_proto.cpp create mode 100644 protocols/Gadu-Gadu/src/gg_proto.h create mode 100644 protocols/Gadu-Gadu/src/groupchat.cpp create mode 100644 protocols/Gadu-Gadu/src/icolib.cpp create mode 100644 protocols/Gadu-Gadu/src/image.cpp create mode 100644 protocols/Gadu-Gadu/src/import.cpp create mode 100644 protocols/Gadu-Gadu/src/keepalive.cpp create mode 100644 protocols/Gadu-Gadu/src/libgadu/COPYING create mode 100644 protocols/Gadu-Gadu/src/libgadu/common.c create mode 100644 protocols/Gadu-Gadu/src/libgadu/compat.h create mode 100644 protocols/Gadu-Gadu/src/libgadu/dcc.c create mode 100644 protocols/Gadu-Gadu/src/libgadu/dcc7.c create mode 100644 protocols/Gadu-Gadu/src/libgadu/events.c create mode 100644 protocols/Gadu-Gadu/src/libgadu/http.c create mode 100644 protocols/Gadu-Gadu/src/libgadu/internal.h create mode 100644 protocols/Gadu-Gadu/src/libgadu/libgadu.c create mode 100644 protocols/Gadu-Gadu/src/libgadu/libgadu.h create mode 100644 protocols/Gadu-Gadu/src/libgadu/obsolete.c create mode 100644 protocols/Gadu-Gadu/src/libgadu/protocol.h create mode 100644 protocols/Gadu-Gadu/src/libgadu/pthread.c create mode 100644 protocols/Gadu-Gadu/src/libgadu/pthread.h create mode 100644 protocols/Gadu-Gadu/src/libgadu/pubdir.c create mode 100644 protocols/Gadu-Gadu/src/libgadu/pubdir50.c create mode 100644 protocols/Gadu-Gadu/src/libgadu/resolver.c create mode 100644 protocols/Gadu-Gadu/src/libgadu/resolver.h create mode 100644 protocols/Gadu-Gadu/src/libgadu/sha1.c create mode 100644 protocols/Gadu-Gadu/src/libgadu/win32.c create mode 100644 protocols/Gadu-Gadu/src/libgadu/win32.h create mode 100644 protocols/Gadu-Gadu/src/links.cpp create mode 100644 protocols/Gadu-Gadu/src/oauth.cpp create mode 100644 protocols/Gadu-Gadu/src/ownerinfo.cpp create mode 100644 protocols/Gadu-Gadu/src/popups.cpp create mode 100644 protocols/Gadu-Gadu/src/resource.h create mode 100644 protocols/Gadu-Gadu/src/services.cpp create mode 100644 protocols/Gadu-Gadu/src/sessions.cpp create mode 100644 protocols/Gadu-Gadu/src/token.cpp create mode 100644 protocols/Gadu-Gadu/src/userutils.cpp create mode 100644 protocols/Gadu-Gadu/src/version.h (limited to 'protocols/Gadu-Gadu/src') diff --git a/protocols/Gadu-Gadu/src/avatar.cpp b/protocols/Gadu-Gadu/src/avatar.cpp new file mode 100644 index 0000000000..ace7871b6a --- /dev/null +++ b/protocols/Gadu-Gadu/src/avatar.cpp @@ -0,0 +1,459 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2009-2012 Bartosz Białek +// +// 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 "gg.h" +#include +#include +#include "protocol.h" + +////////////////////////////////////////////////////////// +// Avatars support +void GGPROTO::getAvatarFilename(HANDLE hContact, TCHAR *pszDest, int cbLen) +{ + int tPathLen; + TCHAR *path = (TCHAR*)alloca(cbLen * sizeof(TCHAR)); + TCHAR *avatartype = NULL; + + if (hAvatarsFolder == NULL || FoldersGetCustomPathT(hAvatarsFolder, path, cbLen, _T(""))) { + mir_ptr tmpPath( Utils_ReplaceVarsT( _T("%miranda_avatarcache%"))); + tPathLen = mir_sntprintf(pszDest, cbLen, _T("%s\\%s"), (TCHAR*)tmpPath, m_tszUserName); + } + else { + _tcscpy(pszDest, path); + tPathLen = (int)_tcslen(pszDest); + } + + if (_taccess(pszDest, 0)) + CallService(MS_UTILS_CREATEDIRTREE, 0, (LPARAM)pszDest); + + switch (db_get_b(hContact, m_szModuleName, GG_KEY_AVATARTYPE, GG_KEYDEF_AVATARTYPE)) { + case PA_FORMAT_JPEG: avatartype = _T("jpg"); break; + case PA_FORMAT_GIF: avatartype = _T("gif"); break; + case PA_FORMAT_PNG: avatartype = _T("png"); break; + } + + if (hContact != NULL) { + DBVARIANT dbv; + if (!db_get_s(hContact, m_szModuleName, GG_KEY_AVATARHASH, &dbv, DBVT_ASCIIZ)) { + mir_sntprintf(pszDest + tPathLen, cbLen - tPathLen, _T("\\%s.%s"), dbv.pszVal, avatartype); + DBFreeVariant(&dbv); + } + } + else mir_sntprintf(pszDest + tPathLen, cbLen - tPathLen, _T("\\%s avatar.%s"), m_szModuleName, avatartype); +} + +void GGPROTO::getAvatarFileInfo(uin_t uin, char **avatarurl, int *type) +{ + NETLIBHTTPREQUEST req = {0}; + NETLIBHTTPREQUEST *resp; + char szUrl[128]; + *avatarurl = NULL; + *type = PA_FORMAT_UNKNOWN; + + req.cbSize = sizeof(req); + req.requestType = REQUEST_GET; + req.szUrl = szUrl; + mir_snprintf(szUrl, 128, "http://api.gadu-gadu.pl/avatars/%d/0.xml", uin); + req.flags = NLHRF_NODUMP | NLHRF_HTTP11 | NLHRF_REDIRECT; + resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)netlib, (LPARAM)&req); + if (resp) { + if (resp->resultCode == 200 && resp->dataLength > 0 && resp->pData) { + HXML hXml; + TCHAR *xmlAction; + TCHAR *tag; + + xmlAction = mir_a2t(resp->pData); + tag = mir_a2t("result"); + hXml = xi.parseString(xmlAction, 0, tag); + + if (hXml != NULL) { + HXML node; + char *blank; + + mir_free(tag); tag = mir_a2t("users/user/avatars/avatar"); + node = xi.getChildByPath(hXml, tag, 0); + mir_free(tag); tag = mir_a2t("blank"); + blank = (node != NULL) ? mir_t2a(xi.getAttrValue(node, tag)) : NULL; + + if (blank != NULL && strcmp(blank, "1")) { + mir_free(tag); tag = mir_a2t("users/user/avatars/avatar/bigAvatar"); + node = xi.getChildByPath(hXml, tag, 0); + *avatarurl = node != NULL ? mir_t2a(xi.getText(node)) : NULL; + + mir_free(tag); tag = mir_a2t("users/user/avatars/avatar/originBigAvatar"); + node = xi.getChildByPath(hXml, tag, 0); + if (node != NULL) { + char *orgavurl = mir_t2a(xi.getText(node)); + char *avtype = strrchr(orgavurl, '.'); + avtype++; + if (!_stricmp(avtype, "jpg")) + *type = PA_FORMAT_JPEG; + else if (!_stricmp(avtype, "gif")) + *type = PA_FORMAT_GIF; + else if (!_stricmp(avtype, "png")) + *type = PA_FORMAT_PNG; + mir_free(orgavurl); + } + } + else *avatarurl = mir_strdup(""); + mir_free(blank); + xi.destroyNode(hXml); + } + mir_free(tag); + mir_free(xmlAction); + } + else netlog("gg_getavatarfileinfo(): Invalid response code from HTTP request"); + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp); + } + else netlog("gg_getavatarfileinfo(): No response from HTTP request"); +} + +char *gg_avatarhash(char *param) +{ + mir_sha1_byte_t digest[MIR_SHA1_HASH_SIZE]; + char *result; + int i; + + if (param == NULL || (result = (char *)mir_alloc(MIR_SHA1_HASH_SIZE * 2 + 1)) == NULL) + return NULL; + + mir_sha1_hash((BYTE*)param, (int)strlen(param), digest); + for (i = 0; i < MIR_SHA1_HASH_SIZE; i++) + sprintf(result + (i<<1), "%02x", digest[i]); + + return result; +} + +typedef struct +{ + HANDLE hContact; + char *AvatarURL; +} GGGETAVATARDATA; + +void GGPROTO::getAvatar(HANDLE hContact, char *szAvatarURL) +{ + if (pth_avatar.dwThreadId) { + GGGETAVATARDATA *data = (GGGETAVATARDATA*)mir_alloc(sizeof(GGGETAVATARDATA)); + data->hContact = hContact; + data->AvatarURL = mir_strdup(szAvatarURL); + EnterCriticalSection(&avatar_mutex); + list_add(&avatar_transfers, data, 0); + LeaveCriticalSection(&avatar_mutex); + } +} + +typedef struct +{ + HANDLE hContact; + int iWaitFor; +} GGREQUESTAVATARDATA; + +void GGPROTO::requestAvatar(HANDLE hContact, int iWaitFor) +{ + if (db_get_b(NULL, m_szModuleName, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS) + && pth_avatar.dwThreadId) { + GGREQUESTAVATARDATA *data = (GGREQUESTAVATARDATA*)mir_alloc(sizeof(GGREQUESTAVATARDATA)); + data->hContact = hContact; + data->iWaitFor = iWaitFor; + EnterCriticalSection(&avatar_mutex); + list_add(&avatar_requests, data, 0); + LeaveCriticalSection(&avatar_mutex); + } +} + +void __cdecl GGPROTO::avatarrequestthread(void*) +{ + list_t l; + + netlog("gg_avatarrequestthread(): Avatar Request Thread Starting"); + while (pth_avatar.dwThreadId) + { + EnterCriticalSection(&avatar_mutex); + if (avatar_requests) { + GGREQUESTAVATARDATA *data = (GGREQUESTAVATARDATA *)avatar_requests->data; + char *AvatarURL; + int AvatarType, iWaitFor = data->iWaitFor; + HANDLE hContact = data->hContact; + + list_remove(&avatar_requests, data, 0); + mir_free(data); + LeaveCriticalSection(&avatar_mutex); + + getAvatarFileInfo( db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0), &AvatarURL, &AvatarType); + if (AvatarURL != NULL && strlen(AvatarURL) > 0) + db_set_s(hContact, m_szModuleName, GG_KEY_AVATARURL, AvatarURL); + else + db_unset(hContact, m_szModuleName, GG_KEY_AVATARURL); + db_set_b(hContact, m_szModuleName, GG_KEY_AVATARTYPE, (BYTE)AvatarType); + db_set_b(hContact, m_szModuleName, GG_KEY_AVATARREQUESTED, 1); + + if (iWaitFor) { + PROTO_AVATAR_INFORMATIONT pai = {0}; + pai.cbSize = sizeof(pai); + pai.hContact = hContact; + if (getavatarinfo((WPARAM)GAIF_FORCE, (LPARAM)&pai) != GAIR_WAITFOR) + ProtoBroadcastAck(m_szModuleName, hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, (HANDLE)&pai, 0); + } + else ProtoBroadcastAck(m_szModuleName, hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, 0, 0); + } + else LeaveCriticalSection(&avatar_mutex); + + EnterCriticalSection(&avatar_mutex); + if (avatar_transfers) { + GGGETAVATARDATA *data = (GGGETAVATARDATA *)avatar_transfers->data; + NETLIBHTTPREQUEST req = {0}; + NETLIBHTTPREQUEST *resp; + PROTO_AVATAR_INFORMATIONT pai = {0}; + int result = 0; + + pai.cbSize = sizeof(pai); + pai.hContact = data->hContact; + pai.format = db_get_b(pai.hContact, m_szModuleName, GG_KEY_AVATARTYPE, GG_KEYDEF_AVATARTYPE); + + req.cbSize = sizeof(req); + req.requestType = REQUEST_GET; + req.szUrl = data->AvatarURL; + req.flags = NLHRF_NODUMP | NLHRF_HTTP11 | NLHRF_REDIRECT; + resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)netlib, (LPARAM)&req); + if (resp) { + if (resp->resultCode == 200 && resp->dataLength > 0 && resp->pData) { + int file_fd; + + getAvatarFilename(pai.hContact, pai.filename, sizeof(pai.filename)); + file_fd = _topen(pai.filename, _O_WRONLY | _O_TRUNC | _O_BINARY | _O_CREAT, _S_IREAD | _S_IWRITE); + if (file_fd != -1) { + _write(file_fd, resp->pData, resp->dataLength); + _close(file_fd); + result = 1; + } + } + else netlog("gg_avatarrequestthread(): Invalid response code from HTTP request"); + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp); + } + else netlog("gg_avatarrequestthread(): No response from HTTP request"); + + ProtoBroadcastAck(m_szModuleName, pai.hContact, ACKTYPE_AVATAR, + result ? ACKRESULT_SUCCESS : ACKRESULT_FAILED, (HANDLE)&pai, 0); + + if (!pai.hContact) + CallService(MS_AV_REPORTMYAVATARCHANGED, (WPARAM)m_szModuleName, 0); + + list_remove(&avatar_transfers, data, 0); + mir_free(data->AvatarURL); + mir_free(data); + } + LeaveCriticalSection(&avatar_mutex); + SleepEx(100, FALSE); + } + + for (l = avatar_requests; l; l = l->next) { + GGREQUESTAVATARDATA *data = (GGREQUESTAVATARDATA *)l->data; + mir_free(data); + } + for (l = avatar_transfers; l; l = l->next) { + GGGETAVATARDATA *data = (GGGETAVATARDATA *)l->data; + mir_free(data->AvatarURL); + mir_free(data); + } + list_destroy(avatar_requests, 0); + list_destroy(avatar_transfers, 0); + netlog("gg_avatarrequestthread(): Avatar Request Thread Ending"); +} + +void GGPROTO::initavatarrequestthread() +{ + DWORD exitCode = 0; + + GetExitCodeThread(pth_avatar.hThread, &exitCode); + if (exitCode != STILL_ACTIVE) { + avatar_requests = avatar_transfers = NULL; + pth_avatar.hThread = forkthreadex(&GGPROTO::avatarrequestthread, NULL, &pth_avatar.dwThreadId); + } +} + +void GGPROTO::uninitavatarrequestthread() +{ + pth_avatar.dwThreadId = 0; +#ifdef DEBUGMODE + netlog("gg_uninitavatarrequestthread(): Waiting until Avatar Request Thread finished, if needed."); +#endif + threadwait(&pth_avatar); +} + +void __cdecl GGPROTO::getuseravatarthread(void*) +{ + char *AvatarURL; + int AvatarType; + + getAvatarFileInfo( db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0), &AvatarURL, &AvatarType); + if (AvatarURL != NULL && strlen(AvatarURL) > 0) + db_set_s(NULL, m_szModuleName, GG_KEY_AVATARURL, AvatarURL); + else + db_unset(NULL, m_szModuleName, GG_KEY_AVATARURL); + db_set_b(NULL, m_szModuleName, GG_KEY_AVATARTYPE, (BYTE)AvatarType); + db_set_b(NULL, m_szModuleName, GG_KEY_AVATARREQUESTED, 1); + mir_free(AvatarURL); + + PROTO_AVATAR_INFORMATIONT pai = {0}; + pai.cbSize = sizeof(pai); + getavatarinfo((WPARAM)GAIF_FORCE, (LPARAM)&pai); +} + +void GGPROTO::getUserAvatar() +{ + if (db_get_b(NULL, m_szModuleName, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS) + && db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0)) + forkthread(&GGPROTO::getuseravatarthread, NULL); +} + +void __cdecl GGPROTO::setavatarthread(void *param) +{ + NETLIBHTTPHEADER httpHeaders[4]; + NETLIBHTTPREQUEST req = {0}; + NETLIBHTTPREQUEST *resp; + TCHAR *szFilename = (TCHAR*)param; + const char *contentend = "\r\n--AaB03x--\r\n"; + char szUrl[128], uin[32], *authHeader, *data, *avatardata, content[256], image_ext[4], image_type[11]; + int file_fd, avatardatalen, datalen, contentlen, contentendlen, res = 0, repeat = 0; + + netlog("gg_setavatar(): Trying to set user avatar using %s...", szFilename); + UIN2ID( db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0), uin); + + file_fd = _topen(szFilename, _O_RDONLY | _O_BINARY, _S_IREAD); + if (file_fd == -1) { + netlog("gg_setavatar(): Failed to open avatar file (%s).", strerror(errno)); + mir_free(szFilename); + getUserAvatar(); + return; + } + avatardatalen = _filelength(file_fd); + avatardata = (char *)mir_alloc(avatardatalen); + + _read(file_fd, avatardata, avatardatalen); + _close(file_fd); + + TCHAR *fileext = _tcsrchr(szFilename, '.'); + fileext++; + if (!_tcsicmp(fileext, _T("jpg"))) { + strcpy(image_ext, "jpg"); + strcpy(image_type, "image/jpeg"); + } + else if (!_tcsicmp(fileext, _T("gif"))) { + strcpy(image_ext, "gif"); + strcpy(image_type, "image/gif"); + } + else { + strcpy(image_ext, "png"); + strcpy(image_type, "image/png"); + } + + mir_snprintf(content, 256, "--AaB03x\r\nContent-Disposition: form-data; name=\"_method\"\r\n\r\nPUT\r\n--AaB03x\r\nContent-Disposition: form-data; name=\"avatar\"; filename=\"%s.%s\"\r\nContent-Type: %s\r\n\r\n", + uin, image_ext, image_type); + contentlen = (int)strlen(content); + contentendlen = (int)strlen(contentend); + + datalen = contentlen + avatardatalen + contentendlen; + data = (char *)mir_alloc(datalen); + memcpy(data, content, contentlen); + memcpy(data + contentlen, avatardata, avatardatalen); + memcpy(data + contentlen + avatardatalen, contentend, contentendlen); + + mir_snprintf(szUrl, 128, "http://api.gadu-gadu.pl/avatars/%s/0.xml", uin); + oauth_checktoken(0); + authHeader = oauth_header("PUT", szUrl); + + req.cbSize = sizeof(req); + req.requestType = REQUEST_POST; + req.szUrl = szUrl; + req.flags = NLHRF_NODUMP | NLHRF_HTTP11; + req.headersCount = 4; + req.headers = httpHeaders; + httpHeaders[0].szName = "User-Agent"; + httpHeaders[0].szValue = GG8_VERSION; + httpHeaders[1].szName = "Authorization"; + httpHeaders[1].szValue = authHeader; + httpHeaders[2].szName = "Accept"; + httpHeaders[2].szValue = "*/*"; + httpHeaders[3].szName = "Content-Type"; + httpHeaders[3].szValue = "multipart/form-data; boundary=AaB03x"; + req.pData = data; + req.dataLength = datalen; + + resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)netlib, (LPARAM)&req); + if (resp) { + if (resp->resultCode == 200 && resp->dataLength > 0 && resp->pData) { +#ifdef DEBUGMODE + netlog("%s", resp->pData); +#endif + res = 1; + } + else netlog("gg_setavatar(): Invalid response code from HTTP request"); + if (resp->resultCode == 403 || resp->resultCode == 401) + repeat = 1; + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp); + } + else netlog("gg_setavatar(): No response from HTTP request"); + + if (repeat) { // Access Token expired - we need to obtain new + mir_free(authHeader); + oauth_checktoken(1); + authHeader = oauth_header("PUT", szUrl); + + ZeroMemory(&req, sizeof(req)); + req.cbSize = sizeof(req); + req.requestType = REQUEST_POST; + req.szUrl = szUrl; + req.flags = NLHRF_NODUMP | NLHRF_HTTP11; + req.headersCount = 4; + req.headers = httpHeaders; + req.pData = data; + req.dataLength = datalen; + + resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)netlib, (LPARAM)&req); + if (resp) { + if (resp->resultCode == 200 && resp->dataLength > 0 && resp->pData) { +#ifdef DEBUGMODE + netlog("%s", resp->pData); +#endif + res = 1; + } + else netlog("gg_setavatar(): Invalid response code from HTTP request"); + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp); + } + else netlog("gg_setavatar(): No response from HTTP request"); + } + + mir_free(authHeader); + mir_free(avatardata); + mir_free(data); + + if (res) + netlog("gg_setavatar(): User avatar set successfully."); + else + netlog("gg_setavatar(): Failed to set user avatar."); + + mir_free(szFilename); + getUserAvatar(); +} + +void GGPROTO::setAvatar(const TCHAR *szFilename) +{ + forkthread(&GGPROTO::setavatarthread, mir_tstrdup(szFilename)); +} diff --git a/protocols/Gadu-Gadu/src/core.cpp b/protocols/Gadu-Gadu/src/core.cpp new file mode 100644 index 0000000000..63d5d37b24 --- /dev/null +++ b/protocols/Gadu-Gadu/src/core.cpp @@ -0,0 +1,1756 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2009 Adam Strzelecki +// Copyright (c) 2009-2012 Bartosz Białek +// +// 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 "gg.h" +#include +#include + +//////////////////////////////////////////////////////////// +// Swap bits in DWORD +uint32_t swap32(uint32_t x) +{ + return (uint32_t) + (((x & (uint32_t) 0x000000ffU) << 24) | + ((x & (uint32_t) 0x0000ff00U) << 8) | + ((x & (uint32_t) 0x00ff0000U) >> 8) | + ((x & (uint32_t) 0xff000000U) >> 24)); +} + +//////////////////////////////////////////////////////////// +// Is online function + +int GGPROTO::isonline() +{ + mir_cslock lck(sess_mutex); + return (sess != NULL); +} + +//////////////////////////////////////////////////////////// +// Send disconnect request and wait for server thread to die +void GGPROTO::disconnect() +{ + // If main loop then send disconnect request + if (isonline()) + { + // Fetch proper status msg + char *szMsg = NULL; + + // Loadup status + if (db_get_b(NULL, m_szModuleName, GG_KEY_LEAVESTATUSMSG, GG_KEYDEF_LEAVESTATUSMSG)) + { + DBVARIANT dbv; + switch (db_get_w(NULL, m_szModuleName, GG_KEY_LEAVESTATUS, GG_KEYDEF_LEAVESTATUS)) { + case ID_STATUS_ONLINE: + EnterCriticalSection(&modemsg_mutex); + szMsg = mir_strdup(modemsg.online); + LeaveCriticalSection(&modemsg_mutex); + if (!szMsg && !db_get_s(NULL, "SRAway", gg_status2db(ID_STATUS_ONLINE, "Default"), &dbv, DBVT_ASCIIZ)) { + if (dbv.pszVal && *(dbv.pszVal)) + szMsg = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + break; + case ID_STATUS_AWAY: + EnterCriticalSection(&modemsg_mutex); + szMsg = mir_strdup(modemsg.away); + LeaveCriticalSection(&modemsg_mutex); + if (!szMsg && !db_get_s(NULL, "SRAway", gg_status2db(ID_STATUS_AWAY, "Default"), &dbv, DBVT_ASCIIZ)) { + if (dbv.pszVal && *(dbv.pszVal)) + szMsg = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + break; + case ID_STATUS_DND: + EnterCriticalSection(&modemsg_mutex); + szMsg = mir_strdup(modemsg.dnd); + LeaveCriticalSection(&modemsg_mutex); + if (!szMsg && !db_get_s(NULL, "SRAway", gg_status2db(ID_STATUS_DND, "Default"), &dbv, DBVT_ASCIIZ)) { + if (dbv.pszVal && *(dbv.pszVal)) + szMsg = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + break; + case ID_STATUS_FREECHAT: + EnterCriticalSection(&modemsg_mutex); + szMsg = mir_strdup(modemsg.freechat); + LeaveCriticalSection(&modemsg_mutex); + if (!szMsg && !db_get_s(NULL, "SRAway", gg_status2db(ID_STATUS_FREECHAT, "Default"), &dbv, DBVT_ASCIIZ)) { + if (dbv.pszVal && *(dbv.pszVal)) + szMsg = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + break; + case ID_STATUS_INVISIBLE: + EnterCriticalSection(&modemsg_mutex); + szMsg = mir_strdup(modemsg.invisible); + LeaveCriticalSection(&modemsg_mutex); + if (!szMsg && !db_get_s(NULL, "SRAway", gg_status2db(ID_STATUS_INVISIBLE, "Default"), &dbv, DBVT_ASCIIZ)) { + if (dbv.pszVal && *(dbv.pszVal)) + szMsg = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + break; + default: + // Set last status + EnterCriticalSection(&modemsg_mutex); + szMsg = mir_strdup(getstatusmsg(m_iStatus)); + LeaveCriticalSection(&modemsg_mutex); + } + } + + EnterCriticalSection(&sess_mutex); + // Check if it has message + if (szMsg) + { + gg_change_status_descr(sess, GG_STATUS_NOT_AVAIL_DESCR, szMsg); + mir_free(szMsg); + // Wait for disconnection acknowledge + } + else + { + gg_change_status(sess, GG_STATUS_NOT_AVAIL); + // Send logoff immediately + gg_logoff(sess); + } + LeaveCriticalSection(&sess_mutex); + } + // Else cancel connection attempt + else if (sock) + closesocket(sock); +} + +//////////////////////////////////////////////////////////// +// DNS lookup function +uint32_t gg_dnslookup(GGPROTO *gg, char *host) +{ + uint32_t ip; + struct hostent *he; + + ip = inet_addr(host); + if (ip != INADDR_NONE) + { +#ifdef DEBUGMODE + gg->netlog("gg_dnslookup(): Parameter \"%s\" is already IP number.", host); +#endif + return ip; + } + he = gethostbyname(host); + if (he) + { + ip = *(uint32_t *) he->h_addr_list[0]; +#ifdef DEBUGMODE + gg->netlog("gg_dnslookup(): Parameter \"%s\" was resolved to %d.%d.%d.%d.", host, + LOBYTE(LOWORD(ip)), HIBYTE(LOWORD(ip)), LOBYTE(HIWORD(ip)), HIBYTE(HIWORD(ip))); +#endif + return ip; + } + gg->netlog("gg_dnslookup(): Cannot resolve hostname \"%s\".", host); + return 0; +} + +//////////////////////////////////////////////////////////// +// Host list decoder +typedef struct +{ + char hostname[128]; + int port; +} GGHOST; +#define ISHOSTALPHA(a) (((a) >= '0' && (a) <= '9') || ((a) >= 'a' && (a) <= 'z') || (a) == '.' || (a) == '-') +int gg_decodehosts(char *var, GGHOST *hosts, int max) +{ + int hp = 0; + char *hostname = NULL; + char *portname = NULL; + + while(var && *var && hp < max) + { + if (ISHOSTALPHA(*var)) + { + hostname = var; + + while(var && *var && ISHOSTALPHA(*var)) var ++; + + if (var && *var == ':' && var++ && *var && isdigit(*var)) + { + *(var - 1) = 0; + portname = var; + while(var && *var && isdigit(*var)) var++; + if (*var) { *var = 0; var ++; } + } + else + if (*var) { *var = 0; var ++; } + + // Insert new item + hosts[hp].hostname[127] = 0; + strncpy(hosts[hp].hostname, hostname, 127); + hosts[hp].port = portname ? atoi(portname) : 443; + hp ++; + + // Zero the names + hostname = NULL; + portname = NULL; + } + else + var ++; + } + return hp; +} + +//////////////////////////////////////////////////////////// +// Main connection session thread +void __cdecl GGPROTO::mainthread(void *) +{ + // Miranda variables + NETLIBUSERSETTINGS nlus = {0}; + DBVARIANT dbv; + // Gadu-Gadu variables + gg_login_params p = {0}; + gg_event *e; + // Host cycling variables + int hostnum = 0, hostcount = 0; + GGHOST hosts[64]; + // Gadu-gadu login errors + static const struct tagReason { int type; TCHAR *str; } reason[] = { + { GG_FAILURE_RESOLVING, LPGENT("Miranda was unable to resolve the name of the Gadu-Gadu server to its numeric address.") }, + { GG_FAILURE_CONNECTING, LPGENT("Miranda was unable to make a connection with a server. It is likely that the server is down, in which case you should wait for a while and try again later.") }, + { GG_FAILURE_INVALID, LPGENT("Received invalid server response.") }, + { GG_FAILURE_READING, LPGENT("The connection with the server was abortively closed during the connection attempt. You may have lost your local network connection.") }, + { GG_FAILURE_WRITING, LPGENT("The connection with the server was abortively closed during the connection attempt. You may have lost your local network connection.") }, + { GG_FAILURE_PASSWORD, LPGENT("Your Gadu-Gadu number and password combination was rejected by the Gadu-Gadu server. Please check login details at M->Options->Network->Gadu-Gadu and try again.") }, + { GG_FAILURE_404, LPGENT("Connecting to Gadu-Gadu hub failed.") }, + { GG_FAILURE_TLS, LPGENT("Cannot establish secure connection.") }, + { GG_FAILURE_NEED_EMAIL, LPGENT("Server disconnected asking you for changing your e-mail.") }, + { GG_FAILURE_INTRUDER, LPGENT("Too many login attempts with invalid password.") }, + { GG_FAILURE_UNAVAILABLE, LPGENT("Gadu-Gadu servers are now down. Try again later.") }, + { 0, LPGENT("Unknown") } + }; + time_t logonTime = 0; + time_t timeDeviation = db_get_w(NULL, m_szModuleName, GG_KEY_TIMEDEVIATION, GG_KEYDEF_TIMEDEVIATION); + int gg_failno = 0; + + netlog("gg_mainthread(%x): Server Thread Starting", this); +#ifdef DEBUGMODE + gg_debug_level = GG_DEBUG_NET | GG_DEBUG_TRAFFIC | GG_DEBUG_FUNCTION | GG_DEBUG_MISC; +#else + gg_debug_level = 0; +#endif + + // Broadcast that service is connecting + broadcastnewstatus(ID_STATUS_CONNECTING); + + // Client version and misc settings + p.client_version = GG_DEFAULT_CLIENT_VERSION; + p.protocol_version = GG_DEFAULT_PROTOCOL_VERSION; + p.protocol_features = GG_FEATURE_DND_FFC | GG_FEATURE_UNKNOWN_100 | GG_FEATURE_USER_DATA | GG_FEATURE_MSG_ACK | GG_FEATURE_TYPING_NOTIFICATION | GG_FEATURE_MULTILOGON; + p.encoding = GG_ENCODING_CP1250; + p.status_flags = GG_STATUS_FLAG_UNKNOWN; + if (db_get_b(NULL, m_szModuleName, GG_KEY_SHOWLINKS, GG_KEYDEF_SHOWLINKS)) + p.status_flags |= GG_STATUS_FLAG_SPAM; + + // Use audio + /* p.has_audio = 1; */ + + // Use async connections + /* p.async = 1; */ + + // Send Era Omnix info if set + p.era_omnix = db_get_b(NULL, m_szModuleName, "EraOmnix", 0); + + // Setup proxy + nlus.cbSize = sizeof(nlus); + if (CallService(MS_NETLIB_GETUSERSETTINGS, (WPARAM)netlib, (LPARAM)&nlus)) + { + if (nlus.useProxy) + netlog("gg_mainthread(%x): Using proxy %s:%d.", this, nlus.szProxyServer, nlus.wProxyPort); + gg_proxy_enabled = nlus.useProxy; + gg_proxy_host = nlus.szProxyServer; + gg_proxy_port = nlus.wProxyPort; + if (nlus.useProxyAuth) + { + gg_proxy_username = nlus.szProxyAuthUser; + gg_proxy_password = nlus.szProxyAuthPassword; + } + else + gg_proxy_username = gg_proxy_password = NULL; + } + else + { + netlog("gg_mainthread(%x): Failed loading proxy settings.", this); + gg_proxy_enabled = 0; + } + + // Check out manual host setting + if (db_get_b(NULL, m_szModuleName, GG_KEY_MANUALHOST, GG_KEYDEF_MANUALHOST)) + { + if (!db_get_s(NULL, m_szModuleName, GG_KEY_SERVERHOSTS, &dbv, DBVT_ASCIIZ)) + { + hostcount = gg_decodehosts(dbv.pszVal, hosts, 64); + DBFreeVariant(&dbv); + } + } + + // Readup password + if (!db_get_s(NULL, m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) + { + CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM) dbv.pszVal); + p.password = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + else + { + netlog("gg_mainthread(%x): No password specified. Exiting.", this); + broadcastnewstatus(ID_STATUS_OFFLINE); + return; + } + + // Readup number + if (!(p.uin = db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0))) + { + netlog("gg_mainthread(%x): No Gadu-Gadu number specified. Exiting.", this); + broadcastnewstatus(ID_STATUS_OFFLINE); + mir_free(p.password); + return; + } + + // Readup SSL/TLS setting + if (p.tls = db_get_b(NULL, m_szModuleName, GG_KEY_SSLCONN, GG_KEYDEF_SSLCONN)) + netlog("gg_mainthread(%x): Using TLS/SSL for connections.", this); + + // Gadu-Gadu accepts image sizes upto 255 + p.image_size = 255; + + ////////////////////////////// DCC STARTUP ///////////////////////////// + // Uin is ok so startup dcc if not started already + if (!dcc) + { + hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + dccstart(); + + // Wait for DCC +#ifdef DEBUGMODE + netlog("gg_mainthread(%x): Waiting DCC service to start...", this); +#endif + while (WaitForSingleObjectEx(hEvent, INFINITE, TRUE) != WAIT_OBJECT_0); + CloseHandle(hEvent); hEvent = NULL; + } + // Check if dcc is running and setup forwarding port + if (dcc && db_get_b(NULL, m_szModuleName, GG_KEY_FORWARDING, GG_KEYDEF_FORWARDING)) + { + if (!db_get_s(NULL, m_szModuleName, GG_KEY_FORWARDHOST, &dbv, DBVT_ASCIIZ)) + { + if (!(p.external_addr = gg_dnslookup(this, dbv.pszVal))) + { + TCHAR error[128]; + mir_sntprintf(error, SIZEOF(error), TranslateT("External direct connections hostname %S is invalid. Disabling external host forwarding."), dbv.pszVal); + showpopup(m_tszUserName, error, GG_POPUP_WARNING | GG_POPUP_ALLOW_MSGBOX); + } + else + netlog("gg_mainthread(%x): Loading forwarding host %s and port %d.", dbv.pszVal, p.external_port, this); + if (p.external_addr) p.external_port = db_get_w(NULL, m_szModuleName, GG_KEY_FORWARDPORT, GG_KEYDEF_FORWARDPORT); + DBFreeVariant(&dbv); + } + } + // Setup client port + if (dcc) p.client_port = dcc->port; + +retry: + // Loadup startup status & description + EnterCriticalSection(&modemsg_mutex); + p.status_descr = mir_strdup(getstatusmsg(m_iDesiredStatus)); + p.status = status_m2gg(m_iDesiredStatus, p.status_descr != NULL); + + netlog("gg_mainthread(%x): Connecting with number %d, status %d and description \"%s\".", this, p.uin, m_iDesiredStatus, + p.status_descr ? p.status_descr : ""); + LeaveCriticalSection(&modemsg_mutex); + + // Check manual hosts + if (hostnum < hostcount) + { + if (!(p.server_addr = gg_dnslookup(this, hosts[hostnum].hostname))) + { + TCHAR error[128]; + mir_sntprintf(error, SIZEOF(error), TranslateT("Server hostname %S is invalid. Using default hostname provided by the network."), hosts[hostnum].hostname); + showpopup(m_tszUserName, error, GG_POPUP_WARNING | GG_POPUP_ALLOW_MSGBOX); + } + else + { + p.server_port = hosts[hostnum].port; + netlog("gg_mainthread(%x): Connecting to manually specified host %s (%d.%d.%d.%d) and port %d.", this, + hosts[hostnum].hostname, LOBYTE(LOWORD(p.server_addr)), HIBYTE(LOWORD(p.server_addr)), + LOBYTE(HIWORD(p.server_addr)), HIBYTE(HIWORD(p.server_addr)), p.server_port); + } + } + else + p.server_port = p.server_addr = 0; + + // Send login request + if (!(sess = gg_login(&p, &sock, &gg_failno))) + { + broadcastnewstatus(ID_STATUS_OFFLINE); + // Check if connection attempt wasn't cancelled by the user + if (m_iDesiredStatus != ID_STATUS_OFFLINE) + { + TCHAR error[128], *perror = NULL; + // Lookup for error desciption + if (errno == EACCES) { + for (int i = 0; reason[i].type; i++) if (reason[i].type == gg_failno) { + perror = TranslateTS(reason[i].str); + break; + } + } + if (!perror) { + mir_sntprintf(error, SIZEOF(error), TranslateT("Connection cannot be established because of error:\n\t%s"), _tcserror(errno)); + perror = error; + } + netlog("gg_mainthread(%x): %s", this, perror); + if (db_get_b(NULL, m_szModuleName, GG_KEY_SHOWCERRORS, GG_KEYDEF_SHOWCERRORS)) + showpopup(m_tszUserName, perror, GG_POPUP_ERROR | GG_POPUP_ALLOW_MSGBOX | GG_POPUP_ONCE); + + // Check if we should reconnect + if ((gg_failno >= GG_FAILURE_RESOLVING && gg_failno != GG_FAILURE_PASSWORD && gg_failno != GG_FAILURE_INTRUDER && gg_failno != GG_FAILURE_UNAVAILABLE) + && errno == EACCES + && (db_get_b(NULL, m_szModuleName, GG_KEY_ARECONNECT, GG_KEYDEF_ARECONNECT) || (hostnum < hostcount - 1))) + { + DWORD dwInterval = db_get_dw(NULL, m_szModuleName, GG_KEY_RECONNINTERVAL, GG_KEYDEF_RECONNINTERVAL), dwResult; + BOOL bRetry = TRUE; + + hConnStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + dwResult = WaitForSingleObjectEx(hConnStopEvent, dwInterval, TRUE); + if ((dwResult == WAIT_OBJECT_0 && m_iDesiredStatus == ID_STATUS_OFFLINE) + || (dwResult == WAIT_IO_COMPLETION && Miranda_Terminated())) + bRetry = FALSE; + CloseHandle(hConnStopEvent); + hConnStopEvent = NULL; + + // Reconnect to the next server on the list + if (bRetry) + { + if (hostnum < hostcount - 1) hostnum++; + mir_free(p.status_descr); + broadcastnewstatus(ID_STATUS_CONNECTING); + goto retry; + } + } + // We cannot do more about this + EnterCriticalSection(&modemsg_mutex); + m_iDesiredStatus = ID_STATUS_OFFLINE; + LeaveCriticalSection(&modemsg_mutex); + } + else + netlog("gg_mainthread(%x)): Connection attempt cancelled by the user.", this); + } + else + { + // Successfully connected + logonTime = time(NULL); + db_set_dw(NULL, m_szModuleName, GG_KEY_LOGONTIME, logonTime); + EnterCriticalSection(&sess_mutex); + sess = sess; + LeaveCriticalSection(&sess_mutex); + // Subscribe users status notifications + notifyall(); + // Set startup status + if (m_iDesiredStatus != status_gg2m(p.status)) + refreshstatus(m_iDesiredStatus); + else + { + broadcastnewstatus(m_iDesiredStatus); + // Change status of the contact with our own UIN (if got yourself added to the contact list) + changecontactstatus(p.uin, p.status, p.status_descr, 0, 0, 0, 0); + } + if (check_first_conn) // First connection to the account + { + // Start search for user data + GetInfo(NULL, 0); + // Fetch user avatar + getUserAvatar(); + check_first_conn = 0; + } + } + + ////////////////////////////////////////////////////////////////////////////////// + // Main loop + while(isonline()) + { + // Connection broken/closed + if (!(e = gg_watch_fd(sess))) + { + netlog("gg_mainthread(%x): Connection closed.", this); + EnterCriticalSection(&sess_mutex); + gg_free_session(sess); + sess = NULL; + LeaveCriticalSection(&sess_mutex); + break; + } + else + netlog("gg_mainthread(%x): Event: %s", this, ggdebug_eventtype(e)); + + switch(e->type) + { + // Client connected + case GG_EVENT_CONN_SUCCESS: + // Nada + break; + + // Client disconnected or connection failure + case GG_EVENT_CONN_FAILED: + case GG_EVENT_DISCONNECT: + EnterCriticalSection(&sess_mutex); + gg_free_session(sess); + sess = NULL; + LeaveCriticalSection(&sess_mutex); + break; + + // Client allowed to disconnect + case GG_EVENT_DISCONNECT_ACK: + // Send logoff + gg_logoff(sess); + break; + + // Received ackowledge + case GG_EVENT_ACK: + if (e->event.ack.seq && e->event.ack.recipient) + { + ProtoBroadcastAck(m_szModuleName, getcontact((DWORD)e->event.ack.recipient, 0, 0, NULL), + ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE) e->event.ack.seq, 0); + } + break; + + // Statuslist notify (deprecated) + case GG_EVENT_NOTIFY: + case GG_EVENT_NOTIFY_DESCR: + { + struct gg_notify_reply *n; + + n = (e->type == GG_EVENT_NOTIFY) ? e->event.notify : e->event.notify_descr.notify; + + for (; n->uin; n++) + { + char *descr = (e->type == GG_EVENT_NOTIFY_DESCR) ? e->event.notify_descr.descr : NULL; + changecontactstatus(n->uin, n->status, descr, 0, n->remote_ip, n->remote_port, n->version); + } + break; + } + // Statuslist notify (version >= 6.0) + case GG_EVENT_NOTIFY60: + { + uin_t uin = (uin_t)db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0); + int i; + for(i = 0; e->event.notify60[i].uin; i++) { + if (e->event.notify60[i].uin == uin) continue; + changecontactstatus(e->event.notify60[i].uin, e->event.notify60[i].status, e->event.notify60[i].descr, + e->event.notify60[i].time, e->event.notify60[i].remote_ip, e->event.notify60[i].remote_port, + e->event.notify60[i].version); + requestAvatar(getcontact(e->event.notify60[i].uin, 0, 0, NULL), 0); + } + break; + } + + // Pubdir search reply && read own data reply + case GG_EVENT_PUBDIR50_SEARCH_REPLY: + case GG_EVENT_PUBDIR50_READ: + case GG_EVENT_PUBDIR50_WRITE: + { + gg_pubdir50_t res = e->event.pubdir50; + int i, count; + + if (e->type == GG_EVENT_PUBDIR50_SEARCH_REPLY) + { + netlog("gg_mainthread(%x): Got user info.", this); + // Store next search UIN + if (res->seq == GG_SEQ_SEARCH) + next_uin = gg_pubdir50_next(res); + } + else if (e->type == GG_EVENT_PUBDIR50_READ) + { + netlog("gg_mainthread(%x): Got owner info.", this); + } + else if (e->type == GG_EVENT_PUBDIR50_WRITE) + { + netlog("gg_mainthread(%x): Public directory save succesful.", this); + // Update user details + GetInfo(NULL, 0); + } + + if ((count = gg_pubdir50_count(res)) > 0) + { + for (i = 0; i < count; i++) + { + // Loadup fields + const char *__fmnumber = gg_pubdir50_get(res, i, GG_PUBDIR50_UIN); + const char *__nick = gg_pubdir50_get(res, i, GG_PUBDIR50_NICKNAME); + const char *__firstname = gg_pubdir50_get(res, i, GG_PUBDIR50_FIRSTNAME); + const char *__lastname = gg_pubdir50_get(res, i, GG_PUBDIR50_LASTNAME); + const char *__familyname = gg_pubdir50_get(res, i, GG_PUBDIR50_FAMILYNAME); + const char *__birthyear = gg_pubdir50_get(res, i, GG_PUBDIR50_BIRTHYEAR); + const char *__city = gg_pubdir50_get(res, i, GG_PUBDIR50_CITY); + const char *__origincity = gg_pubdir50_get(res, i, GG_PUBDIR50_FAMILYCITY); + const char *__gender = gg_pubdir50_get(res, i, GG_PUBDIR50_GENDER); + const char *__status = gg_pubdir50_get(res, i, GG_PUBDIR50_STATUS); + uin_t uin = __fmnumber ? atoi(__fmnumber) : 0; + + HANDLE hContact = (res->seq == GG_SEQ_CHINFO) ? NULL : getcontact(uin, 0, 0, NULL); + netlog("gg_mainthread(%x): Search result for uin %d, seq %d.", this, uin, res->seq); + if (res->seq == GG_SEQ_SEARCH) + { + char strFmt1[64]; + char strFmt2[64]; + + mir_snprintf(strFmt2, sizeof(strFmt2), "%s", (char *)CallService(MS_CLIST_GETSTATUSMODEDESCRIPTION, status_gg2m(atoi(__status)), 0)); + if (__city) + { + mir_snprintf(strFmt1, sizeof(strFmt1), ", %s %s", Translate("City:"), __city); + strncat(strFmt2, strFmt1, sizeof(strFmt2) - strlen(strFmt2)); + } + if (__birthyear) + { + time_t t = time(NULL); + struct tm *lt = localtime(&t); + int br = atoi(__birthyear); + + if (br < (lt->tm_year + 1900) && br > 1900) + { + mir_snprintf(strFmt1, sizeof(strFmt1), ", %s %d", Translate("Age:"), (lt->tm_year + 1900) - br); + strncat(strFmt2, strFmt1, sizeof(strFmt2) - strlen(strFmt2)); + } + } + + GGSEARCHRESULT sr; + memset(&sr, 0, sizeof(sr)); + sr.cbSize = sizeof(sr); + sr.nick = mir_a2t(__nick); + sr.firstName = mir_a2t(__firstname); + sr.lastName = mir_a2t(__lastname); + sr.email = mir_a2t(strFmt2); + sr.id = mir_a2t(_ultoa(uin, strFmt1, 10)); + sr.uin = uin; + ProtoBroadcastAck(m_szModuleName, NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE) 1, (LPARAM)&sr); + mir_free(sr.nick); + mir_free(sr.firstName); + mir_free(sr.lastName); + mir_free(sr.email); + mir_free(sr.id); + } + + if (((res->seq == GG_SEQ_INFO || res->seq == GG_SEQ_GETNICK) && hContact != NULL) + || res->seq == GG_SEQ_CHINFO) + { + // Change nickname if it's not present + if (__nick && (res->seq == GG_SEQ_GETNICK || res->seq == GG_SEQ_CHINFO)) + db_set_s(hContact, m_szModuleName, GG_KEY_NICK, __nick); + + if (__nick) + db_set_s(hContact, m_szModuleName, "NickName", __nick); + else if (res->seq == GG_SEQ_CHINFO) + db_unset(NULL, m_szModuleName, "NickName"); + + // Change other info + if (__city) + db_set_s(hContact, m_szModuleName, "City", __city); + else if (res->seq == GG_SEQ_CHINFO) + db_unset(NULL, m_szModuleName, "City"); + + if (__firstname) + db_set_s(hContact, m_szModuleName, "FirstName", __firstname); + else if (res->seq == GG_SEQ_CHINFO) + db_unset(NULL, m_szModuleName, "FirstName"); + + if (__lastname) + db_set_s(hContact, m_szModuleName, "LastName", __lastname); + else if (res->seq == GG_SEQ_CHINFO) + db_unset(NULL, m_szModuleName, "LastName"); + + if (__familyname) + db_set_s(hContact, m_szModuleName, "FamilyName", __familyname); + else if (res->seq == GG_SEQ_CHINFO) + db_unset(NULL, m_szModuleName, "FamilyName"); + + if (__origincity) + db_set_s(hContact, m_szModuleName, "CityOrigin", __origincity); + else if (res->seq == GG_SEQ_CHINFO) + db_unset(NULL, m_szModuleName, "CityOrigin"); + + if (__birthyear) + { + time_t t = time(NULL); + struct tm *lt = localtime(&t); + int br = atoi(__birthyear); + if (br > 0) + { + db_set_w(hContact, m_szModuleName, "Age", (WORD)(lt->tm_year + 1900 - br)); + db_set_w(hContact, m_szModuleName, "BirthYear", (WORD)br); + } + } + else if (res->seq == GG_SEQ_CHINFO) + { + db_unset(NULL, m_szModuleName, "Age"); + db_unset(NULL, m_szModuleName, "BirthYear"); + } + + // Gadu-Gadu Male <-> Female + if (__gender) + { + if (res->seq == GG_SEQ_CHINFO) + db_set_b(hContact, m_szModuleName, "Gender", + (BYTE)(!strcmp(__gender, GG_PUBDIR50_GENDER_SET_MALE) ? 'M' : + (!strcmp(__gender, GG_PUBDIR50_GENDER_SET_FEMALE) ? 'F' : '?'))); + else + db_set_b(hContact, m_szModuleName, "Gender", + (BYTE)(!strcmp(__gender, GG_PUBDIR50_GENDER_MALE) ? 'M' : + (!strcmp(__gender, GG_PUBDIR50_GENDER_FEMALE) ? 'F' : '?'))); + } + else if (res->seq == GG_SEQ_CHINFO) + { + db_unset(NULL, m_szModuleName, "Gender"); + } + + netlog("gg_mainthread(%x): Setting user info for uin %d.", this, uin); + ProtoBroadcastAck(m_szModuleName, hContact, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, (HANDLE) 1, 0); + } + } + } + if (res->seq == GG_SEQ_SEARCH) + ProtoBroadcastAck(m_szModuleName, NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE) 1, 0); + break; + } + + // Status (deprecated) + case GG_EVENT_STATUS: + changecontactstatus(e->event.status.uin, e->event.status.status, e->event.status.descr, 0, 0, 0, 0); + break; + + // Status (version >= 6.0) + case GG_EVENT_STATUS60: + { + HANDLE hContact = getcontact(e->event.status60.uin, 0, 0, NULL); + int oldstatus = db_get_w(hContact, m_szModuleName, GG_KEY_STATUS, (WORD)ID_STATUS_OFFLINE); + uin_t uin = (uin_t)db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0); + + if (e->event.status60.uin == uin) + { + // Status was changed by the user simultaneously logged on using different Miranda account or IM client + int iStatus = status_gg2m(e->event.status60.status); + CallProtoService(m_szModuleName, PS_SETAWAYMSG, iStatus, (LPARAM)e->event.status60.descr); + CallProtoService(m_szModuleName, PS_SETSTATUS, iStatus, 0); + } + + changecontactstatus(e->event.status60.uin, e->event.status60.status, e->event.status60.descr, + e->event.status60.time, e->event.status60.remote_ip, e->event.status60.remote_port, e->event.status60.version); + + if (oldstatus == ID_STATUS_OFFLINE && db_get_w(hContact, m_szModuleName, GG_KEY_STATUS, (WORD)ID_STATUS_OFFLINE) != ID_STATUS_OFFLINE) + requestAvatar(hContact, 0); + } + break; + + // Received userlist / or put info + case GG_EVENT_USERLIST: + switch (e->event.userlist.type) { + case GG_USERLIST_GET_REPLY: + if (e->event.userlist.reply) { + parsecontacts(e->event.userlist.reply); + MessageBox(NULL, TranslateT("List import successful."), m_tszUserName, MB_OK | MB_ICONINFORMATION); + } + break; + + case GG_USERLIST_PUT_REPLY: + if (is_list_remove) + MessageBox(NULL, TranslateT("List remove successful."), m_tszUserName, MB_OK | MB_ICONINFORMATION); + else + MessageBox(NULL, TranslateT("List export successful."), m_tszUserName, MB_OK | MB_ICONINFORMATION); + break; + } + break; + + // Received message + case GG_EVENT_MSG: + // This is CTCP request + if ((e->event.msg.msgclass & GG_CLASS_CTCP)) + { + dccconnect(e->event.msg.sender); + } + // Check if not conference and block + else if (!e->event.msg.recipients_count || gc_enabled) + { + // Check if groupchat + if (e->event.msg.recipients_count && gc_enabled && !db_get_b(NULL, m_szModuleName, GG_KEY_IGNORECONF, GG_KEYDEF_IGNORECONF)) + { + char *chat = gc_getchat(e->event.msg.sender, e->event.msg.recipients, e->event.msg.recipients_count); + if (chat) + { + char id[32]; + GCDEST gcdest = {m_szModuleName, chat, GC_EVENT_MESSAGE}; + GCEVENT gcevent = {sizeof(GCEVENT), &gcdest}; + time_t t = time(NULL); + + UIN2ID(e->event.msg.sender, id); + + gcevent.pszUID = id; + gcevent.pszText = e->event.msg.message; + gcevent.pszNick = (char *) CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM) getcontact(e->event.msg.sender, 1, 0, NULL), 0); + gcevent.time = (!(e->event.msg.msgclass & GG_CLASS_OFFLINE) || e->event.msg.time > (t - timeDeviation)) ? t : e->event.msg.time; + gcevent.dwFlags = GCEF_ADDTOLOG; + netlog("gg_mainthread(%x): Conference message to room %s & id %s.", this, chat, id); + CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gcevent); + } + } + // Check if not empty message ( who needs it? ) + else if (!e->event.msg.recipients_count && e->event.msg.message && *e->event.msg.message && strcmp(e->event.msg.message, "\xA0\0")) + { + CCSDATA ccs = {0}; + PROTORECVEVENT pre = {0}; + time_t t = time(NULL); + ccs.szProtoService = PSR_MESSAGE; + ccs.hContact = getcontact(e->event.msg.sender, 1, 0, NULL); + ccs.lParam = (LPARAM)⪯ + pre.timestamp = (!(e->event.msg.msgclass & GG_CLASS_OFFLINE) || e->event.msg.time > (t - timeDeviation)) ? t : e->event.msg.time; + pre.szMessage = e->event.msg.message; + CallService(MS_PROTO_CHAINRECV, 0, (LPARAM) &ccs); + } + + // RichEdit format included (image) + if (e->event.msg.formats_length && + db_get_b(NULL, m_szModuleName, GG_KEY_IMGRECEIVE, GG_KEYDEF_IMGRECEIVE) && + !(db_get_dw(getcontact(e->event.msg.sender, 1, 0, NULL), "Ignore", "Mask1", 0) & IGNOREEVENT_MESSAGE)) + { + char *formats = (char*)e->event.msg.formats; + int len = 0, formats_len = e->event.msg.formats_length, add_ptr; + + while (len < formats_len) + { + add_ptr = sizeof(struct gg_msg_richtext_format); + if (((struct gg_msg_richtext_format*)formats)->font & GG_FONT_IMAGE) + { + struct gg_msg_richtext_image *image = (struct gg_msg_richtext_image *)(formats + add_ptr); + EnterCriticalSection(&sess_mutex); + gg_image_request(sess, e->event.msg.sender, image->size, image->crc32); + LeaveCriticalSection(&sess_mutex); + + netlog("gg_mainthread: image request sent!"); + add_ptr += sizeof(struct gg_msg_richtext_image); + } + if (((struct gg_msg_richtext_format*)formats)->font & GG_FONT_COLOR) + add_ptr += sizeof(struct gg_msg_richtext_color); + len += add_ptr; + formats += add_ptr; + } + } + } + break; + + // Message sent from concurrent user session + case GG_EVENT_MULTILOGON_MSG: + if (e->event.multilogon_msg.recipients_count && gc_enabled && !db_get_b(NULL, m_szModuleName, GG_KEY_IGNORECONF, GG_KEYDEF_IGNORECONF)) + { + char *chat = gc_getchat(e->event.multilogon_msg.sender, e->event.multilogon_msg.recipients, e->event.multilogon_msg.recipients_count); + if (chat) + { + char id[32]; + DBVARIANT dbv; + GCDEST gcdest = {m_szModuleName, chat, GC_EVENT_MESSAGE}; + GCEVENT gcevent = {sizeof(GCEVENT), &gcdest}; + + UIN2ID( db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0), id); + + gcevent.pszUID = id; + gcevent.pszText = e->event.multilogon_msg.message; + if (!db_get_s(NULL, m_szModuleName, GG_KEY_NICK, &dbv, DBVT_ASCIIZ)) + gcevent.pszNick = dbv.pszVal; + else + gcevent.pszNick = Translate("Me"); + gcevent.time = e->event.multilogon_msg.time; + gcevent.bIsMe = 1; + gcevent.dwFlags = GCEF_ADDTOLOG; + netlog("gg_mainthread(%x): Sent conference message to room %s.", this, chat); + CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gcevent); + if (gcevent.pszNick == dbv.pszVal) DBFreeVariant(&dbv); + } + } + else if (!e->event.multilogon_msg.recipients_count && e->event.multilogon_msg.message && *e->event.multilogon_msg.message + && strcmp(e->event.multilogon_msg.message, "\xA0\0")) + { + DBEVENTINFO dbei = {0}; + dbei.cbSize = sizeof(dbei); + dbei.szModule = m_szModuleName; + dbei.timestamp = (DWORD)e->event.multilogon_msg.time; + dbei.flags = DBEF_SENT; + dbei.eventType = EVENTTYPE_MESSAGE; + dbei.cbBlob = (DWORD)strlen(e->event.multilogon_msg.message) + 1; + dbei.pBlob = (PBYTE)e->event.multilogon_msg.message; + CallService(MS_DB_EVENT_ADD, (WPARAM)getcontact(e->event.multilogon_msg.sender, 1, 0, NULL), (LPARAM)&dbei); + } + break; + + // Information on active concurrent sessions + case GG_EVENT_MULTILOGON_INFO: + { + list_t l; + int* iIndexes = NULL, i; + netlog("gg_mainthread(): Concurrent sessions count: %d.", e->event.multilogon_info.count); + if (e->event.multilogon_info.count > 0) + iIndexes = (int*)mir_calloc(e->event.multilogon_info.count * sizeof(int)); + EnterCriticalSection(&sessions_mutex); + for (l = sessions; l; l = l->next) + { + struct gg_multilogon_session* sess = (struct gg_multilogon_session*)l->data; + for (i = 0; i < e->event.multilogon_info.count; i++) + { + if (!memcmp(&sess->id, &e->event.multilogon_info.sessions[i].id, sizeof(gg_multilogon_id_t)) && iIndexes) + { + iIndexes[i]++; + break; + } + } + mir_free(sess->name); + mir_free(sess); + } + list_destroy(sessions, 0); + sessions = NULL; + for (i = 0; i < e->event.multilogon_info.count; i++) + { + gg_multilogon_session* sess = (gg_multilogon_session*)mir_alloc(sizeof(struct gg_multilogon_session)); + memcpy(sess, &e->event.multilogon_info.sessions[i], sizeof(struct gg_multilogon_session)); + sess->name = mir_strdup(*e->event.multilogon_info.sessions[i].name != '\0' + ? e->event.multilogon_info.sessions[i].name + : Translate("Unknown client")); + list_add(&sessions, sess, 0); + } + LeaveCriticalSection(&sessions_mutex); + sessions_updatedlg(); + if (ServiceExists(MS_POPUP_ADDPOPUPCLASS)) + { + const TCHAR* szText = time(NULL) - logonTime > 3 + ? TranslateT("You have logged in at another location") + : TranslateT("You are logged in at another location"); + for (i = 0; i < e->event.multilogon_info.count; i++) + { + TCHAR szMsg[MAX_SECONDLINE]; + if (iIndexes && iIndexes[i]) + continue; + + mir_sntprintf(szMsg, SIZEOF(szMsg), _T("%s (%s)"), szText, + *e->event.multilogon_info.sessions[i].name != '\0' ? + _A2T(e->event.multilogon_info.sessions[i].name) : TranslateT("Unknown client")); + showpopup(m_tszUserName, szMsg, GG_POPUP_MULTILOGON); + } + } + mir_free(iIndexes); + } + break; + + // Image reply sent + case GG_EVENT_IMAGE_REPLY: + // Get rid of empty image + if (e->event.image_reply.size && e->event.image_reply.image) + { + HANDLE hContact = getcontact(e->event.image_reply.sender, 1, 0, NULL); + void *img = (void *)img_loadpicture(e, 0); + + if (!img) + break; + + if (db_get_b(NULL, m_szModuleName, GG_KEY_IMGMETHOD, GG_KEYDEF_IMGMETHOD) == 1 || img_opened(e->event.image_reply.sender)) + { + img_display(hContact, img); + } + else if (db_get_b(NULL, m_szModuleName, GG_KEY_IMGMETHOD, GG_KEYDEF_IMGMETHOD) == 2) + { + img_displayasmsg(hContact, img); + } + else + { + CLISTEVENT cle = {0}; + char service[128]; + mir_snprintf(service, sizeof(service), GGS_RECVIMAGE, m_szModuleName); + + cle.cbSize = sizeof(cle); + cle.hContact = hContact; + cle.hIcon = LoadIconEx("image", FALSE); + cle.flags = CLEF_URGENT; + cle.hDbEvent = (HANDLE)"img"; + cle.lParam = (LPARAM)img; + cle.pszService = service; + cle.pszTooltip = Translate("Incoming image"); + CallService(MS_CLIST_ADDEVENT, 0, (LPARAM)&cle); + ReleaseIconEx("image", FALSE); + } + } + break; + + // Image send request + case GG_EVENT_IMAGE_REQUEST: + img_sendonrequest(e); + break; + + // Incoming direct connection + case GG_EVENT_DCC7_NEW: + { + struct gg_dcc7 *dcc7 = e->event.dcc7_new; + netlog("gg_mainthread(%x): Incoming direct connection.", this); + dcc7->contact = getcontact(dcc7->peer_uin, 0, 0, NULL); + + // Check if user is on the list and if it is my uin + if (!dcc7->contact || db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, -1) != dcc7->uin) { + gg_dcc7_free(dcc7); + e->event.dcc7_new = NULL; + break; + } + + // Add to waiting transfers + EnterCriticalSection(&ft_mutex); + list_add(&transfers, dcc7, 0); + LeaveCriticalSection(&ft_mutex); + + ////////////////////////////////////////////////// + // Add file recv request + + netlog("gg_mainthread(%x): Client: %d, File ack filename \"%s\" size %d.", this, dcc7->peer_uin, + dcc7->filename, dcc7->size); + + TCHAR* filenameT = mir_utf8decodeT((char*)dcc7->filename); + + PROTORECVFILET pre = {0}; + pre.flags = PREF_TCHAR; + pre.fileCount = 1; + pre.timestamp = time(NULL); + pre.tszDescription = filenameT; + pre.ptszFiles = &filenameT; + pre.lParam = (LPARAM)dcc7; + + CCSDATA ccs = { dcc7->contact, PSR_FILE, 0, (LPARAM)&pre }; + CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs); + + mir_free(filenameT); + e->event.dcc7_new = NULL; + } + break; + + // Direct connection rejected + case GG_EVENT_DCC7_REJECT: + { + struct gg_dcc7 *dcc7 = e->event.dcc7_reject.dcc7; + if (dcc7->type == GG_SESSION_DCC7_SEND) + { + netlog("gg_mainthread(%x): File transfer denied by client %d (reason = %d).", this, dcc7->peer_uin, e->event.dcc7_reject.reason); + ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_DENIED, dcc7, 0); + + // Remove from watches and free + EnterCriticalSection(&ft_mutex); + list_remove(&watches, dcc7, 0); + LeaveCriticalSection(&ft_mutex); + gg_dcc7_free(dcc7); + } + else + { + netlog("gg_mainthread(%x): File transfer aborted by client %d.", this, dcc7->peer_uin); + + // Remove transfer from waiting list + EnterCriticalSection(&ft_mutex); + list_remove(&transfers, dcc7, 0); + LeaveCriticalSection(&ft_mutex); + } + } + break; + + // Direct connection error + case GG_EVENT_DCC7_ERROR: + { + struct gg_dcc7 *dcc7 = e->event.dcc7_error_ex.dcc7; + switch (e->event.dcc7_error) + { + case GG_ERROR_DCC7_HANDSHAKE: + netlog("gg_mainthread(%x): Client: %d, Handshake error.", this, dcc7 ? dcc7->peer_uin : 0); + break; + case GG_ERROR_DCC7_NET: + netlog("gg_mainthread(%x): Client: %d, Network error.", this, dcc7 ? dcc7->peer_uin : 0); + break; + case GG_ERROR_DCC7_FILE: + netlog("gg_mainthread(%x): Client: %d, File read/write error.", this, dcc7 ? dcc7->peer_uin : 0); + break; + case GG_ERROR_DCC7_EOF: + netlog("gg_mainthread(%x): Client: %d, End of file/connection error.", this, dcc7 ? dcc7->peer_uin : 0); + break; + case GG_ERROR_DCC7_REFUSED: + netlog("gg_mainthread(%x): Client: %d, Connection refused error.", this, dcc7 ? dcc7->peer_uin : 0); + break; + case GG_ERROR_DCC7_RELAY: + netlog("gg_mainthread(%x): Client: %d, Relay connection error.", this, dcc7 ? dcc7->peer_uin : 0); + break; + default: + netlog("gg_mainthread(%x): Client: %d, Unknown error.", this, dcc7 ? dcc7->peer_uin : 0); + } + if (!dcc7) break; + + // Remove from watches + list_remove(&watches, dcc7, 0); + + // Close file & fail + if (dcc7->file_fd != -1) + { + _close(dcc7->file_fd); + dcc7->file_fd = -1; + } + + if (dcc7->contact) + ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_FAILED, dcc7, 0); + + // Free dcc + gg_dcc7_free(dcc7); + } + break; + + case GG_EVENT_XML_ACTION: + if (db_get_b(NULL, m_szModuleName, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS)) { + HXML hXml; + TCHAR *xmlAction; + TCHAR *tag; + + xmlAction = mir_a2t(e->event.xml_action.data); + tag = mir_a2t("events"); + hXml = xi.parseString(xmlAction, 0, tag); + + if (hXml != NULL) { + HXML node; + char *type, *sender; + + mir_free(tag); + tag = mir_a2t("event/type"); + node = xi.getChildByPath(hXml, tag, 0); + type = node != NULL ? mir_t2a(xi.getText(node)) : NULL; + + mir_free(tag); + tag = mir_a2t("event/sender"); + node = xi.getChildByPath(hXml, tag, 0); + sender = node != NULL ? mir_t2a(xi.getText(node)) : NULL; + netlog("gg_mainthread(%x): XML Action type: %s.", this, type != NULL ? type : "unknown"); + // Avatar change notify + if (type != NULL && !strcmp(type, "28")) { + netlog("gg_mainthread(%x): Client %s changed his avatar.", this, sender); + requestAvatar(getcontact(atoi(sender), 0, 0, NULL), 0); + } + mir_free(type); + mir_free(sender); + xi.destroyNode(hXml); + } + mir_free(tag); + mir_free(xmlAction); + } + break; + + case GG_EVENT_TYPING_NOTIFICATION: + { + HANDLE hContact = getcontact(e->event.typing_notification.uin, 0, 0, NULL); +#ifdef DEBUGMODE + netlog("gg_mainthread(%x): Typing notification from %d (%d).", this, + e->event.typing_notification.uin, e->event.typing_notification.length); +#endif + CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hContact, + e->event.typing_notification.length > 0 ? 7 : PROTOTYPE_CONTACTTYPING_OFF); + } + break; + } + // Free event struct + gg_free_event(e); + } + + broadcastnewstatus(ID_STATUS_OFFLINE); + setalloffline(); + db_set_dw(NULL, m_szModuleName, GG_KEY_LOGONTIME, 0); + + // If it was unwanted disconnection reconnect + if (m_iDesiredStatus != ID_STATUS_OFFLINE + && db_get_b(NULL, m_szModuleName, GG_KEY_ARECONNECT, GG_KEYDEF_ARECONNECT)) + { + netlog("gg_mainthread(%x): Unintentional disconnection detected. Going to reconnect...", this); + hostnum = 0; + broadcastnewstatus(ID_STATUS_CONNECTING); + mir_free(p.status_descr); + goto retry; + } + + mir_free(p.password); + mir_free(p.status_descr); + + // Destroy concurrent sessions list + { + list_t l; + EnterCriticalSection(&sessions_mutex); + for (l = sessions; l; l = l->next) + { + struct gg_multilogon_session* sess = (struct gg_multilogon_session*)l->data; + mir_free(sess->name); + mir_free(sess); + } + list_destroy(sessions, 0); + sessions = NULL; + LeaveCriticalSection(&sessions_mutex); + } + + // Stop dcc server + pth_dcc.dwThreadId = 0; +#ifdef DEBUGMODE + netlog("gg_mainthread(%x): Waiting until DCC Server Thread finished, if needed.", this); +#endif + threadwait(&pth_dcc); + + netlog("gg_mainthread(%x): Server Thread Ending", this); + return; +} + +//////////////////////////////////////////////////////////// +// Change status function +void GGPROTO::broadcastnewstatus(int newStatus) +{ + int oldStatus; + + EnterCriticalSection(&modemsg_mutex); + oldStatus = m_iStatus; + if (oldStatus == newStatus) + { + LeaveCriticalSection(&modemsg_mutex); + return; + } + m_iStatus = newStatus; + LeaveCriticalSection(&modemsg_mutex); + + ProtoBroadcastAck(m_szModuleName, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE) oldStatus, newStatus); + + netlog("gg_broadcastnewstatus(): Broadcast new status: %d.", newStatus); +} + +//////////////////////////////////////////////////////////// +// When contact is deleted +int GGPROTO::contactdeleted(WPARAM wParam, LPARAM lParam) +{ + HANDLE hContact = (HANDLE) wParam; + uin_t uin; int type; + DBVARIANT dbv; + + uin = (uin_t)db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0); + type = db_get_b(hContact, m_szModuleName, "ChatRoom", 0); + + // Terminate conference if contact is deleted + if (type && !db_get_s(hContact, m_szModuleName, "ChatRoomID", &dbv, DBVT_ASCIIZ) && gc_enabled) + { + GCDEST gcdest = {m_szModuleName, dbv.pszVal, GC_EVENT_CONTROL}; + GCEVENT gcevent = {sizeof(GCEVENT), &gcdest}; + GGGC *chat = gc_lookup(dbv.pszVal); + + netlog("gg_gc_event(): Terminating chat %x, id %s from contact list...", chat, dbv.pszVal); + if (chat) + { + // Destroy chat entry + free(chat->recipients); + list_remove(&chats, chat, 1); + // Terminate chat window / shouldn't cascade entry is deleted + CallServiceSync(MS_GC_EVENT, SESSION_OFFLINE, (LPARAM)&gcevent); + CallServiceSync(MS_GC_EVENT, SESSION_TERMINATE, (LPARAM)&gcevent); + } + + DBFreeVariant(&dbv); + return 0; + } + + if (uin && isonline()) + { + EnterCriticalSection(&sess_mutex); + gg_remove_notify_ex(sess, uin, GG_USER_NORMAL); + LeaveCriticalSection(&sess_mutex); + } + + return 0; +} + +//////////////////////////////////////////////////////////// +// When db settings changed + +int GGPROTO::dbsettingchanged(WPARAM wParam, LPARAM lParam) +{ + DBCONTACTWRITESETTING *cws = (DBCONTACTWRITESETTING *) lParam; + HANDLE hContact = (HANDLE) wParam; + char *szProto = NULL; + + // Check if the contact is NULL or we are not online + if (!isonline()) + return 0; + + // If contact has been blocked + if (!strcmp(cws->szModule, m_szModuleName) && !strcmp(cws->szSetting, GG_KEY_BLOCK)) + { + notifyuser(hContact, 1); + return 0; + } + + // Contact is being renamed + if (gc_enabled && !strcmp(cws->szModule, m_szModuleName) && !strcmp(cws->szSetting, GG_KEY_NICK) + && cws->value.pszVal) + { + // Groupchat window contact is being renamed + DBVARIANT dbv; + int type = db_get_b(hContact, m_szModuleName, "ChatRoom", 0); + if (type && !db_get_s(hContact, m_szModuleName, "ChatRoomID", &dbv, DBVT_ASCIIZ)) + { + // Most important... check redundancy (fucking cascading) + static int cascade = 0; + if (!cascade && dbv.pszVal) + { + GCDEST gcdest = {m_szModuleName, dbv.pszVal, GC_EVENT_CHANGESESSIONAME}; + GCEVENT gcevent = {sizeof(GCEVENT), &gcdest}; + gcevent.pszText = cws->value.pszVal; + netlog("gg_dbsettingchanged(): Conference %s was renamed to %s.", dbv.pszVal, cws->value.pszVal); + // Mark cascading + /* FIXME */ cascade = 1; + CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gcevent); + /* FIXME */ cascade = 0; + } + DBFreeVariant(&dbv); + } + else + // Change contact name on all chats + gc_changenick(hContact, cws->value.pszVal); + } + + // Contact list changes + if (!strcmp(cws->szModule, "CList")) + { + // If name changed... change nick + if (!strcmp(cws->szSetting, "MyHandle") && cws->value.type == DBVT_ASCIIZ && cws->value.pszVal) + db_set_s(hContact, m_szModuleName, GG_KEY_NICK, cws->value.pszVal); + + // If not on list changed + if (!strcmp(cws->szSetting, "NotOnList")) + { + if (db_get_b(hContact, "CList", "Hidden", 0)) + return 0; + // Notify user normally this time if added to the list permanently + if (cws->value.type == DBVT_DELETED || (cws->value.type == DBVT_BYTE && cws->value.bVal == 0)) + notifyuser(hContact, 1); + } + } + return 0; +} + +//////////////////////////////////////////////////////////// +// All users set offline + +void GGPROTO::setalloffline() +{ + netlog("gg_setalloffline(): Setting buddies offline"); + db_set_w(NULL, m_szModuleName, GG_KEY_STATUS, ID_STATUS_OFFLINE); + HANDLE hContact = db_find_first(); + while (hContact) + { + char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0); + if (szProto != NULL && !strcmp(szProto, m_szModuleName)) + { + db_set_w(hContact, m_szModuleName, GG_KEY_STATUS, ID_STATUS_OFFLINE); + // Clear IP and port settings + db_unset(hContact, m_szModuleName, GG_KEY_CLIENTIP); + db_unset(hContact, m_szModuleName, GG_KEY_CLIENTPORT); + // Delete status descr + db_unset(hContact, "CList", GG_KEY_STATUSDESCR); + } + hContact = db_find_next(hContact); + } +#ifdef DEBUGMODE + netlog("gg_setalloffline(): End"); +#endif +} + +//////////////////////////////////////////////////////////// +// All users set offline + +void GGPROTO::notifyuser(HANDLE hContact, int refresh) +{ + uin_t uin; + if (!hContact) return; + if (isonline() && (uin = (uin_t)db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0))) + { + // Check if user should be invisible + // Or be blocked ? + if ((db_get_w(hContact, m_szModuleName, GG_KEY_APPARENT, (WORD) ID_STATUS_ONLINE) == ID_STATUS_OFFLINE) || + db_get_b(hContact, "CList", "NotOnList", 0)) + { + mir_cslock l(sess_mutex); + if (refresh) { + gg_remove_notify_ex(sess, uin, GG_USER_NORMAL); + gg_remove_notify_ex(sess, uin, GG_USER_BLOCKED); + } + + gg_add_notify_ex(sess, uin, GG_USER_OFFLINE); + } + else if (db_get_b(hContact, m_szModuleName, GG_KEY_BLOCK, 0)) + { + mir_cslock l(sess_mutex); + if (refresh) + gg_remove_notify_ex(sess, uin, GG_USER_OFFLINE); + + gg_add_notify_ex(sess, uin, GG_USER_BLOCKED); + } + else { + mir_cslock l(sess_mutex); + if (refresh) + gg_remove_notify_ex(sess, uin, GG_USER_BLOCKED); + + gg_add_notify_ex(sess, uin, GG_USER_NORMAL); + } + } +} + +void GGPROTO::notifyall() +{ + HANDLE hContact; + char *szProto; + int count = 0, cc = 0; + uin_t *uins; + char *types; + + netlog("gg_notifyall(): Subscribing notification to all users"); + // Readup count + hContact = db_find_first(); + while (hContact) + { + szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0); + if (szProto != NULL && !strcmp(szProto, m_szModuleName)) count ++; + hContact = db_find_next(hContact); + } + + // Readup list + /* FIXME: If we have nothing on the list but we omit gg_notify_ex we have problem with receiving any contacts */ + if (count == 0) + { + if (isonline()) + { + EnterCriticalSection(&sess_mutex); + gg_notify_ex(sess, NULL, NULL, 0); + LeaveCriticalSection(&sess_mutex); + } + return; + } + uins = (uin_t*)calloc(sizeof(uin_t), count); + types = (char*)calloc(sizeof(char), count); + + hContact = db_find_first(); + while (hContact && cc < count) + { + szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0); + if (szProto != NULL && !strcmp(szProto, m_szModuleName) && (uins[cc] = db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0))) + { + if ((db_get_w(hContact, m_szModuleName, GG_KEY_APPARENT, (WORD) ID_STATUS_ONLINE) == ID_STATUS_OFFLINE) || + db_get_b(hContact, "CList", "NotOnList", 0)) + types[cc] = GG_USER_OFFLINE; + else if (db_get_b(hContact, m_szModuleName, GG_KEY_BLOCK, 0)) + types[cc] = GG_USER_BLOCKED; + else + types[cc] = GG_USER_NORMAL; + cc ++; + } + hContact = db_find_next(hContact); + } + if (cc < count) count = cc; + + // Send notification + if (isonline()) + { + EnterCriticalSection(&sess_mutex); + gg_notify_ex(sess, uins, types, count); + LeaveCriticalSection(&sess_mutex); + } + + // Free variables + free(uins); free(types); +} + +//////////////////////////////////////////////////////////// +// Get contact by uin + +HANDLE GGPROTO::getcontact(uin_t uin, int create, int inlist, TCHAR *szNick) +{ + // Look for contact in DB + HANDLE hContact = db_find_first(); + while (hContact) { + char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0); + if (szProto != NULL && !strcmp(szProto, m_szModuleName)) { + if ((uin_t)db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0) == uin + && db_get_b(hContact, m_szModuleName, "ChatRoom", 0) == 0) + { + if (inlist) { + db_unset(hContact, "CList", "NotOnList"); + db_unset(hContact, "CList", "Hidden"); + } + return hContact; + } + } + hContact = db_find_next(hContact); + } + if (!create) return NULL; + + hContact = (HANDLE) CallService(MS_DB_CONTACT_ADD, 0, 0); + if (!hContact) { + netlog("gg_getcontact(): Failed to create Gadu-Gadu contact %s", szNick); + return NULL; + } + + if (CallService(MS_PROTO_ADDTOCONTACT, (WPARAM) hContact, (LPARAM) m_szModuleName) != 0) { + // For some reason we failed to register the protocol for this contact + CallService(MS_DB_CONTACT_DELETE, (WPARAM) hContact, 0); + netlog("Failed to register GG contact %d", uin); + return NULL; + } + + netlog("gg_getcontact(): Added buddy: %d", uin); + if (!inlist) + db_set_b(hContact, "CList", "NotOnList", 1); + + db_set_dw(hContact, m_szModuleName, GG_KEY_UIN, (DWORD) uin); + db_set_w(hContact, m_szModuleName, GG_KEY_STATUS, ID_STATUS_OFFLINE); + + // If nick specified use it + if (szNick) + db_set_ts(hContact, m_szModuleName, GG_KEY_NICK, szNick); + else if (isonline()) { + gg_pubdir50_t req; + + // Search for that nick + if (req = gg_pubdir50_new(GG_PUBDIR50_SEARCH)) { + // Add uin and search it + gg_pubdir50_add(req, GG_PUBDIR50_UIN, ditoa(uin)); + gg_pubdir50_seq_set(req, GG_SEQ_GETNICK); + EnterCriticalSection(&sess_mutex); + gg_pubdir50(sess, req); + LeaveCriticalSection(&sess_mutex); + gg_pubdir50_free(req); + db_set_s(hContact, m_szModuleName, GG_KEY_NICK, ditoa(uin)); + netlog("gg_getcontact(): Search for nick on uin: %d", uin); + } + } + + // Add to notify list and pull avatar for the new contact + if (isonline()) + { + PROTO_AVATAR_INFORMATIONT pai = {0}; + + EnterCriticalSection(&sess_mutex); + gg_add_notify_ex(sess, uin, (char)(inlist ? GG_USER_NORMAL : GG_USER_OFFLINE)); + LeaveCriticalSection(&sess_mutex); + + pai.cbSize = sizeof(pai); + pai.hContact = hContact; + getavatarinfo((WPARAM)GAIF_FORCE, (LPARAM)&pai); + + // Change status of the contact with our own UIN (if got yourself added to the contact list) + if (db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0) == uin) { + char *szMsg; + EnterCriticalSection(&modemsg_mutex); + szMsg = mir_strdup(getstatusmsg(m_iStatus)); + LeaveCriticalSection(&modemsg_mutex); + changecontactstatus(uin, status_m2gg(m_iStatus, szMsg != NULL), szMsg, 0, 0, 0, 0); + mir_free(szMsg); + } + } + + // TODO server side list & add buddy + return hContact; +} + +//////////////////////////////////////////////////////////// +// Status conversion + +int GGPROTO::status_m2gg(int status, int descr) +{ + // check frends only + int mask = db_get_b(NULL, m_szModuleName, GG_KEY_FRIENDSONLY, GG_KEYDEF_FRIENDSONLY) ? GG_STATUS_FRIENDS_MASK : 0; + + if (descr) + { + switch(status) + { + case ID_STATUS_OFFLINE: return GG_STATUS_NOT_AVAIL_DESCR | mask; + case ID_STATUS_ONLINE: return GG_STATUS_AVAIL_DESCR | mask; + case ID_STATUS_AWAY: return GG_STATUS_BUSY_DESCR | mask; + case ID_STATUS_DND: return GG_STATUS_DND_DESCR | mask; + case ID_STATUS_FREECHAT: return GG_STATUS_FFC_DESCR | mask; + case ID_STATUS_INVISIBLE: return GG_STATUS_INVISIBLE_DESCR | mask; + default: return GG_STATUS_BUSY_DESCR | mask; + } + } + else + { + switch(status) + { + case ID_STATUS_OFFLINE: return GG_STATUS_NOT_AVAIL | mask; + case ID_STATUS_ONLINE: return GG_STATUS_AVAIL | mask; + case ID_STATUS_AWAY: return GG_STATUS_BUSY | mask; + case ID_STATUS_DND: return GG_STATUS_DND | mask; + case ID_STATUS_FREECHAT: return GG_STATUS_FFC | mask; + case ID_STATUS_INVISIBLE: return GG_STATUS_INVISIBLE | mask; + default: return GG_STATUS_BUSY | mask; + } + } +} + +int GGPROTO::status_gg2m(int status) +{ + // ignore additional flags + status = GG_S(status); + + // when user has status description but is offline (show it invisible) + if (status == GG_STATUS_NOT_AVAIL_DESCR && db_get_b(NULL, m_szModuleName, GG_KEY_SHOWINVISIBLE, GG_KEYDEF_SHOWINVISIBLE)) + return ID_STATUS_INVISIBLE; + + // rest of cases + switch(status) + { + case GG_STATUS_NOT_AVAIL: + case GG_STATUS_NOT_AVAIL_DESCR: + return ID_STATUS_OFFLINE; + + case GG_STATUS_AVAIL: + case GG_STATUS_AVAIL_DESCR: + return ID_STATUS_ONLINE; + + case GG_STATUS_BUSY: + case GG_STATUS_BUSY_DESCR: + return ID_STATUS_AWAY; + + case GG_STATUS_DND: + case GG_STATUS_DND_DESCR: + return ID_STATUS_DND; + + case GG_STATUS_FFC: + case GG_STATUS_FFC_DESCR: + return ID_STATUS_FREECHAT; + + case GG_STATUS_INVISIBLE: + case GG_STATUS_INVISIBLE_DESCR: + return ID_STATUS_INVISIBLE; + + case GG_STATUS_BLOCKED: + return ID_STATUS_NA; + + default: + return ID_STATUS_OFFLINE; + } +} + +//////////////////////////////////////////////////////////// +// Called when contact status is changed + +void GGPROTO::changecontactstatus(uin_t uin, int status, const char *idescr, int time, uint32_t remote_ip, uint16_t remote_port, uint32_t version) +{ + HANDLE hContact = getcontact(uin, 0, 0, NULL); + + // Check if contact is on list + if (!hContact) return; + + // Write contact status + db_set_w(hContact, m_szModuleName, GG_KEY_STATUS, (WORD)status_gg2m(status)); + + // Check if there's description and if it's not empty + if (idescr && *idescr) + { + netlog("gg_changecontactstatus(): Saving for %d status descr \"%s\".", uin, idescr); + db_set_s(hContact, "CList", GG_KEY_STATUSDESCR, idescr); + } + else + // Remove status if there's nothing + db_unset(hContact, "CList", GG_KEY_STATUSDESCR); + + // Store contact ip and port + if (remote_ip) db_set_dw(hContact, m_szModuleName, GG_KEY_CLIENTIP, (DWORD) swap32(remote_ip)); + if (remote_port) db_set_w(hContact, m_szModuleName, GG_KEY_CLIENTPORT, (WORD) remote_port); + if (version) + { + char sversion[48]; + db_set_dw(hContact, m_szModuleName, GG_KEY_CLIENTVERSION, (DWORD) version); + mir_snprintf(sversion, sizeof(sversion), "%sGadu-Gadu %s", (version & 0x00ffffff) > 0x2b ? "Nowe " : "", gg_version2string(version)); + db_set_s(hContact, m_szModuleName, "MirVer", sversion); + } +} + +//////////////////////////////////////////////////////////// +// Returns GG client version string from packet version +const char *gg_version2string(int v) +{ + const char *pstr = "???"; + v &= 0x00ffffff; + switch(v) + { + case 0x2e: + pstr = "8.0 build 8283"; break; + case 0x2d: + pstr = "8.0 build 4881"; break; + case 0x2b: + pstr = "< 8.0"; break; + case 0x2a: + pstr = "7.7 build 3315"; break; + case 0x29: + pstr = "7.6 build 1688"; break; + case 0x28: + pstr = "7.5 build 2201"; break; + case 0x27: + pstr = "7.0 build 22"; break; + case 0x26: + pstr = "7.0 build 20"; break; + case 0x25: + pstr = "7.0 build 1"; break; + case 0x24: + pstr = "6.1 (155) / 7.6 (1359)"; break; + case 0x22: + pstr = "6.0 build 140"; break; + case 0x21: + pstr = "6.0 build 133"; break; + case 0x20: + pstr = "6.0b"; break; + case 0x1e: + pstr = "5.7b build 121"; break; + case 0x1c: + pstr = "5.7b"; break; + case 0x1b: + pstr = "5.0.5"; break; + case 0x19: + pstr = "5.0.3"; break; + case 0x18: + pstr = "5.0.0-1"; break; + case 0x17: + pstr = "4.9.2"; break; + case 0x16: + pstr = "4.9.1"; break; + case 0x15: + pstr = "4.8.9"; break; + case 0x14: + pstr = "4.8.1-3"; break; + case 0x11: + pstr = "4.6.1-10"; break; + case 0x10: + pstr = "4.5.15-22"; break; + case 0x0f: + pstr = "4.5.12"; break; + case 0x0b: + pstr = "4.0.25-30"; break; + default: + if (v < 0x0b) + pstr = "< 4.0.25"; + else if (v > 0x2e) + pstr = ">= 8.0"; + break; + } + return pstr; +} diff --git a/protocols/Gadu-Gadu/src/dialogs.cpp b/protocols/Gadu-Gadu/src/dialogs.cpp new file mode 100644 index 0000000000..345624715b --- /dev/null +++ b/protocols/Gadu-Gadu/src/dialogs.cpp @@ -0,0 +1,1016 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2006 Adam Strzelecki +// +// 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 "gg.h" + +static INT_PTR CALLBACK gg_genoptsdlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); +static INT_PTR CALLBACK gg_confoptsdlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); +static INT_PTR CALLBACK gg_advoptsdlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); +extern INT_PTR CALLBACK gg_userutildlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); + +//////////////////////////////////////////////////////////////////////////////// +// SetValue + +#define SVS_NORMAL 0 +#define SVS_GENDER 1 +#define SVS_ZEROISUNSPEC 2 +#define SVS_IP 3 +#define SVS_COUNTRY 4 +#define SVS_MONTH 5 +#define SVS_SIGNED 6 +#define SVS_TIMEZONE 7 +#define SVS_GGVERSION 9 + +static void SetValue(HWND hwndDlg, int idCtrl, HANDLE hContact, char *szModule, char *szSetting, int special, int disableIfUndef) +{ + DBVARIANT dbv = {0}; + char str[80], *pstr = NULL; + int unspecified = 0; + + dbv.type = DBVT_DELETED; + if (szModule == NULL) unspecified = 1; + else unspecified = DBGetContactSettingW(hContact, szModule, szSetting, &dbv); + if (!unspecified) { + switch (dbv.type) { + case DBVT_BYTE: + if (special == SVS_GENDER) { + if (dbv.cVal == 'M') pstr = Translate("Male"); + else if (dbv.cVal == 'F') pstr = Translate("Female"); + else unspecified = 1; + } + else if (special == SVS_MONTH) { + if (dbv.bVal > 0 && dbv.bVal <= 12) { + pstr = str; + GetLocaleInfoA(LOCALE_USER_DEFAULT, LOCALE_SABBREVMONTHNAME1 - 1 + dbv.bVal, str, SIZEOF(str)); + } + else unspecified = 1; + } + else if (special == SVS_TIMEZONE) { + if (dbv.cVal == -100) unspecified = 1; + else { + pstr = str; + mir_snprintf(str, SIZEOF(str), dbv.cVal ? "GMT%+d:%02d" : "GMT", -dbv.cVal / 2, (dbv.cVal & 1) * 30); + } + } + else { + unspecified = (special == SVS_ZEROISUNSPEC && dbv.bVal == 0); + pstr = _itoa(special == SVS_SIGNED ? dbv.cVal : dbv.bVal, str, 10); + } + break; + case DBVT_WORD: + if (special == SVS_COUNTRY) { + pstr = (char*)CallService(MS_UTILS_GETCOUNTRYBYNUMBER, dbv.wVal, 0); + unspecified = pstr == NULL; + } + else { + unspecified = (special == SVS_ZEROISUNSPEC && dbv.wVal == 0); + pstr = _itoa(special == SVS_SIGNED ? dbv.sVal : dbv.wVal, str, 10); + } + break; + case DBVT_DWORD: + unspecified = (special == SVS_ZEROISUNSPEC && dbv.dVal == 0); + if (special == SVS_IP) { + struct in_addr ia; + ia.S_un.S_addr = htonl(dbv.dVal); + pstr = inet_ntoa(ia); + if (dbv.dVal == 0) unspecified = 1; + } + else if (special == SVS_GGVERSION) + pstr = (char *)gg_version2string(dbv.dVal); + else + pstr = _itoa(special == SVS_SIGNED ? dbv.lVal : dbv.dVal, str, 10); + break; + case DBVT_ASCIIZ: + unspecified = (special == SVS_ZEROISUNSPEC && dbv.pszVal[0] == '\0'); + pstr = dbv.pszVal; + break; + default: pstr = str; lstrcpyA(str, "???"); break; + } + } + + if (disableIfUndef) { + EnableWindow(GetDlgItem(hwndDlg, idCtrl), !unspecified); + if (unspecified) + SetDlgItemText(hwndDlg, idCtrl, TranslateT("")); + else + SetDlgItemTextA(hwndDlg, idCtrl, pstr); + } + else { + EnableWindow(GetDlgItem(hwndDlg, idCtrl), TRUE); + if (!unspecified) + SetDlgItemTextA(hwndDlg, idCtrl, pstr); + } + DBFreeVariant(&dbv); +} + +//////////////////////////////////////////////////////////////////////////////// +// Options Page : Init + +int GGPROTO::options_init(WPARAM wParam, LPARAM lParam) +{ + OPTIONSDIALOGPAGE odp = { 0 }; + odp.cbSize = sizeof(odp); + odp.flags = ODPF_TCHAR; + odp.position = 1003000; + odp.hInstance = hInstance; + odp.ptszGroup = LPGENT("Network"); + odp.ptszTitle = m_tszUserName; + odp.dwInitParam = (LPARAM)this; + + odp.ptszTab = LPGENT("General"); + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_GG_GENERAL); + odp.pfnDlgProc = gg_genoptsdlgproc; + odp.flags = ODPF_TCHAR | ODPF_BOLDGROUPS | ODPF_DONTTRANSLATE; + Options_AddPage(wParam, &odp); + + odp.ptszTab = LPGENT("Conference"); + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_GG_CONFERENCE); + odp.pfnDlgProc = gg_confoptsdlgproc; + Options_AddPage(wParam, &odp); + + odp.ptszTab = LPGENT("Advanced"); + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_GG_ADVANCED); + odp.pfnDlgProc = gg_advoptsdlgproc; + odp.flags |= ODPF_EXPERTONLY; + Options_AddPage(wParam, &odp); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +// Check if new user data has been filled in for specified account +void GGPROTO::checknewuser(uin_t uin, const char* passwd) +{ + char oldpasswd[128]; + DBVARIANT dbv; + uin_t olduin = (uin_t)db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0); + + oldpasswd[0] = '\0'; + if (!db_get_s(NULL, m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) + { + if (dbv.pszVal) strcpy(oldpasswd, dbv.pszVal); + DBFreeVariant(&dbv); + } + + if (uin > 0 && strlen(passwd) > 0 && (uin != olduin || strcmp(oldpasswd, passwd))) + check_first_conn = 1; +} + +//////////////////////////////////////////////////////////////////////////////// +// Options Page : Proc + +static void gg_optsdlgcheck(HWND hwndDlg) +{ + TCHAR text[128]; + GetDlgItemText(hwndDlg, IDC_UIN, text, SIZEOF(text)); + if (text[0]) { + GetDlgItemText(hwndDlg, IDC_EMAIL, text, SIZEOF(text)); + if (text[0]) + ShowWindow(GetDlgItem(hwndDlg, IDC_CHEMAIL), SW_SHOW); + else + ShowWindow(GetDlgItem(hwndDlg, IDC_CHEMAIL), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_CHPASS), SW_SHOW); + ShowWindow(GetDlgItem(hwndDlg, IDC_LOSTPASS), SW_SHOW); + ShowWindow(GetDlgItem(hwndDlg, IDC_REMOVEACCOUNT), SW_SHOW); + ShowWindow(GetDlgItem(hwndDlg, IDC_CREATEACCOUNT), SW_HIDE); + } + else { + ShowWindow(GetDlgItem(hwndDlg, IDC_REMOVEACCOUNT), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_LOSTPASS), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_CHPASS), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_CHEMAIL), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_CREATEACCOUNT), SW_SHOW); + } +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// Proc: General options dialog +static INT_PTR CALLBACK gg_genoptsdlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + GGPROTO *gg = (GGPROTO *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch (msg) { + case WM_INITDIALOG: + { + DBVARIANT dbv; + DWORD num; + GGPROTO *gg = (GGPROTO *)lParam; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); + + TranslateDialogDefault(hwndDlg); + if (num = db_get_dw(NULL, gg->m_szModuleName, GG_KEY_UIN, 0)) + { + SetDlgItemTextA(hwndDlg, IDC_UIN, ditoa(num)); + ShowWindow(GetDlgItem(hwndDlg, IDC_CREATEACCOUNT), SW_HIDE); + } + else + { + ShowWindow(GetDlgItem(hwndDlg, IDC_CHPASS), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_REMOVEACCOUNT), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_LOSTPASS), SW_HIDE); + } + if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) { + CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM) dbv.pszVal); + SetDlgItemTextA(hwndDlg, IDC_PASSWORD, dbv.pszVal); + DBFreeVariant(&dbv); + } + if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_EMAIL, &dbv, DBVT_ASCIIZ)) { + SetDlgItemTextA(hwndDlg, IDC_EMAIL, dbv.pszVal); + DBFreeVariant(&dbv); + } + else + { + ShowWindow(GetDlgItem(hwndDlg, IDC_LOSTPASS), SW_HIDE); + ShowWindow(GetDlgItem(hwndDlg, IDC_CHPASS), SW_HIDE); + } + + CheckDlgButton(hwndDlg, IDC_FRIENDSONLY, db_get_b(NULL, gg->m_szModuleName, GG_KEY_FRIENDSONLY, GG_KEYDEF_FRIENDSONLY)); + CheckDlgButton(hwndDlg, IDC_SHOWINVISIBLE, db_get_b(NULL, gg->m_szModuleName, GG_KEY_SHOWINVISIBLE, GG_KEYDEF_SHOWINVISIBLE)); + CheckDlgButton(hwndDlg, IDC_LEAVESTATUSMSG, db_get_b(NULL, gg->m_szModuleName, GG_KEY_LEAVESTATUSMSG, GG_KEYDEF_LEAVESTATUSMSG)); + if (gg->gc_enabled) + CheckDlgButton(hwndDlg, IDC_IGNORECONF, db_get_b(NULL, gg->m_szModuleName, GG_KEY_IGNORECONF, GG_KEYDEF_IGNORECONF)); + else + { + EnableWindow(GetDlgItem(hwndDlg, IDC_IGNORECONF), FALSE); + CheckDlgButton(hwndDlg, IDC_IGNORECONF, TRUE); + } + CheckDlgButton(hwndDlg, IDC_IMGRECEIVE, db_get_b(NULL, gg->m_szModuleName, GG_KEY_IMGRECEIVE, GG_KEYDEF_IMGRECEIVE)); + CheckDlgButton(hwndDlg, IDC_SHOWLINKS, db_get_b(NULL, gg->m_szModuleName, GG_KEY_SHOWLINKS, GG_KEYDEF_SHOWLINKS)); + CheckDlgButton(hwndDlg, IDC_ENABLEAVATARS, db_get_b(NULL, gg->m_szModuleName, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS)); + + EnableWindow(GetDlgItem(hwndDlg, IDC_LEAVESTATUS), IsDlgButtonChecked(hwndDlg, IDC_LEAVESTATUSMSG)); + EnableWindow(GetDlgItem(hwndDlg, IDC_IMGMETHOD), IsDlgButtonChecked(hwndDlg, IDC_IMGRECEIVE)); + SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_ADDSTRING, 0, (LPARAM)TranslateT("")); // 0 + SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_ADDSTRING, 0, (LPARAM)TranslateT("Online")); // ID_STATUS_ONLINE + SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_ADDSTRING, 0, (LPARAM)TranslateT("Away")); // ID_STATUS_AWAY + SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_ADDSTRING, 0, (LPARAM)TranslateT("DND")); // ID_STATUS_DND + SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_ADDSTRING, 0, (LPARAM)TranslateT("Free for chat")); // ID_STATUS_FREECHAT + SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_ADDSTRING, 0, (LPARAM)TranslateT("Invisible")); // ID_STATUS_INVISIBLE + switch(db_get_w(NULL, gg->m_szModuleName, GG_KEY_LEAVESTATUS, GG_KEYDEF_LEAVESTATUS)) { + case ID_STATUS_ONLINE: + SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_SETCURSEL, 1, 0); + break; + case ID_STATUS_AWAY: + SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_SETCURSEL, 2, 0); + break; + case ID_STATUS_DND: + SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_SETCURSEL, 3, 0); + break; + case ID_STATUS_FREECHAT: + SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_SETCURSEL, 4, 0); + break; + case ID_STATUS_INVISIBLE: + SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_SETCURSEL, 5, 0); + break; + default: + SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_SETCURSEL, 0, 0); + } + + SendDlgItemMessage(hwndDlg, IDC_IMGMETHOD, CB_ADDSTRING, 0, (LPARAM)TranslateT("System tray icon")); + SendDlgItemMessage(hwndDlg, IDC_IMGMETHOD, CB_ADDSTRING, 0, (LPARAM)TranslateT("Popup window")); + SendDlgItemMessage(hwndDlg, IDC_IMGMETHOD, CB_ADDSTRING, 0, (LPARAM)TranslateT("Message with [img] BBCode")); + SendDlgItemMessage(hwndDlg, IDC_IMGMETHOD, CB_SETCURSEL, + db_get_b(NULL, gg->m_szModuleName, GG_KEY_IMGMETHOD, GG_KEYDEF_IMGMETHOD), 0); + break; + } + case WM_COMMAND: + { + if ((LOWORD(wParam) == IDC_UIN || LOWORD(wParam) == IDC_PASSWORD || LOWORD(wParam) == IDC_EMAIL) + && (HIWORD(wParam) != EN_CHANGE || (HWND) lParam != GetFocus())) + return 0; + + switch (LOWORD(wParam)) { + case IDC_EMAIL: + case IDC_UIN: + gg_optsdlgcheck(hwndDlg); + break; + + case IDC_LEAVESTATUSMSG: + EnableWindow(GetDlgItem(hwndDlg, IDC_LEAVESTATUS), IsDlgButtonChecked(hwndDlg, IDC_LEAVESTATUSMSG)); + break; + + case IDC_IMGRECEIVE: + EnableWindow(GetDlgItem(hwndDlg, IDC_IMGMETHOD), IsDlgButtonChecked(hwndDlg, IDC_IMGRECEIVE)); + break; + + case IDC_LOSTPASS: + { + char email[128]; + uin_t uin; + GetDlgItemTextA(hwndDlg, IDC_UIN, email, sizeof(email)); + uin = atoi(email); + GetDlgItemTextA(hwndDlg, IDC_EMAIL, email, sizeof(email)); + if (!strlen(email)) + MessageBox(NULL, TranslateT("You need to specify your registration e-mail first."), + gg->m_tszUserName, MB_OK | MB_ICONEXCLAMATION); + else if (MessageBox(NULL, + TranslateT("Your password will be sent to your registration e-mail.\nDo you want to continue ?"), + gg->m_tszUserName, + MB_OKCANCEL | MB_ICONQUESTION) == IDOK) + gg->remindpassword(uin, email); + return FALSE; + } + case IDC_CREATEACCOUNT: + case IDC_REMOVEACCOUNT: + if (gg->isonline()) + { + if (MessageBox( + NULL, + TranslateT("You should disconnect before making any permanent changes with your account.\nDo you want to disconnect now ?"), + gg->m_tszUserName, + MB_OKCANCEL | MB_ICONEXCLAMATION) == IDCANCEL) + break; + else + gg->disconnect(); + } + case IDC_CHPASS: + case IDC_CHEMAIL: + { + // Readup data + GGUSERUTILDLGDATA dat; + int ret; + char pass[128], email[128]; + GetDlgItemTextA(hwndDlg, IDC_UIN, pass, sizeof(pass)); + dat.uin = atoi(pass); + GetDlgItemTextA(hwndDlg, IDC_PASSWORD, pass, sizeof(pass)); + GetDlgItemTextA(hwndDlg, IDC_EMAIL, email, sizeof(email)); + dat.pass = pass; + dat.email = email; + dat.gg = gg; + if (LOWORD(wParam) == IDC_CREATEACCOUNT) + { + dat.mode = GG_USERUTIL_CREATE; + ret = DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_CREATEACCOUNT), hwndDlg, gg_userutildlgproc, (LPARAM)&dat); + } + else if (LOWORD(wParam) == IDC_CHPASS) + { + dat.mode = GG_USERUTIL_PASS; + ret = DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_CHPASS), hwndDlg, gg_userutildlgproc, (LPARAM)&dat); + } + else if (LOWORD(wParam) == IDC_CHEMAIL) + { + dat.mode = GG_USERUTIL_EMAIL; + ret = DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_CHEMAIL), hwndDlg, gg_userutildlgproc, (LPARAM)&dat); + } + else + { + dat.mode = GG_USERUTIL_REMOVE; + ret = DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_REMOVEACCOUNT), hwndDlg, gg_userutildlgproc, (LPARAM)&dat); + } + + if (ret == IDOK) + { + DBVARIANT dbv; + DWORD num; + // Show reload required window + ShowWindow(GetDlgItem(hwndDlg, IDC_RELOADREQD), SW_SHOW); + + // Update uin + if (num = db_get_dw(NULL, gg->m_szModuleName, GG_KEY_UIN, 0)) + SetDlgItemTextA(hwndDlg, IDC_UIN, ditoa(num)); + else + SetDlgItemTextA(hwndDlg, IDC_UIN, ""); + + // Update password + if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) { + CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM) dbv.pszVal); + SetDlgItemTextA(hwndDlg, IDC_PASSWORD, dbv.pszVal); + DBFreeVariant(&dbv); + } + else SetDlgItemTextA(hwndDlg, IDC_PASSWORD, ""); + + // Update e-mail + if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_EMAIL, &dbv, DBVT_ASCIIZ)) { + SetDlgItemTextA(hwndDlg, IDC_EMAIL, dbv.pszVal); + DBFreeVariant(&dbv); + } + else SetDlgItemTextA(hwndDlg, IDC_EMAIL, ""); + + // Update links + gg_optsdlgcheck(hwndDlg); + + // Remove details + if (LOWORD(wParam) != IDC_CHPASS && LOWORD(wParam) != IDC_CHEMAIL) + { + db_unset(NULL, gg->m_szModuleName, GG_KEY_NICK); + db_unset(NULL, gg->m_szModuleName, "NickName"); + db_unset(NULL, gg->m_szModuleName, "City"); + db_unset(NULL, gg->m_szModuleName, "FirstName"); + db_unset(NULL, gg->m_szModuleName, "LastName"); + db_unset(NULL, gg->m_szModuleName, "FamilyName"); + db_unset(NULL, gg->m_szModuleName, "CityOrigin"); + db_unset(NULL, gg->m_szModuleName, "Age"); + db_unset(NULL, gg->m_szModuleName, "BirthYear"); + db_unset(NULL, gg->m_szModuleName, "Gender"); + } + } + } + break; + } + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + } + case WM_NOTIFY: + { + switch (((LPNMHDR) lParam)->code) { + case PSN_APPLY: + { + int status_flags = GG_STATUS_FLAG_UNKNOWN; + char str[128]; + uin_t uin; + + // Write Gadu-Gadu number & password + GetDlgItemTextA(hwndDlg, IDC_UIN, str, sizeof(str)); + uin = atoi(str); + GetDlgItemTextA(hwndDlg, IDC_PASSWORD, str, sizeof(str)); + CallService(MS_DB_CRYPT_ENCODESTRING, sizeof(str), (LPARAM) str); + gg->checknewuser(uin, str); + db_set_dw(NULL, gg->m_szModuleName, GG_KEY_UIN, uin); + db_set_s(NULL, gg->m_szModuleName, GG_KEY_PASSWORD, str); + + // Write Gadu-Gadu email + GetDlgItemTextA(hwndDlg, IDC_EMAIL, str, sizeof(str)); + db_set_s(NULL, gg->m_szModuleName, GG_KEY_EMAIL, str); + + // Write checkboxes + db_set_b(NULL, gg->m_szModuleName, GG_KEY_FRIENDSONLY, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_FRIENDSONLY)); + db_set_b(NULL, gg->m_szModuleName, GG_KEY_SHOWINVISIBLE, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_SHOWINVISIBLE)); + db_set_b(NULL, gg->m_szModuleName, GG_KEY_LEAVESTATUSMSG, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_LEAVESTATUSMSG)); + if (gg->gc_enabled) + db_set_b(NULL, gg->m_szModuleName, GG_KEY_IGNORECONF, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_IGNORECONF)); + db_set_b(NULL, gg->m_szModuleName, GG_KEY_IMGRECEIVE, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_IMGRECEIVE)); + db_set_b(NULL, gg->m_szModuleName, GG_KEY_SHOWLINKS, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_SHOWLINKS)); + if (IsDlgButtonChecked(hwndDlg, IDC_SHOWLINKS)) + status_flags |= GG_STATUS_FLAG_SPAM; + EnterCriticalSection(&gg->sess_mutex); + gg_change_status_flags(gg->sess, status_flags); + LeaveCriticalSection(&gg->sess_mutex); + db_set_b(NULL, gg->m_szModuleName, GG_KEY_ENABLEAVATARS, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_ENABLEAVATARS)); + + db_set_b(NULL, gg->m_szModuleName, GG_KEY_IMGMETHOD, + (BYTE)SendDlgItemMessage(hwndDlg, IDC_IMGMETHOD, CB_GETCURSEL, 0, 0)); + + // Write leave status + switch(SendDlgItemMessage(hwndDlg, IDC_LEAVESTATUS, CB_GETCURSEL, 0, 0)) + { + case 1: + db_set_w(NULL, gg->m_szModuleName, GG_KEY_LEAVESTATUS, ID_STATUS_ONLINE); + break; + case 2: + db_set_w(NULL, gg->m_szModuleName, GG_KEY_LEAVESTATUS, ID_STATUS_AWAY); + break; + case 3: + db_set_w(NULL, gg->m_szModuleName, GG_KEY_LEAVESTATUS, ID_STATUS_DND); + break; + case 4: + db_set_w(NULL, gg->m_szModuleName, GG_KEY_LEAVESTATUS, ID_STATUS_FREECHAT); + break; + case 5: + db_set_w(NULL, gg->m_szModuleName, GG_KEY_LEAVESTATUS, ID_STATUS_INVISIBLE); + break; + default: + db_set_w(NULL, gg->m_szModuleName, GG_KEY_LEAVESTATUS, GG_KEYDEF_LEAVESTATUS); + } + break; + } + } + break; + } + } + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// Proc: Conference options dialog + +static INT_PTR CALLBACK gg_confoptsdlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + GGPROTO *gg = (GGPROTO *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch (msg) { + case WM_INITDIALOG: + { + DWORD num; + GGPROTO *gg = (GGPROTO *)lParam; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); + + TranslateDialogDefault(hwndDlg); + SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_TOTAL, CB_ADDSTRING, 0, (LPARAM)TranslateT("Allow")); + SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_TOTAL, CB_ADDSTRING, 0, (LPARAM)TranslateT("Ask")); + SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_TOTAL, CB_ADDSTRING, 0, (LPARAM)TranslateT("Ignore")); + SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_TOTAL, CB_SETCURSEL, + db_get_w(NULL, gg->m_szModuleName, GG_KEY_GC_POLICY_TOTAL, GG_KEYDEF_GC_POLICY_TOTAL), 0); + + if (num = db_get_w(NULL, gg->m_szModuleName, GG_KEY_GC_COUNT_TOTAL, GG_KEYDEF_GC_COUNT_TOTAL)) + SetDlgItemTextA(hwndDlg, IDC_GC_COUNT_TOTAL, ditoa(num)); + + SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_UNKNOWN, CB_ADDSTRING, 0, (LPARAM)TranslateT("Allow")); + SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_UNKNOWN, CB_ADDSTRING, 0, (LPARAM)TranslateT("Ask")); + SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_UNKNOWN, CB_ADDSTRING, 0, (LPARAM)TranslateT("Ignore")); + SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_UNKNOWN, CB_SETCURSEL, + db_get_w(NULL, gg->m_szModuleName, GG_KEY_GC_POLICY_UNKNOWN, GG_KEYDEF_GC_POLICY_UNKNOWN), 0); + + if (num = db_get_w(NULL, gg->m_szModuleName, GG_KEY_GC_COUNT_UNKNOWN, GG_KEYDEF_GC_COUNT_UNKNOWN)) + SetDlgItemTextA(hwndDlg, IDC_GC_COUNT_UNKNOWN, ditoa(num)); + + SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_DEFAULT, CB_ADDSTRING, 0, (LPARAM)TranslateT("Allow")); + SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_DEFAULT, CB_ADDSTRING, 0, (LPARAM)TranslateT("Ask")); + SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_DEFAULT, CB_ADDSTRING, 0, (LPARAM)TranslateT("Ignore")); + SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_DEFAULT, CB_SETCURSEL, + db_get_w(NULL, gg->m_szModuleName, GG_KEY_GC_POLICY_DEFAULT, GG_KEYDEF_GC_POLICY_DEFAULT), 0); + break; + } + case WM_COMMAND: + { + if ((LOWORD(wParam) == IDC_GC_COUNT_TOTAL || LOWORD(wParam) == IDC_GC_COUNT_UNKNOWN) + && (HIWORD(wParam) != EN_CHANGE || (HWND) lParam != GetFocus())) + return 0; + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + } + case WM_NOTIFY: + { + switch (((LPNMHDR) lParam)->code) { + case PSN_APPLY: + { + char str[128]; + + // Write groupchat policy + db_set_w(NULL, gg->m_szModuleName, GG_KEY_GC_POLICY_TOTAL, + (WORD)SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_TOTAL, CB_GETCURSEL, 0, 0)); + db_set_w(NULL, gg->m_szModuleName, GG_KEY_GC_POLICY_UNKNOWN, + (WORD)SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_UNKNOWN, CB_GETCURSEL, 0, 0)); + db_set_w(NULL, gg->m_szModuleName, GG_KEY_GC_POLICY_DEFAULT, + (WORD)SendDlgItemMessage(hwndDlg, IDC_GC_POLICY_DEFAULT, CB_GETCURSEL, 0, 0)); + + GetDlgItemTextA(hwndDlg, IDC_GC_COUNT_TOTAL, str, sizeof(str)); + db_set_w(NULL, gg->m_szModuleName, GG_KEY_GC_COUNT_TOTAL, (WORD)atoi(str)); + GetDlgItemTextA(hwndDlg, IDC_GC_COUNT_UNKNOWN, str, sizeof(str)); + db_set_w(NULL, gg->m_szModuleName, GG_KEY_GC_COUNT_UNKNOWN, (WORD)atoi(str)); + + break; + } + } + break; + } + } + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// Proc: Advanced options dialog +static INT_PTR CALLBACK gg_advoptsdlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + GGPROTO *gg = (GGPROTO *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch (msg) { + case WM_INITDIALOG: + { + DBVARIANT dbv; + DWORD num; + GGPROTO *gg = (GGPROTO *)lParam; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); + + TranslateDialogDefault(hwndDlg); + if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_SERVERHOSTS, &dbv, DBVT_ASCIIZ)) { + SetDlgItemTextA(hwndDlg, IDC_HOST, dbv.pszVal); + DBFreeVariant(&dbv); + } + else SetDlgItemTextA(hwndDlg, IDC_HOST, GG_KEYDEF_SERVERHOSTS); + + CheckDlgButton(hwndDlg, IDC_KEEPALIVE, db_get_b(NULL, gg->m_szModuleName, GG_KEY_KEEPALIVE, GG_KEYDEF_KEEPALIVE)); + CheckDlgButton(hwndDlg, IDC_SHOWCERRORS, db_get_b(NULL, gg->m_szModuleName, GG_KEY_SHOWCERRORS, GG_KEYDEF_SHOWCERRORS)); + CheckDlgButton(hwndDlg, IDC_ARECONNECT, db_get_b(NULL, gg->m_szModuleName, GG_KEY_ARECONNECT, GG_KEYDEF_ARECONNECT)); + CheckDlgButton(hwndDlg, IDC_MSGACK, db_get_b(NULL, gg->m_szModuleName, GG_KEY_MSGACK, GG_KEYDEF_MSGACK)); + CheckDlgButton(hwndDlg, IDC_MANUALHOST, db_get_b(NULL, gg->m_szModuleName, GG_KEY_MANUALHOST, GG_KEYDEF_MANUALHOST)); + CheckDlgButton(hwndDlg, IDC_SSLCONN, db_get_b(NULL, gg->m_szModuleName, GG_KEY_SSLCONN, GG_KEYDEF_SSLCONN)); + + EnableWindow(GetDlgItem(hwndDlg, IDC_HOST), IsDlgButtonChecked(hwndDlg, IDC_MANUALHOST)); + EnableWindow(GetDlgItem(hwndDlg, IDC_PORT), IsDlgButtonChecked(hwndDlg, IDC_MANUALHOST)); + + CheckDlgButton(hwndDlg, IDC_DIRECTCONNS, db_get_b(NULL, gg->m_szModuleName, GG_KEY_DIRECTCONNS, GG_KEYDEF_DIRECTCONNS)); + if (num = db_get_w(NULL, gg->m_szModuleName, GG_KEY_DIRECTPORT, GG_KEYDEF_DIRECTPORT)) + SetDlgItemTextA(hwndDlg, IDC_DIRECTPORT, ditoa(num)); + CheckDlgButton(hwndDlg, IDC_FORWARDING, db_get_b(NULL, gg->m_szModuleName, GG_KEY_FORWARDING, GG_KEYDEF_FORWARDING)); + if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_FORWARDHOST, &dbv, DBVT_ASCIIZ)) { + SetDlgItemTextA(hwndDlg, IDC_FORWARDHOST, dbv.pszVal); + DBFreeVariant(&dbv); + } + if (num = db_get_w(NULL, gg->m_szModuleName, GG_KEY_FORWARDPORT, GG_KEYDEF_FORWARDPORT)) + SetDlgItemTextA(hwndDlg, IDC_FORWARDPORT, ditoa(num)); + + EnableWindow(GetDlgItem(hwndDlg, IDC_DIRECTPORT), IsDlgButtonChecked(hwndDlg, IDC_DIRECTCONNS)); + EnableWindow(GetDlgItem(hwndDlg, IDC_FORWARDING), IsDlgButtonChecked(hwndDlg, IDC_DIRECTCONNS)); + EnableWindow(GetDlgItem(hwndDlg, IDC_FORWARDPORT), IsDlgButtonChecked(hwndDlg, IDC_FORWARDING) && IsDlgButtonChecked(hwndDlg, IDC_DIRECTCONNS)); + EnableWindow(GetDlgItem(hwndDlg, IDC_FORWARDHOST), IsDlgButtonChecked(hwndDlg, IDC_FORWARDING) && IsDlgButtonChecked(hwndDlg, IDC_DIRECTCONNS)); + break; + } + case WM_COMMAND: + { + if ((LOWORD(wParam) == IDC_DIRECTPORT || LOWORD(wParam) == IDC_FORWARDHOST || LOWORD(wParam) == IDC_FORWARDPORT) + && (HIWORD(wParam) != EN_CHANGE || (HWND) lParam != GetFocus())) + return 0; + switch (LOWORD(wParam)) { + case IDC_MANUALHOST: + { + EnableWindow(GetDlgItem(hwndDlg, IDC_HOST), IsDlgButtonChecked(hwndDlg, IDC_MANUALHOST)); + EnableWindow(GetDlgItem(hwndDlg, IDC_PORT), IsDlgButtonChecked(hwndDlg, IDC_MANUALHOST)); + ShowWindow(GetDlgItem(hwndDlg, IDC_RELOADREQD), SW_SHOW); + break; + } + case IDC_DIRECTCONNS: + case IDC_FORWARDING: + { + EnableWindow(GetDlgItem(hwndDlg, IDC_DIRECTPORT), IsDlgButtonChecked(hwndDlg, IDC_DIRECTCONNS)); + EnableWindow(GetDlgItem(hwndDlg, IDC_FORWARDING), IsDlgButtonChecked(hwndDlg, IDC_DIRECTCONNS)); + EnableWindow(GetDlgItem(hwndDlg, IDC_FORWARDPORT), IsDlgButtonChecked(hwndDlg, IDC_FORWARDING) && IsDlgButtonChecked(hwndDlg, IDC_DIRECTCONNS)); + EnableWindow(GetDlgItem(hwndDlg, IDC_FORWARDHOST), IsDlgButtonChecked(hwndDlg, IDC_FORWARDING) && IsDlgButtonChecked(hwndDlg, IDC_DIRECTCONNS)); + ShowWindow(GetDlgItem(hwndDlg, IDC_RELOADREQD), SW_SHOW); + break; + } + } + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + } + case WM_NOTIFY: + { + switch (((LPNMHDR) lParam)->code) { + case PSN_APPLY: + { + char str[512]; + db_set_b(NULL, gg->m_szModuleName, GG_KEY_KEEPALIVE, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_KEEPALIVE)); + db_set_b(NULL, gg->m_szModuleName, GG_KEY_SHOWCERRORS, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_SHOWCERRORS)); + db_set_b(NULL, gg->m_szModuleName, GG_KEY_ARECONNECT, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_ARECONNECT)); + db_set_b(NULL, gg->m_szModuleName, GG_KEY_MSGACK, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_MSGACK)); + db_set_b(NULL, gg->m_szModuleName, GG_KEY_MANUALHOST, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_MANUALHOST)); + db_set_b(NULL, gg->m_szModuleName, GG_KEY_SSLCONN, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_SSLCONN)); + + // Transfer settings + db_set_b(NULL, gg->m_szModuleName, GG_KEY_DIRECTCONNS, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_DIRECTCONNS)); + db_set_b(NULL, gg->m_szModuleName, GG_KEY_FORWARDING, (BYTE) IsDlgButtonChecked(hwndDlg, IDC_FORWARDING)); + + // Write custom servers + GetDlgItemTextA(hwndDlg, IDC_HOST, str, sizeof(str)); + db_set_s(NULL, gg->m_szModuleName, GG_KEY_SERVERHOSTS, str); + + // Write direct port + GetDlgItemTextA(hwndDlg, IDC_DIRECTPORT, str, sizeof(str)); + db_set_w(NULL, gg->m_szModuleName, GG_KEY_DIRECTPORT, (WORD)atoi(str)); + // Write forwarding host + GetDlgItemTextA(hwndDlg, IDC_FORWARDHOST, str, sizeof(str)); + db_set_s(NULL, gg->m_szModuleName, GG_KEY_FORWARDHOST, str); + GetDlgItemTextA(hwndDlg, IDC_FORWARDPORT, str, sizeof(str)); + db_set_w(NULL, gg->m_szModuleName, GG_KEY_FORWARDPORT, (WORD)atoi(str)); + break; + } + } + break; + } + } + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////////// +// Info Page : Data +struct GGDETAILSDLGDATA +{ + GGPROTO *gg; + HANDLE hContact; + int disableUpdate; + int updating; +}; + +//////////////////////////////////////////////////////////////////////////////// +// Info Page : Proc +static INT_PTR CALLBACK gg_detailsdlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + struct GGDETAILSDLGDATA *dat = (struct GGDETAILSDLGDATA *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch(msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + + dat = (struct GGDETAILSDLGDATA *)mir_alloc(sizeof(struct GGDETAILSDLGDATA)); + dat->hContact=(HANDLE)lParam; + dat->disableUpdate = FALSE; + dat->updating = FALSE; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + // Add genders + if (!dat->hContact) + { + SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_ADDSTRING, 0, (LPARAM)_T("")); // 0 + SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_ADDSTRING, 0, (LPARAM)TranslateT("Female")); // 1 + SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_ADDSTRING, 0, (LPARAM)TranslateT("Male")); // 2 + } + break; + + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->idFrom) { + case 0: + switch (((LPNMHDR)lParam)->code) { + case PSN_PARAMCHANGED: + dat->gg = (GGPROTO *)((LPPSHNOTIFY)lParam)->lParam; + break; + + case PSN_INFOCHANGED: + { + char *szProto; + HANDLE hContact = (HANDLE)((LPPSHNOTIFY)lParam)->lParam; + GGPROTO *gg = dat->gg; + + // Show updated message + if (dat && dat->updating) + { + MessageBox(NULL, TranslateT("Your details has been uploaded to the public directory."), + gg->m_tszUserName, MB_OK | MB_ICONINFORMATION); + dat->updating = FALSE; + break; + } + + if (hContact == NULL) + szProto = gg->m_szModuleName; + else + szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + if (szProto == NULL) + break; + + // Disable when updating + if (dat) dat->disableUpdate = TRUE; + + SetValue(hwndDlg, IDC_UIN, hContact, szProto, GG_KEY_UIN, 0, hContact != NULL); + SetValue(hwndDlg, IDC_REALIP, hContact, szProto, GG_KEY_CLIENTIP, SVS_IP, hContact != NULL); + SetValue(hwndDlg, IDC_PORT, hContact, szProto, GG_KEY_CLIENTPORT, SVS_ZEROISUNSPEC, hContact != NULL); + SetValue(hwndDlg, IDC_VERSION, hContact, szProto, GG_KEY_CLIENTVERSION, SVS_GGVERSION, hContact != NULL); + + SetValue(hwndDlg, IDC_FIRSTNAME, hContact, szProto, "FirstName", SVS_NORMAL, hContact != NULL); + SetValue(hwndDlg, IDC_LASTNAME, hContact, szProto, "LastName", SVS_NORMAL, hContact != NULL); + SetValue(hwndDlg, IDC_NICKNAME, hContact, szProto, "NickName", SVS_NORMAL, hContact != NULL); + SetValue(hwndDlg, IDC_BIRTHYEAR, hContact, szProto, "BirthYear", SVS_ZEROISUNSPEC, hContact != NULL); + SetValue(hwndDlg, IDC_CITY, hContact, szProto, "City", SVS_NORMAL, hContact != NULL); + SetValue(hwndDlg, IDC_FAMILYNAME, hContact, szProto, "FamilyName", SVS_NORMAL, hContact != NULL); + SetValue(hwndDlg, IDC_CITYORIGIN, hContact, szProto, "CityOrigin", SVS_NORMAL, hContact != NULL); + + if (hContact) + { + SetValue(hwndDlg, IDC_GENDER, hContact, szProto, "Gender", SVS_GENDER, hContact != NULL); + SetValue(hwndDlg, IDC_STATUSDESCR, hContact, "CList", GG_KEY_STATUSDESCR, SVS_NORMAL, hContact != NULL); + } + else switch((char)db_get_b(hContact, gg->m_szModuleName, "Gender", (BYTE)'?')) + { + case 'F': + SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_SETCURSEL, 1, 0); + break; + case 'M': + SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_SETCURSEL, 2, 0); + break; + default: + SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_SETCURSEL, 0, 0); + } + + // Disable when updating + if (dat) dat->disableUpdate = FALSE; + break; + } + } + break; + } + break; + case WM_COMMAND: + if (dat && !dat->hContact && LOWORD(wParam) == IDC_SAVE && HIWORD(wParam) == BN_CLICKED) + { + // Save user data + char text[256]; + gg_pubdir50_t req; + GGPROTO *gg = dat->gg; + + if (!gg->isonline()) + { + MessageBox(NULL, + TranslateT("You have to be logged in before you can change your details."), + gg->m_tszUserName, MB_OK | MB_ICONSTOP); + break; + } + + EnableWindow(GetDlgItem(hwndDlg, IDC_SAVE), FALSE); + + req = gg_pubdir50_new(GG_PUBDIR50_WRITE); + + GetDlgItemTextA(hwndDlg, IDC_FIRSTNAME, text, sizeof(text)); + if (strlen(text)) gg_pubdir50_add(req, GG_PUBDIR50_FIRSTNAME, text); + + GetDlgItemTextA(hwndDlg, IDC_LASTNAME, text, sizeof(text)); + if (strlen(text)) gg_pubdir50_add(req, GG_PUBDIR50_LASTNAME, text); + + GetDlgItemTextA(hwndDlg, IDC_NICKNAME, text, sizeof(text)); + if (strlen(text)) gg_pubdir50_add(req, GG_PUBDIR50_NICKNAME, text); + + GetDlgItemTextA(hwndDlg, IDC_CITY, text, sizeof(text)); + if (strlen(text)) gg_pubdir50_add(req, GG_PUBDIR50_CITY, text); + + // Gadu-Gadu Female <-> Male + switch(SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_GETCURSEL, 0, 0)) + { + case 1: + gg_pubdir50_add(req, GG_PUBDIR50_GENDER, GG_PUBDIR50_GENDER_SET_FEMALE); + break; + case 2: + gg_pubdir50_add(req, GG_PUBDIR50_GENDER, GG_PUBDIR50_GENDER_SET_MALE); + break; + default: + gg_pubdir50_add(req, GG_PUBDIR50_GENDER, ""); + } + + GetDlgItemTextA(hwndDlg, IDC_BIRTHYEAR, text, sizeof(text)); + if (strlen(text)) gg_pubdir50_add(req, GG_PUBDIR50_BIRTHYEAR, text); + + GetDlgItemTextA(hwndDlg, IDC_FAMILYNAME, text, sizeof(text)); + if (strlen(text)) gg_pubdir50_add(req, GG_PUBDIR50_FAMILYNAME, text); + + GetDlgItemTextA(hwndDlg, IDC_CITYORIGIN, text, sizeof(text)); + if (strlen(text)) gg_pubdir50_add(req, GG_PUBDIR50_FAMILYCITY, text); + + // Run update + gg_pubdir50_seq_set(req, GG_SEQ_CHINFO); + EnterCriticalSection(&gg->sess_mutex); + gg_pubdir50(gg->sess, req); + LeaveCriticalSection(&gg->sess_mutex); + dat->updating = TRUE; + + gg_pubdir50_free(req); + } + + if (dat && !dat->hContact && !dat->disableUpdate && (HIWORD(wParam) == EN_CHANGE && ( + LOWORD(wParam) == IDC_NICKNAME || LOWORD(wParam) == IDC_FIRSTNAME || LOWORD(wParam) == IDC_LASTNAME || LOWORD(wParam) == IDC_FAMILYNAME || + LOWORD(wParam) == IDC_CITY || LOWORD(wParam) == IDC_CITYORIGIN || LOWORD(wParam) == IDC_BIRTHYEAR) || + HIWORD(wParam) == CBN_SELCHANGE && LOWORD(wParam) == IDC_GENDER)) + EnableWindow(GetDlgItem(hwndDlg, IDC_SAVE), TRUE); + + switch(LOWORD(wParam)) { + case IDCANCEL: + SendMessage(GetParent(hwndDlg),msg,wParam,lParam); + break; + } + break; + + case WM_DESTROY: + if (dat) mir_free(dat); + break; + } + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////////// +// Info Page : Init + +int GGPROTO::details_init(WPARAM wParam, LPARAM lParam) +{ + char* szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, lParam, 0); + if ((szProto == NULL || strcmp(szProto, m_szModuleName)) && lParam || lParam && db_get_b((HANDLE)lParam, m_szModuleName, "ChatRoom", 0)) + return 0; + + // Here goes init + { + OPTIONSDIALOGPAGE odp = {0}; + + odp.cbSize = sizeof(odp); + odp.flags = ODPF_DONTTRANSLATE | ODPF_TCHAR; + odp.hInstance = hInstance; + odp.pfnDlgProc = gg_detailsdlgproc; + odp.position = -1900000000; + odp.pszTemplate = ((HANDLE)lParam != NULL) ? MAKEINTRESOURCEA(IDD_INFO_GG) : MAKEINTRESOURCEA(IDD_CHINFO_GG); + odp.ptszTitle = m_tszUserName; + odp.dwInitParam = (LPARAM)this; + UserInfo_AddPage(wParam, &odp); + } + + // Start search for user data + if ((HANDLE)lParam == NULL) + GetInfo(NULL, 0); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////////// +// Proc: Account manager options dialog +INT_PTR CALLBACK gg_acc_mgr_guidlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +//////////////////////////////////////////////////////////////////////////////////////////// +{ + GGPROTO *gg = (GGPROTO *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch (msg) { + case WM_INITDIALOG: + { + DBVARIANT dbv; + DWORD num; + GGPROTO *gg = (GGPROTO *)lParam; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); + + TranslateDialogDefault(hwndDlg); + if (num = db_get_dw(NULL, gg->m_szModuleName, GG_KEY_UIN, 0)) + SetDlgItemTextA(hwndDlg, IDC_UIN, ditoa(num)); + if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) { + CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM) dbv.pszVal); + SetDlgItemTextA(hwndDlg, IDC_PASSWORD, dbv.pszVal); + DBFreeVariant(&dbv); + } + if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_EMAIL, &dbv, DBVT_ASCIIZ)) { + SetDlgItemTextA(hwndDlg, IDC_EMAIL, dbv.pszVal); + DBFreeVariant(&dbv); + } + break; + } + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_CREATEACCOUNT: + { + // Readup data + GGUSERUTILDLGDATA dat; + int ret; + char pass[128], email[128]; + GetDlgItemTextA(hwndDlg, IDC_UIN, pass, sizeof(pass)); + dat.uin = atoi(pass); + GetDlgItemTextA(hwndDlg, IDC_PASSWORD, pass, sizeof(pass)); + GetDlgItemTextA(hwndDlg, IDC_EMAIL, email, sizeof(email)); + dat.pass = pass; + dat.email = email; + dat.gg = gg; + dat.mode = GG_USERUTIL_CREATE; + ret = DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_CREATEACCOUNT), hwndDlg, gg_userutildlgproc, (LPARAM)&dat); + + if (ret == IDOK) + { + DBVARIANT dbv; + DWORD num; + // Show reload required window + ShowWindow(GetDlgItem(hwndDlg, IDC_RELOADREQD), SW_SHOW); + + // Update uin + if (num = db_get_dw(NULL, gg->m_szModuleName, GG_KEY_UIN, 0)) + SetDlgItemTextA(hwndDlg, IDC_UIN, ditoa(num)); + else + SetDlgItemTextA(hwndDlg, IDC_UIN, ""); + + // Update password + if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) { + CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM) dbv.pszVal); + SetDlgItemTextA(hwndDlg, IDC_PASSWORD, dbv.pszVal); + DBFreeVariant(&dbv); + } + else SetDlgItemTextA(hwndDlg, IDC_PASSWORD, ""); + + // Update e-mail + if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_EMAIL, &dbv, DBVT_ASCIIZ)) { + SetDlgItemTextA(hwndDlg, IDC_EMAIL, dbv.pszVal); + DBFreeVariant(&dbv); + } + else SetDlgItemTextA(hwndDlg, IDC_EMAIL, ""); + } + } + } + break; + + case WM_NOTIFY: + switch(((LPNMHDR)lParam)->idFrom) { + case 0: + switch (((LPNMHDR) lParam)->code) { + case PSN_APPLY: + { + char str[128]; + uin_t uin; + + // Write Gadu-Gadu number & password + GetDlgItemTextA(hwndDlg, IDC_UIN, str, sizeof(str)); + uin = atoi(str); + GetDlgItemTextA(hwndDlg, IDC_PASSWORD, str, sizeof(str)); + CallService(MS_DB_CRYPT_ENCODESTRING, sizeof(str), (LPARAM) str); + gg->checknewuser(uin, str); + db_set_dw(NULL, gg->m_szModuleName, GG_KEY_UIN, uin); + db_set_s(NULL, gg->m_szModuleName, GG_KEY_PASSWORD, str); + + // Write Gadu-Gadu email + GetDlgItemTextA(hwndDlg, IDC_EMAIL, str, sizeof(str)); + db_set_s(NULL, gg->m_szModuleName, GG_KEY_EMAIL, str); + } + } + } + break; + } + return FALSE; +} diff --git a/protocols/Gadu-Gadu/src/dynstuff.cpp b/protocols/Gadu-Gadu/src/dynstuff.cpp new file mode 100644 index 0000000000..9ae41daf59 --- /dev/null +++ b/protocols/Gadu-Gadu/src/dynstuff.cpp @@ -0,0 +1,612 @@ +/* $Id: dynstuff.c 11259 2010-02-17 04:47:22Z borkra $ */ + +/* + * (C) Copyright 2001-2003 Wojtek Kaniewski + * Dawid Jarosz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "gg.h" + +#include +#include +#include +#include + +/* + * list_add_sorted() + * + * dodaje do listy dany element. przy okazji może też skopiować zawarto¶ć. + * je¶li poda się jako ostatni parametr funkcję porównuj±c± zawarto¶ć + * elementów, może posortować od razu. + * + * - list - wskaĽnik do listy, + * - data - wskaĽnik do elementu, + * - alloc_size - rozmiar elementu, je¶li chcemy go skopiować. + * + * zwraca wskaĽnik zaalokowanego elementu lub NULL w przpadku błędu. + */ +void *list_add_sorted(list_t *list, void *data, int alloc_size, int (*comparision)(void *, void *)) +{ + if (!list) { + errno = EFAULT; + return NULL; + } + + list_t newlist = (list_t)malloc(sizeof(struct list)); + + newlist->data = data; + newlist->next = NULL; + + if (alloc_size) { + newlist->data = malloc(alloc_size); + memcpy(newlist->data, data, alloc_size); + } + + list_t tmp; + if (!(tmp = *list)) { + *list = newlist; + } else { + if (!comparision) { + while (tmp->next) + tmp = tmp->next; + tmp->next = newlist; + } else { + list_t prev = NULL; + + while (comparision(newlist->data, tmp->data) > 0) { + prev = tmp; + tmp = tmp->next; + if (!tmp) + break; + } + + if (!prev) { + tmp = *list; + *list = newlist; + newlist->next = tmp; + } else { + prev->next = newlist; + newlist->next = tmp; + } + } + } + + return newlist->data; +} + +/* + * list_add() + * + * wrapper do list_add_sorted(), który zachowuje poprzedni± składnię. + */ +void *list_add(list_t *list, void *data, int alloc_size) +{ + return list_add_sorted(list, data, alloc_size, NULL); +} + +/* + * list_remove() + * + * usuwa z listy wpis z podanym elementem. + * + * - list - wskaĽnik do listy, + * - data - element, + * - free_data - zwolnić pamięć po elemencie. + */ +int list_remove(list_t *list, void *data, int free_data) +{ + list_t tmp, last = NULL; + + if (!list || !*list) { + errno = EFAULT; + return -1; + } + + tmp = *list; + if (tmp->data == data) { + *list = tmp->next; + } else { + for (; tmp && tmp->data != data; tmp = tmp->next) + last = tmp; + if (!tmp) { + errno = ENOENT; + return -1; + } + last->next = tmp->next; + } + + if (free_data) + free(tmp->data); + free(tmp); + + return 0; +} + +/* + * list_count() + * + * zwraca ilo¶ć elementów w danej li¶cie. + * + * - list - lista. + */ +int list_count(list_t list) +{ + int count = 0; + + for (; list; list = list->next) + count++; + + return count; +} + +/* + * list_destroy() + * + * niszczy wszystkie elementy listy. + * + * - list - lista, + * - free_data - czy zwalniać bufor danych? + */ +int list_destroy(list_t list, int free_data) +{ + list_t tmp; + + while (list) { + if (free_data) + free(list->data); + + tmp = list->next; + + free(list); + + list = tmp; + } + + return 0; +} + +/* + * string_realloc() + * + * upewnia się, że w stringu będzie wystarczaj±co dużo miejsca. + * + * - s - ci±g znaków, + * - count - wymagana ilo¶ć znaków (bez końcowego '\0'). + */ +static void string_realloc(string_t s, int count) +{ + char *tmp; + + if (s->str && count + 1 <= s->size) + return; + + tmp = (char*)realloc(s->str, count + 81); + if (!s->str) + *tmp = 0; + tmp[count + 80] = 0; + s->size = count + 81; + s->str = tmp; +} + +/* + * string_append_c() + * + * dodaje do danego ci±gu jeden znak, alokuj±c przy tym odpowiedni± ilo¶ć + * pamięci. + * + * - s - ci±g znaków. + * - c - znaczek do dopisania. + */ +int string_append_c(string_t s, char c) +{ + if (!s) { + errno = EFAULT; + return -1; + } + + string_realloc(s, s->len + 1); + + s->str[s->len + 1] = 0; + s->str[s->len++] = c; + + return 0; +} + +/* + * string_append_n() + * + * dodaje tekst do bufora alokuj±c odpowiedni± ilo¶ć pamięci. + * + * - s - ci±g znaków, + * - str - tekst do dopisania, + * - count - ile znaków tego tekstu dopisać? (-1 znaczy, że cały). + */ +int string_append_n(string_t s, const char *str, int count) +{ + if (!s || !str) { + errno = EFAULT; + return -1; + } + + if (count == -1) + count = (int)strlen(str); + + string_realloc(s, s->len + count); + + s->str[s->len + count] = 0; + strncpy(s->str + s->len, str, count); + + s->len += count; + + return 0; +} + +int string_append(string_t s, const char *str) +{ + return string_append_n(s, str, -1); +} + +/* + * string_insert_n() + * + * wstawia tekst w podane miejsce bufora. + * + * - s - ci±g znaków, + * - index - miejsce, gdzie mamy wpisać (liczone od 0), + * - str - tekst do dopisania, + * - count - ilo¶ć znaków do dopisania (-1 znaczy, że wszystkie). + */ +void string_insert_n(string_t s, int index, const char *str, int count) +{ + if (!s || !str) + return; + + if (count == -1) + count = (int)strlen(str); + + if (index > s->len) + index = s->len; + + string_realloc(s, s->len + count); + + memmove(s->str + index + count, s->str + index, s->len + 1 - index); + memmove(s->str + index, str, count); + + s->len += count; +} + +void string_insert(string_t s, int index, const char *str) +{ + string_insert_n(s, index, str, -1); +} + +/* + * string_init() + * + * inicjuje strukturę string. alokuje pamięć i przypisuje pierwsz± warto¶ć. + * + * - value - je¶li NULL, ci±g jest pusty, inaczej kopiuje tam. + * + * zwraca zaalokowan± strukturę `string'. + */ +string_t string_init(const char *value) +{ + string_t tmp = (string_t)malloc(sizeof(struct string)); + + if (!value) + value = ""; + + tmp->str = _strdup(value); + tmp->len = (int)strlen(value); + tmp->size = (int)strlen(value) + 1; + + return tmp; +} + +/* + * string_clear() + * + * czy¶ci zawarto¶ć struktury `string'. + * + * - s - ci±g znaków. + */ +void string_clear(string_t s) +{ + if (!s) + return; + + if (s->size > 160) { + s->str = (char*)realloc(s->str, 80); + s->size = 80; + } + + s->str[0] = 0; + s->len = 0; +} + +/* + * string_free() + * + * zwalnia pamięć po strukturze string i może też zwolnić pamięć po samym + * ci±gu znaków. + * + * - s - struktura, któr± wycinamy, + * - free_string - zwolnić pamięć po ci±gu znaków? + * + * je¶li free_string=0 zwraca wskaĽnik do ci±gu, inaczej NULL. + */ +char *string_free(string_t s, int free_string) +{ + char *tmp = NULL; + + if (!s) + return NULL; + + if (free_string) + free(s->str); + else + tmp = s->str; + + free(s); + + return tmp; +} + +/* + * _itoa() + * + * prosta funkcja, która zwraca tekstow± reprezentację liczby. w obrębie + * danego wywołania jakiej¶ funkcji lub wyrażenia może być wywołania 10 + * razy, ponieważ tyle mamy statycznych buforów. lepsze to niż ci±głe + * tworzenie tymczasowych buforów na stosie i sprintf()owanie. + * + * - i - liczba do zamiany. + * + * zwraca adres do bufora, którego _NIE_NALEŻY_ zwalniać. + */ + +const char *ditoa(long int i) +{ + static char bufs[10][16]; + static int index = 0; + char *tmp = bufs[index++]; + + if (index > 9) + index = 0; + + mir_snprintf(tmp, 16, "%ld", i); + return tmp; +} + +/* + * array_make() + * + * tworzy tablicę tekstów z jednego, rozdzielonego podanymi znakami. + * + * - string - tekst wej¶ciowy, + * - sep - lista elementów oddzielaj±cych, + * - max - maksymalna ilo¶ć elementów tablicy. je¶li równe 0, nie ma + * ograniczeń rozmiaru tablicy. + * - trim - czy większ± ilo¶ć elementów oddzielaj±cych traktować jako + * jeden (na przykład spacje, tabulacja itp.) + * - quotes - czy pola mog± być zapisywane w cudzysłowiach lub + * apostrofach z escapowanymi znakami. + * + * zaalokowan± tablicę z zaalokowanymi ci±gami znaków, któr± należy + * zwolnić funkcj± array_free() + */ +char **array_make(const char *string, const char *sep, int max, int trim, int quotes) +{ + const char *p, *q; + char **result = NULL; + int items = 0, last = 0; + + if (!string || !sep) + goto failure; + + for (p = string; ; ) { + int len = 0; + char *token = NULL; + + if (max && items >= max - 1) + last = 1; + + if (trim) { + while (*p && strchr(sep, *p)) + p++; + if (!*p) + break; + } + + if (!last && quotes && (*p == '\'' || *p == '\"')) { + char sep = *p; + + for (q = p + 1, len = 0; *q; q++, len++) { + if (*q == '\\') { + q++; + if (!*q) + break; + } else if (*q == sep) + break; + } + + if ((token = (char*)calloc(1, len + 1))) { + char *r = token; + + for (q = p + 1; *q; q++, r++) { + if (*q == '\\') { + q++; + + if (!*q) + break; + + switch (*q) { + case 'n': + *r = '\n'; + break; + case 'r': + *r = '\r'; + break; + case 't': + *r = '\t'; + break; + default: + *r = *q; + } + } else if (*q == sep) { + break; + } else + *r = *q; + } + + *r = 0; + } + + p = (*q) ? q + 1 : q; + + } else { + for (q = p, len = 0; *q && (last || !strchr(sep, *q)); q++, len++); + token = (char*)calloc(1, len + 1); + strncpy(token, p, len); + token[len] = 0; + p = q; + } + + result = (char**)realloc(result, (items + 2) * sizeof(char*)); + result[items] = token; + result[++items] = NULL; + + if (!*p) + break; + + p++; + } + +failure: + if (!items) + result = (char**)calloc(1, sizeof(char*)); + + return result; +} + +/* + * array_count() + * + * zwraca ilo¶ć elementów tablicy. + */ +int array_count(char **array) +{ + int result = 0; + + if (!array) + return 0; + + while (*array) { + result++; + array++; + } + + return result; +} + +/* + * array_add() + * + * dodaje element do tablicy. + */ +void array_add(char ***array, char *string) +{ + int count = array_count(*array); + + *array = (char**)realloc(*array, (count + 2) * sizeof(char*)); + (*array)[count + 1] = NULL; + (*array)[count] = string; +} + +/* + * array_join() + * + * ł±czy elementy tablicy w jeden string oddzielaj±c elementy odpowiednim + * separatorem. + * + * - array - wskaĽnik do tablicy, + * - sep - seperator. + * + * zwrócony ci±g znaków należy zwolnić. + */ +char *array_join(char **array, const char *sep) +{ + string_t s = string_init(NULL); + int i; + + if (!array) + return _strdup(""); + + for (i = 0; array[i]; i++) { + if (i) + string_append(s, sep); + + string_append(s, array[i]); + } + + return string_free(s, 0); +} + +/* + * array_contains() + * + * stwierdza, czy tablica zawiera podany element. + * + * - array - tablica, + * - string - szukany ci±g znaków, + * - casesensitive - czy mamy zwracać uwagę na wielko¶ć znaków? + * + * 0/1 + */ +int array_contains(char **array, const char *string, int casesensitive) +{ + int i; + + if (!array || !string) + return 0; + + for (i = 0; array[i]; i++) { + if (casesensitive && !strcmp(array[i], string)) + return 1; + if (!casesensitive && !strcasecmp(array[i], string)) + return 1; + } + + return 0; +} + +/* + * array_free() + * + * zwalnia pamieć zajmowan± przez tablicę. + */ +void array_free(char **array) +{ + char **tmp; + + if (!array) + return; + + for (tmp = array; *tmp; tmp++) + free(*tmp); + + free(array); +} diff --git a/protocols/Gadu-Gadu/src/dynstuff.h b/protocols/Gadu-Gadu/src/dynstuff.h new file mode 100644 index 0000000000..8e36c67ede --- /dev/null +++ b/protocols/Gadu-Gadu/src/dynstuff.h @@ -0,0 +1,70 @@ +/* $Id: dynstuff.h 4366 2006-12-20 22:40:37Z ono $ */ + +/* + * (C) Copyright 2001-2002 Wojtek Kaniewski + * Dawid Jarosz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License Version 2 as + * published by the Free Software Foundation. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __DYNSTUFF_H +#define __DYNSTUFF_H + +/* listy */ + +struct list { + void *data; + struct list *next; +}; + +typedef struct list * list_t; + +void *list_add(list_t *list, void *data, int alloc_size); +void *list_add_sorted(list_t *list, void *data, int alloc_size, int (*comparision)(void *, void *)); +int list_remove(list_t *list, void *data, int free_data); +int list_count(list_t list); +int list_destroy(list_t list, int free_data); + +/* stringi */ + +struct string { + char *str; + int len, size; +}; + +typedef struct string * string_t; + +string_t string_init(const char *str); +int string_append(string_t s, const char *str); +int string_append_n(string_t s, const char *str, int count); +int string_append_c(string_t s, char ch); +void string_insert(string_t s, int index, const char *str); +void string_insert_n(string_t s, int index, const char *str, int count); +void string_clear(string_t s); +char *string_free(string_t s, int free_string); + +/* tablice stringów */ + +char **array_make(const char *string, const char *sep, int max, int trim, int quotes); +char *array_join(char **array, const char *sep); +void array_add(char ***array, char *string); +int array_count(char **array); +int array_contains(char **array, const char *string, int casesensitive); +void array_free(char **array); + +/* rozszerzenia libców */ + +const char *ditoa(long int i); + +#endif /* __DYNSTUFF_H */ diff --git a/protocols/Gadu-Gadu/src/filetransfer.cpp b/protocols/Gadu-Gadu/src/filetransfer.cpp new file mode 100644 index 0000000000..e7560e16a2 --- /dev/null +++ b/protocols/Gadu-Gadu/src/filetransfer.cpp @@ -0,0 +1,977 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2006 Adam Strzelecki +// +// 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 "gg.h" +#include +#include +#include + +void GGPROTO::dccstart() +{ + DWORD exitCode = 0; + + if (dcc) return; + + // Startup dcc thread + GetExitCodeThread(pth_dcc.hThread, &exitCode); + // Check if dcc thread isn't running already + if (exitCode == STILL_ACTIVE) + { +#ifdef DEBUGMODE + netlog("gg_dccstart(): DCC thread still active. Exiting..."); +#endif + // Signalize mainthread it's started + if (hEvent) SetEvent(hEvent); + return; + } + + // Check if we wan't direct connections + if (!db_get_b(NULL, m_szModuleName, GG_KEY_DIRECTCONNS, GG_KEYDEF_DIRECTCONNS)) + { + netlog("gg_dccstart(): No direct connections setup."); + if (hEvent) SetEvent(hEvent); + return; + } + + // Start thread + pth_dcc.hThread = forkthreadex(&GGPROTO::dccmainthread, NULL, &pth_dcc.dwThreadId); +} + +void GGPROTO::dccconnect(uin_t uin) +{ + struct gg_dcc *dcc; + HANDLE hContact = getcontact(uin, 0, 0, NULL); + DWORD ip, myuin; WORD port; + + netlog("gg_dccconnect(): Connecting to uin %d.", uin); + + // If unknown user or not on list ignore + if (!hContact) return; + + // Read user IP and port + ip = swap32(db_get_dw(hContact, m_szModuleName, GG_KEY_CLIENTIP, 0)); + port = db_get_w(hContact, m_szModuleName, GG_KEY_CLIENTPORT, 0); + myuin = db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0); + + // If not port nor ip nor my uin (?) specified + if (!ip || !port || !uin) return; + + if (!(dcc = gg_dcc_get_file(ip, port, myuin, uin))) + return; + + // Add client dcc to watches + EnterCriticalSection(&ft_mutex); + list_add(&watches, dcc, 0); + LeaveCriticalSection(&ft_mutex); +} + +////////////////////////////////////////////////////////// +// THREAD: File transfer fail +struct ftfaildata +{ + HANDLE hContact; + HANDLE hProcess; +}; + +void __cdecl GGPROTO::ftfailthread(void *param) +{ + struct ftfaildata *ft = (struct ftfaildata *)param; + SleepEx(100, FALSE); + netlog("gg_ftfailthread(): Sending failed file transfer."); + ProtoBroadcastAck(m_szModuleName, ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft->hProcess, 0); + free(ft); +} + +HANDLE ftfail(GGPROTO *gg, HANDLE hContact) +{ + ftfaildata *ft = (ftfaildata*)malloc(sizeof(struct ftfaildata)); +#ifdef DEBUGMODE + gg->netlog("gg_ftfail(): Failing file transfer..."); +#endif + srand(time(NULL)); + ft->hProcess = (HANDLE)rand(); + ft->hContact = hContact; + gg->forkthread(&GGPROTO::ftfailthread, ft); + return ft->hProcess; +} + +//////////////////////////////////////////////////////////// +// Main DCC connection session thread + +// Info refresh min time (msec) / half-sec +#define GGSTATREFRESHEVERY 500 + +void __cdecl GGPROTO::dccmainthread(void*) +{ + uin_t uin; + gg_event *e; + struct timeval tv; + fd_set rd, wd; + int ret; + SOCKET maxfd; + DWORD tick; + list_t l; + char filename[MAX_PATH]; + + // Zero up lists + watches = transfers = requests = l = NULL; + + netlog("gg_dccmainthread(): DCC Server Thread Starting"); + + // Readup number + if (!(uin = db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0))) + { + netlog("gg_dccmainthread(): No Gadu-Gadu number specified. Exiting."); + if (hEvent) SetEvent(hEvent); + return; + } + + // Create listen socket on config direct port + if (!(dcc = gg_dcc_socket_create(uin, (uint16_t)db_get_w(NULL, m_szModuleName, GG_KEY_DIRECTPORT, GG_KEYDEF_DIRECTPORT)))) + { + netlog("gg_dccmainthread(): Cannot create DCC listen socket. Exiting."); + // Signalize mainthread we haven't start + if (hEvent) SetEvent(hEvent); + return; + } + + gg_dcc_port = dcc->port; + gg_dcc_ip = inet_addr("255.255.255.255"); + netlog("gg_dccmainthread(): Listening on port %d.", gg_dcc_port); + + // Signalize mainthread we started + if (hEvent) SetEvent(hEvent); + + // Add main dcc handler to watches + list_add(&watches, dcc, 0); + + // Do while we are in the main server thread + while(pth_dcc.dwThreadId && dcc) + { + // Timeouts + tv.tv_sec = 1; + tv.tv_usec = 0; + + // Prepare descriptiors for select + FD_ZERO(&rd); + FD_ZERO(&wd); + + for (maxfd = 0, l = watches; l; l = l->next) + { + gg_common *w = (gg_common*)l->data; + + if (!w || w->state == GG_STATE_ERROR || w->state == GG_STATE_IDLE || w->state == GG_STATE_DONE) + continue; + + // Check if it's proper descriptor + if (w->fd == -1) continue; + + if (w->fd > maxfd) + maxfd = w->fd; + if ((w->check & GG_CHECK_READ)) + FD_SET(w->fd, &rd); + if ((w->check & GG_CHECK_WRITE)) + FD_SET(w->fd, &wd); + } + + // Wait for data on selects + ret = select(maxfd + 1, &rd, &wd, NULL, &tv); + + // Check for select error + if (ret == -1) + { + if (errno == EBADF) + netlog("gg_dccmainthread(): Bad descriptor on select()."); + else if (errno != EINTR) + netlog("gg_dccmainthread(): Unknown error on select()."); + continue; + } + + // Process watches (carefull with l) + l = watches; + EnterCriticalSection(&ft_mutex); + while (l) + { + struct gg_common *c = (gg_common*)l->data; + struct gg_dcc *dcc = (gg_dcc*)l->data; + struct gg_dcc7 *dcc7 = (gg_dcc7*)l->data; + l = l->next; + + switch (c->type) + { + default: + if (!dcc || (!FD_ISSET(dcc->fd, &rd) && !FD_ISSET(dcc->fd, &wd))) + continue; + + ///////////////////////////////////////////////////////////////// + // Process DCC events + + // Connection broken/closed + if (!(e = gg_dcc_socket_watch_fd(dcc))) + { + netlog("gg_dccmainthread(): Socket closed."); + // Remove socket and _close + list_remove(&watches, dcc, 0); + gg_dcc_socket_free(dcc); + + // Check if it's main socket + if (dcc == dcc) dcc = NULL; + continue; + } + else netlog("gg_dccmainthread(): Event: %s", ggdebug_eventtype(e)); + + switch(e->type) + { + // Client connected + case GG_EVENT_DCC_NEW: + list_add(&watches, e->event.dcc_new, 0); + e->event.dcc_new = NULL; + break; + + // + case GG_EVENT_NONE: + // If transfer in progress do status + if (dcc->file_fd != -1 && dcc->offset > 0 && (((tick = GetTickCount()) - dcc->tick) > GGSTATREFRESHEVERY)) + { + PROTOFILETRANSFERSTATUS pfts; + dcc->tick = tick; + strncpy(filename, dcc->folder, sizeof(filename)); + strncat(filename, (char*)dcc->file_info.filename, sizeof(filename) - strlen(filename)); + memset(&pfts, 0, sizeof(PROTOFILETRANSFERSTATUS)); + pfts.cbSize = sizeof(PROTOFILETRANSFERSTATUS); + pfts.hContact = (HANDLE)dcc->contact; + pfts.flags = (dcc->type == GG_SESSION_DCC_SEND); + pfts.pszFiles = NULL; + pfts.totalFiles = 1; + pfts.currentFileNumber = 0; + pfts.totalBytes = dcc->file_info.size; + pfts.totalProgress = dcc->offset; + pfts.szWorkingDir = dcc->folder; + pfts.szCurrentFile = filename; + pfts.currentFileSize = dcc->file_info.size; + pfts.currentFileProgress = dcc->offset; + pfts.currentFileTime = 0; + ProtoBroadcastAck(m_szModuleName, dcc->contact, ACKTYPE_FILE, ACKRESULT_DATA, dcc, (LPARAM)&pfts); + } + break; + + // Connection was successfuly ended + case GG_EVENT_DCC_DONE: + netlog("gg_dccmainthread(): Client: %d, Transfer done ! Closing connection.", dcc->peer_uin); + // Remove from watches + list_remove(&watches, dcc, 0); + // Close file & success + if (dcc->file_fd != -1) + { + PROTOFILETRANSFERSTATUS pfts; + strncpy(filename, dcc->folder, sizeof(filename)); + strncat(filename, (char*)dcc->file_info.filename, sizeof(filename) - strlen(filename)); + memset(&pfts, 0, sizeof(PROTOFILETRANSFERSTATUS)); + pfts.cbSize = sizeof(PROTOFILETRANSFERSTATUS); + pfts.hContact = (HANDLE)dcc->contact; + pfts.flags = (dcc->type == GG_SESSION_DCC_SEND); + pfts.pszFiles = NULL; + pfts.totalFiles = 1; + pfts.currentFileNumber = 0; + pfts.totalBytes = dcc->file_info.size; + pfts.totalProgress = dcc->file_info.size; + pfts.szWorkingDir = dcc->folder; + pfts.szCurrentFile = filename; + pfts.currentFileSize = dcc->file_info.size; + pfts.currentFileProgress = dcc->file_info.size; + pfts.currentFileTime = 0; + ProtoBroadcastAck(m_szModuleName, dcc->contact, ACKTYPE_FILE, ACKRESULT_DATA, dcc, (LPARAM)&pfts); + _close(dcc->file_fd); dcc->file_fd = -1; + ProtoBroadcastAck(m_szModuleName, dcc->contact, ACKTYPE_FILE, ACKRESULT_SUCCESS, dcc, 0); + } + // Free dcc + gg_free_dcc(dcc); if (dcc == dcc) dcc = NULL; + break; + + // Client error + case GG_EVENT_DCC_ERROR: + switch (e->event.dcc_error) + { + case GG_ERROR_DCC_HANDSHAKE: + netlog("gg_dccmainthread(): Client: %d, Handshake error.", dcc->peer_uin); + break; + case GG_ERROR_DCC_NET: + netlog("gg_dccmainthread(): Client: %d, Network error.", dcc->peer_uin); + break; + case GG_ERROR_DCC_FILE: + netlog("gg_dccmainthread(): Client: %d, File read/write error.", dcc->peer_uin); + break; + case GG_ERROR_DCC_EOF: + netlog("gg_dccmainthread(): Client: %d, End of file/connection error.", dcc->peer_uin); + break; + case GG_ERROR_DCC_REFUSED: + netlog("gg_dccmainthread(): Client: %d, Connection refused error.", dcc->peer_uin); + break; + default: + netlog("gg_dccmainthread(): Client: %d, Unknown error.", dcc->peer_uin); + } + // Don't do anything if it's main socket + if (dcc == dcc) break; + + // Remove from watches + list_remove(&watches, dcc, 0); + + // Close file & fail + if (dcc->contact) + { + _close(dcc->file_fd); dcc->file_fd = -1; + ProtoBroadcastAck(m_szModuleName, dcc->contact, ACKTYPE_FILE, ACKRESULT_FAILED, dcc, 0); + } + // Free dcc + gg_free_dcc(dcc); if (dcc == dcc) dcc = NULL; + break; + + // Need file acknowledgement + case GG_EVENT_DCC_NEED_FILE_ACK: + netlog("gg_dccmainthread(): Client: %d, File ack filename \"%s\" size %d.", dcc->peer_uin, + dcc->file_info.filename, dcc->file_info.size); + // Do not watch for transfer until user accept it + list_remove(&watches, dcc, 0); + // Add to waiting transfers + list_add(&transfers, dcc, 0); + + ////////////////////////////////////////////////// + // Add file recv request + { + // Make new ggtransfer struct + dcc->contact = getcontact(dcc->peer_uin, 0, 0, NULL); + TCHAR* filenameT = mir_utf8decodeT((char*)dcc->file_info.filename); + + PROTORECVFILET pre = {0}; + pre.flags = PREF_TCHAR; + pre.fileCount = 1; + pre.timestamp = time(NULL); + pre.tszDescription = filenameT; + pre.ptszFiles = &filenameT; + pre.lParam = (LPARAM)dcc7; + + CCSDATA ccs = { dcc7->contact, PSR_FILE, 0, (LPARAM)&pre }; + CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs); + + mir_free(filenameT); + } + break; + + // Need client accept + case GG_EVENT_DCC_CLIENT_ACCEPT: + netlog("gg_dccmainthread(): Client: %d, Client accept.", dcc->peer_uin); + // Check if user is on the list and if it is my uin + if (getcontact(dcc->peer_uin, 0, 0, NULL) && + db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, -1) == dcc->uin) + break; + + // Kill unauthorized dcc + list_remove(&watches, dcc, 0); + gg_free_dcc(dcc); if (dcc == dcc) dcc = NULL; + break; + + // Client connected as we wished to (callback) + case GG_EVENT_DCC_CALLBACK: + { + int found = 0; + netlog("gg_dccmainthread(): Callback from client %d.", dcc->peer_uin); + // Seek for stored callback request + for (l = requests; l; l = l->next) + { + struct gg_dcc *req = (gg_dcc*)l->data; + + if (req && req->peer_uin == dcc->peer_uin) + { + gg_dcc_set_type(dcc, GG_SESSION_DCC_SEND); + found = 1; + + // Copy data req ===> dcc + dcc->folder = req->folder; + dcc->contact = req->contact; + dcc->file_fd = req->file_fd; + memcpy(&dcc->file_info, &req->file_info, sizeof(struct gg_file_info)); + // Copy data back to dcc ===> req + memcpy(req, dcc, sizeof(struct gg_dcc)); + + // Remove request + list_remove(&requests, req, 0); + // Remove dcc from watches + list_remove(&watches, dcc, 0); + // Add request to watches + list_add(&watches, req, 0); + // Free old dat + gg_free_dcc(dcc); + netlog("gg_dccmainthread(): Found stored request to client %d, filename \"%s\" size %d, folder \"%s\".", + req->peer_uin, req->file_info.filename, req->file_info.size, req->folder); + break; + } + } + + if (!found) + { + netlog("gg_dccmainthread(): Unknown request to client %d.", dcc->peer_uin); + // Kill unauthorized dcc + list_remove(&watches, dcc, 0); + gg_free_dcc(dcc); if (dcc == dcc) dcc = NULL; + } + break; + } + } + + // Free event + gg_free_event(e); + break; + + case GG_SESSION_DCC7_SOCKET: + case GG_SESSION_DCC7_GET: + case GG_SESSION_DCC7_SEND: + case GG_SESSION_DCC7_VOICE: + if (!dcc7 || (!FD_ISSET(dcc7->fd, &rd) && !FD_ISSET(dcc7->fd, &wd))) + continue; + + ///////////////////////////////////////////////////////////////// + // Process DCC7 events + + // Connection broken/closed + if (!(e = gg_dcc7_watch_fd(dcc7))) + { + netlog("gg_dccmainthread(): Socket closed."); + // Remove socket and _close + list_remove(&watches, dcc7, 0); + gg_dcc7_free(dcc7); + continue; + } + else netlog("gg_dccmainthread(): Event: %s", ggdebug_eventtype(e)); + + switch(e->type) + { + // + case GG_EVENT_NONE: + // If transfer in progress do status + if (dcc7->file_fd != -1 && dcc7->offset > 0 && (((tick = GetTickCount()) - dcc7->tick) > GGSTATREFRESHEVERY)) + { + PROTOFILETRANSFERSTATUS pfts; + dcc7->tick = tick; + strncpy(filename, dcc7->folder, sizeof(filename)); + strncat(filename, (char*)dcc7->filename, sizeof(filename) - strlen(filename)); + memset(&pfts, 0, sizeof(PROTOFILETRANSFERSTATUS)); + pfts.cbSize = sizeof(PROTOFILETRANSFERSTATUS); + pfts.hContact = (HANDLE)dcc7->contact; + pfts.flags = (dcc7->type == GG_SESSION_DCC7_SEND); + pfts.pszFiles = NULL; + pfts.totalFiles = 1; + pfts.currentFileNumber = 0; + pfts.totalBytes = dcc7->size; + pfts.totalProgress = dcc7->offset; + pfts.szWorkingDir = dcc7->folder; + pfts.szCurrentFile = filename; + pfts.currentFileSize = dcc7->size; + pfts.currentFileProgress = dcc7->offset; + pfts.currentFileTime = 0; + ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_DATA, dcc7, (LPARAM)&pfts); + } + break; + + // Connection was successfuly ended + case GG_EVENT_DCC7_DONE: + netlog("gg_dccmainthread(): Client: %d, Transfer done ! Closing connection.", dcc->peer_uin); + // Remove from watches + list_remove(&watches, dcc7, 0); + // Close file & success + if (dcc7->file_fd != -1) + { + PROTOFILETRANSFERSTATUS pfts; + strncpy(filename, dcc7->folder, sizeof(filename)); + strncat(filename, (char*)dcc7->filename, sizeof(filename) - strlen(filename)); + memset(&pfts, 0, sizeof(PROTOFILETRANSFERSTATUS)); + pfts.cbSize = sizeof(PROTOFILETRANSFERSTATUS); + pfts.hContact = (HANDLE)dcc7->contact; + pfts.flags = (dcc7->type == GG_SESSION_DCC7_SEND); + pfts.pszFiles = NULL; + pfts.totalFiles = 1; + pfts.currentFileNumber = 0; + pfts.totalBytes = dcc7->size; + pfts.totalProgress = dcc7->size; + pfts.szWorkingDir = dcc7->folder; + pfts.szCurrentFile = filename; + pfts.currentFileSize = dcc7->size; + pfts.currentFileProgress = dcc7->size; + pfts.currentFileTime = 0; + ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_DATA, dcc7, (LPARAM)&pfts); + _close(dcc7->file_fd); dcc7->file_fd = -1; + ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_SUCCESS, dcc7, 0); + } + // Free dcc + gg_dcc7_free(dcc7); + break; + + // Client error + case GG_EVENT_DCC7_ERROR: + switch (e->event.dcc7_error) + { + case GG_ERROR_DCC7_HANDSHAKE: + netlog("gg_dccmainthread(): Client: %d, Handshake error.", dcc7->peer_uin); + break; + case GG_ERROR_DCC7_NET: + netlog("gg_dccmainthread(): Client: %d, Network error.", dcc7->peer_uin); + break; + case GG_ERROR_DCC7_FILE: + netlog("gg_dccmainthread(): Client: %d, File read/write error.", dcc7->peer_uin); + break; + case GG_ERROR_DCC7_EOF: + netlog("gg_dccmainthread(): Client: %d, End of file/connection error.", dcc7->peer_uin); + break; + case GG_ERROR_DCC7_REFUSED: + netlog("gg_dccmainthread(): Client: %d, Connection refused error.", dcc7->peer_uin); + break; + case GG_ERROR_DCC7_RELAY: + netlog("gg_dccmainthread(): Client: %d, Relay connection error.", dcc7->peer_uin); + break; + default: + netlog("gg_dccmainthread(): Client: %d, Unknown error.", dcc7->peer_uin); + } + // Remove from watches + list_remove(&watches, dcc7, 0); + + // Close file & fail + if (dcc7->file_fd != -1) + { + _close(dcc7->file_fd); + dcc7->file_fd = -1; + } + + if (dcc7->contact) + ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_FAILED, dcc7, 0); + + // Free dcc + gg_dcc7_free(dcc7); + break; + } + + // Free event + gg_free_event(e); + break; + } + } + LeaveCriticalSection(&ft_mutex); + } + + // Close all dcc client sockets + for (l = watches; l; l = l->next) + { + struct gg_common *c = (gg_common*)l->data; + if (!c) continue; + if (c->type == GG_SESSION_DCC7_SOCKET || c->type == GG_SESSION_DCC7_SEND || c->type == GG_SESSION_DCC7_GET) + { + struct gg_dcc7 *dcc7 = (gg_dcc7*)l->data; + gg_dcc7_free(dcc7); + } + else + { + struct gg_dcc *dcc = (gg_dcc*)l->data; + gg_dcc_socket_free(dcc); + + // Check if it's main socket + if (dcc == dcc) dcc = NULL; + } + } + // Close all waiting for aknowledgle transfers + for (l = transfers; l; l = l->next) + { + struct gg_common *c = (gg_common*)l->data; + if (!c) continue; + if (c->type == GG_SESSION_DCC7_SOCKET || c->type == GG_SESSION_DCC7_SEND || c->type == GG_SESSION_DCC7_GET) + { + struct gg_dcc7 *dcc7 = (gg_dcc7*)l->data; + gg_dcc7_free(dcc7); + } + else + { + struct gg_dcc *dcc = (gg_dcc*)l->data; + gg_dcc_socket_free(dcc); + } + } + // Close all waiting dcc requests + for (l = requests; l; l = l->next) + { + struct gg_dcc *dcc = (gg_dcc*)l->data; + if (dcc) gg_free_dcc(dcc); + } + list_destroy(watches, 0); + list_destroy(transfers, 0); + list_destroy(requests, 0); + + gg_dcc_port = 0; + gg_dcc_ip = 0; + netlog("gg_dccmainthread(): DCC Server Thread Ending"); +} + +HANDLE GGPROTO::dccfileallow(HANDLE hTransfer, const PROTOCHAR* szPath) +{ + struct gg_dcc *dcc = (struct gg_dcc *) hTransfer; + char fileName[MAX_PATH], *path = mir_t2a(szPath); + strncpy(fileName, path, sizeof(fileName)); + strncat(fileName, (char*)dcc->file_info.filename, sizeof(fileName) - strlen(fileName)); + dcc->folder = _strdup((char *) path); + dcc->tick = 0; + mir_free(path); + + // Remove transfer from waiting list + EnterCriticalSection(&ft_mutex); + list_remove(&transfers, dcc, 0); + LeaveCriticalSection(&ft_mutex); + + // Open file for appending and check if ok + if ((dcc->file_fd = _open(fileName, _O_WRONLY | _O_APPEND | _O_BINARY | _O_CREAT, _S_IREAD | _S_IWRITE)) == -1) + { + netlog("gg_dccfileallow(): Failed to create file \"%s\".", fileName); + ProtoBroadcastAck(m_szModuleName, dcc->contact, ACKTYPE_FILE, ACKRESULT_FAILED, dcc, 0); + // Free transfer + gg_free_dcc(dcc); + return 0; + } + + // Put an offset to the file + dcc->offset = _lseek(dcc->file_fd, 0, SEEK_END); + + // Add to watches and start transfer + EnterCriticalSection(&ft_mutex); + list_add(&watches, dcc, 0); + LeaveCriticalSection(&ft_mutex); + + netlog("gg_dccfileallow(): Receiving file \"%s\" from %d.", dcc->file_info.filename, dcc->peer_uin); + + return hTransfer; +} + +HANDLE GGPROTO::dcc7fileallow(HANDLE hTransfer, const PROTOCHAR* szPath) +{ + struct gg_dcc7 *dcc7 = (struct gg_dcc7 *) hTransfer; + char fileName[MAX_PATH], *path = mir_t2a(szPath); + int iFtRemoveRes; + strncpy(fileName, path, sizeof(fileName)); + strncat(fileName, (char*)dcc7->filename, sizeof(fileName) - strlen(fileName)); + dcc7->folder = _strdup((char *) path); + dcc7->tick = 0; + mir_free(path); + + // Remove transfer from waiting list + EnterCriticalSection(&ft_mutex); + iFtRemoveRes = list_remove(&transfers, dcc7, 0); + LeaveCriticalSection(&ft_mutex); + + if (iFtRemoveRes == -1) + { + netlog("gg_dcc7fileallow(): File transfer denied."); + ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_DENIED, dcc7, 0); + // Free transfer + gg_dcc7_free(dcc7); + return 0; + } + + // Open file for appending and check if ok + if ((dcc7->file_fd = _open(fileName, _O_WRONLY | _O_APPEND | _O_BINARY | _O_CREAT, _S_IREAD | _S_IWRITE)) == -1) + { + netlog("gg_dcc7fileallow(): Failed to create file \"%s\".", fileName); + gg_dcc7_reject(dcc7, GG_DCC7_REJECT_USER); + ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_FAILED, dcc7, 0); + // Free transfer + gg_dcc7_free(dcc7); + return 0; + } + + // Put an offset to the file + dcc7->offset = _lseek(dcc7->file_fd, 0, SEEK_END); + gg_dcc7_accept(dcc7, dcc7->offset); + + // Add to watches and start transfer + EnterCriticalSection(&ft_mutex); + list_add(&watches, dcc7, 0); + LeaveCriticalSection(&ft_mutex); + + netlog("gg_dcc7fileallow(): Receiving file \"%s\" from %d.", dcc7->filename, dcc7->peer_uin); + + return hTransfer; +} + +int GGPROTO::dccfiledeny(HANDLE hTransfer) +{ + struct gg_dcc *dcc = (struct gg_dcc *) hTransfer; + + // Remove transfer from any list + EnterCriticalSection(&ft_mutex); + if (watches) list_remove(&watches, dcc, 0); + if (requests) list_remove(&requests, dcc, 0); + if (transfers) list_remove(&transfers, dcc, 0); + LeaveCriticalSection(&ft_mutex); + + netlog("gg_dccfiledeny(): Rejected file \"%s\" from/to %d.", dcc->file_info.filename, dcc->peer_uin); + + // Free transfer + gg_free_dcc(dcc); + + return 0; +} + +int GGPROTO::dcc7filedeny(HANDLE hTransfer) +{ + struct gg_dcc7 *dcc7 = (struct gg_dcc7 *) hTransfer; + + gg_dcc7_reject(dcc7, GG_DCC7_REJECT_USER); + + // Remove transfer from any list + EnterCriticalSection(&ft_mutex); + if (watches) list_remove(&watches, dcc7, 0); + if (transfers) list_remove(&transfers, dcc7, 0); + LeaveCriticalSection(&ft_mutex); + + netlog("gg_dcc7filedeny(): Rejected file \"%s\" from/to %d.", dcc7->filename, dcc7->peer_uin); + + // Free transfer + gg_dcc7_free(dcc7); + + return 0; +} + +int GGPROTO::dccfilecancel(HANDLE hTransfer) +{ + struct gg_dcc *dcc = (struct gg_dcc *) hTransfer; + + // Remove transfer from any list + EnterCriticalSection(&ft_mutex); + if (watches) list_remove(&watches, dcc, 0); + if (requests) list_remove(&requests, dcc, 0); + if (transfers) list_remove(&transfers, dcc, 0); + LeaveCriticalSection(&ft_mutex); + + // Send failed info + ProtoBroadcastAck(m_szModuleName, dcc->contact, ACKTYPE_FILE, ACKRESULT_FAILED, dcc, 0); + // Close file + if (dcc->file_fd != -1) + { + _close(dcc->file_fd); + dcc->file_fd = -1; + } + + netlog("gg_dccfilecancel(): Canceled file \"%s\" from/to %d.", dcc->file_info.filename, dcc->peer_uin); + + // Free transfer + gg_free_dcc(dcc); + + return 0; +} + +int GGPROTO::dcc7filecancel(HANDLE hTransfer) +{ + struct gg_dcc7 *dcc7 = (struct gg_dcc7 *) hTransfer; + + if (dcc7->type == GG_SESSION_DCC7_SEND && dcc7->state == GG_STATE_WAITING_FOR_ACCEPT) + gg_dcc7_abort(dcc7); + + // Remove transfer from any list + EnterCriticalSection(&ft_mutex); + if (watches) list_remove(&watches, dcc7, 0); + if (transfers) list_remove(&transfers, dcc7, 0); + LeaveCriticalSection(&ft_mutex); + + // Send failed info + ProtoBroadcastAck(m_szModuleName, dcc7->contact, ACKTYPE_FILE, ACKRESULT_FAILED, dcc7, 0); + // Close file + if (dcc7->file_fd != -1) + { + _close(dcc7->file_fd); + dcc7->file_fd = -1; + } + + netlog("gg_dcc7filecancel(): Canceled file \"%s\" from/to %d.", dcc7->filename, dcc7->peer_uin); + + // Free transfer + gg_dcc7_free(dcc7); + + return 0; +} + +//////////////////////////////////////////////////////////// +// File receiving allowed + +HANDLE GGPROTO::FileAllow(HANDLE hContact, HANDLE hTransfer, const PROTOCHAR* szPath) +{ + // Check if its proper dcc + struct gg_common *c = (struct gg_common *) hTransfer; + if (!c) + return NULL; + + if (c->type == GG_SESSION_DCC7_GET) + return dcc7fileallow(hTransfer, szPath); + + return dccfileallow(hTransfer, szPath); +} + +//////////////////////////////////////////////////////////// +// File transfer canceled + +int GGPROTO::FileCancel(HANDLE hContact, HANDLE hTransfer) +{ + // Check if its proper dcc + struct gg_common *c = (struct gg_common *) hTransfer; + if (!c) + return 0; + + if (c->type == GG_SESSION_DCC7_SEND || c->type == GG_SESSION_DCC7_GET) + return dcc7filecancel(hTransfer); + + return dccfilecancel(hTransfer); +} + +//////////////////////////////////////////////////////////// +// File receiving denied + +int GGPROTO::FileDeny(HANDLE hContact, HANDLE hTransfer, const PROTOCHAR* szReason) +{ + // Check if its proper dcc + struct gg_common *c = (struct gg_common *) hTransfer; + if (!c) + return 0; + + if (c->type == GG_SESSION_DCC7_GET) + return dcc7filedeny(hTransfer); + + return dccfiledeny(hTransfer); +} + +//////////////////////////////////////////////////////////// +// Called when received an file + +int GGPROTO::RecvFile(HANDLE hContact, PROTOFILEEVENT* pre) +{ + return Proto_RecvFile(hContact, pre); +} + +//////////////////////////////////////////////////////////// +// Called when user sends a file + +HANDLE GGPROTO::SendFile(HANDLE hContact, const PROTOCHAR* szDescription, PROTOCHAR** ppszFiles) +{ + char *bslash, *filename; + struct gg_dcc *dcc; + DWORD ip, ver; + WORD port; + uin_t myuin, uin; + + // Check if main dcc thread is on + if (!isonline()) + return ftfail(this, hContact); + + filename = mir_t2a(ppszFiles[0]); + + // Read user IP and port + ip = swap32(db_get_dw(hContact, m_szModuleName, GG_KEY_CLIENTIP, 0)); + port = db_get_w(hContact, m_szModuleName, GG_KEY_CLIENTPORT, 0); + myuin = db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0); + uin = db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0); + ver = db_get_dw(hContact, m_szModuleName, GG_KEY_CLIENTVERSION, 0); + + // Use DCC7 if a contact is using at least version 7.6 or unknown version + if ((ver & 0x00ffffff) >= 0x29 || !ver) { + struct gg_dcc7 *dcc7; + + EnterCriticalSection(&sess_mutex); + if (!(dcc7 = gg_dcc7_send_file(sess, uin, filename, NULL, NULL))) { + LeaveCriticalSection(&sess_mutex); + netlog("gg_sendfile(): Failed to send file \"%s\".", filename); + mir_free(filename); + return ftfail(this, hContact); + } + LeaveCriticalSection(&sess_mutex); + + netlog("gg_sendfile(): Sending file \"%s\" to %d.", filename, uin); + + // Add dcc to watches + list_add(&watches, dcc7, 0); + + // Store handle + dcc7->contact = hContact; + dcc7->folder = _strdup(filename); + dcc7->tick = 0; + // Make folder name + bslash = strrchr(dcc7->folder, '\\'); + if (bslash) + *(bslash + 1) = 0; + else + *(dcc7->folder) = 0; + mir_free(filename); + return dcc7; + } + + // Return if bad connection info + if (!port || !uin || !myuin) + { + netlog("gg_sendfile(): Bad contact uin or my uin. Exit."); + mir_free(filename); + return ftfail(this, hContact); + } + + // Try to connect if not ask user to connect me + if ((ip && port >= 10 && !(dcc = gg_dcc_send_file(ip, port, myuin, uin))) || (port < 10 && port > 0)) + { + // Make fake dcc structure + dcc = (gg_dcc*)malloc(sizeof(struct gg_dcc)); + memset(dcc, 0, sizeof(struct gg_dcc)); + // Fill up structures + dcc->uin = myuin; + dcc->peer_uin = uin; + dcc->fd = -1; + dcc->type = GG_SESSION_DCC_SEND; + netlog("gg_sendfile(): Requesting user to connect us and scheduling gg_dcc struct for a later use."); + EnterCriticalSection(&sess_mutex); + gg_dcc_request(sess, uin); + LeaveCriticalSection(&sess_mutex); + list_add(&requests, dcc, 0); + } + + // Write filename + if (gg_dcc_fill_file_info(dcc, filename) == -1) + { + netlog("gg_sendfile(): Cannot open and file fileinfo \"%s\".", filename); + gg_free_dcc(dcc); + mir_free(filename); + return ftfail(this, hContact); + } + + netlog("gg_sendfile(): Sending file \"%s\" to %d in %s mode.", filename, uin, (dcc->fd != -1) ? "active" : "passive"); + + // Add dcc to watches if not passive + if (dcc->fd != -1) list_add(&watches, dcc, 0); + + // Store handle + dcc->contact = hContact; + dcc->folder = _strdup(filename); + dcc->tick = 0; + // Make folder name + bslash = strrchr(dcc->folder, '\\'); + if (bslash) + *(bslash + 1) = 0; + else + *(dcc->folder) = 0; + + mir_free(filename); + return dcc; +} + diff --git a/protocols/Gadu-Gadu/src/gg.cpp b/protocols/Gadu-Gadu/src/gg.cpp new file mode 100644 index 0000000000..5bd9f6ceb5 --- /dev/null +++ b/protocols/Gadu-Gadu/src/gg.cpp @@ -0,0 +1,523 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2009 Adam Strzelecki +// Copyright (c) 2009-2012 Bartosz Białek +// +// 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 "gg.h" +#include "version.h" +#include + +// Plugin info +PLUGININFOEX pluginInfo = { + sizeof(PLUGININFOEX), + "Gadu-Gadu Protocol", + __VERSION_DWORD, + "Provides support for Gadu-Gadu protocol", + "Bartosz Białek, Adam Strzelecki", + "dezred"/*antispam*/"@"/*antispam*/"gmail"/*antispam*/"."/*antispam*/"com", + "© 2009-2012 Bartosz Białek, 2003-2009 Adam Strzelecki", + "http://miranda-ng.org/", + UNICODE_AWARE, + // {F3FF65F3-250E-416A-BEE9-58C93F85AB33} + { 0xf3ff65f3, 0x250e, 0x416a, { 0xbe, 0xe9, 0x58, 0xc9, 0x3f, 0x85, 0xab, 0x33 } } +}; + +// Other variables +HINSTANCE hInstance; + +XML_API xi; +SSL_API si; +CLIST_INTERFACE *pcli; +int hLangpack; +list_t g_Instances; + +// Event hooks +static HANDLE hHookModulesLoaded = NULL; +static HANDLE hHookPreShutdown = NULL; + +static unsigned long crc_table[256]; + +////////////////////////////////////////////////////////// +// Extra winsock function for error description + +TCHAR* ws_strerror(int code) +{ + static TCHAR err_desc[160]; + + // Not a windows error display WinSock + if (code == 0) + { + TCHAR buff[128]; + int len = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, WSAGetLastError(), 0, buff, SIZEOF(buff), NULL); + if (len == 0) + mir_sntprintf(err_desc, SIZEOF(err_desc), _T("WinSock %u: Unknown error."), WSAGetLastError()); + else + mir_sntprintf(err_desc, SIZEOF(err_desc), _T("WinSock %d: %s"), WSAGetLastError(), buff); + return err_desc; + } + + // Return normal error + return _tcserror(code); +} + +////////////////////////////////////////////////////////// +// Build the crc table +void crc_gentable(void) +{ + unsigned long crc, poly; + int i, j; + + poly = 0xEDB88320L; + for (i = 0; i < 256; i++) + { + crc = i; + for (j = 8; j > 0; j--) + { + if (crc & 1) + crc = (crc >> 1) ^ poly; + else + crc >>= 1; + } + crc_table[i] = crc; + } +} + +////////////////////////////////////////////////////////// +// Calculate the crc value +unsigned long crc_get(char *mem) +{ + register unsigned long crc = 0xFFFFFFFF; + while(mem && *mem) + crc = ((crc>>8) & 0x00FFFFFF) ^ crc_table[(crc ^ *(mem++)) & 0xFF]; + + return (crc ^ 0xFFFFFFFF); +} + +////////////////////////////////////////////////////////// +// http_error_string() +// +// returns http error text +const char *http_error_string(int h) +{ + switch (h) + { + case 0: + return Translate((errno == ENOMEM) ? "HTTP failed memory" : "HTTP failed connecting"); + case GG_ERROR_RESOLVING: + return Translate("HTTP failed resolving"); + case GG_ERROR_CONNECTING: + return Translate("HTTP failed connecting"); + case GG_ERROR_READING: + return Translate("HTTP failed reading"); + case GG_ERROR_WRITING: + return Translate("HTTP failed writing"); + } + + return Translate("Unknown HTTP error"); +} + +////////////////////////////////////////////////////////// +// Gets plugin info + +extern "C" __declspec(dllexport) PLUGININFOEX *MirandaPluginInfoEx(DWORD mirandaVersion) +{ + return &pluginInfo; +} + +extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = {MIID_PROTOCOL, MIID_LAST}; + +////////////////////////////////////////////////////////// +// Cleanups from last plugin + +void GGPROTO::cleanuplastplugin(DWORD version) +{ + HANDLE hContact; + char *szProto; + + // Remove bad e-mail and phones from + if (version < PLUGIN_MAKE_VERSION(0, 0, 1, 4)) + { +#ifdef DEBUGMODE + netlog("gg_cleanuplastplugin(%d): Cleaning junk Phone settings from < 0.0.1.4 ...", version); +#endif + // Look for contact in DB + hContact = db_find_first(); + while (hContact) + { + szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0); + if (szProto != NULL && !strcmp(szProto, m_szModuleName)) + { + // Do contact cleanup + db_unset(hContact, m_szModuleName, GG_KEY_EMAIL); + db_unset(hContact, m_szModuleName, "Phone"); + } + hContact = db_find_next(hContact); + } + } + + // Remove GG entries for non GG contacts + if (version < PLUGIN_MAKE_VERSION(0, 0, 3, 5)) + { +#ifdef DEBUGMODE + netlog("gg_cleanuplastplugin(%d): Cleaning junk Nick settings from < 0.0.3.5 ...", version); +#endif + // Look for contact in DB + hContact = db_find_first(); + while (hContact) + { + szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0); + if (szProto != NULL && strcmp(szProto, m_szModuleName)) + { + // Do nick entry cleanup + db_unset(hContact, m_szModuleName, GG_KEY_NICK); + } + hContact = db_find_next(hContact); + } + } + + // Remove old unneeded entry + if (version < PLUGIN_MAKE_VERSION(0, 0, 5, 3)) + db_unset(NULL, m_szModuleName, "ShowNotOnMyList"); + + // Store this plugin version + db_set_dw(NULL, m_szModuleName, GG_PLUGINVERSION, pluginInfo.version); +} + +////////////////////////////////////////////////////////// +// When Miranda loaded its modules +static int gg_modulesloaded(WPARAM wParam, LPARAM lParam) +{ + // Get SSL API + mir_getSI(&si); + + // File Association Manager support + gg_links_init(); + + return 0; +} + +////////////////////////////////////////////////////////// +// When Miranda starting shutdown sequence +static int gg_preshutdown(WPARAM wParam, LPARAM lParam) +{ + gg_links_destroy(); + + return 0; +} + +////////////////////////////////////////////////////////// +// Gets protocol instance associated with a contact +static GGPROTO* gg_getprotoinstance(HANDLE hContact) +{ + char* szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + list_t l = g_Instances; + + if (szProto == NULL) + return NULL; + + for (; l; l = l->next) + { + GGPROTO* gg = (GGPROTO*)l->data; + if (strcmp(szProto, gg->m_szModuleName) == 0) + return gg; + } + + return NULL; +} + +////////////////////////////////////////////////////////// +// Handles PrebuildContactMenu event +static int gg_prebuildcontactmenu(WPARAM wParam, LPARAM lParam) +{ + const HANDLE hContact = (HANDLE)wParam; + CLISTMENUITEM mi = {0}; + GGPROTO* gg = gg_getprotoinstance(hContact); + + if (gg == NULL) + return 0; + + mi.cbSize = sizeof(mi); + mi.flags = CMIM_NAME | CMIM_FLAGS | CMIF_ICONFROMICOLIB; + if ( db_get_dw(hContact, gg->m_szModuleName, GG_KEY_UIN, 0) == db_get_b(NULL, gg->m_szModuleName, GG_KEY_UIN, 0) || + db_get_b(hContact, gg->m_szModuleName, "ChatRoom", 0) || + db_get_b(hContact, "CList", "NotOnList", 0)) + mi.flags |= CMIF_HIDDEN; + mi.pszName = db_get_b(hContact, gg->m_szModuleName, GG_KEY_BLOCK, 0) ? LPGEN("&Unblock") : LPGEN("&Block"); + CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)gg->hBlockMenuItem, (LPARAM)&mi); + + return 0; +} + +////////////////////////////////////////////////////////// +// Contact block service function +INT_PTR GGPROTO::blockuser(WPARAM wParam, LPARAM lParam) +{ + const HANDLE hContact = (HANDLE)wParam; + db_set_b(hContact, m_szModuleName, GG_KEY_BLOCK, !db_get_b(hContact, m_szModuleName, GG_KEY_BLOCK, 0)); + notifyuser(hContact, 1); + return 0; +} + + +////////////////////////////////////////////////////////// +// Contact blocking initialization + +#define GGS_BLOCKUSER "%s/BlockUser" +void GGPROTO::block_init() +{ + CLISTMENUITEM mi = {0}; + char service[64]; + + mi.cbSize = sizeof(mi); + mi.flags = CMIF_ICONFROMICOLIB; + + mir_snprintf(service, sizeof(service), GGS_BLOCKUSER, m_szModuleName); + createObjService(service, &GGPROTO::blockuser); + mi.position = -500050000; + mi.icolibItem = GetIconHandle(IDI_BLOCK); + mi.pszName = LPGEN("&Block"); + mi.pszService = service; + mi.pszContactOwner = m_szModuleName; + hBlockMenuItem = Menu_AddContactMenuItem(&mi); + + hPrebuildMenuHook = HookEvent(ME_CLIST_PREBUILDCONTACTMENU, gg_prebuildcontactmenu); +} + +////////////////////////////////////////////////////////// +// Contact blocking uninitialization + +void GGPROTO::block_uninit() +{ + UnhookEvent(hPrebuildMenuHook); + CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)hBlockMenuItem, 0); +} + +////////////////////////////////////////////////////////// +// Menus initialization +void GGPROTO::menus_init() +{ + HGENMENU hGCRoot, hCLRoot, hRoot = MO_GetProtoRootMenu(m_szModuleName); + CLISTMENUITEM mi = {0}; + + mi.cbSize = sizeof(mi); + if (hRoot == NULL) + { + mi.ptszName = m_tszUserName; + mi.position = 500090000; + mi.hParentMenu = HGENMENU_ROOT; + mi.flags = CMIF_ICONFROMICOLIB | CMIF_ROOTPOPUP | CMIF_TCHAR | CMIF_KEEPUNTRANSLATED; + mi.icolibItem = GetIconHandle(IDI_GG); + hGCRoot = hCLRoot = hRoot = hMenuRoot = Menu_AddProtoMenuItem(&mi); + } + else + { + mi.hParentMenu = hRoot; + mi.flags = CMIF_ICONFROMICOLIB | CMIF_ROOTHANDLE | CMIF_TCHAR; + + mi.ptszName = LPGENT("Conference"); + mi.position = 200001; + mi.icolibItem = GetIconHandle(IDI_CONFERENCE); + hGCRoot = Menu_AddProtoMenuItem(&mi); + + mi.ptszName = LPGENT("Contact list"); + mi.position = 200002; + mi.icolibItem = GetIconHandle(IDI_LIST); + hCLRoot = Menu_AddProtoMenuItem(&mi); + + if (hMenuRoot) + CallService(MS_CLIST_REMOVEMAINMENUITEM, (WPARAM)hMenuRoot, 0); + hMenuRoot = NULL; + } + + gc_menus_init(hGCRoot); + import_init(hCLRoot); + sessions_menus_init(hRoot); +} + +////////////////////////////////////////////////////////// +// Module instance initialization + +static GGPROTO *gg_proto_init(const char* pszProtoName, const TCHAR* tszUserName) +{ + GGPROTO *gg = new GGPROTO(pszProtoName, tszUserName); + list_add(&g_Instances, gg, 0); + return gg; +} + +////////////////////////////////////////////////////////// +// Module instance uninitialization + +static int gg_proto_uninit(PROTO_INTERFACE *proto) +{ + GGPROTO *gg = (GGPROTO *)proto; + list_remove(&g_Instances, gg, 0); + delete gg; + return 0; +} + +////////////////////////////////////////////////////////// +// When plugin is loaded + +extern "C" int __declspec(dllexport) Load(void) +{ + mir_getXI(&xi); + mir_getLP(&pluginInfo); + + pcli = (CLIST_INTERFACE*)CallService(MS_CLIST_RETRIEVE_INTERFACE, 0, (LPARAM)hInstance); + + // Hook system events + hHookModulesLoaded = HookEvent(ME_SYSTEM_MODULESLOADED, gg_modulesloaded); + hHookPreShutdown = HookEvent(ME_SYSTEM_PRESHUTDOWN, gg_preshutdown); + + // Prepare protocol name + PROTOCOLDESCRIPTOR pd = { 0 }; + pd.cbSize = sizeof(pd); + pd.szName = GGDEF_PROTO; + pd.fnInit = (pfnInitProto)gg_proto_init; + pd.fnUninit = (pfnUninitProto)gg_proto_uninit; + pd.type = PROTOTYPE_PROTOCOL; + + // Register module + CallService(MS_PROTO_REGISTERMODULE, 0, (LPARAM) &pd); + gg_links_instancemenu_init(); + + // Instance list + g_Instances = NULL; + + return 0; +} + +////////////////////////////////////////////////////////// +// When plugin is unloaded + +extern "C" int __declspec(dllexport) Unload() +{ + LocalEventUnhook(hHookModulesLoaded); + LocalEventUnhook(hHookPreShutdown); + + // Cleanup WinSock + WSACleanup(); + return 0; +} + +////////////////////////////////////////////////////////// +// DEBUGING FUNCTIONS +struct +{ + int type; + char *text; +} +static const ggdebug_eventype2string[] = +{ + {GG_EVENT_NONE, "GG_EVENT_NONE"}, + {GG_EVENT_MSG, "GG_EVENT_MSG"}, + {GG_EVENT_NOTIFY, "GG_EVENT_NOTIFY"}, + {GG_EVENT_NOTIFY_DESCR, "GG_EVENT_NOTIFY_DESCR"}, + {GG_EVENT_STATUS, "GG_EVENT_STATUS"}, + {GG_EVENT_ACK, "GG_EVENT_ACK"}, + {GG_EVENT_PONG, "GG_EVENT_PONG"}, + {GG_EVENT_CONN_FAILED, "GG_EVENT_CONN_FAILED"}, + {GG_EVENT_CONN_SUCCESS, "GG_EVENT_CONN_SUCCESS"}, + {GG_EVENT_DISCONNECT, "GG_EVENT_DISCONNECT"}, + {GG_EVENT_DCC_NEW, "GG_EVENT_DCC_NEW"}, + {GG_EVENT_DCC_ERROR, "GG_EVENT_DCC_ERROR"}, + {GG_EVENT_DCC_DONE, "GG_EVENT_DCC_DONE"}, + {GG_EVENT_DCC_CLIENT_ACCEPT, "GG_EVENT_DCC_CLIENT_ACCEPT"}, + {GG_EVENT_DCC_CALLBACK, "GG_EVENT_DCC_CALLBACK"}, + {GG_EVENT_DCC_NEED_FILE_INFO, "GG_EVENT_DCC_NEED_FILE_INFO"}, + {GG_EVENT_DCC_NEED_FILE_ACK, "GG_EVENT_DCC_NEED_FILE_ACK"}, + {GG_EVENT_DCC_NEED_VOICE_ACK, "GG_EVENT_DCC_NEED_VOICE_ACK"}, + {GG_EVENT_DCC_VOICE_DATA, "GG_EVENT_DCC_VOICE_DATA"}, + {GG_EVENT_PUBDIR50_SEARCH_REPLY,"GG_EVENT_PUBDIR50_SEARCH_REPLY"}, + {GG_EVENT_PUBDIR50_READ, "GG_EVENT_PUBDIR50_READ"}, + {GG_EVENT_PUBDIR50_WRITE, "GG_EVENT_PUBDIR50_WRITE"}, + {GG_EVENT_STATUS60, "GG_EVENT_STATUS60"}, + {GG_EVENT_NOTIFY60, "GG_EVENT_NOTIFY60"}, + {GG_EVENT_USERLIST, "GG_EVENT_USERLIST"}, + {GG_EVENT_IMAGE_REQUEST, "GG_EVENT_IMAGE_REQUEST"}, + {GG_EVENT_IMAGE_REPLY, "GG_EVENT_IMAGE_REPLY"}, + {GG_EVENT_DCC_ACK, "GG_EVENT_DCC_ACK"}, + {GG_EVENT_DCC7_NEW, "GG_EVENT_DCC7_NEW"}, + {GG_EVENT_DCC7_ACCEPT, "GG_EVENT_DCC7_ACCEPT"}, + {GG_EVENT_DCC7_REJECT, "GG_EVENT_DCC7_REJECT"}, + {GG_EVENT_DCC7_CONNECTED, "GG_EVENT_DCC7_CONNECTED"}, + {GG_EVENT_DCC7_ERROR, "GG_EVENT_DCC7_ERROR"}, + {GG_EVENT_DCC7_DONE, "GG_EVENT_DCC7_DONE"}, + {GG_EVENT_DCC7_PENDING, "GG_EVENT_DCC7_PENDING"}, + {GG_EVENT_XML_EVENT, "GG_EVENT_XML_EVENT"}, + {GG_EVENT_DISCONNECT_ACK, "GG_EVENT_DISCONNECT_ACK"}, + {GG_EVENT_XML_ACTION, "GG_EVENT_XML_ACTION"}, + {GG_EVENT_TYPING_NOTIFICATION, "GG_EVENT_TYPING_NOTIFICATION"}, + {GG_EVENT_USER_DATA, "GG_EVENT_USER_DATA"}, + {GG_EVENT_MULTILOGON_MSG, "GG_EVENT_MULTILOGON_MSG"}, + {GG_EVENT_MULTILOGON_INFO, "GG_EVENT_MULTILOGON_INFO"}, + {-1, ""} +}; + +const char *ggdebug_eventtype(gg_event *e) +{ + int i; + for(i = 0; ggdebug_eventype2string[i].type != -1; i++) + if (ggdebug_eventype2string[i].type == e->type) + return ggdebug_eventype2string[i].text; + return ggdebug_eventype2string[i].text; +} + +#ifdef DEBUGMODE +void gg_debughandler(int level, const char *format, va_list ap) +{ + char szText[1024], *szFormat = _strdup(format); + // Kill end line + char *nl = strrchr(szFormat, '\n'); + if (nl) *nl = 0; + + strncpy(szText, "[libgadu] \0", sizeof(szText)); + + mir_vsnprintf(szText + strlen(szText), sizeof(szText) - strlen(szText), szFormat, ap); + CallService(MS_NETLIB_LOG, (WPARAM) NULL, (LPARAM) szText); + free(szFormat); +} +#endif + +////////////////////////////////////////////////////////// +// Log funcion + +int GGPROTO::netlog(const char *fmt, ...) +{ + va_list va; + char szText[1024]; + + va_start(va, fmt); + mir_vsnprintf(szText, sizeof(szText), fmt, va); + va_end(va); + return CallService(MS_NETLIB_LOG, (WPARAM)netlib, (LPARAM) szText); +} + +////////////////////////////////////////////////////////// +// main DLL function + +BOOL APIENTRY DllMain(HINSTANCE hInst, DWORD reason, LPVOID reserved) +{ + crc_gentable(); + hInstance = hInst; +#ifdef DEBUGMODE + gg_debug_handler = gg_debughandler; +#endif + return TRUE; +} diff --git a/protocols/Gadu-Gadu/src/gg.h b/protocols/Gadu-Gadu/src/gg.h new file mode 100644 index 0000000000..742f06b093 --- /dev/null +++ b/protocols/Gadu-Gadu/src/gg.h @@ -0,0 +1,341 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2009 Adam Strzelecki +// Copyright (c) 2009-2012 Bartosz Białek +// +// 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. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GG_H +#define GG_H + +#define MIRANDA_VER 0x0A00 + +#if defined(__DEBUG__) || defined(_DEBUG) || defined(DEBUG) +#define DEBUGMODE // Debug Mode +#endif + +#if _WIN32_WINNT < 0x0501 +#define _WIN32_WINNT 0x0501 +#endif + +#include + +// Windows headers +// Visual C++ .NET tries to include winsock.h +// which is very ver bad +#if (_MSC_VER >= 1300) +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include + +// Miranda IM headers +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Custom profile folders plugin header +#include "m_folders.h" + +// Visual C++ extras +#ifdef _MSC_VER +#define vsnprintf _vsnprintf +#define snprintf _snprintf +#define GGINLINE +#else +#define GGINLINE inline +#endif + +// Plugin headers +#include "resource.h" + +// libgadu headers +extern "C" { +#include "libgadu/libgadu.h" +#include "dynstuff.h" +}; + +// Search +// Extended search result structure, used for all searches +struct GGSEARCHRESULT : public PROTOSEARCHRESULT +{ + uin_t uin; +}; + +typedef struct +{ + HANDLE hThread; + UINT dwThreadId; +} GGTHREAD; + +typedef struct +{ + uin_t *recipients; + int recipients_count; + char id[32]; + BOOL ignore; +} GGGC; + +typedef struct +{ + char id[256]; + char val[256]; +} GGTOKEN; + +#if 0 /* #ifdef DEBUGMODE */ +#define EnterCriticalSection(lpCS) {netlog(gg,"EnterCriticalSection @ %s:%d", __FILE__, __LINE__); EnterCriticalSection(lpCS);} +#define LeaveCriticalSection(lpCS) {netlog(gg,"LeaveCriticalSection @ %s:%d", __FILE__, __LINE__); LeaveCriticalSection(lpCS);} +#endif + + +// Wrappers of the old interface +#define GGDEF_PROTO "GG" // Default Proto +#define GGDEF_PROTONAME "Gadu-Gadu" // Default ProtoName + + +// Process handles / seqs +#define GG_SEQ_INFO 100 +#define GG_SEQ_SEARCH 200 +#define GG_SEQ_GETNICK 300 +#define GG_SEQ_CHINFO 400 + +// Services +#define GGS_IMPORT_SERVER "%s/ImportFromServer" +#define GGS_REMOVE_SERVER "%s/RemoveFromServer" +#define GGS_IMPORT_TEXT "%s/ImportFromText" +#define GGS_EXPORT_SERVER "%s/ExportFromServer" +#define GGS_EXPORT_TEXT "%s/ExportFromText" + +#define GGS_SENDIMAGE "%s/SendImage" +#define GGS_RECVIMAGE "%s/RecvImage" + +// Keys +#define GG_PLUGINVERSION "Version" // Plugin version.. user for cleanup from previous versions + +#define GG_KEY_UIN "UIN" // Uin - unique number +#define GG_KEY_PASSWORD "Password" // Password +#define GG_KEY_EMAIL "e-mail" // E-mail +#define GG_KEY_STATUS "Status" // Status +#define GG_KEY_NICK "Nick" // Nick +#define GG_KEY_STATUSDESCR "StatusMsg" // Users status description, to be compatible with MWClist + // should be stored in "CList" group +#define GG_KEY_TOKEN "Token" // OAuth Access Token +#define GG_KEY_TOKENSECRET "TokenSecret" // OAuth Access Token Secret + +#define GG_KEY_KEEPALIVE "KeepAlive" // Keep-alive support +#define GG_KEYDEF_KEEPALIVE 1 + +#define GG_KEY_SHOWCERRORS "ShowCErrors" // Show connection errors +#define GG_KEYDEF_SHOWCERRORS 1 + +#define GG_KEY_ARECONNECT "AReconnect" // Automatically reconnect +#define GG_KEYDEF_ARECONNECT 0 + +#define GG_KEY_LEAVESTATUSMSG "LeaveStatusMsg"// Leave status msg when disconnected +#define GG_KEYDEF_LEAVESTATUSMSG 0 +#define GG_KEY_LEAVESTATUS "LeaveStatus" +#define GG_KEYDEF_LEAVESTATUS 0 + +#define GG_KEY_FRIENDSONLY "FriendsOnly" // Friend only visibility +#define GG_KEYDEF_FRIENDSONLY 0 + +#define GG_KEY_SHOWLINKS "ShowLinks" // Show links from unknown contacts +#define GG_KEYDEF_SHOWLINKS 0 + +#define GG_KEY_ENABLEAVATARS "EnableAvatars" // Enable avatars support +#define GG_KEYDEF_ENABLEAVATARS 1 + +#define GG_KEY_AVATARHASH "AvatarHash" // Contact's avatar hash + +#define GG_KEY_AVATARURL "AvatarURL" // Contact's avatar URL + +#define GG_KEY_AVATARTYPE "AvatarType" // Contact's avatar format +#define GG_KEYDEF_AVATARTYPE PA_FORMAT_UNKNOWN + +#define GG_KEY_AVATARREQUESTED "AvatarRequested" // When contact's avatar is requested +#define GG_KEYDEF_AVATARREQUESTED 0 + +#define GG_KEY_SHOWINVISIBLE "ShowInvisible" // Show invisible users when described +#define GG_KEYDEF_SHOWINVISIBLE 0 + +#define GG_KEY_IGNORECONF "IgnoreConf" // Ignore incoming conference messages +#define GG_KEYDEF_IGNORECONF 0 + +#define GG_KEY_IMGRECEIVE "ReceiveImg" // Popup image window automatically +#define GG_KEYDEF_IMGRECEIVE 1 + +#define GG_KEY_IMGMETHOD "PopupImg" // Popup image window automatically +#define GG_KEYDEF_IMGMETHOD 1 + +#define GG_KEY_MSGACK "MessageAck" // Acknowledge when sending msg +#define GG_KEYDEF_MSGACK 1 + +#define GG_KEY_MANUALHOST "ManualHost" // Specify by hand server host/port +#define GG_KEYDEF_MANUALHOST 0 +#define GG_KEY_SSLCONN "SSLConnection" // Use SSL/TLS for connections +#define GG_KEYDEF_SSLCONN 0 +#define GG_KEY_SERVERHOSTS "ServerHosts" // NL separated list of hosts for server connection +#define GG_KEYDEF_SERVERHOSTS "91.197.13.54\r\n91.197.13.66\r\n91.197.13.69\r\n91.197.13.72\r\n91.197.13.75\r\n91.197.13.81" + +#define GG_KEY_CLIENTIP "IP" // Contact IP (by notify) +#define GG_KEY_CLIENTPORT "ClientPort" // Contact port +#define GG_KEY_CLIENTVERSION "ClientVersion" // Contact app version + +#define GG_KEY_DIRECTCONNS "DirectConns" // Use direct connections +#define GG_KEYDEF_DIRECTCONNS 1 +#define GG_KEY_DIRECTPORT "DirectPort" // Direct connections port +#define GG_KEYDEF_DIRECTPORT 1550 + +#define GG_KEY_FORWARDING "Forwarding" // Use forwarding +#define GG_KEYDEF_FORWARDING 0 +#define GG_KEY_FORWARDHOST "ForwardHost" // Forwarding host (firewall) +#define GG_KEY_FORWARDPORT "ForwardPort" // Forwarding port (firewall port) +#define GG_KEYDEF_FORWARDPORT 1550 // Forwarding port (firewall port) + +#define GG_KEY_GC_POLICY_UNKNOWN "GCPolicyUnknown" +#define GG_KEYDEF_GC_POLICY_UNKNOWN 1 + +#define GG_KEY_GC_COUNT_UNKNOWN "GCCountUnknown" +#define GG_KEYDEF_GC_COUNT_UNKNOWN 5 + +#define GG_KEY_GC_POLICY_TOTAL "GCPolicyTotal" +#define GG_KEYDEF_GC_POLICY_TOTAL 1 + +#define GG_KEY_GC_COUNT_TOTAL "GCCountTotal" +#define GG_KEYDEF_GC_COUNT_TOTAL 10 + +#define GG_KEY_GC_POLICY_DEFAULT "GCPolicyDefault" +#define GG_KEYDEF_GC_POLICY_DEFAULT 0 + +#define GG_KEY_BLOCK "Block" // Contact is blocked +#define GG_KEY_APPARENT "ApparentMode" // Visible list + +#define GG_KEY_TIMEDEVIATION "TimeDeviation" // Max time deviation for connections (seconds) +#define GG_KEYDEF_TIMEDEVIATION 300 + +#define GG_KEY_LOGONTIME "LogonTS" + +#define GG_KEY_RECONNINTERVAL "ReconnectInterval" +#define GG_KEYDEF_RECONNINTERVAL 3000 + +// chpassdlgproc() multipurpose dialog proc modes +#define GG_USERUTIL_PASS 0 +#define GG_USERUTIL_CREATE 1 +#define GG_USERUTIL_REMOVE 2 +#define GG_USERUTIL_EMAIL 3 + +// popup flags +#define GG_POPUP_ALLOW_MSGBOX 1 +#define GG_POPUP_ONCE 2 +#define GG_POPUP_ERROR 4 +#define GG_POPUP_WARNING 8 +#define GG_POPUP_MULTILOGON 16 + +#define LocalEventUnhook(hook) if (hook) UnhookEvent(hook) + +// Some MSVC compatibility with gcc +#ifdef _MSC_VER +#ifndef strcasecmp +#define strcasecmp _strcmpi +#endif +#ifndef strncasecmp +#define strncasecmp _strnicmp +#endif +#endif + +// Global variables +///////////////////////////////////////////////// + +extern HINSTANCE hInstance; +extern CLIST_INTERFACE *pcli; +extern list_t g_Instances; +extern PLUGININFOEX pluginInfo; + +// Screen saver +#ifndef SPI_GETSCREENSAVERRUNNING +#define SPI_GETSCREENSAVERRUNNING 114 +#endif + +///////////////////////////////////////////////// +// Methods + +/* Helper functions */ +const char *http_error_string(int h); +unsigned long crc_get(char *mem); +int gg_normalizestatus(int status); +char *gg_status2db(int status, const char *suffix); +TCHAR *ws_strerror(int code); +uint32_t swap32(uint32_t x); +const char *gg_version2string(int v); + +/* Avatar functions */ +char *gg_avatarhash(char *param); + +/* IcoLib functions */ +void gg_icolib_init(); +HICON LoadIconEx(const char* name, BOOL big); +HANDLE GetIconHandle(int iconId); +void ReleaseIconEx(const char* name, BOOL big); +void WindowSetIcon(HWND hWnd, const char* name); +void WindowFreeIcon(HWND hWnd); + +/* URI parser functions */ +void gg_links_instancemenu_init(); +void gg_links_init(); +void gg_links_destroy(); + +#define UIN2ID(uin,id) _itoa(uin,id,10) + +// Debug functions +const char *ggdebug_eventtype(gg_event *e); + +#include "gg_proto.h" + +#endif diff --git a/protocols/Gadu-Gadu/src/gg_proto.cpp b/protocols/Gadu-Gadu/src/gg_proto.cpp new file mode 100644 index 0000000000..0a0db3a173 --- /dev/null +++ b/protocols/Gadu-Gadu/src/gg_proto.cpp @@ -0,0 +1,804 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2009 Adam Strzelecki +// Copyright (c) 2009-2012 Bartosz Białek +// +// 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 "gg.h" + +GGPROTO::GGPROTO(const char* pszProtoName, const TCHAR* tszUserName) +{ + // Init mutexes + InitializeCriticalSection(&sess_mutex); + InitializeCriticalSection(&ft_mutex); + InitializeCriticalSection(&img_mutex); + InitializeCriticalSection(&modemsg_mutex); + InitializeCriticalSection(&avatar_mutex); + InitializeCriticalSection(&sessions_mutex); + + // Init instance names + m_szModuleName = mir_strdup(pszProtoName); + m_tszUserName = mir_tstrdup(tszUserName); + m_szProtoName = GGDEF_PROTONAME; + m_iVersion = 2; + + // Register netlib user + TCHAR name[128]; + mir_sntprintf(name, SIZEOF(name), TranslateT("%s connection"), m_tszUserName); + + NETLIBUSER nlu = { 0 }; + nlu.cbSize = sizeof(nlu); + nlu.flags = NUF_TCHAR | NUF_OUTGOING | NUF_INCOMING | NUF_HTTPCONNS; + nlu.szSettingsModule = m_szModuleName; + nlu.ptszDescriptiveName = name; + + netlib = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu); + + // Register services + createProtoService(PS_GETAVATARCAPS, &GGPROTO::getavatarcaps); + createProtoService(PS_GETAVATARINFOT, &GGPROTO::getavatarinfo); + createProtoService(PS_GETMYAVATAR, &GGPROTO::getmyavatar); + createProtoService(PS_SETMYAVATAR, &GGPROTO::setmyavatar); + + createProtoService(PS_GETMYAWAYMSG, &GGPROTO::getmyawaymsg); + createProtoService(PS_CREATEACCMGRUI, &GGPROTO::get_acc_mgr_gui); + + createProtoService(PS_LEAVECHAT, &GGPROTO::leavechat); + + // Offline contacts and clear logon time + setalloffline(); + db_set_dw(NULL, m_szModuleName, GG_KEY_LOGONTIME, 0); + + DWORD dwVersion; + if ((dwVersion = db_get_dw(NULL, m_szModuleName, GG_PLUGINVERSION, 0)) < pluginInfo.version) + cleanuplastplugin(dwVersion); + + links_instance_init(); + initavatarrequestthread(); + + TCHAR szPath[MAX_PATH]; + TCHAR *tmpPath = Utils_ReplaceVarsT( _T("%miranda_avatarcache%")); + mir_sntprintf(szPath, MAX_PATH, _T("%s\\%s"), tmpPath, m_szModuleName); + mir_free(tmpPath); + hAvatarsFolder = FoldersRegisterCustomPathT(m_szModuleName, "Avatars", szPath); + + tmpPath = Utils_ReplaceVarsT( _T("%miranda_userdata%")); + mir_sntprintf(szPath, MAX_PATH, _T("%s\\%s\\ImageCache"), tmpPath, m_szModuleName); + mir_free(tmpPath); + hImagesFolder = FoldersRegisterCustomPathT(m_szModuleName, "Images", szPath); +} + +GGPROTO::~GGPROTO() +{ +#ifdef DEBUGMODE + netlog("gg_proto_uninit(): destroying protocol interface"); +#endif + + // Destroy modules + block_uninit(); + img_destroy(); + keepalive_destroy(); + gc_destroy(); + + if (hMenuRoot) + CallService(MS_CLIST_REMOVEMAINMENUITEM, (WPARAM)hMenuRoot, 0); + + // Close handles + Netlib_CloseHandle(netlib); + + // Destroy mutexes + DeleteCriticalSection(&sess_mutex); + DeleteCriticalSection(&ft_mutex); + DeleteCriticalSection(&img_mutex); + DeleteCriticalSection(&modemsg_mutex); + DeleteCriticalSection(&avatar_mutex); + DeleteCriticalSection(&sessions_mutex); + + // Free status messages + if (modemsg.online) mir_free(modemsg.online); + if (modemsg.away) mir_free(modemsg.away); + if (modemsg.dnd) mir_free(modemsg.dnd); + if (modemsg.freechat) mir_free(modemsg.freechat); + if (modemsg.invisible) mir_free(modemsg.invisible); + if (modemsg.offline) mir_free(modemsg.offline); + + mir_free(m_szModuleName); + mir_free(m_tszUserName); +} + +////////////////////////////////////////////////////////// +// Dummies for function that have to be implemented + +HANDLE GGPROTO::AddToListByEvent(int flags, int iContact, HANDLE hDbEvent) { return NULL; } +int GGPROTO::Authorize(HANDLE hDbEvent) { return 1; } +int GGPROTO::AuthDeny(HANDLE hDbEvent, const TCHAR *szReason) { return 1; } +int GGPROTO::AuthRecv(HANDLE hContact, PROTORECVEVENT *pre) { return 1; } +int GGPROTO::AuthRequest(HANDLE hContact, const TCHAR *szMessage) { return 1; } +HANDLE GGPROTO::ChangeInfo(int iInfoType, void *pInfoData) { return NULL; } +int GGPROTO::FileResume(HANDLE hTransfer, int *action, const PROTOCHAR** szFilename) { return 1; } +HANDLE GGPROTO::SearchByEmail(const PROTOCHAR *email) { return NULL; } +int GGPROTO::RecvContacts(HANDLE hContact, PROTORECVEVENT *pre) { return 1; } +int GGPROTO::RecvUrl(HANDLE hContact, PROTORECVEVENT *pre) { return 1; } +int GGPROTO::SendContacts(HANDLE hContact, int flags, int nContacts, HANDLE *hContactsList) { return 1; } +int GGPROTO::SendUrl(HANDLE hContact, int flags, const char *url) { return 1; } +int GGPROTO::RecvAwayMsg(HANDLE hContact, int mode, PROTORECVEVENT *evt) { return 1; } +int GGPROTO::SendAwayMsg(HANDLE hContact, HANDLE hProcess, const char *msg) { return 1; } + +////////////////////////////////////////////////////////// +// when contact is added to list + +HANDLE GGPROTO::AddToList(int flags, PROTOSEARCHRESULT *psr) +{ + GGSEARCHRESULT *sr = (GGSEARCHRESULT *)psr; + uin_t uin; + + if (psr->cbSize == sizeof(GGSEARCHRESULT)) + uin = sr->uin; + else + uin = _ttoi(psr->id); + + return getcontact(uin, 1, flags & PALF_TEMPORARY ? 0 : 1, sr->nick); +} + +////////////////////////////////////////////////////////// +// checks proto capabilities + +DWORD_PTR GGPROTO::GetCaps(int type, HANDLE hContact) +{ + switch (type) { + case PFLAGNUM_1: + return PF1_IM | PF1_BASICSEARCH | PF1_EXTSEARCH | PF1_EXTSEARCHUI | PF1_SEARCHBYNAME | + PF1_MODEMSG | PF1_NUMERICUSERID | PF1_VISLIST | PF1_FILE; + case PFLAGNUM_2: + return PF2_ONLINE | PF2_SHORTAWAY | PF2_HEAVYDND | PF2_FREECHAT | PF2_INVISIBLE | + PF2_LONGAWAY; + case PFLAGNUM_3: + return PF2_ONLINE | PF2_SHORTAWAY | PF2_HEAVYDND | PF2_FREECHAT | PF2_INVISIBLE; + case PFLAGNUM_4: + return PF4_NOCUSTOMAUTH | PF4_SUPPORTTYPING | PF4_AVATARS | PF4_IMSENDOFFLINE; + case PFLAGNUM_5: + return PF2_LONGAWAY; + case PFLAG_UNIQUEIDTEXT: + return (DWORD_PTR) Translate("Gadu-Gadu Number"); + case PFLAG_UNIQUEIDSETTING: + return (DWORD_PTR) GG_KEY_UIN; + } + return 0; +} + +////////////////////////////////////////////////////////// +// loads protocol icon + +HICON GGPROTO::GetIcon(int iconIndex) +{ + if (LOWORD(iconIndex) == PLI_PROTOCOL) + { + if (iconIndex & PLIF_ICOLIBHANDLE) + return (HICON)GetIconHandle(IDI_GG); + + BOOL big = (iconIndex & PLIF_SMALL) == 0; + HICON hIcon = LoadIconEx("main", big); + + if (iconIndex & PLIF_ICOLIB) + return hIcon; + + hIcon = CopyIcon(hIcon); + ReleaseIconEx("main", big); + return hIcon; + } + + return (HICON)NULL; +} + +////////////////////////////////////////////////////////// +// user info request + +void __cdecl GGPROTO::cmdgetinfothread(void *hContact) +{ + SleepEx(100, FALSE); + netlog("gg_cmdgetinfothread(): Failed info retreival."); + ProtoBroadcastAck(m_szModuleName, hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE) 1, 0); +} + +int GGPROTO::GetInfo(HANDLE hContact, int infoType) +{ + gg_pubdir50_t req; + + // Custom contact info + if (hContact) + { + if (!(req = gg_pubdir50_new(GG_PUBDIR50_SEARCH))) + { + forkthread(&GGPROTO::cmdgetinfothread, hContact); + return 1; + } + + // Add uin and search it + gg_pubdir50_add(req, GG_PUBDIR50_UIN, ditoa((uin_t)db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0))); + gg_pubdir50_seq_set(req, GG_SEQ_INFO); + + netlog("gg_getinfo(): Requesting user info.", req->seq); + if (isonline()) + { + EnterCriticalSection(&sess_mutex); + if (!gg_pubdir50(sess, req)) + { + LeaveCriticalSection(&sess_mutex); + forkthread(&GGPROTO::cmdgetinfothread, hContact); + return 1; + } + LeaveCriticalSection(&sess_mutex); + } + } + // Own contact info + else + { + if (!(req = gg_pubdir50_new(GG_PUBDIR50_READ))) + { + forkthread(&GGPROTO::cmdgetinfothread, hContact); + return 1; + } + + // Add seq + gg_pubdir50_seq_set(req, GG_SEQ_CHINFO); + + netlog("gg_getinfo(): Requesting owner info.", req->seq); + if (isonline()) + { + EnterCriticalSection(&sess_mutex); + if (!gg_pubdir50(sess, req)) + { + LeaveCriticalSection(&sess_mutex); + forkthread(&GGPROTO::cmdgetinfothread, hContact); + return 1; + } + LeaveCriticalSection(&sess_mutex); + } + } + netlog("gg_getinfo(): Seq %d.", req->seq); + gg_pubdir50_free(req); + + return 1; +} + +////////////////////////////////////////////////////////// +// when basic search + +void __cdecl GGPROTO::searchthread(void *) +{ + SleepEx(100, FALSE); + netlog("gg_searchthread(): Failed search."); + ProtoBroadcastAck(m_szModuleName, NULL, ACKTYPE_SEARCH, ACKRESULT_FAILED, (HANDLE)1, 0); +} + +HANDLE GGPROTO::SearchBasic(const PROTOCHAR *id) +{ + if (!isonline()) + return (HANDLE)0; + + gg_pubdir50_t req; + if (!(req = gg_pubdir50_new(GG_PUBDIR50_SEARCH))) { + forkthread(&GGPROTO::searchthread, NULL); + return (HANDLE)1; + } + + TCHAR *ida = mir_tstrdup(id); + + // Add uin and search it + gg_pubdir50_add(req, GG_PUBDIR50_UIN, _T2A(ida)); + gg_pubdir50_seq_set(req, GG_SEQ_SEARCH); + + mir_free(ida); + + EnterCriticalSection(&sess_mutex); + if (!gg_pubdir50(sess, req)) + { + LeaveCriticalSection(&sess_mutex); + forkthread(&GGPROTO::searchthread, NULL); + return (HANDLE)1; + } + LeaveCriticalSection(&sess_mutex); + netlog("gg_basicsearch(): Seq %d.", req->seq); + gg_pubdir50_free(req); + + return (HANDLE)1; +} + +////////////////////////////////////////////////////////// +// search by details + +HANDLE GGPROTO::SearchByName(const PROTOCHAR *nick, const PROTOCHAR *firstName, const PROTOCHAR *lastName) +{ + gg_pubdir50_t req; + unsigned long crc; + char data[512] = "\0"; + + // Check if connected and if there's a search data + if (!isonline()) + return 0; + + if (!nick && !firstName && !lastName) + return 0; + + if (!(req = gg_pubdir50_new(GG_PUBDIR50_SEARCH))) + { + forkthread(&GGPROTO::searchthread, NULL); + return (HANDLE)1; + } + + // Add uin and search it + if (nick) + { + char *nickA = mir_t2a(nick); + gg_pubdir50_add(req, GG_PUBDIR50_NICKNAME, nickA); + strncat(data, nickA, sizeof(data) - strlen(data)); + mir_free(nickA); + } + strncat(data, ".", sizeof(data) - strlen(data)); + + if (firstName) + { + char *firstNameA = mir_t2a(firstName); + gg_pubdir50_add(req, GG_PUBDIR50_FIRSTNAME, firstNameA); + strncat(data, firstNameA, sizeof(data) - strlen(data)); + mir_free(firstNameA); + } + strncat(data, ".", sizeof(data) - strlen(data)); + + if (lastName) + { + char *lastNameA = mir_t2a(lastName); + gg_pubdir50_add(req, GG_PUBDIR50_LASTNAME, lastNameA); + strncat(data, lastNameA, sizeof(data) - strlen(data)); + mir_free(lastNameA); + } + strncat(data, ".", sizeof(data) - strlen(data)); + + // Count crc & check if the data was equal if yes do same search with shift + crc = crc_get(data); + + if (crc == last_crc && next_uin) + gg_pubdir50_add(req, GG_PUBDIR50_START, ditoa(next_uin)); + else + last_crc = crc; + + gg_pubdir50_seq_set(req, GG_SEQ_SEARCH); + + EnterCriticalSection(&sess_mutex); + if (!gg_pubdir50(sess, req)) + { + LeaveCriticalSection(&sess_mutex); + forkthread(&GGPROTO::searchthread, NULL); + return (HANDLE)1; + } + LeaveCriticalSection(&sess_mutex); + netlog("gg_searchbyname(): Seq %d.", req->seq); + gg_pubdir50_free(req); + + return (HANDLE)1; +} + +////////////////////////////////////////////////////////// +// search by advanced + +HWND GGPROTO::SearchAdvanced(HWND hwndDlg) +{ + gg_pubdir50_t req; + char text[64], data[512] = "\0"; + unsigned long crc; + + // Check if connected + if (!isonline()) return (HWND)0; + + if (!(req = gg_pubdir50_new(GG_PUBDIR50_SEARCH))) + { + forkthread(&GGPROTO::searchthread, NULL); + return (HWND)1; + } + + // Fetch search data + GetDlgItemTextA(hwndDlg, IDC_FIRSTNAME, text, sizeof(text)); + if (strlen(text)) + { + gg_pubdir50_add(req, GG_PUBDIR50_FIRSTNAME, text); + strncat(data, text, sizeof(data) - strlen(data)); + } + /* 1 */ strncat(data, ".", sizeof(data) - strlen(data)); + + GetDlgItemTextA(hwndDlg, IDC_LASTNAME, text, sizeof(text)); + if (strlen(text)) + { + gg_pubdir50_add(req, GG_PUBDIR50_LASTNAME, text); + strncat(data, text, sizeof(data) - strlen(data)); + } + /* 2 */ strncat(data, ".", sizeof(data) - strlen(data)); + + GetDlgItemTextA(hwndDlg, IDC_NICKNAME, text, sizeof(text)); + if (strlen(text)) + { + gg_pubdir50_add(req, GG_PUBDIR50_NICKNAME, text); + strncat(data, text, sizeof(data) - strlen(data)); + } + /* 3 */ strncat(data, ".", sizeof(data) - strlen(data)); + + GetDlgItemTextA(hwndDlg, IDC_CITY, text, sizeof(text)); + if (strlen(text)) + { + gg_pubdir50_add(req, GG_PUBDIR50_CITY, text); + strncat(data, text, sizeof(data) - strlen(data)); + } + /* 4 */ strncat(data, ".", sizeof(data) - strlen(data)); + + GetDlgItemTextA(hwndDlg, IDC_AGEFROM, text, sizeof(text)); + if (strlen(text)) + { + int yearTo = atoi(text); + int yearFrom; + time_t t = time(NULL); + struct tm *lt = localtime(&t); + int ay = lt->tm_year + 1900; + char age[16]; + + GetDlgItemTextA(hwndDlg, IDC_AGETO, age, sizeof(age)); + yearFrom = atoi(age); + + // Count & fix ranges + if (!yearTo) + yearTo = ay; + else + yearTo = ay - yearTo; + if (!yearFrom) + yearFrom = 0; + else + yearFrom = ay - yearFrom; + mir_snprintf(text, sizeof(text), "%d %d", yearFrom, yearTo); + + gg_pubdir50_add(req, GG_PUBDIR50_BIRTHYEAR, text); + strncat(data, text, sizeof(data) - strlen(data)); + } + /* 5 */ strncat(data, ".", sizeof(data) - strlen(data)); + + switch(SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_GETCURSEL, 0, 0)) + { + case 1: + gg_pubdir50_add(req, GG_PUBDIR50_GENDER, GG_PUBDIR50_GENDER_FEMALE); + strncat(data, GG_PUBDIR50_GENDER_MALE, sizeof(data) - strlen(data)); + break; + case 2: + gg_pubdir50_add(req, GG_PUBDIR50_GENDER, GG_PUBDIR50_GENDER_MALE); + strncat(data, GG_PUBDIR50_GENDER_FEMALE, sizeof(data) - strlen(data)); + break; + } + /* 6 */ strncat(data, ".", sizeof(data) - strlen(data)); + + if (IsDlgButtonChecked(hwndDlg, IDC_ONLYCONNECTED)) + { + gg_pubdir50_add(req, GG_PUBDIR50_ACTIVE, GG_PUBDIR50_ACTIVE_TRUE); + strncat(data, GG_PUBDIR50_ACTIVE_TRUE, sizeof(data) - strlen(data)); + } + /* 7 */ strncat(data, ".", sizeof(data) - strlen(data)); + + // No data entered + if (strlen(data) <= 7 || (strlen(data) == 8 && IsDlgButtonChecked(hwndDlg, IDC_ONLYCONNECTED))) return (HWND)0; + + // Count crc & check if the data was equal if yes do same search with shift + crc = crc_get(data); + + if (crc == last_crc && next_uin) + gg_pubdir50_add(req, GG_PUBDIR50_START, ditoa(next_uin)); + else + last_crc = crc; + + gg_pubdir50_seq_set(req, GG_SEQ_SEARCH); + + if (isonline()) + { + EnterCriticalSection(&sess_mutex); + if (!gg_pubdir50(sess, req)) + { + LeaveCriticalSection(&sess_mutex); + forkthread(&GGPROTO::searchthread, NULL); + return (HWND)1; + } + LeaveCriticalSection(&sess_mutex); + } + netlog("gg_searchbyadvanced(): Seq %d.", req->seq); + gg_pubdir50_free(req); + + return (HWND)1; +} + +////////////////////////////////////////////////////////// +// create adv search dialog + +static INT_PTR CALLBACK gg_advancedsearchdlgproc(HWND hwndDlg,UINT message,WPARAM wParam,LPARAM lParam) +{ + switch(message) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_ADDSTRING, 0, (LPARAM)_T("")); // 0 + SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_ADDSTRING, 0, (LPARAM)TranslateT("Female")); // 1 + SendDlgItemMessage(hwndDlg, IDC_GENDER, CB_ADDSTRING, 0, (LPARAM)TranslateT("Male")); // 2 + return TRUE; + + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDOK: + SendMessage(GetParent(hwndDlg), WM_COMMAND,MAKEWPARAM(IDOK,BN_CLICKED), (LPARAM)GetDlgItem(GetParent(hwndDlg),IDOK)); + break; + } + break; + } + return FALSE; +} + +HWND GGPROTO::CreateExtendedSearchUI(HWND owner) +{ + return CreateDialogParam(hInstance, + MAKEINTRESOURCE(IDD_GGADVANCEDSEARCH), owner, gg_advancedsearchdlgproc, (LPARAM)this); +} + +////////////////////////////////////////////////////////// +// when messsage received + +int GGPROTO::RecvMsg(HANDLE hContact, PROTORECVEVENT *pre) +{ + return Proto_RecvMessage(hContact, pre); +} + +////////////////////////////////////////////////////////// +// when messsage sent + +typedef struct +{ + HANDLE hContact; + int seq; +} GG_SEQ_ACK; + +void __cdecl GGPROTO::sendackthread(void *ack) +{ + SleepEx(100, FALSE); + ProtoBroadcastAck(m_szModuleName, ((GG_SEQ_ACK *)ack)->hContact, + ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE) ((GG_SEQ_ACK *)ack)->seq, 0); + mir_free(ack); +} + +int GGPROTO::SendMsg(HANDLE hContact, int flags, const char *msg) +{ + uin_t uin; + + if (msg && isonline() && (uin = (uin_t)db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0))) + { + int seq; + EnterCriticalSection(&sess_mutex); + seq = gg_send_message(sess, GG_CLASS_CHAT, uin, (BYTE*)msg); + LeaveCriticalSection(&sess_mutex); + if (!db_get_b(NULL, m_szModuleName, GG_KEY_MSGACK, GG_KEYDEF_MSGACK)) + { + // Auto-ack message without waiting for server ack + GG_SEQ_ACK *ack = (GG_SEQ_ACK*)mir_alloc(sizeof(GG_SEQ_ACK)); + if (ack) + { + ack->seq = seq; + ack->hContact = hContact; + forkthread(&GGPROTO::sendackthread, ack); + } + } + return seq; + } + return 0; +} + +////////////////////////////////////////////////////////// +// visible lists + +int GGPROTO::SetApparentMode(HANDLE hContact, int mode) +{ + db_set_w(hContact, m_szModuleName, GG_KEY_APPARENT, (WORD)mode); + notifyuser(hContact, 1); + return 0; +} + +////////////////////////////////////////////////////////// +// sets protocol status + +int GGPROTO::SetStatus(int iNewStatus) +{ + int nNewStatus = gg_normalizestatus(iNewStatus); + + EnterCriticalSection(&modemsg_mutex); + m_iDesiredStatus = nNewStatus; + LeaveCriticalSection(&modemsg_mutex); + + // If waiting for connection retry attempt then signal to stop that + if (hConnStopEvent) SetEvent(hConnStopEvent); + + if (m_iStatus == nNewStatus) return 0; + netlog("gg_setstatus(): PS_SETSTATUS(%d) normalized to %d.", iNewStatus, nNewStatus); + refreshstatus(nNewStatus); + + return 0; +} + +////////////////////////////////////////////////////////// +// when away message is requested + +void __cdecl GGPROTO::getawaymsgthread(void *hContact) +{ + DBVARIANT dbv; + + SleepEx(100, FALSE); + if (!db_get_s(hContact, "CList", GG_KEY_STATUSDESCR, &dbv, DBVT_TCHAR)) + { + ProtoBroadcastAck(m_szProtoName, hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE) 1, (LPARAM) dbv.ptszVal); + netlog("gg_getawaymsg(): Reading away msg <" TCHAR_STR_PARAM ">.", dbv.ptszVal); + DBFreeVariant(&dbv); + } + else + ProtoBroadcastAck(m_szProtoName, hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE) 1, (LPARAM) NULL); +} + +HANDLE GGPROTO::GetAwayMsg(HANDLE hContact) +{ + forkthread(&GGPROTO::getawaymsgthread, hContact); + return (HANDLE)1; +} + +////////////////////////////////////////////////////////// +// when away message is being set + +int GGPROTO::SetAwayMsg(int iStatus, const PROTOCHAR *msgt) +{ + int status = gg_normalizestatus(iStatus); + char **szMsg; + char *msg = mir_t2a(msgt); + + netlog("gg_setawaymsg(): PS_SETAWAYMSG(%d, \"%s\").", iStatus, msg); + + EnterCriticalSection(&modemsg_mutex); + // Select proper msg + switch(status) + { + case ID_STATUS_ONLINE: + szMsg = &modemsg.online; + break; + case ID_STATUS_AWAY: + szMsg = &modemsg.away; + break; + case ID_STATUS_DND: + szMsg = &modemsg.dnd; + break; + case ID_STATUS_FREECHAT: + szMsg = &modemsg.freechat; + break; + case ID_STATUS_INVISIBLE: + szMsg = &modemsg.invisible; + break; + default: + LeaveCriticalSection(&modemsg_mutex); + mir_free(msg); + return 1; + } + + // Check if we change status here somehow + if (*szMsg && msg && !strcmp(*szMsg, msg) + || !*szMsg && (!msg || !*msg)) + { + if (status == m_iDesiredStatus && m_iDesiredStatus == m_iStatus) + { + netlog("gg_setawaymsg(): Message hasn't been changed, return."); + LeaveCriticalSection(&modemsg_mutex); + mir_free(msg); + return 0; + } + } + else + { + if (*szMsg) + mir_free(*szMsg); + *szMsg = msg && *msg ? mir_strdup(msg) : NULL; +#ifdef DEBUGMODE + netlog("gg_setawaymsg(): Message changed."); +#endif + } + LeaveCriticalSection(&modemsg_mutex); + + // Change the status if it was desired by PS_SETSTATUS + if (status == m_iDesiredStatus) + refreshstatus(status); + + mir_free(msg); + return 0; +} + +////////////////////////////////////////////////////////// +// sends a notification that the user is typing a message + +int GGPROTO::UserIsTyping(HANDLE hContact, int type) +{ + uin_t uin = db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0); + + if (!uin || !isonline()) return 0; + + if (type == PROTOTYPE_SELFTYPING_ON || type == PROTOTYPE_SELFTYPING_OFF) { + EnterCriticalSection(&sess_mutex); + gg_typing_notification(sess, uin, (type == PROTOTYPE_SELFTYPING_ON)); + LeaveCriticalSection(&sess_mutex); + } + + return 0; +} + +////////////////////////////////////////////////////////// +// Custom protocol event + +int GGPROTO::OnEvent(PROTOEVENTTYPE eventType, WPARAM wParam, LPARAM lParam) +{ + switch( eventType ) { + case EV_PROTO_ONLOAD: + { + hookProtoEvent(ME_OPT_INITIALISE, &GGPROTO::options_init); + hookProtoEvent(ME_USERINFO_INITIALISE, &GGPROTO::details_init); + + // Init misc stuff + gg_icolib_init(); + initpopups(); + gc_init(); + keepalive_init(); + img_init(); + block_init(); + + // Try to fetch user avatar + getUserAvatar(); + break; + } + case EV_PROTO_ONEXIT: + // Stop avatar request thread + uninitavatarrequestthread(); + + // Stop main connection session thread + threadwait(&pth_sess); + img_shutdown(); + sessions_closedlg(); + break; + + case EV_PROTO_ONOPTIONS: + return options_init(wParam, lParam); + + case EV_PROTO_ONMENU: + menus_init(); + break; + + case EV_PROTO_ONRENAME: + if (hMenuRoot) { + CLISTMENUITEM mi = {0}; + mi.cbSize = sizeof(mi); + mi.flags = CMIM_NAME | CMIF_TCHAR | CMIF_KEEPUNTRANSLATED; + mi.ptszName = m_tszUserName; + CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hMenuRoot, (LPARAM)&mi); + } + break; + + case EV_PROTO_ONCONTACTDELETED: + return contactdeleted(wParam, lParam); + + case EV_PROTO_DBSETTINGSCHANGED: + return dbsettingchanged(wParam, lParam); + } + return TRUE; +} diff --git a/protocols/Gadu-Gadu/src/gg_proto.h b/protocols/Gadu-Gadu/src/gg_proto.h new file mode 100644 index 0000000000..8765392328 --- /dev/null +++ b/protocols/Gadu-Gadu/src/gg_proto.h @@ -0,0 +1,295 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2009 Adam Strzelecki +// Copyright (c) 2009-2012 Bartosz Białek +// +// 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. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef GGPROTO_H +#define GGPROTO_H + +struct GGPROTO; +typedef void ( __cdecl GGPROTO::*GGThreadFunc )( void* ); +typedef int ( __cdecl GGPROTO::*GGEventFunc )( WPARAM, LPARAM ); +typedef INT_PTR ( __cdecl GGPROTO::*GGServiceFunc )( WPARAM, LPARAM ); + +struct GGPROTO : public PROTO_INTERFACE, public MZeroedObject +{ + GGPROTO( const char*, const TCHAR* ); + ~GGPROTO(); + + //==================================================================================== + // PROTO_INTERFACE + //==================================================================================== + + virtual HANDLE __cdecl AddToList( int flags, PROTOSEARCHRESULT* psr ); + virtual HANDLE __cdecl AddToListByEvent( int flags, int iContact, HANDLE hDbEvent ); + + virtual int __cdecl Authorize( HANDLE hDbEvent ); + virtual int __cdecl AuthDeny( HANDLE hDbEvent, const TCHAR* szReason ); + virtual int __cdecl AuthRecv( HANDLE hContact, PROTORECVEVENT* ); + virtual int __cdecl AuthRequest( HANDLE hContact, const TCHAR* szMessage ); + + virtual HANDLE __cdecl ChangeInfo( int iInfoType, void* pInfoData ); + + virtual HANDLE __cdecl FileAllow( HANDLE hContact, HANDLE hTransfer, const TCHAR* szPath ); + virtual int __cdecl FileCancel( HANDLE hContact, HANDLE hTransfer ); + virtual int __cdecl FileDeny( HANDLE hContact, HANDLE hTransfer, const TCHAR* szReason ); + virtual int __cdecl FileResume( HANDLE hTransfer, int* action, const TCHAR** szFilename ); + + virtual DWORD_PTR __cdecl GetCaps( int type, HANDLE hContact = NULL ); + virtual HICON __cdecl GetIcon( int iconIndex ); + virtual int __cdecl GetInfo( HANDLE hContact, int infoType ); + + virtual HANDLE __cdecl SearchBasic( const TCHAR* id ); + virtual HANDLE __cdecl SearchByEmail( const TCHAR* email ); + virtual HANDLE __cdecl SearchByName( const TCHAR* nick, const TCHAR* firstName, const TCHAR* lastName ); + virtual HWND __cdecl SearchAdvanced( HWND owner ); + virtual HWND __cdecl CreateExtendedSearchUI( HWND owner ); + + virtual int __cdecl RecvContacts( HANDLE hContact, PROTORECVEVENT* ); + virtual int __cdecl RecvFile( HANDLE hContact, PROTORECVFILET* ); + virtual int __cdecl RecvMsg( HANDLE hContact, PROTORECVEVENT* ); + virtual int __cdecl RecvUrl( HANDLE hContact, PROTORECVEVENT* ); + + virtual int __cdecl SendContacts( HANDLE hContact, int flags, int nContacts, HANDLE* hContactsList ); + virtual HANDLE __cdecl SendFile( HANDLE hContact, const TCHAR* szDescription, TCHAR** ppszFiles ); + virtual int __cdecl SendMsg( HANDLE hContact, int flags, const char* msg ); + virtual int __cdecl SendUrl( HANDLE hContact, int flags, const char* url ); + + virtual int __cdecl SetApparentMode( HANDLE hContact, int mode ); + virtual int __cdecl SetStatus( int iNewStatus ); + + virtual HANDLE __cdecl GetAwayMsg( HANDLE hContact ); + virtual int __cdecl RecvAwayMsg( HANDLE hContact, int mode, PROTORECVEVENT* evt ); + virtual int __cdecl SendAwayMsg( HANDLE hContact, HANDLE hProcess, const char* msg ); + virtual int __cdecl SetAwayMsg( int m_iStatus, const TCHAR* msg ); + + virtual int __cdecl UserIsTyping( HANDLE hContact, int type ); + + virtual int __cdecl OnEvent( PROTOEVENTTYPE eventType, WPARAM wParam, LPARAM lParam ); + + ////////////////////////////////////////////////////////////////////////////////////// + // Services + + INT_PTR __cdecl blockuser(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl getmyawaymsg(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl get_acc_mgr_gui(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl leavechat(WPARAM wParam, LPARAM lParam); + + void __cdecl sendackthread(void *); + void __cdecl searchthread(void *); + void __cdecl cmdgetinfothread(void *hContact); + void __cdecl getawaymsgthread(void *hContact); + void __cdecl dccmainthread(void *); + void __cdecl ftfailthread(void *param); + void __cdecl remindpasswordthread(void *param); + + ////////////////////////////////////////////////////////////////////////////////////// + + /* Helper functions */ + int status_m2gg(int status, int descr); + int status_gg2m(int status); + void checknewuser(uin_t uin, const char* passwd); + + /* Thread functions */ + void forkthread(GGThreadFunc pFunc, void *param); + HANDLE forkthreadex(GGThreadFunc pFunc, void *param, UINT *threadId); + void threadwait(GGTHREAD *thread); + + /* Global GG functions */ + void notifyuser(HANDLE hContact, int refresh); + void setalloffline(); + void disconnect(); + HANDLE getcontact(uin_t uin, int create, int inlist, TCHAR *nick); + void __cdecl mainthread(void *empty); + int isonline(); + int refreshstatus(int status); + + void broadcastnewstatus(int newStatus); + void cleanuplastplugin(DWORD version); + int contactdeleted(WPARAM wParam, LPARAM lParam); + int dbsettingchanged(WPARAM wParam, LPARAM lParam); + void notifyall(); + void changecontactstatus(uin_t uin, int status, const char *idescr, int time, uint32_t remote_ip, uint16_t remote_port, uint32_t version); + char *getstatusmsg(int status); + void dccstart(); + void dccconnect(uin_t uin); + int gettoken(GGTOKEN *token); + void parsecontacts(char *contacts); + void remindpassword(uin_t uin, const char *email); + void menus_init(); + + /* Avatar functions */ + void getAvatarFilename(HANDLE hContact, TCHAR *pszDest, int cbLen); + void getAvatar(HANDLE hContact, char *szAvatarURL); + void requestAvatar(HANDLE hContact, int iWaitFor); + void getUserAvatar(); + void setAvatar(const TCHAR *szFilename); + void getAvatarFileInfo(uin_t uin, char **avatarurl, int *type); + + INT_PTR __cdecl getavatarcaps(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl getavatarinfo(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl getmyavatar(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl setmyavatar(WPARAM wParam, LPARAM lParam); + + void initavatarrequestthread(); + void uninitavatarrequestthread(); + + void __cdecl avatarrequestthread(void*); + void __cdecl getuseravatarthread(void*); + void __cdecl setavatarthread(void*); + + /* File transfer functions */ + HANDLE fileallow(HANDLE hContact, HANDLE hTransfer, const PROTOCHAR* szPath); + int filecancel(HANDLE hContact, HANDLE hTransfer); + int filedeny(HANDLE hContact, HANDLE hTransfer, const PROTOCHAR* szReason); + int recvfile(HANDLE hContact, PROTOFILEEVENT* pre); + HANDLE sendfile(HANDLE hContact, const PROTOCHAR* szDescription, PROTOCHAR** ppszFiles); + + HANDLE dccfileallow(HANDLE hTransfer, const PROTOCHAR* szPath); + HANDLE dcc7fileallow(HANDLE hTransfer, const PROTOCHAR* szPath); + + int dccfiledeny(HANDLE hTransfer); + int dcc7filedeny(HANDLE hTransfer); + + int dccfilecancel(HANDLE hTransfer); + int dcc7filecancel(HANDLE hTransfer); + + /* Import module */ + void import_init(HGENMENU hRoot); + + INT_PTR __cdecl import_server(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl import_text(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl remove_server(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl export_server(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl export_text(WPARAM wParam, LPARAM lParam); + + /* Keep-alive module */ + void keepalive_init(); + void keepalive_destroy(); + + /* Image reception functions */ + int img_init(); + int img_destroy(); + int img_shutdown(); + int img_sendonrequest(gg_event* e); + BOOL img_opened(uin_t uin); + void *img_loadpicture(gg_event* e, TCHAR *szFileName); + int img_display(HANDLE hContact, void *img); + int img_displayasmsg(HANDLE hContact, void *img); + + void __cdecl img_dlgcallthread(void *param); + + INT_PTR __cdecl img_recvimage(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl img_sendimg(WPARAM wParam, LPARAM lParam); + + void links_instance_init(); + + /* OAuth functions */ + char *oauth_header(const char *httpmethod, const char *url); + int oauth_checktoken(int force); + int oauth_receivetoken(); + + /* UI page initializers */ + int __cdecl options_init(WPARAM wParam, LPARAM lParam); + int __cdecl details_init(WPARAM wParam, LPARAM lParam); + + /* Groupchat functions */ + int gc_init(); + void gc_menus_init(HGENMENU hRoot); + int gc_destroy(); + char * gc_getchat(uin_t sender, uin_t *recipients, int recipients_count); + GGGC *gc_lookup(char *id); + int gc_changenick(HANDLE hContact, char *pszNick); + #define UIN2ID(uin,id) _itoa(uin,id,10) + + int __cdecl gc_event(WPARAM wParam, LPARAM lParam); + + INT_PTR __cdecl gc_openconf(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl gc_clearignored(WPARAM wParam, LPARAM lParam); + + /* Popups functions */ + void initpopups(); + void showpopup(const TCHAR* nickname, const TCHAR* msg, int flags); + + /* Sessions functions */ + INT_PTR __cdecl sessions_view(WPARAM wParam, LPARAM lParam); + void sessions_updatedlg(); + BOOL sessions_closedlg(); + void sessions_menus_init(HGENMENU hRoot); + + /* Event helpers */ + void createObjService(const char* szService, GGServiceFunc serviceProc); + void createProtoService(const char* szService, GGServiceFunc serviceProc); + HANDLE hookProtoEvent(const char*, GGEventFunc); + void forkThread(GGThreadFunc, void* ); + HANDLE forkThreadEx(GGThreadFunc, void*, UINT* threadID = NULL); + + // Debug functions + int netlog(const char *fmt, ...); + + void block_init(); + void block_uninit(); + + ////////////////////////////////////////////////////////////////////////////////////// + + CRITICAL_SECTION ft_mutex, sess_mutex, img_mutex, modemsg_mutex, avatar_mutex, sessions_mutex; + list_t watches, transfers, requests, chats, imagedlgs, avatar_requests, avatar_transfers, sessions; + int gc_enabled, gc_id, is_list_remove, check_first_conn; + uin_t next_uin; + unsigned long last_crc; + GGTHREAD pth_dcc; + GGTHREAD pth_sess; + GGTHREAD pth_avatar; + struct gg_session *sess; + struct gg_dcc *dcc; + HANDLE hEvent; + HANDLE hConnStopEvent; + SOCKET sock; + UINT_PTR timer; + struct + { + char *online; + char *away; + char *dnd; + char *freechat; + char *invisible; + char *offline; + } modemsg; + HANDLE netlib; + HGENMENU hMenuRoot; + HGENMENU hMainMenu[7]; + HANDLE hPrebuildMenuHook; + HANDLE hBlockMenuItem; + HANDLE hImageMenuItem; + HANDLE hInstanceMenuItem; + HANDLE hAvatarsFolder; + HANDLE hImagesFolder; + HWND hwndSessionsDlg; +}; + +typedef struct +{ + int mode; + uin_t uin; + char *pass; + char *email; + GGPROTO *gg; +} GGUSERUTILDLGDATA; + +#endif diff --git a/protocols/Gadu-Gadu/src/groupchat.cpp b/protocols/Gadu-Gadu/src/groupchat.cpp new file mode 100644 index 0000000000..4089c8fac3 --- /dev/null +++ b/protocols/Gadu-Gadu/src/groupchat.cpp @@ -0,0 +1,669 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2006 Adam Strzelecki +// +// 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 "gg.h" +#include "m_metacontacts.h" + +#define GG_GC_GETCHAT "%s/GCGetChat" +#define GGS_OPEN_CONF "%s/OpenConf" +#define GGS_CLEAR_IGNORED "%s/ClearIgnored" + +//////////////////////////////////////////////////////////////////////////////// +// Inits Gadu-Gadu groupchat module using chat.dll + +int GGPROTO::gc_init() +{ + if (ServiceExists(MS_GC_REGISTER)) + { + char service[64]; + GCREGISTER gcr = {0}; + + // Register Gadu-Gadu proto + gcr.cbSize = sizeof(GCREGISTER); + gcr.dwFlags = GC_TCHAR; + gcr.iMaxText = 0; + gcr.nColors = 0; + gcr.pColors = 0; + gcr.ptszModuleDispName = m_tszUserName; + gcr.pszModule = m_szModuleName; + CallServiceSync(MS_GC_REGISTER, 0, (LPARAM)&gcr); + hookProtoEvent(ME_GC_EVENT, &GGPROTO::gc_event); + gc_enabled = TRUE; + // create & hook event + mir_snprintf(service, 64, GG_GC_GETCHAT, m_szModuleName); + netlog("gg_gc_init(): Registered with groupchat plugin."); + } + else + netlog("gg_gc_init(): Cannot register with groupchat plugin !!!"); + + return 1; +} + +//////////////////////////////////////////////////////////////////////////////// +// Groupchat menus initialization + +void GGPROTO::gc_menus_init(HGENMENU hRoot) +{ + if (gc_enabled) + { + CLISTMENUITEM mi = {0}; + char service[64]; + + mi.cbSize = sizeof(mi); + mi.flags = CMIF_ICONFROMICOLIB | CMIF_ROOTHANDLE; + mi.hParentMenu = hRoot; + + // Conferencing + mir_snprintf(service, sizeof(service), GGS_OPEN_CONF, m_szModuleName); + createObjService(service, &GGPROTO::gc_openconf); + mi.position = 2000050001; + mi.icolibItem = GetIconHandle(IDI_CONFERENCE); + mi.pszName = LPGEN("Open &conference..."); + mi.pszService = service; + hMainMenu[0] = Menu_AddProtoMenuItem(&mi); + + // Clear ignored conferences + mir_snprintf(service, sizeof(service), GGS_CLEAR_IGNORED, m_szModuleName); + createObjService(service, &GGPROTO::gc_clearignored); + mi.position = 2000050002; + mi.icolibItem = GetIconHandle(IDI_CLEAR_CONFERENCE); + mi.pszName = LPGEN("&Clear ignored conferences"); + mi.pszService = service; + hMainMenu[1] = Menu_AddProtoMenuItem(&mi); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Releases Gadu-Gadu groupchat module using chat.dll + +int GGPROTO::gc_destroy() +{ + list_t l; + for(l = chats; l; l = l->next) + { + GGGC *chat = (GGGC *)l->data; + if (chat->recipients) free(chat->recipients); + } + list_destroy(chats, 1); chats = NULL; + return 1; +} + +GGGC* GGPROTO::gc_lookup(char *id) +{ + GGGC *chat; + list_t l; + + for(l = chats; l; l = l->next) + { + chat = (GGGC *)l->data; + if (chat && !strcmp(chat->id, id)) + return chat; + } + + return NULL; +} + +int GGPROTO::gc_event(WPARAM wParam, LPARAM lParam) +{ + GCHOOK *gch = (GCHOOK *)lParam; + GGGC *chat = NULL; + uin_t uin; + + // Check if we got our protocol, and fields are set + if (!gch + || !gch->pDest + || !gch->pDest->pszID + || !gch->pDest->pszModule + || lstrcmpiA(gch->pDest->pszModule, m_szModuleName) + || !(uin = db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0)) + || !(chat = gc_lookup(gch->pDest->pszID))) + return 0; + + // Window terminated + if (gch->pDest->iType == SESSION_TERMINATE) + { + HANDLE hContact = NULL; + netlog("gg_gc_event(): Terminating chat %x, id %s from chat window...", chat, gch->pDest->pszID); + // Destroy chat entry + free(chat->recipients); + list_remove(&chats, chat, 1); + // Remove contact from contact list (duh!) should be done by chat.dll !! + hContact = db_find_first(); + while (hContact) + { + DBVARIANT dbv; + if (!db_get_s(hContact, m_szModuleName, "ChatRoomID", &dbv, DBVT_ASCIIZ)) + { + if (dbv.pszVal && !strcmp(gch->pDest->pszID, dbv.pszVal)) + CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0); + DBFreeVariant(&dbv); + } + hContact = db_find_next(hContact); + } + return 1; + } + + // Message typed / send only if online + if (isonline() && (gch->pDest->iType == GC_USER_MESSAGE) && gch->pszText) + { + char id[32]; + DBVARIANT dbv; + GCDEST gcdest = {m_szModuleName, gch->pDest->pszID, GC_EVENT_MESSAGE}; + GCEVENT gcevent = {sizeof(GCEVENT), &gcdest}; + int lc; + + UIN2ID(uin, id); + + gcevent.pszUID = id; + gcevent.pszText = gch->pszText; + if (!db_get_s(NULL, m_szModuleName, GG_KEY_NICK, &dbv, DBVT_ASCIIZ)) + gcevent.pszNick = dbv.pszVal; + else + gcevent.pszNick = Translate("Me"); + + // Get rid of CRLF at back + lc = (int)strlen(gch->pszText) - 1; + while(lc >= 0 && (gch->pszText[lc] == '\n' || gch->pszText[lc] == '\r')) gch->pszText[lc --] = 0; + gcevent.time = time(NULL); + gcevent.bIsMe = 1; + gcevent.dwFlags = GCEF_ADDTOLOG; + netlog("gg_gc_event(): Sending conference message to room %s, \"%s\".", gch->pDest->pszID, gch->pszText); + CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gcevent); + if (gcevent.pszNick == dbv.pszVal) DBFreeVariant(&dbv); + EnterCriticalSection(&sess_mutex); + gg_send_message_confer(sess, GG_CLASS_CHAT, chat->recipients_count, chat->recipients, (BYTE*)gch->pszText); + LeaveCriticalSection(&sess_mutex); + return 1; + } + + // Privmessage selected + if (gch->pDest->iType == GC_USER_PRIVMESS) + { + HANDLE hContact = NULL; + if ((uin = atoi(gch->pszUID)) && (hContact = getcontact(uin, 1, 0, NULL))) + CallService(MS_MSG_SENDMESSAGE, (WPARAM)hContact, (LPARAM)0); + } + netlog("gg_gc_event(): Unhandled event %d, chat %x, uin %d, text \"%s\".", gch->pDest->iType, chat, uin, gch->pszText); + + return 0; +} + +typedef struct _gg_gc_echat +{ + uin_t sender; + uin_t *recipients; + int recipients_count; + char * chat_id; +} gg_gc_echat; + +//////////////////////////////////////////////////////////////////////////////// +// This is main groupchat initialization routine + +char* GGPROTO::gc_getchat(uin_t sender, uin_t *recipients, int recipients_count) +{ + list_t l; int i; + GGGC *chat; + char id[32]; + uin_t uin; DBVARIANT dbv; + GCDEST gcdest = {m_szModuleName, 0, GC_EVENT_ADDGROUP}; + GCEVENT gcevent = {sizeof(GCEVENT), &gcdest}; + + netlog("gg_gc_getchat(): Count %d.", recipients_count); + if (!recipients) return NULL; + + // Look for existing chat + for(l = chats; l; l = l->next) + { + GGGC *chat = (GGGC *)l->data; + if (!chat) continue; + + if (chat->recipients_count == recipients_count + (sender ? 1 : 0)) + { + int i, j, found = 0, sok = (sender == 0); + if (!sok) for(i = 0; i < chat->recipients_count; i++) + if (sender == chat->recipients[i]) + { + sok = 1; + break; + } + if (sok) + for(i = 0; i < chat->recipients_count; i++) + for(j = 0; j < recipients_count; j++) + if (recipients[j] == chat->recipients[i]) found++; + // Found all recipients + if (found == recipients_count) + { + if (chat->ignore) + netlog("gg_gc_getchat(): Ignoring existing id %s, size %d.", chat->id, chat->recipients_count); + else + netlog("gg_gc_getchat(): Returning existing id %s, size %d.", chat->id, chat->recipients_count); + return !(chat->ignore) ? chat->id : NULL; + } + } + } + + // Make new uin list to chat mapping + chat = (GGGC *)malloc(sizeof(GGGC)); + UIN2ID(gc_id ++, chat->id); chat->ignore = FALSE; + + // Check groupchat policy (new) / only for incoming + if (sender) + { + int unknown = (getcontact(sender, 0, 0, NULL) == NULL), + unknownSender = unknown; + for(i = 0; i < recipients_count; i++) + if (!getcontact(recipients[i], 0, 0, NULL)) + unknown ++; + if ((db_get_w(NULL, m_szModuleName, GG_KEY_GC_POLICY_DEFAULT, GG_KEYDEF_GC_POLICY_DEFAULT) == 2) || + (db_get_w(NULL, m_szModuleName, GG_KEY_GC_POLICY_TOTAL, GG_KEYDEF_GC_POLICY_TOTAL) == 2 && + recipients_count >= db_get_w(NULL, m_szModuleName, GG_KEY_GC_COUNT_TOTAL, GG_KEYDEF_GC_COUNT_TOTAL)) || + (db_get_w(NULL, m_szModuleName, GG_KEY_GC_POLICY_UNKNOWN, GG_KEYDEF_GC_POLICY_UNKNOWN) == 2 && + unknown >= db_get_w(NULL, m_szModuleName, GG_KEY_GC_COUNT_UNKNOWN, GG_KEYDEF_GC_COUNT_UNKNOWN))) + chat->ignore = TRUE; + if (!chat->ignore && ((db_get_w(NULL, m_szModuleName, GG_KEY_GC_POLICY_DEFAULT, GG_KEYDEF_GC_POLICY_DEFAULT) == 1) || + (db_get_w(NULL, m_szModuleName, GG_KEY_GC_POLICY_TOTAL, GG_KEYDEF_GC_POLICY_TOTAL) == 1 && + recipients_count >= db_get_w(NULL, m_szModuleName, GG_KEY_GC_COUNT_TOTAL, GG_KEYDEF_GC_COUNT_TOTAL)) || + (db_get_w(NULL, m_szModuleName, GG_KEY_GC_POLICY_UNKNOWN, GG_KEYDEF_GC_POLICY_UNKNOWN) == 1 && + unknown >= db_get_w(NULL, m_szModuleName, GG_KEY_GC_COUNT_UNKNOWN, GG_KEYDEF_GC_COUNT_UNKNOWN)))) + { + TCHAR *senderName = unknownSender ? + TranslateT("Unknown") : pcli->pfnGetContactDisplayName(getcontact(sender, 0, 0, NULL), 0); + TCHAR error[256]; + mir_sntprintf(error, SIZEOF(error), TranslateT("%s has initiated conference with %d participants (%d unknowns).\nDo you want do participate ?"), + senderName, recipients_count + 1, unknown); + chat->ignore = MessageBox(NULL, error, m_tszUserName, MB_OKCANCEL | MB_ICONEXCLAMATION) != IDOK; + } + if (chat->ignore) + { + // Copy recipient list + chat->recipients_count = recipients_count + (sender ? 1 : 0); + chat->recipients = (uin_t *)calloc(chat->recipients_count, sizeof(uin_t)); + for(i = 0; i < recipients_count; i++) + chat->recipients[i] = recipients[i]; + if (sender) chat->recipients[i] = sender; + netlog("gg_gc_getchat(): Ignoring new chat %s, count %d.", chat->id, chat->recipients_count); + list_add(&chats, chat, 0); + return NULL; + } + } + + // Create new chat window + TCHAR status[256]; + TCHAR *senderName = sender ? pcli->pfnGetContactDisplayName(getcontact(sender, 1, 0, NULL), 0) : NULL; + mir_sntprintf(status, 255, (sender) ? TranslateT("%s initiated the conference.") : TranslateT("This is my own conference."), senderName); + GCSESSION gcwindow = { 0 }; + gcwindow.cbSize = sizeof(GCSESSION); + gcwindow.iType = GCW_CHATROOM; + gcwindow.pszModule = m_szModuleName; + gcwindow.ptszName = sender ? senderName : TranslateT("Conference"); + gcwindow.pszID = chat->id; + gcwindow.dwFlags = GC_TCHAR; + gcwindow.dwItemData = (DWORD)chat; + gcwindow.ptszStatusbarText = status; + + // Here we put nice new hash sign + TCHAR *name = (TCHAR*)calloc(_tcslen(gcwindow.ptszName) + 2, sizeof(TCHAR)); + *name = '#'; _tcscpy(name + 1, gcwindow.ptszName); + gcwindow.ptszName = name; + // Create new room + if (CallServiceSync(MS_GC_NEWSESSION, 0, (LPARAM) &gcwindow)) + { + netlog("gg_gc_getchat(): Cannot create new chat window %s.", chat->id); + free(name); + free(chat); + return NULL; + } + free(name); + + gcdest.pszID = chat->id; + gcevent.pszUID = id; + gcevent.dwFlags = GC_TCHAR | GCEF_ADDTOLOG; + gcevent.time = 0; + + // Add normal group + gcevent.ptszStatus = TranslateT("Participants"); + CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gcevent); + gcdest.iType = GC_EVENT_JOIN; + + // Add myself + if (uin = db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0)) + { + UIN2ID(uin, id); + if (!db_get_s(NULL, m_szModuleName, GG_KEY_NICK, &dbv, DBVT_TCHAR)) { + gcevent.ptszNick = NEWTSTR_ALLOCA(dbv.ptszVal); + db_free(&dbv); + } + else gcevent.ptszNick = TranslateT("Me"); + gcevent.bIsMe = 1; + CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gcevent); + netlog("gg_gc_getchat(): Myself %s: %S (%S) to the list...", gcevent.pszUID, gcevent.ptszNick, gcevent.ptszStatus); + } + else netlog("gg_gc_getchat(): Myself adding failed with uin %d !!!", uin); + + // Copy recipient list + chat->recipients_count = recipients_count + (sender ? 1 : 0); + chat->recipients = (uin_t *)calloc(chat->recipients_count, sizeof(uin_t)); + for(i = 0; i < recipients_count; i++) + chat->recipients[i] = recipients[i]; + if (sender) chat->recipients[i] = sender; + + // Add contacts + for(i = 0; i < chat->recipients_count; i++) { + HANDLE hContact = getcontact(chat->recipients[i], 1, 0, NULL); + UIN2ID(chat->recipients[i], id); + if (hContact && (name = pcli->pfnGetContactDisplayName(hContact, 0)) != NULL) + gcevent.ptszNick = name; + else + gcevent.ptszNick = TranslateT("'Unknown'"); + gcevent.bIsMe = 0; + gcevent.dwFlags = GC_TCHAR; + netlog("gg_gc_getchat(): Added %s: %S (%S) to the list...", gcevent.pszUID, gcevent.ptszNick, gcevent.pszStatus); + CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gcevent); + } + gcdest.iType = GC_EVENT_CONTROL; + CallServiceSync(MS_GC_EVENT, SESSION_INITDONE, (LPARAM)&gcevent); + CallServiceSync(MS_GC_EVENT, SESSION_ONLINE, (LPARAM)&gcevent); + + netlog("gg_gc_getchat(): Returning new chat window %s, count %d.", chat->id, chat->recipients_count); + list_add(&chats, chat, 0); + return chat->id; +} + +static HANDLE gg_getsubcontact(GGPROTO* gg, HANDLE hContact) +{ + char* szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + char* szMetaProto = (char*)CallService(MS_MC_GETPROTOCOLNAME, 0, 0); + + if (szProto && szMetaProto && (INT_PTR)szMetaProto != CALLSERVICE_NOTFOUND && !lstrcmpA(szProto, szMetaProto)) + { + int nSubContacts = (int)CallService(MS_MC_GETNUMCONTACTS, (WPARAM)hContact, 0), i; + HANDLE hMetaContact; + for (i = 0; i < nSubContacts; i++) + { + hMetaContact = (HANDLE)CallService(MS_MC_GETSUBCONTACT, (WPARAM)hContact, i); + szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hMetaContact, 0); + if (szProto && !lstrcmpA(szProto, gg->m_szModuleName)) + return hMetaContact; + } + } + return NULL; +} + +static void gg_gc_resetclistopts(HWND hwndList) +{ + int i; + SendMessage(hwndList, CLM_SETLEFTMARGIN, 2, 0); + SendMessage(hwndList, CLM_SETBKBITMAP, 0, (LPARAM)(HBITMAP)NULL); + SendMessage(hwndList, CLM_SETBKCOLOR, GetSysColor(COLOR_WINDOW), 0); + SendMessage(hwndList, CLM_SETGREYOUTFLAGS, 0, 0); + SendMessage(hwndList, CLM_SETINDENT, 10, 0); + SendMessage(hwndList, CLM_SETHIDEEMPTYGROUPS, (WPARAM)TRUE, 0); + for (i = 0; i <= FONTID_MAX; i++) + SendMessage(hwndList, CLM_SETTEXTCOLOR, i, GetSysColor(COLOR_WINDOWTEXT)); +} + +static int gg_gc_countcheckmarks(HWND hwndList) +{ + int count = 0; + HANDLE hItem, hContact = db_find_first(); + while (hContact) + { + hItem = (HANDLE)SendMessage(hwndList, CLM_FINDCONTACT, (WPARAM)hContact, 0); + if (hItem && SendMessage(hwndList, CLM_GETCHECKMARK, (WPARAM)hItem, 0)) + count++; + hContact = db_find_next(hContact); + } + return count; +} + +#define HM_SUBCONTACTSCHANGED (WM_USER + 100) + +static INT_PTR CALLBACK gg_gc_openconfdlg(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch(message) + { + case WM_INITDIALOG: + { + CLCINFOITEM cii = {0}; + HANDLE hMetaContactsEvent; + + SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)lParam); + TranslateDialogDefault(hwndDlg); + WindowSetIcon(hwndDlg, "conference"); + gg_gc_resetclistopts(GetDlgItem(hwndDlg, IDC_CLIST)); + + // Hook MetaContacts event (if available) + hMetaContactsEvent = HookEventMessage(ME_MC_SUBCONTACTSCHANGED, hwndDlg, HM_SUBCONTACTSCHANGED); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)hMetaContactsEvent); + } + return TRUE; + + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDOK: + { + HWND hwndList = GetDlgItem(hwndDlg, IDC_CLIST); + GGPROTO* gg = (GGPROTO*)GetWindowLongPtr(hwndDlg, DWLP_USER); + int count = 0, i = 0; + // Check if connected + if (!gg->isonline()) + { + MessageBox(NULL, + TranslateT("You have to be connected to open new conference."), + gg->m_tszUserName, MB_OK | MB_ICONSTOP); + } + else if (hwndList && (count = gg_gc_countcheckmarks(hwndList)) >= 2) + { + // Create new participiants table + char* chat; + uin_t* participants = (uin_t*)calloc(count, sizeof(uin_t)); + HANDLE hItem, hContact = db_find_first(); + gg->netlog("gg_gc_getchat(): Opening new conference for %d contacts.", count); + while (hContact && i < count) + { + hItem = (HANDLE)SendMessage(hwndList, CLM_FINDCONTACT, (WPARAM)hContact, 0); + if (hItem && SendMessage(hwndList, CLM_GETCHECKMARK, (WPARAM)hItem, 0)) + { + HANDLE hMetaContact = gg_getsubcontact(gg, hContact); // MetaContacts support + participants[i++] = db_get_dw(hMetaContact ? hMetaContact : hContact, gg->m_szModuleName, GG_KEY_UIN, 0); + } + hContact = db_find_next(hContact); + } + if (count > i) i = count; + chat = gg->gc_getchat(0, participants, count); + if (chat) + { + GCDEST gcdest = {gg->m_szModuleName, chat, GC_EVENT_CONTROL}; + GCEVENT gcevent = {sizeof(GCEVENT), &gcdest}; + CallServiceSync(MS_GC_EVENT, WINDOW_VISIBLE, (LPARAM)&gcevent); + } + free(participants); + } + } + + case IDCANCEL: + DestroyWindow(hwndDlg); + break; + } + break; + } + + case WM_NOTIFY: + { + switch(((NMHDR*)lParam)->idFrom) + { + case IDC_CLIST: + { + switch(((NMHDR*)lParam)->code) + { + case CLN_OPTIONSCHANGED: + gg_gc_resetclistopts(GetDlgItem(hwndDlg, IDC_CLIST)); + break; + + case CLN_NEWCONTACT: + case CLN_CONTACTMOVED: + case CLN_LISTREBUILT: + { + HANDLE hContact; + HANDLE hItem; + char* szProto; + uin_t uin; + GGPROTO* gg = (GGPROTO*)GetWindowLongPtr(hwndDlg, DWLP_USER); + + if (!gg) break; + + // Delete non-gg contacts + hContact = db_find_first(); + while (hContact) + { + hItem = (HANDLE)SendDlgItemMessage(hwndDlg, IDC_CLIST, CLM_FINDCONTACT, (WPARAM)hContact, 0); + if (hItem) + { + HANDLE hMetaContact = gg_getsubcontact(gg, hContact); // MetaContacts support + if (hMetaContact) + { + szProto = gg->m_szModuleName; + uin = (uin_t)db_get_dw(hMetaContact, gg->m_szModuleName, GG_KEY_UIN, 0); + } + else + { + szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + uin = (uin_t)db_get_dw(hContact, gg->m_szModuleName, GG_KEY_UIN, 0); + } + + if (szProto == NULL || lstrcmpA(szProto, gg->m_szModuleName) || !uin || uin == db_get_dw(NULL, gg->m_szModuleName, GG_KEY_UIN, 0)) + SendDlgItemMessage(hwndDlg, IDC_CLIST, CLM_DELETEITEM, (WPARAM)hItem, 0); + } + hContact = db_find_next(hContact); + } + } + break; + + case CLN_CHECKCHANGED: + EnableWindow(GetDlgItem(hwndDlg, IDOK), gg_gc_countcheckmarks(GetDlgItem(hwndDlg, IDC_CLIST)) >= 2); + break; + } + break; + } + } + break; + } + + case HM_SUBCONTACTSCHANGED: + { + HWND hwndList = GetDlgItem(hwndDlg, IDC_CLIST); + SendMessage(hwndList, CLM_AUTOREBUILD, 0, 0); + EnableWindow(GetDlgItem(hwndDlg, IDOK), gg_gc_countcheckmarks(hwndList) >= 2); + break; + } + + case WM_CLOSE: + DestroyWindow(hwndDlg); + break; + + case WM_DESTROY: + { + HANDLE hMetaContactsEvent = (HANDLE)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if (hMetaContactsEvent) UnhookEvent(hMetaContactsEvent); + WindowFreeIcon(hwndDlg); + break; + } + } + + return FALSE; +} + +INT_PTR GGPROTO::gc_clearignored(WPARAM wParam, LPARAM lParam) +{ + list_t l = chats; BOOL cleared = FALSE; + while(l) + { + GGGC *chat = (GGGC *)l->data; + l = l->next; + if (chat->ignore) + { + if (chat->recipients) free(chat->recipients); + list_remove(&chats, chat, 1); + cleared = TRUE; + } + } + MessageBox( NULL, + cleared ? + TranslateT("All ignored conferences are now unignored and the conference policy will act again.") : + TranslateT("There are no ignored conferences."), + m_tszUserName, MB_OK | MB_ICONINFORMATION + ); + + return 0; +} + +INT_PTR GGPROTO::gc_openconf(WPARAM wParam, LPARAM lParam) +{ + // Check if connected + if (!isonline()) + { + MessageBox(NULL, + TranslateT("You have to be connected to open new conference."), + m_tszUserName, MB_OK | MB_ICONSTOP + ); + return 0; + } + + CreateDialogParam(hInstance, MAKEINTRESOURCE(IDD_CONFERENCE), NULL, gg_gc_openconfdlg, (LPARAM)this); + return 1; +} + +int GGPROTO::gc_changenick(HANDLE hContact, char *pszNick) +{ + list_t l; + uin_t uin = db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0); + if (!uin || !pszNick) return 0; + + netlog("gg_gc_changenick(): Nickname for uin %d changed to %s.", uin, pszNick); + // Lookup for chats having this nick + for(l = chats; l; l = l->next) { + GGGC *chat = (GGGC *)l->data; + if (chat->recipients && chat->recipients_count) + for(int i = 0; i < chat->recipients_count; i++) + // Rename this window if it's exising in the chat + if (chat->recipients[i] == uin) + { + char id[32]; + GCEVENT gce = {sizeof(GCEVENT)}; + GCDEST gcd; + + UIN2ID(uin, id); + gcd.iType = GC_EVENT_NICK; + gcd.pszModule = m_szModuleName; + gce.pDest = &gcd; + gcd.pszID = chat->id; + gce.pszUID = id; + gce.pszText = pszNick; + netlog("gg_gc_changenick(): Found room %s with uin %d, sending nick change %s.", chat->id, uin, id); + CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce); + + break; + } + } + + return 1; +} diff --git a/protocols/Gadu-Gadu/src/icolib.cpp b/protocols/Gadu-Gadu/src/icolib.cpp new file mode 100644 index 0000000000..f99540ac76 --- /dev/null +++ b/protocols/Gadu-Gadu/src/icolib.cpp @@ -0,0 +1,109 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2007 Adam Strzelecki +// +// 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 "gg.h" + +struct tagiconList +{ + char* szDescr; + char* szName; + int defIconID; +} +static const iconList[] = +{ + { LPGEN("Protocol icon"), "main", IDI_GG }, + { LPGEN("Import list from server"), "importserver", IDI_IMPORT_SERVER }, + { LPGEN("Import list from text file"), "importtext", IDI_IMPORT_TEXT }, + { LPGEN("Remove list from server"), "removeserver", IDI_REMOVE_SERVER }, + { LPGEN("Export list to server"), "exportserver", IDI_EXPORT_SERVER }, + { LPGEN("Export list to text file"), "exporttext", IDI_EXPORT_TEXT }, + { LPGEN("Account settings"), "settings", IDI_SETTINGS }, + { LPGEN("Contact list"), "list", IDI_LIST }, + { LPGEN("Block user"), "block", IDI_BLOCK }, + { LPGEN("Previous image"), "previous", IDI_PREV }, + { LPGEN("Next image"), "next", IDI_NEXT }, + { LPGEN("Send image"), "image", IDI_IMAGE }, + { LPGEN("Save image"), "save", IDI_SAVE }, + { LPGEN("Delete image"), "delete", IDI_DELETE }, + { LPGEN("Open new conference"), "conference", IDI_CONFERENCE }, + { LPGEN("Clear ignored conferences"), "clearignored", IDI_CLEAR_CONFERENCE }, + { LPGEN("Concurrent sessions"), "sessions", IDI_SESSIONS } +}; + +HANDLE hIconLibItem[SIZEOF(iconList)]; + +void gg_icolib_init() +{ + TCHAR szFile[MAX_PATH]; + GetModuleFileName(hInstance, szFile, MAX_PATH); + + char szSectionName[100]; + mir_snprintf(szSectionName, sizeof( szSectionName ), "%s/%s", LPGEN("Protocols"), LPGEN(GGDEF_PROTO)); + + SKINICONDESC sid = {0}; + sid.cbSize = sizeof(SKINICONDESC); + sid.ptszDefaultFile = szFile; + sid.pszSection = szSectionName; + sid.flags = SIDF_PATH_TCHAR; + + for (int i = 0; i < SIZEOF(iconList); i++) { + char szSettingName[100]; + mir_snprintf(szSettingName, sizeof(szSettingName), "%s_%s", GGDEF_PROTO, iconList[i].szName); + sid.pszName = szSettingName; + sid.pszDescription = (char*)iconList[i].szDescr; + sid.iDefaultIndex = -iconList[i].defIconID; + hIconLibItem[i] = Skin_AddIcon(&sid); + } +} + +HICON LoadIconEx(const char* name, BOOL big) +{ + char szSettingName[100]; + mir_snprintf(szSettingName, sizeof(szSettingName), "%s_%s", GGDEF_PROTO, name); + return (HICON)CallService(MS_SKIN2_GETICON, big, (LPARAM)szSettingName); +} + +HANDLE GetIconHandle(int iconId) +{ + int i; + for(i = 0; i < SIZEOF(iconList); i++) + if (iconList[i].defIconID == iconId) + return hIconLibItem[i]; + return NULL; +} + +void ReleaseIconEx(const char* name, BOOL big) +{ + char szSettingName[100]; + mir_snprintf(szSettingName, sizeof(szSettingName), "%s_%s", GGDEF_PROTO, name); + CallService(big ? MS_SKIN2_RELEASEICONBIG : MS_SKIN2_RELEASEICON, 0, (LPARAM)szSettingName); +} + +void WindowSetIcon(HWND hWnd, const char* name) +{ + SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)LoadIconEx(name, TRUE)); + SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)LoadIconEx(name, FALSE)); +} + +void WindowFreeIcon(HWND hWnd) +{ + CallService(MS_SKIN2_RELEASEICONBIG, SendMessage(hWnd, WM_SETICON, ICON_BIG, 0), 0); + CallService(MS_SKIN2_RELEASEICON, SendMessage(hWnd, WM_SETICON, ICON_SMALL, 0), 0); +} diff --git a/protocols/Gadu-Gadu/src/image.cpp b/protocols/Gadu-Gadu/src/image.cpp new file mode 100644 index 0000000000..0ad225ad06 --- /dev/null +++ b/protocols/Gadu-Gadu/src/image.cpp @@ -0,0 +1,1191 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2009 Adam Strzelecki +// Copyright (c) 2009-2012 Bartosz Białek +// +// 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 "gg.h" +#include + +#define WM_ADDIMAGE WM_USER + 1 +#define WM_SENDIMG WM_USER + 2 +#define WM_CHOOSEIMG WM_USER + 3 +#define TIMERID_FLASHWND WM_USER + 4 + +//////////////////////////////////////////////////////////////////////////// +// Image Window : Data + +// tablica zawiera uin kolesia i uchwyt do okna, okno zawiera GGIMAGEDLGDATA +// ktore przechowuje handle kontaktu, i wskaznik na pierwszy obrazek +// obrazki sa poukladane jako lista jednokierunkowa. +// przy tworzeniu okna podaje sie handle do kontaktu +// dodajac obrazek tworzy sie element listy i wysyla do okna +// wyswietlajac obrazek idzie po liscie jednokierunkowej + +typedef struct _GGIMAGEENTRY +{ + HBITMAP hBitmap; + TCHAR *lpszFileName; + char *lpData; + unsigned long nSize; + struct _GGIMAGEENTRY *lpNext; + uint32_t crc32; +} GGIMAGEENTRY; + +typedef struct +{ + HANDLE hContact; + HANDLE hEvent; + HWND hWnd; + uin_t uin; + int nImg, nImgTotal; + GGIMAGEENTRY *lpImages; + SIZE minSize; + BOOL bReceiving; + GGPROTO *gg; +} GGIMAGEDLGDATA; + +// Prototypes +int gg_img_remove(GGIMAGEDLGDATA *dat); + +//////////////////////////////////////////////////////////////////////////// +// Image Module : Adding item to contact menu, creating sync objects + +int GGPROTO::img_init() +{ + CLISTMENUITEM mi = {0}; + char service[64]; + + mi.cbSize = sizeof(mi); + mi.flags = CMIF_ICONFROMICOLIB; + + // Send image contact menu item + mir_snprintf(service, sizeof(service), GGS_SENDIMAGE, m_szModuleName); + createObjService(service, &GGPROTO::img_sendimg); + mi.position = -2000010000; + mi.icolibItem = GetIconHandle(IDI_IMAGE); + mi.pszName = LPGEN("&Image"); + mi.pszService = service; + mi.pszContactOwner = m_szModuleName; + hImageMenuItem = Menu_AddContactMenuItem(&mi); + + // Receive image + mir_snprintf(service, sizeof(service), GGS_RECVIMAGE, m_szModuleName); + createObjService(service, &GGPROTO::img_recvimage); + + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////// +// Image Module : closing dialogs, sync objects +int GGPROTO::img_shutdown() +{ + list_t l; +#ifdef DEBUGMODE + netlog("gg_img_shutdown(): Closing all dialogs..."); +#endif + // Rather destroy window instead of just removing structures + for (l = imagedlgs; l;) + { + GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)l->data; + l = l->next; + + if (dat && dat->hWnd) + { + if (IsWindow(dat->hWnd)) + { + // Post message async, since it maybe be different thread + if (!PostMessage(dat->hWnd, WM_CLOSE, 0, 0)) + netlog("gg_img_shutdown(): Image dlg %x cannot be released !!", dat->hWnd); + } + else + netlog("gg_img_shutdown(): Image dlg %x not exists, but structure does !!", dat->hWnd); + } + } + + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////// +// Image Module : destroying list + +int GGPROTO::img_destroy() +{ + // Release all dialogs + while (imagedlgs && gg_img_remove((GGIMAGEDLGDATA *)imagedlgs->data)); + + // Destroy list + list_destroy(imagedlgs, 1); + CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)hImageMenuItem, (LPARAM) 0); + + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////// +// Image Window : Frees image entry structure + +static int gg_img_releasepicture(void *img) +{ + if (!img) + return FALSE; + if (((GGIMAGEENTRY *)img)->lpszFileName) + free(((GGIMAGEENTRY *)img)->lpszFileName); + if (((GGIMAGEENTRY *)img)->hBitmap) + DeleteObject(((GGIMAGEENTRY *)img)->hBitmap); + if (((GGIMAGEENTRY *)img)->lpData) + free(((GGIMAGEENTRY *)img)->lpData); + free(img); + + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////// +// Painting image +int gg_img_paint(HWND hwnd, GGIMAGEENTRY *dat) +{ + PAINTSTRUCT paintStruct; + HDC hdc = BeginPaint(hwnd, &paintStruct); + RECT rc; + + GetWindowRect(GetDlgItem(hwnd, IDC_IMG_IMAGE), &rc); + ScreenToClient(hwnd, (POINT *)&rc.left); + ScreenToClient(hwnd, (POINT *)&rc.right); + FillRect(hdc, &rc, (HBRUSH)GetSysColorBrush(COLOR_WINDOW)); + + if (dat->hBitmap) + { + HDC hdcBmp = NULL; + int nWidth, nHeight; + BITMAP bmp; + + GetObject(dat->hBitmap, sizeof(bmp), &bmp); + nWidth = bmp.bmWidth; nHeight = bmp.bmHeight; + + hdcBmp = CreateCompatibleDC(hdc); + SelectObject(hdcBmp, dat->hBitmap); + if (hdcBmp) + { + SetStretchBltMode(hdc, HALFTONE); + // Draw bitmap + if (nWidth > (rc.right-rc.left) || nHeight > (rc.bottom-rc.top)) + { + if ((double)nWidth / (double)nHeight > (double) (rc.right-rc.left) / (double)(rc.bottom-rc.top)) + { + StretchBlt(hdc, + rc.left, + ((rc.top + rc.bottom) - (rc.right - rc.left) * nHeight / nWidth) / 2, + (rc.right - rc.left), + (rc.right - rc.left) * nHeight / nWidth, + hdcBmp, 0, 0, nWidth, nHeight, SRCCOPY); + } + else + { + StretchBlt(hdc, + ((rc.left + rc.right) - (rc.bottom - rc.top) * nWidth / nHeight) / 2, + rc.top, + (rc.bottom - rc.top) * nWidth / nHeight, + (rc.bottom - rc.top), + hdcBmp, 0, 0, nWidth, nHeight, SRCCOPY); + } + } + else + { + BitBlt(hdc, + (rc.left + rc.right - nWidth) / 2, + (rc.top + rc.bottom - nHeight) / 2, + nWidth, nHeight, + hdcBmp, 0, 0, SRCCOPY); + } + DeleteDC(hdcBmp); + } + } + EndPaint(hwnd, &paintStruct); + + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////////// +// Returns supported image filters + +TCHAR *gg_img_getfilter(TCHAR *szFilter, int nSize) +{ + TCHAR *szFilterName, *szFilterMask; + TCHAR *pFilter = szFilter; + + // Match relative to ImgDecoder presence + szFilterName = TranslateT("Image files (*.bmp,*.gif,*.jpeg,*.jpg,*.png)"); + szFilterMask = _T("*.bmp;*.gif;*.jpeg;*.jpg;*.png"); + + // Make up filter + _tcsncpy(pFilter, szFilterName, nSize); + pFilter += _tcslen(pFilter) + 1; + if (pFilter >= szFilter + nSize) return NULL; + _tcsncpy(pFilter, szFilterMask, nSize - (pFilter - szFilter)); + pFilter += _tcslen(pFilter) + 1; + if (pFilter >= szFilter + nSize) return NULL; + *pFilter = 0; + + return szFilter; +} + +//////////////////////////////////////////////////////////////////////////////// +// Save specified image entry + +int gg_img_saveimage(HWND hwnd, GGIMAGEENTRY *dat) +{ + if (!dat) + return FALSE; + + GGPROTO* gg = ((GGIMAGEDLGDATA *)GetWindowLongPtr(hwnd, GWLP_USERDATA))->gg; + + TCHAR szFilter[128]; + gg_img_getfilter(szFilter, SIZEOF(szFilter)); + + TCHAR szFileName[MAX_PATH]; + _tcsncpy(szFileName, dat->lpszFileName, SIZEOF(szFileName)); + + OPENFILENAME ofn = {0}; + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; + ofn.hwndOwner = hwnd; + ofn.hInstance = hInstance; + ofn.lpstrFile = szFileName; + ofn.lpstrFilter = szFilter; + ofn.nMaxFile = MAX_PATH; + ofn.Flags = OFN_PATHMUSTEXIST | OFN_NOCHANGEDIR | OFN_OVERWRITEPROMPT; + if (GetSaveFileName(&ofn)) + { + FILE *fp = _tfopen(szFileName, _T("w+b")); + if (fp) + { + fwrite(dat->lpData, dat->nSize, 1, fp); + fclose(fp); + gg->netlog("gg_img_saveimage(): Image saved to %s.", szFileName); + } + else + { + gg->netlog("gg_img_saveimage(): Cannot save image to %s.", szFileName); + MessageBox(hwnd, TranslateT("Image cannot be written to disk."), gg->m_tszUserName, MB_OK | MB_ICONERROR); + } + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////// +// Fit window size to image size + +BOOL gg_img_fit(HWND hwndDlg) +{ + GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + RECT dlgRect, imgRect, wrkRect; + int nWidth, nHeight; + int rWidth = 0, rHeight = 0; + int oWidth = 0, oHeight = 0; + BITMAP bmp; + GGIMAGEENTRY *img = NULL; + HDC hdc; + + // Check if image is loaded + if (!dat || !dat->lpImages || !dat->lpImages->hBitmap) + return FALSE; + + img = dat->lpImages; + + // Go to last image + while (img->lpNext && dat->lpImages->hBitmap) + img = img->lpNext; + + // Get rects of display + GetWindowRect(hwndDlg, &dlgRect); + GetClientRect(GetDlgItem(hwndDlg, IDC_IMG_IMAGE), &imgRect); + + hdc = GetDC(hwndDlg); + + GetObject(img->hBitmap, sizeof(bmp), &bmp); + nWidth = bmp.bmWidth; nHeight = bmp.bmHeight; + SystemParametersInfo(SPI_GETWORKAREA, 0, &wrkRect, 0); + + ReleaseDC(hwndDlg, hdc); + + if ((imgRect.right - imgRect.left) < nWidth) + rWidth = nWidth - imgRect.right + imgRect.left; + if ((imgRect.bottom - imgRect.top) < nWidth) + rHeight = nHeight - imgRect.bottom + imgRect.top; + + // Check if anything needs resize + if (!rWidth && !rHeight) + return FALSE; + + oWidth = dlgRect.right - dlgRect.left + rWidth; + oHeight = dlgRect.bottom - dlgRect.top + rHeight; + + if (oHeight > wrkRect.bottom - wrkRect.top) + { + oWidth = (int)((double)(wrkRect.bottom - wrkRect.top + imgRect.bottom - imgRect.top - dlgRect.bottom + dlgRect.top) * nWidth / nHeight) + - imgRect.right + imgRect.left + dlgRect.right - dlgRect.left; + if (oWidth < dlgRect.right - dlgRect.left) + oWidth = dlgRect.right - dlgRect.left; + oHeight = wrkRect.bottom - wrkRect.top; + } + if (oWidth > wrkRect.right - wrkRect.left) + { + oHeight = (int)((double)(wrkRect.right - wrkRect.left + imgRect.right - imgRect.left - dlgRect.right + dlgRect.left) * nHeight / nWidth) + - imgRect.bottom + imgRect.top + dlgRect.bottom - dlgRect.top; + if (oHeight < dlgRect.bottom - dlgRect.top) + oHeight = dlgRect.bottom - dlgRect.top; + oWidth = wrkRect.right - wrkRect.left; + } + SetWindowPos(hwndDlg, NULL, + (wrkRect.left + wrkRect.right - oWidth) / 2, + (wrkRect.top + wrkRect.bottom - oHeight) / 2, + oWidth, oHeight, + SWP_SHOWWINDOW | SWP_NOZORDER /* | SWP_NOACTIVATE */); + + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////// +// Dialog resizer procedure +static int sttImageDlgResizer(HWND hwndDlg, LPARAM lParam, UTILRESIZECONTROL* urc) +{ + switch (urc->wId) + { + case IDC_IMG_PREV: + case IDC_IMG_NEXT: + case IDC_IMG_DELETE: + case IDC_IMG_SAVE: + return RD_ANCHORX_RIGHT | RD_ANCHORY_TOP; + case IDC_IMG_IMAGE: + return RD_ANCHORX_LEFT | RD_ANCHORY_TOP | RD_ANCHORY_HEIGHT | RD_ANCHORX_WIDTH; + case IDC_IMG_SEND: + case IDC_IMG_CANCEL: + return RD_ANCHORX_RIGHT | RD_ANCHORY_BOTTOM; + } + return RD_ANCHORX_LEFT | RD_ANCHORY_TOP; +} + +//////////////////////////////////////////////////////////////////////////// +// Send / Recv main dialog procedure +static INT_PTR CALLBACK gg_img_dlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch (msg) { + case WM_INITDIALOG: + { + RECT rect; + + TranslateDialogDefault(hwndDlg); + // This should be already initialized + // InitCommonControls(); + + // Get dialog data + dat = (GGIMAGEDLGDATA *)lParam; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + + // Save dialog handle + dat->hWnd = hwndDlg; + + // Send event if someone's waiting + if (dat->hEvent) SetEvent(dat->hEvent); + else dat->gg->netlog("gg_img_dlgproc(): Creation event not found, but someone might be waiting."); + + // Making buttons flat + SendDlgItemMessage(hwndDlg, IDC_IMG_PREV, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hwndDlg, IDC_IMG_NEXT, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hwndDlg, IDC_IMG_DELETE, BUTTONSETASFLATBTN, TRUE, 0); + SendDlgItemMessage(hwndDlg, IDC_IMG_SAVE, BUTTONSETASFLATBTN, TRUE, 0); + + // Setting images for buttons + SendDlgItemMessage(hwndDlg, IDC_IMG_PREV, BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadIconEx("previous", FALSE)); + SendDlgItemMessage(hwndDlg, IDC_IMG_NEXT, BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadIconEx("next", FALSE)); + SendDlgItemMessage(hwndDlg, IDC_IMG_DELETE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadIconEx("delete", FALSE)); + SendDlgItemMessage(hwndDlg, IDC_IMG_SAVE, BM_SETIMAGE, IMAGE_ICON, (LPARAM)LoadIconEx("save", FALSE)); + + // Setting tooltips for buttons + SendDlgItemMessage(hwndDlg, IDC_IMG_PREV, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Previous image"), BATF_TCHAR); + SendDlgItemMessage(hwndDlg, IDC_IMG_NEXT, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Next image"), BATF_TCHAR); + SendDlgItemMessage(hwndDlg, IDC_IMG_DELETE, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Delete image from the list"), BATF_TCHAR); + SendDlgItemMessage(hwndDlg, IDC_IMG_SAVE, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Save image to disk"), BATF_TCHAR); + + // Set main window image + WindowSetIcon(hwndDlg, "image"); + + TCHAR *szName = pcli->pfnGetContactDisplayName(dat->hContact, 0), szTitle[128]; + if (dat->bReceiving) + mir_sntprintf(szTitle, SIZEOF(szTitle), TranslateT("Image from %s"), szName); + else + mir_sntprintf(szTitle, SIZEOF(szTitle), TranslateT("Image for %s"), szName); + SetWindowText(hwndDlg, szTitle); + + // Store client extents + GetClientRect(hwndDlg, &rect); + dat->minSize.cx = rect.right - rect.left; + dat->minSize.cy = rect.bottom - rect.top; + } + return TRUE; + + case WM_SIZE: + { + UTILRESIZEDIALOG urd = {0}; + urd.cbSize = sizeof(urd); + urd.hInstance = hInstance; + urd.hwndDlg = hwndDlg; + urd.lpTemplate = dat->bReceiving ? MAKEINTRESOURCEA(IDD_IMAGE_RECV) : MAKEINTRESOURCEA(IDD_IMAGE_SEND); + urd.pfnResizer = sttImageDlgResizer; + CallService(MS_UTILS_RESIZEDIALOG, 0, (LPARAM)&urd); + if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) + InvalidateRect(hwndDlg, NULL, FALSE); + return 0; + } + + case WM_SIZING: + { + RECT *pRect = (RECT *)lParam; + if (pRect->right - pRect->left < dat->minSize.cx) + { + if (wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_LEFT || wParam == WMSZ_TOPLEFT) + pRect->left = pRect->right - dat->minSize.cx; + else + pRect->right = pRect->left + dat->minSize.cx; + } + if (pRect->bottom - pRect->top < dat->minSize.cy) + { + if (wParam == WMSZ_TOPLEFT || wParam == WMSZ_TOP || wParam == WMSZ_TOPRIGHT) + pRect->top = pRect->bottom - dat->minSize.cy; + else + pRect->bottom = pRect->top + dat->minSize.cy; + } + } + return TRUE; + + case WM_CLOSE: + EndDialog(hwndDlg, 0); + break; + + // Flash the window + case WM_TIMER: + if (wParam == TIMERID_FLASHWND) + FlashWindow(hwndDlg, TRUE); + break; + + // Kill the timer + case WM_ACTIVATE: + if (LOWORD(wParam) != WA_ACTIVE) + break; + case WM_MOUSEACTIVATE: + if (KillTimer(hwndDlg, TIMERID_FLASHWND)) + FlashWindow(hwndDlg, FALSE); + break; + + case WM_PAINT: + if (dat->lpImages) + { + GGIMAGEENTRY *img = dat->lpImages; + int i; + + for (i = 1; img && (i < dat->nImg); i++) + img = img->lpNext; + + if (!img) + { + dat->gg->netlog("gg_img_dlgproc(): Image was not found on the list. Cannot paint the window."); + return FALSE; + } + + if (dat->bReceiving) + { + TCHAR szTitle[128]; + mir_sntprintf(szTitle, SIZEOF(szTitle), _T("%s (%d / %d)"), img->lpszFileName, dat->nImg, dat->nImgTotal); + SetDlgItemText(hwndDlg, IDC_IMG_NAME, szTitle); + } + else + SetDlgItemText(hwndDlg, IDC_IMG_NAME, img->lpszFileName); + gg_img_paint(hwndDlg, img); + } + break; + + case WM_DESTROY: + if (dat) + { + // Deleting all image entries + GGIMAGEENTRY *temp, *img = dat->lpImages; + GGPROTO *gg = dat->gg; + while (temp = img) + { + img = img->lpNext; + gg_img_releasepicture(temp); + } + ReleaseIconEx("previous", FALSE); + ReleaseIconEx("next", FALSE); + ReleaseIconEx("delete", FALSE); + ReleaseIconEx("save", FALSE); + WindowFreeIcon(hwndDlg); + EnterCriticalSection(&gg->img_mutex); + list_remove(&gg->imagedlgs, dat, 1); + LeaveCriticalSection(&gg->img_mutex); + } + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_IMG_CANCEL: + EndDialog(hwndDlg, 0); + return TRUE; + + case IDC_IMG_PREV: + if (dat->nImg > 1) + { + dat->nImg--; + InvalidateRect(hwndDlg, NULL, FALSE); + } + return TRUE; + + case IDC_IMG_NEXT: + if (dat->nImg < dat->nImgTotal) + { + dat->nImg++; + InvalidateRect(hwndDlg, NULL, FALSE); + } + return TRUE; + + case IDC_IMG_DELETE: + { + GGIMAGEENTRY *del, *img = dat->lpImages; + if (dat->nImg == 1) + { + del = dat->lpImages; + dat->lpImages = img->lpNext; + } + else + { + int i; + for (i = 1; img && (i < dat->nImg - 1); i++) + img = img->lpNext; + if (!img) + { + dat->gg->netlog("gg_img_dlgproc(): Image was not found on the list. Cannot delete it from the list."); + return FALSE; + } + del = img->lpNext; + img->lpNext = del->lpNext; + dat->nImg --; + } + + if ((-- dat->nImgTotal) == 0) + EndDialog(hwndDlg, 0); + else + InvalidateRect(hwndDlg, NULL, FALSE); + + gg_img_releasepicture(del); + } + return TRUE; + + case IDC_IMG_SAVE: + { + GGIMAGEENTRY *img = dat->lpImages; + int i; + + for (i = 1; img && (i < dat->nImg); i++) + img = img->lpNext; + if (!img) + { + dat->gg->netlog("gg_img_dlgproc(): Image was not found on the list. Cannot launch saving."); + return FALSE; + } + gg_img_saveimage(hwndDlg, img); + } + return TRUE; + + case IDC_IMG_SEND: + { + unsigned char format[20]; + char *msg = "\xA0\0"; + GGPROTO *gg = dat->gg; + + if (dat->lpImages && gg->isonline()) + { + uin_t uin = (uin_t)db_get_dw(dat->hContact, gg->m_szModuleName, GG_KEY_UIN, 0); + struct gg_msg_richtext_format *r = NULL; + struct gg_msg_richtext_image *p = NULL; + LPVOID pvData = NULL; + int len; + + ((struct gg_msg_richtext*)format)->flag = 2; + + r = (struct gg_msg_richtext_format *)(format + sizeof(struct gg_msg_richtext)); + r->position = 0; + r->font = GG_FONT_IMAGE; + + p = (struct gg_msg_richtext_image *)(format + sizeof(struct gg_msg_richtext) + sizeof(struct gg_msg_richtext_format)); + p->unknown1 = 0x109; + p->size = dat->lpImages->nSize; + + dat->lpImages->crc32 = p->crc32 = gg_fix32(gg_crc32(0, (BYTE*)dat->lpImages->lpData, dat->lpImages->nSize)); + + len = sizeof(struct gg_msg_richtext_format) + sizeof(struct gg_msg_richtext_image); + ((struct gg_msg_richtext*)format)->length = len; + + EnterCriticalSection(&gg->sess_mutex); + gg_send_message_richtext(gg->sess, GG_CLASS_CHAT, (uin_t)uin, (unsigned char*)msg, format, len + sizeof(struct gg_msg_richtext)); + LeaveCriticalSection(&gg->sess_mutex); + + // Protect dat from releasing + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)0); + + EndDialog(hwndDlg, 0); + } + return TRUE; + } + break; + } + break; + + case WM_ADDIMAGE: // lParam == GGIMAGEENTRY *dat + { + GGIMAGEENTRY *lpImage = (GGIMAGEENTRY *)lParam; + GGIMAGEENTRY *lpImages = dat->lpImages; + + if (!dat->lpImages) // first image entry + dat->lpImages = lpImage; + else // adding at the end of the list + { + while (lpImages->lpNext) + lpImages = lpImages->lpNext; + lpImages->lpNext = lpImage; + } + dat->nImg = ++ dat->nImgTotal; + } + // Fit window to image + if (!gg_img_fit(hwndDlg)) + InvalidateRect(hwndDlg, NULL, FALSE); + return TRUE; + + case WM_CHOOSEIMG: + { + TCHAR szFilter[128]; + TCHAR szFileName[MAX_PATH]; + OPENFILENAME ofn = {0}; + + gg_img_getfilter(szFilter, sizeof(szFilter)); + *szFileName = 0; + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; + ofn.hwndOwner = hwndDlg; + ofn.hInstance = hInstance; + ofn.lpstrFilter = szFilter; + ofn.lpstrFile = szFileName; + ofn.nMaxFile = MAX_PATH; + ofn.lpstrTitle = TranslateT("Select picture to send"); + ofn.Flags = OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR | OFN_HIDEREADONLY; + if (GetOpenFileName(&ofn)) + { + if (dat->lpImages) + gg_img_releasepicture(dat->lpImages); + if (!(dat->lpImages = (GGIMAGEENTRY *)dat->gg->img_loadpicture(0, szFileName))) + { + EndDialog(hwndDlg, 0); + return FALSE; + } + if (!gg_img_fit(hwndDlg)) + InvalidateRect(hwndDlg, NULL, FALSE); + } + else + { + EndDialog(hwndDlg, 0); + return FALSE; + } + return TRUE; + } + } + + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////// +// Image dialog call thread + +void __cdecl GGPROTO::img_dlgcallthread(void *param) +{ + HWND hMIWnd = 0; //(HWND) CallService(MS_CLUI_GETHWND, 0, 0); + + GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)param; + DialogBoxParam(hInstance, dat->bReceiving ? MAKEINTRESOURCE(IDD_IMAGE_RECV) : MAKEINTRESOURCE(IDD_IMAGE_SEND), + hMIWnd, gg_img_dlgproc, (LPARAM) dat); +} + +//////////////////////////////////////////////////////////////////////////// +// Open dialog receive for specified contact +GGIMAGEDLGDATA *gg_img_recvdlg(GGPROTO *gg, HANDLE hContact) +{ + // Create dialog data + GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)calloc(1, sizeof(GGIMAGEDLGDATA)); + dat->hContact = hContact; + dat->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + dat->bReceiving = TRUE; + dat->gg = gg; + ResetEvent(dat->hEvent); + gg->forkthread(&GGPROTO::img_dlgcallthread, dat); + return dat; +} + +//////////////////////////////////////////////////////////////////////////// +// Checks if an image is already saved to the specified path +// Returns 1 if yes, 0 if no or -1 if different image on this path is found +int gg_img_isexists(TCHAR *szPath, GGIMAGEENTRY *dat) +{ + struct _stat st; + + if (_tstat(szPath, &st) != 0) + return 0; + + if (st.st_size == dat->nSize) + { + FILE *fp = _tfopen(szPath, _T("rb")); + if (!fp) return 0; + char *lpData = (char*)mir_alloc(dat->nSize); + if (fread(lpData, 1, dat->nSize, fp) == dat->nSize) + { + if (dat->crc32 == gg_fix32(gg_crc32(0, (BYTE*)lpData, dat->nSize)) || + memcmp(lpData, dat->lpData, dat->nSize) == 0) + { + mir_free(lpData); + fclose(fp); + return 1; + } + } + mir_free(lpData); + fclose(fp); + } + + return -1; +} + +//////////////////////////////////////////////////////////////////////////// +// Determine if image's file name has the proper extension +TCHAR *gg_img_hasextension(TCHAR *filename) +{ + if (filename != NULL && *filename != '\0') + { + TCHAR *imgtype = _tcsrchr(filename, '.'); + if (imgtype != NULL) + { + size_t len = _tcslen(imgtype); + imgtype++; + if (len == 4 && (_tcsicmp(imgtype, _T("bmp")) == 0 || + _tcsicmp(imgtype, _T("gif")) == 0 || + _tcsicmp(imgtype, _T("jpg")) == 0 || + _tcsicmp(imgtype, _T("png")) == 0)) + return --imgtype; + if (len == 5 && _tcsicmp(imgtype, _T("jpeg")) == 0) + return --imgtype; + } + } + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +// Display received image using message with [img] BBCode + +int GGPROTO::img_displayasmsg(HANDLE hContact, void *img) +{ + GGIMAGEENTRY *dat = (GGIMAGEENTRY *)img; + TCHAR szPath[MAX_PATH], path[MAX_PATH], *pImgext, imgext[6]; + size_t tPathLen; + int i, res; + + if (hImagesFolder == NULL || FoldersGetCustomPathT(hImagesFolder, path, MAX_PATH, _T(""))) { + TCHAR *tmpPath = Utils_ReplaceVarsT( _T("%miranda_userdata%")); + tPathLen = mir_sntprintf(szPath, MAX_PATH, _T("%s\\%s\\ImageCache"), tmpPath, m_tszUserName); + mir_free(tmpPath); + } + else { + _tcscpy(szPath, path); + tPathLen = _tcslen(szPath); + } + + if ( _taccess(szPath, 0)) + CallService(MS_UTILS_CREATEDIRTREET, 0, (LPARAM)szPath); + + mir_sntprintf(szPath + tPathLen, MAX_PATH - tPathLen, _T("\\%s"), dat->lpszFileName); + if ((pImgext = gg_img_hasextension(szPath)) == NULL) + pImgext = szPath + _tcslen(szPath); + mir_sntprintf(imgext, SIZEOF(imgext), _T("%s"), pImgext); + for (i = 1; ; ++i) + { + if ((res = gg_img_isexists(szPath, dat)) != -1) break; + mir_sntprintf(szPath, MAX_PATH, _T("%.*s (%u)%s"), pImgext - szPath, szPath, i, imgext); + } + + if (res == 0) { + // Image file not found, thus create it + FILE *fp = _tfopen(szPath, _T("w+b")); + if (fp) { + res = fwrite(dat->lpData, dat->nSize, 1, fp) > 0; + fclose(fp); + } + } + + if (res != 0) { + char image_msg[MAX_PATH + 11]; + CCSDATA ccs = {0}; + PROTORECVEVENT pre = {0}; + + ccs.szProtoService = PSR_MESSAGE; + ccs.hContact = hContact; + ccs.lParam = (LPARAM)⪯ + mir_snprintf(image_msg, SIZEOF(image_msg), "[img]%s[/img]", szPath); + pre.timestamp = time(NULL); + pre.szMessage = image_msg; + CallService(MS_PROTO_CHAINRECV, 0, (LPARAM) &ccs); + netlog("gg_img_displayasmsg: Image saved to %s.", szPath); + } + else + { + netlog("gg_img_displayasmsg: Cannot save image to %s.", szPath); + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////// +// Return if uin has it's window already opened + +BOOL GGPROTO::img_opened(uin_t uin) +{ + list_t l = imagedlgs; + while (l) + { + GGIMAGEDLGDATA *dat = (GGIMAGEDLGDATA *)l->data; + if (dat->uin == uin) + return TRUE; + l = l->next; + } + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////// +// Image Module : Looking for window entry, create if not found + +int GGPROTO::img_display(HANDLE hContact, void *img) +{ + list_t l = imagedlgs; + GGIMAGEDLGDATA *dat; + + if (!img) return FALSE; + + // Look for already open dialog + EnterCriticalSection(&img_mutex); + while (l) + { + dat = (GGIMAGEDLGDATA *)l->data; + if (dat->bReceiving && dat->hContact == hContact) + break; + l = l->next; + } + + if (!l) dat = NULL; + + if (!dat) + { + dat = gg_img_recvdlg(this, hContact); + dat->uin = db_get_dw(hContact, m_szModuleName, GG_KEY_UIN, 0); + + while (WaitForSingleObjectEx(dat->hEvent, INFINITE, TRUE) != WAIT_OBJECT_0); + CloseHandle(dat->hEvent); + dat->hEvent = NULL; + + list_add(&imagedlgs, dat, 0); + } + LeaveCriticalSection(&img_mutex); + + SendMessage(dat->hWnd, WM_ADDIMAGE, 0, (LPARAM)img); + if (/*db_get_b(NULL, "Chat", "FlashWindowHighlight", 0) != 0 && */ + GetActiveWindow() != dat->hWnd && GetForegroundWindow() != dat->hWnd) + SetTimer(dat->hWnd, TIMERID_FLASHWND, 900, NULL); + + /* DEPRECATED: No more grabbing the focus... just flashing + SetForegroundWindow(dat->hWnd); + SetFocus(dat->hWnd); + */ + + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////// +// Helper function to determine image file format and the right extension + +const TCHAR *gg_img_guessfileextension(const char *lpData) +{ + if (lpData != NULL) + { + if (memcmp(lpData, "BM", 2) == 0) + return _T(".bmp"); + if (memcmp(lpData, "GIF8", 4) == 0) + return _T(".gif"); + if (memcmp(lpData, "\xFF\xD8", 2) == 0) + return _T(".jpg"); + if (memcmp(lpData, "\x89PNG", 4) == 0) + return _T(".png"); + } + return _T(""); +} + +//////////////////////////////////////////////////////////////////////////// +// Image Window : Loading picture and sending for display + +void* GGPROTO::img_loadpicture(gg_event* e, TCHAR *szFileName) +{ + GGIMAGEENTRY *dat; + + if (!szFileName && + (!e || !e->event.image_reply.size || !e->event.image_reply.image || !e->event.image_reply.filename)) + return NULL; + + dat = (GGIMAGEENTRY *)calloc(1, sizeof(GGIMAGEENTRY)); + if (dat == NULL) + return NULL; + + // Copy the file name + if (szFileName) + { + FILE *fp = _tfopen(szFileName, _T("rb")); + if (!fp) { + free(dat); + netlog("gg_img_loadpicture(): fopen(\"%s\", \"rb\") failed.", szFileName); + return NULL; + } + fseek(fp, 0, SEEK_END); + dat->nSize = ftell(fp); + if (dat->nSize <= 0) + { + fclose(fp); + free(dat); + netlog("gg_img_loadpicture(): Zero file size \"%s\" failed.", szFileName); + return NULL; + } + // Maximum acceptable image size + if (dat->nSize > 255 * 1024) + { + fclose(fp); + free(dat); + netlog("gg_img_loadpicture(): Image size of \"%s\" exceeds 255 KB.", szFileName); + MessageBox(NULL, TranslateT("Image exceeds maximum allowed size of 255 KB."), m_tszUserName, MB_OK | MB_ICONEXCLAMATION); + return NULL; + } + fseek(fp, 0, SEEK_SET); + dat->lpData = (char*)malloc(dat->nSize); + if (fread(dat->lpData, 1, dat->nSize, fp) < dat->nSize) + { + free(dat->lpData); + fclose(fp); + free(dat); + netlog("gg_img_loadpicture(): Reading file \"%s\" failed.", szFileName); + return NULL; + } + fclose(fp); + dat->lpszFileName = _tcsdup(szFileName); + } + // Copy picture from packet + else if (e && e->event.image_reply.filename) + { + dat->nSize = e->event.image_reply.size; + dat->lpData = (char*)malloc(dat->nSize); + memcpy(dat->lpData, e->event.image_reply.image, dat->nSize); + + mir_ptr tmpFileName( mir_a2t(e->event.image_reply.filename)); + if (!gg_img_hasextension(tmpFileName)) { + // Add missing file extension + const TCHAR *szImgType = gg_img_guessfileextension(dat->lpData); + if (*szImgType) { + dat->lpszFileName = (TCHAR*)calloc(sizeof(TCHAR), lstrlen(tmpFileName) + lstrlen(szImgType) + 1); + if (dat->lpszFileName != NULL) { + _tcscpy(dat->lpszFileName, tmpFileName); + _tcscat(dat->lpszFileName, szImgType); + } + } + } + + if (dat->lpszFileName == NULL) + dat->lpszFileName = _tcsdup( _A2T( e->event.image_reply.filename)); + } + + //////////////////////////////////////////////////////////////////// + // Loading picture using Miranda Image services + + // Load image from memory + if (!szFileName) + { + IMGSRVC_MEMIO memio; + memio.iLen = dat->nSize; + memio.pBuf = (void *)dat->lpData; + memio.fif = FIF_UNKNOWN; /* detect */ + memio.flags = 0; + dat->hBitmap = (HBITMAP) CallService(MS_IMG_LOADFROMMEM, (WPARAM) &memio, 0); + } + // Load image from file + else + dat->hBitmap = (HBITMAP) CallService(MS_IMG_LOAD, (WPARAM) szFileName, 0); + + // If everything is fine return the handle + if (dat->hBitmap) return dat; + + netlog("gg_img_loadpicture(): MS_IMG_LOAD(MEM) failed."); + if (dat) + { + if (dat->lpData) + free(dat->lpData); + if (dat->lpszFileName) + free(dat->lpszFileName); + free(dat); + } + return NULL; +} + +//////////////////////////////////////////////////////////////////////////// +// Image Recv : AddEvent proc +INT_PTR GGPROTO::img_recvimage(WPARAM wParam, LPARAM lParam) +{ + CLISTEVENT *cle = (CLISTEVENT *)lParam; + GGIMAGEENTRY *img = (GGIMAGEENTRY *)cle->lParam; + + netlog("gg_img_recvimage(%x, %x): Popup new image.", wParam, lParam); + if (!img) return FALSE; + + img_display(cle->hContact, img); + + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////// +// Windows queue management +//////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////// +// Removes dat structure +int gg_img_remove(GGIMAGEDLGDATA *dat) +{ + GGIMAGEENTRY *temp = NULL, *img = NULL; + GGPROTO *gg; + + if (!dat) return FALSE; + gg = dat->gg; + + EnterCriticalSection(&gg->img_mutex); + + // Remove the structure + img = dat->lpImages; + + // Destroy picture handle + while (temp = img) + { + img = img->lpNext; + gg_img_releasepicture(temp); + } + + // Remove from list + list_remove(&gg->imagedlgs, dat, 1); + LeaveCriticalSection(&gg->img_mutex); + + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////// +// +GGIMAGEDLGDATA* gg_img_find(GGPROTO *gg, uin_t uin, uint32_t crc32) +{ + int res = 0; + list_t l = gg->imagedlgs; + GGIMAGEDLGDATA *dat; + + EnterCriticalSection(&gg->img_mutex); + while (l) + { + uin_t c_uin; + + dat = (GGIMAGEDLGDATA *)l->data; + if (!dat) break; + + c_uin = db_get_dw(dat->hContact, dat->gg->m_szModuleName, GG_KEY_UIN, 0); + + if (!dat->bReceiving && dat->lpImages && dat->lpImages->crc32 == crc32 && c_uin == uin) + { + LeaveCriticalSection(&gg->img_mutex); + return dat; + } + + l = l->next; + } + LeaveCriticalSection(&gg->img_mutex); + + gg->netlog("gg_img_find(): Image not found on the list. It might be released before calling this function."); + return NULL; +} + + +//////////////////////////////////////////////////////////////////////////// +// Image Module : Send on Request + +BOOL GGPROTO::img_sendonrequest(gg_event* e) +{ + GGIMAGEDLGDATA *dat = gg_img_find(this, e->event.image_request.sender, e->event.image_request.crc32); + if (!this || !dat || !isonline()) + return FALSE; + + EnterCriticalSection(&sess_mutex); + gg_image_reply(sess, e->event.image_request.sender, dat->lpImages->lpszFileName, dat->lpImages->lpData, dat->lpImages->nSize); + LeaveCriticalSection(&sess_mutex); + + gg_img_remove(dat); + + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////// +// Send Image : Run (Thread and main) + +INT_PTR GGPROTO::img_sendimg(WPARAM wParam, LPARAM lParam) +{ + HANDLE hContact = (HANDLE)wParam; + GGIMAGEDLGDATA *dat = NULL; + + EnterCriticalSection(&img_mutex); + if (!dat) + { + dat = (GGIMAGEDLGDATA *)calloc(1, sizeof(GGIMAGEDLGDATA)); + dat->hContact = hContact; + dat->hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + dat->gg = this; + ResetEvent(dat->hEvent); + + // Create new dialog + forkthread(&GGPROTO::img_dlgcallthread, dat); + + while (WaitForSingleObjectEx(dat->hEvent, INFINITE, TRUE) != WAIT_OBJECT_0); + CloseHandle(dat->hEvent); + dat->hEvent = NULL; + + list_add(&imagedlgs, dat, 0); + } + + // Request choose dialog + SendMessage(dat->hWnd, WM_CHOOSEIMG, 0, 0); + LeaveCriticalSection(&img_mutex); + + return 0; +} diff --git a/protocols/Gadu-Gadu/src/import.cpp b/protocols/Gadu-Gadu/src/import.cpp new file mode 100644 index 0000000000..e4ff066d11 --- /dev/null +++ b/protocols/Gadu-Gadu/src/import.cpp @@ -0,0 +1,651 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2006 Adam Strzelecki +// +// 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 "gg.h" + +//////////////////////////////////////////////////////////////////////////////// +// Checks if a group already exists in Miranda with +// the specified name. +// Returns 1 if a group with the name exists, returns 0 otherwise. +int GroupNameExists(const char *name) +{ + char idstr[33]; + DBVARIANT dbv; + int i; + + for (i = 0; ; i++) { + _itoa(i, idstr, 10); + if (db_get_s(NULL, "CListGroups", idstr, &dbv, DBVT_ASCIIZ)) break; + if (!strcmp(dbv.pszVal + 1, name)) { + DBFreeVariant(&dbv); + return 1; + } + DBFreeVariant(&dbv); + } + return 0; +} + + +//////////////////////////////////////////////////////////////////////////////// +// Creates a group with a specified name in the +// Miranda contact list. +// Returns proper group name + +char *CreateGroup(char *groupName) +{ + int groupId; + char groupIdStr[11]; + char groupName2[127]; + char *p; + DBVARIANT dbv; + + // Cleanup group name from weird characters + + // Skip first break + while(*groupName && *groupName == '\\') groupName++; + + p = strrchr(groupName, '\\'); + // Cleanup end + while(p && !(*(p + 1))) + { + *p = 0; + p = strrchr(groupName, '\\'); + } + // Create upper groups + if (p) + { + *p = 0; + CreateGroup(groupName); + *p = '\\'; + } + + // Is this a duplicate? + if (!GroupNameExists(groupName)) + { + lstrcpynA(groupName2 + 1, groupName, (int)strlen(groupName) + 1); + + // Find an unused id + for (groupId = 0; ; groupId++) { + _itoa(groupId, groupIdStr,10); + if (db_get_s(NULL, "CListGroups", groupIdStr, &dbv, DBVT_ASCIIZ)) + break; + DBFreeVariant(&dbv); + } + + groupName2[0] = 1|GROUPF_EXPANDED; // 1 is required so we never get '\0' + db_set_s(NULL, "CListGroups", groupIdStr, groupName2); + } + return groupName; +} + +char *gg_makecontacts(GGPROTO *gg, int cr) +{ + string_t s = string_init(NULL); + char *contacts; + + // Readup contacts + HANDLE hContact = db_find_first(); + while (hContact) + { + char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0); + if (szProto != NULL && !strcmp(szProto, gg->m_szModuleName) && !db_get_b(hContact, gg->m_szModuleName, "ChatRoom", 0)) + { + DBVARIANT dbv; + + // Readup FirstName + if (!db_get_s(hContact, gg->m_szModuleName, "FirstName", &dbv, DBVT_ASCIIZ)) + { + string_append(s, dbv.pszVal); + DBFreeVariant(&dbv); + } + string_append_c(s, ';'); + // Readup LastName + if (!db_get_s(hContact, gg->m_szModuleName, "LastName", &dbv, DBVT_ASCIIZ)) + { + string_append(s, dbv.pszVal); + DBFreeVariant(&dbv); + } + string_append_c(s, ';'); + + // Readup Nick + if (!db_get_s(hContact, "CList", "MyHandle", &dbv, DBVT_ASCIIZ) || !db_get_s(hContact, gg->m_szModuleName, GG_KEY_NICK, &dbv, DBVT_ASCIIZ)) + { + DBVARIANT dbv2; + if (!db_get_s(hContact, gg->m_szModuleName, "NickName", &dbv2, DBVT_ASCIIZ)) + { + string_append(s, dbv2.pszVal); + DBFreeVariant(&dbv2); + } + else + string_append(s, dbv.pszVal); + string_append_c(s, ';'); + string_append(s, dbv.pszVal); + DBFreeVariant(&dbv); + } + else + string_append_c(s, ';'); + string_append_c(s, ';'); + + // Readup Phone (fixed: uses stored editable phones) + if (!db_get_s(hContact, "UserInfo", "MyPhone0", &dbv, DBVT_ASCIIZ)) + { + // Remove SMS postfix + char *sms = strstr(dbv.pszVal, " SMS"); + if (sms) *sms = 0; + + string_append(s, dbv.pszVal); + DBFreeVariant(&dbv); + } + string_append_c(s, ';'); + // Readup Group + if (!db_get_s(hContact, "CList", "Group", &dbv, DBVT_ASCIIZ)) + { + string_append(s, dbv.pszVal); + DBFreeVariant(&dbv); + } + string_append_c(s, ';'); + // Readup Uin + string_append(s, ditoa(db_get_dw(hContact, gg->m_szModuleName, GG_KEY_UIN, 0))); + string_append_c(s, ';'); + // Readup Mail (fixed: uses stored editable mails) + if (!db_get_s(hContact, "UserInfo", "Mye-mail0", &dbv, DBVT_ASCIIZ)) + { + string_append(s, dbv.pszVal); + DBFreeVariant(&dbv); + } + if (cr) + string_append(s, ";0;;0;\r\n"); + else + string_append(s, ";0;;0;\n"); + } + hContact = db_find_next(hContact); + } + + contacts = string_free(s, 0); + +#ifdef DEBUGMODE + gg->netlog("gg_makecontacts(): \n%s", contacts); +#endif + + return contacts; +} + +char *strndup(char *str, int c) +{ + char *ret = (char*)malloc(c + 1); + ret[c] = 0; + strncpy(ret, str, c); + return ret; +} + +void GGPROTO::parsecontacts(char *contacts) +{ + char *p = strchr(contacts, ':'), *n; + char *strFirstName, *strLastName, *strNickname, *strNick, *strPhone, *strGroup, *strUin, *strMail; + uin_t uin; + + // Skip to proper data + if (p && p < strchr(contacts, ';')) p++; + else p = contacts; + + while(p) + { + // Processing line + strFirstName = strLastName = strNickname = strNick = strPhone = strGroup = strUin = strMail = NULL; + uin = 0; + + // FirstName + if (p) + { + n = strchr(p, ';'); + if (n && n != p) strFirstName = strndup(p, (n - p)); + p = (n + 1); + } + // LastName + if (n && p) + { + n = strchr(p, ';'); + if (n && n != p) strLastName = strndup(p, (n - p)); + p = (n + 1); + } + // Nickname + if (n && p) + { + n = strchr(p, ';'); + if (n && n != p) strNickname = strndup(p, (n - p)); + p = (n + 1); + } + // Nick + if (n && p) + { + n = strchr(p, ';'); + if (n && n != p) strNick = strndup(p, (n - p)); + p = (n + 1); + } + // Phone + if (n && p) + { + n = strchr(p, ';'); + if (n && n != p) + { + strPhone = (char*)malloc((n - p) + 5); + strncpy(strPhone, p, (n - p)); + strcpy((strPhone + (n - p)), " SMS"); // Add SMS postfix + } + p = (n + 1); + } + // Group + if (n && p) + { + n = strchr(p, ';'); + if (n && n != p) strGroup = strndup(p, (n - p)); + p = (n + 1); + } + // Uin + if (n && p) + { + n = strchr(p, ';'); + if (n && n != p) + { + strUin = strndup(p, (n - p)); + uin = atoi(strUin); + } + p = (n + 1); + } + // Mail + if (n && p) + { + n = strchr(p, ';'); + if (n && n != p) strMail = strndup(p, (n - p)); + n = strchr(p, '\n'); + p = (n + 1); + } + if (!n) p = NULL; + + // Loadup contact + if (uin && strNick) + { + HANDLE hContact = getcontact(uin, 1, 1, _A2T(strNick)); +#ifdef DEBUGMODE + netlog("gg_parsecontacts(): Found contact %d with nickname \"%s\".", uin, strNick); +#endif + // Write group + if (hContact && strGroup) + db_set_s(hContact, "CList", "Group", CreateGroup(strGroup)); + + // Write misc data + if (hContact && strFirstName) db_set_s(hContact, m_szModuleName, "FirstName", strFirstName); + if (hContact && strLastName) db_set_s(hContact, m_szModuleName, "LastName", strLastName); + if (hContact && strPhone) db_set_s(hContact, "UserInfo", "MyPhone0", strPhone); // Store now in User Info + if (hContact && strMail) db_set_s(hContact, "UserInfo", "Mye-mail0", strMail); // Store now in User Info + } + + // Release stuff + if (strFirstName) free(strFirstName); + if (strLastName) free(strLastName); + if (strNickname) free(strNickname); + if (strNick) free(strNick); + if (strPhone) free(strPhone); + if (strGroup) free(strGroup); + if (strUin) free(strUin); + if (strMail) free(strMail); + } +} + +////////////////////////////////////////////////////////// +// import from server + +INT_PTR GGPROTO::import_server(WPARAM wParam, LPARAM lParam) +{ + char *password; + uin_t uin; + DBVARIANT dbv; + + // Check if connected + if (!isonline()) + { + MessageBox(NULL, + TranslateT("You have to be connected before you can import/export contacts from/to server."), + m_tszUserName, MB_OK | MB_ICONSTOP + ); + return 0; + } + + // Readup password + if (!db_get_s(NULL, m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) + { + CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM) dbv.pszVal); + password = _strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + else return 0; + + if (!(uin = db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0))) + return 0; + + // Making contacts list + EnterCriticalSection(&sess_mutex); + if (gg_userlist_request(sess, GG_USERLIST_GET, NULL) == -1) + { + TCHAR error[128]; + LeaveCriticalSection(&sess_mutex); + mir_sntprintf(error, SIZEOF(error), TranslateT("List cannot be imported because of error:\n\t%s"), _tcserror(errno)); + MessageBox(NULL, error, m_tszUserName, MB_OK | MB_ICONSTOP); + netlog("gg_import_server(): Cannot import list because of \"%s\".", strerror(errno)); + } + LeaveCriticalSection(&sess_mutex); + free(password); + + return 0; +} + +////////////////////////////////////////////////////////// +// remove from server + +INT_PTR GGPROTO::remove_server(WPARAM wParam, LPARAM lParam) +{ + char *password; + uin_t uin; + DBVARIANT dbv; + + // Check if connected + if (!isonline()) + { + MessageBox(NULL, + TranslateT("You have to be connected before you can import/export contacts from/to server."), + m_tszUserName, MB_OK | MB_ICONSTOP + ); + return 0; + } + + // Readup password + if (!db_get_s(NULL, m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) + { + CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM) dbv.pszVal); + password = _strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + else return 0; + + if (!(uin = db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0))) + return 0; + + // Making contacts list + EnterCriticalSection(&sess_mutex); + if (gg_userlist_request(sess, GG_USERLIST_PUT, NULL) == -1) + { + TCHAR error[128]; + LeaveCriticalSection(&sess_mutex); + mir_sntprintf(error, SIZEOF(error), TranslateT("List cannot be removeed because of error:\n\t%s"), strerror(errno)); + MessageBox(NULL, error, m_tszUserName, MB_OK | MB_ICONSTOP); + netlog("gg_remove_server(): Cannot remove list because of \"%s\".", strerror(errno)); + } + LeaveCriticalSection(&sess_mutex); + + // Set list removal + is_list_remove = TRUE; + free(password); + + return 0; +} + +INT_PTR GGPROTO::import_text(WPARAM wParam, LPARAM lParam) +{ + TCHAR str[MAX_PATH]; + TCHAR filter[512], *pfilter; + struct _stat st; + FILE *f; + + OPENFILENAME ofn = {0}; + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; + _tcsncpy(filter, TranslateT("Text files"), SIZEOF(filter)); + _tcsncat(filter, _T(" (*.txt)"), SIZEOF(filter) - _tcslen(filter)); + pfilter = filter + _tcslen(filter) + 1; + if (pfilter >= filter + SIZEOF(filter)) + return 0; + + _tcsncpy(pfilter, _T("*.TXT"), SIZEOF(filter) - (pfilter - filter)); + pfilter = pfilter + _tcslen(pfilter) + 1; + if (pfilter >= filter + SIZEOF(filter)) + return 0; + _tcsncpy(pfilter, TranslateT("All Files"), SIZEOF(filter) - (pfilter - filter)); + _tcsncat(pfilter, _T(" (*)"), SIZEOF(filter) - (pfilter - filter) - _tcslen(pfilter)); + pfilter = pfilter + _tcslen(pfilter) + 1; + + if (pfilter >= filter + SIZEOF(filter)) + return 0; + + _tcsncpy(pfilter, _T("*"), SIZEOF(filter) - (pfilter - filter)); + pfilter = pfilter + _tcslen(pfilter) + 1; + if (pfilter >= filter + SIZEOF(filter)) + return 0; + + *pfilter = '\0'; + ofn.lpstrFilter = filter; + ofn.lpstrFile = str; + ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY; + ofn.nMaxFile = sizeof(str); + ofn.nMaxFileTitle = MAX_PATH; + ofn.lpstrDefExt = _T("txt"); + +#ifdef DEBUGMODE + netlog("gg_import_text()"); +#endif + if (!GetOpenFileName(&ofn)) return 0; + + f = _tfopen(str, _T("r")); + _tstat(str, &st); + + if (f && st.st_size) + { + char *contacts = (char*)malloc(st.st_size * sizeof(char)); + fread(contacts, sizeof(char), st.st_size, f); + fclose(f); + parsecontacts(contacts); + free(contacts); + + MessageBox(NULL, TranslateT("List import successful."), m_tszUserName, MB_OK | MB_ICONINFORMATION); + } + else + { + TCHAR error[128]; + mir_sntprintf(error, SIZEOF(error), TranslateT("List cannot be imported from file \"%s\" because of error:\n\t%s"), str, _tcserror(errno)); + MessageBox(NULL, error, m_tszUserName, MB_OK | MB_ICONSTOP); + netlog("gg_import_text(): Cannot import list from file \"%S\" because of \"%s\".", str, strerror(errno)); + } + + return 0; +} + +INT_PTR GGPROTO::export_text(WPARAM wParam, LPARAM lParam) +{ + TCHAR str[MAX_PATH]; + OPENFILENAME ofn = {0}; + TCHAR filter[512], *pfilter; + FILE *f; + + _tcsncpy(str, TranslateT("contacts"), sizeof(str)); + _tcsncat(str, _T(".txt"), sizeof(str) - _tcslen(str)); + + ofn.lStructSize = OPENFILENAME_SIZE_VERSION_400; + _tcsncpy(filter, TranslateT("Text files"), SIZEOF(filter)); + _tcsncat(filter, _T(" (*.txt)"), SIZEOF(filter) - _tcslen(filter)); + pfilter = filter + _tcslen(filter) + 1; + if (pfilter >= filter + SIZEOF(filter)) + return 0; + _tcsncpy(pfilter, _T("*.TXT"), SIZEOF(filter) - (pfilter - filter)); + pfilter = pfilter + _tcslen(pfilter) + 1; + if (pfilter >= filter + SIZEOF(filter)) + return 0; + _tcsncpy(pfilter, TranslateT("All Files"), SIZEOF(filter) - (pfilter - filter)); + _tcsncat(pfilter, _T(" (*)"), SIZEOF(filter) - (pfilter - filter) - _tcslen(pfilter)); + pfilter = pfilter + _tcslen(pfilter) + 1; + if (pfilter >= filter + SIZEOF(filter)) + return 0; + _tcsncpy(pfilter, _T("*"), SIZEOF(filter) - (pfilter - filter)); + pfilter = pfilter + _tcslen(pfilter) + 1; + if (pfilter >= filter + SIZEOF(filter)) + return 0; + *pfilter = '\0'; + ofn.lpstrFilter = filter; + ofn.lpstrFile = str; + ofn.Flags = OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY; + ofn.nMaxFile = sizeof(str); + ofn.nMaxFileTitle = MAX_PATH; + ofn.lpstrDefExt = _T("txt"); + +#ifdef DEBUGMODE + netlog("gg_export_text(%s).", str); +#endif + if (!GetSaveFileName(&ofn)) return 0; + + if (f = _tfopen(str, _T("w"))) { + char *contacts = gg_makecontacts(this, 0); + fwrite(contacts, sizeof(char), strlen(contacts), f); + fclose(f); + free(contacts); + + MessageBox(NULL, TranslateT("List export successful."), m_tszUserName, MB_OK | MB_ICONINFORMATION); + } + else + { + TCHAR error[128]; + mir_sntprintf(error, SIZEOF(error), TranslateT("List cannot be exported to file \"%s\" because of error:\n\t%s"), str, _tcserror(errno)); + MessageBox(NULL, error, m_tszUserName, MB_OK | MB_ICONSTOP); + netlog("gg_import_text(): Cannot export list to file \"%s\" because of \"%s\".", str, strerror(errno)); + } + + return 0; +} + +////////////////////////////////////////////////////////// +// export to server + +INT_PTR GGPROTO::export_server(WPARAM wParam, LPARAM lParam) +{ + char *password, *contacts; + uin_t uin; + DBVARIANT dbv; + + // Check if connected + if (!isonline()) + { + MessageBox(NULL, + TranslateT("You have to be connected before you can import/export contacts from/to server."), + m_tszUserName, MB_OK | MB_ICONSTOP + ); + return 0; + } + + // Readup password + if (!db_get_s(NULL, m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) + { + CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM) dbv.pszVal); + password = _strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + else return 0; + + if (!(uin = db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0))) + return 0; + + // Making contacts list + contacts = gg_makecontacts(this, 1); + +#ifdef DEBUGMODE + netlog("gg_userlist_request(%s).", contacts); +#endif + + EnterCriticalSection(&sess_mutex); + if (gg_userlist_request(sess, GG_USERLIST_PUT, contacts) == -1) + { + TCHAR error[128]; + LeaveCriticalSection(&sess_mutex); + mir_sntprintf(error, SIZEOF(error), TranslateT("List cannot be exported because of error:\n\t%s"), _tcserror(errno)); + MessageBox(NULL, error, m_tszUserName, MB_OK | MB_ICONSTOP); + netlog("gg_export_server(): Cannot export list because of \"%s\".", strerror(errno)); + } + LeaveCriticalSection(&sess_mutex); + + // Set list removal + is_list_remove = FALSE; + free(contacts); + free(password); + + return 0; +} + +////////////////////////////////////////////////////////// +// Import menus and stuff + +void GGPROTO::import_init(HGENMENU hRoot) +{ + CLISTMENUITEM mi = {0}; + char service[64]; + + mi.cbSize = sizeof(mi); + mi.flags = CMIF_ICONFROMICOLIB | CMIF_ROOTHANDLE; + mi.hParentMenu = hRoot; + + // Import from server item + mir_snprintf(service, sizeof(service), GGS_IMPORT_SERVER, m_szModuleName); + createObjService(service, &GGPROTO::import_server); + mi.position = 2000500001; + mi.icolibItem = GetIconHandle(IDI_IMPORT_SERVER); + mi.pszName = LPGEN("Import List From &Server"); + mi.pszService = service; + hMainMenu[2] = Menu_AddProtoMenuItem(&mi); + + // Import from textfile + mir_snprintf(service, sizeof(service), GGS_IMPORT_TEXT, m_szModuleName); + createObjService(service, &GGPROTO::import_text); + mi.position = 2000500002; + mi.icolibItem = GetIconHandle(IDI_IMPORT_TEXT); + mi.pszName = LPGEN("Import List From &Text File..."); + mi.pszService = service; + hMainMenu[3] = Menu_AddProtoMenuItem(&mi); + + // Remove from server + mir_snprintf(service, sizeof(service), GGS_REMOVE_SERVER, m_szModuleName); + createObjService(service, &GGPROTO::remove_server); + mi.position = 2000500003; + mi.icolibItem = GetIconHandle(IDI_REMOVE_SERVER); + mi.pszName = LPGEN("&Remove List From Server"); + mi.pszService = service; + hMainMenu[4] = Menu_AddProtoMenuItem(&mi); + + // Export to server + mir_snprintf(service, sizeof(service), GGS_EXPORT_SERVER, m_szModuleName); + createObjService(service, &GGPROTO::export_server); + mi.position = 2005000001; + mi.icolibItem = GetIconHandle(IDI_EXPORT_SERVER); + mi.pszName = LPGEN("Export List To &Server"); + mi.pszService = service; + hMainMenu[5] = Menu_AddProtoMenuItem(&mi); + + // Export to textfile + mir_snprintf(service, sizeof(service), GGS_EXPORT_TEXT, m_szModuleName); + createObjService(service, &GGPROTO::export_text); + mi.position = 2005000002; + mi.icolibItem = GetIconHandle(IDI_EXPORT_TEXT); + mi.pszName = LPGEN("Export List To &Text File..."); + mi.pszService = service; + hMainMenu[6] = Menu_AddProtoMenuItem(&mi); +} diff --git a/protocols/Gadu-Gadu/src/keepalive.cpp b/protocols/Gadu-Gadu/src/keepalive.cpp new file mode 100644 index 0000000000..02b661c222 --- /dev/null +++ b/protocols/Gadu-Gadu/src/keepalive.cpp @@ -0,0 +1,92 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2006 Adam Strzelecki +// +// 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 "gg.h" + +/* NOTE: Eventhough SetTimer seems to support UINT_PTR for idEvent, it seems that TimerProc + * does not get full pointer but just 2 byte lower bytes. + */ +#define MAX_TIMERS 8 +GGPROTO *g_timers[MAX_TIMERS] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; + +static VOID CALLBACK gg_keepalive(HWND hwnd, UINT message, UINT_PTR idEvent, DWORD dwTime) +{ + int i; + + //Search for GGPROTO* context + for(i = 0; i < MAX_TIMERS; i++) + if (g_timers[i]->timer == idEvent) + break; + + if (i < MAX_TIMERS) + { + GGPROTO *gg = g_timers[i]; + if (gg->isonline()) + { + #ifdef DEBUGMODE + gg->netlog("Sending keep-alive"); + #endif + EnterCriticalSection(&gg->sess_mutex); + gg_ping(gg->sess); + LeaveCriticalSection(&gg->sess_mutex); + } + } +} + +void GGPROTO::keepalive_init() +{ + if (db_get_b(NULL, m_szModuleName, GG_KEY_KEEPALIVE, GG_KEYDEF_KEEPALIVE)) + { + int i; + for(i = 0; i < MAX_TIMERS && g_timers[i] != NULL; i++); + if (i < MAX_TIMERS) + { + #ifdef DEBUGMODE + netlog("gg_keepalive_init(): Initializing Timer %d", i); + #endif + timer = SetTimer(NULL, 0, 1000 * 30, gg_keepalive); + g_timers[i] = this; + } + } +} + +void GGPROTO::keepalive_destroy() +{ +#ifdef DEBUGMODE + netlog("gg_destroykeepalive(): Killing Timer"); +#endif + if (timer) + { + int i; + KillTimer(NULL, timer); + for(i = 0; i < MAX_TIMERS; i++) + if (g_timers[i] == this) { + g_timers[i] = NULL; + break; + } + timer = 0; +#ifdef DEBUGMODE + netlog("gg_destroykeepalive(): Killed Timer %d", i); +#endif + } +#ifdef DEBUGMODE + netlog("gg_destroykeepalive(): End"); +#endif +} diff --git a/protocols/Gadu-Gadu/src/libgadu/COPYING b/protocols/Gadu-Gadu/src/libgadu/COPYING new file mode 100644 index 0000000000..6e816402b7 --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/protocols/Gadu-Gadu/src/libgadu/common.c b/protocols/Gadu-Gadu/src/libgadu/common.c new file mode 100644 index 0000000000..b9b41c0547 --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/common.c @@ -0,0 +1,975 @@ +/* coding: UTF-8 */ +/* $Id: common.c 13762 2011-08-09 12:35:16Z dezred $ */ + +/* + * (C) Copyright 2001-2002 Wojtek Kaniewski + * Robert J. WoĹşny + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +/* + * Funkcje konwersji miÄ™dzy UTF-8 i CP1250 sÄ… oparte o kod biblioteki iconv. + * Informacje o prawach autorskich oryginalnego kodu zamieszczono poniĹĽej: + * + * Copyright (C) 1999-2001, 2004 Free Software Foundation, Inc. + * This file is part of the GNU LIBICONV Library. + * + * The GNU LIBICONV Library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * The GNU LIBICONV Library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the GNU LIBICONV Library; see the file COPYING.LIB. + * If not, write to the Free Software Foundation, Inc., 51 Franklin Street, + * Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** + * \file common.c + * + * \brief Funkcje wykorzystywane przez różne moduĹ‚y biblioteki + */ +#include +#ifdef _WIN32 +#include "win32.h" +#else +#include +#include +#include +#include +#ifdef sun +# include +#endif +#endif /* _WIN32 */ + +#include +#include +#ifndef _WIN32 +#include +#endif /* _WIN32 */ +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif /* _WIN32 */ + +#include "libgadu.h" +#include "internal.h" + +/** + * Plik, do ktĂłrego bÄ™dÄ… przekazywane informacje odpluskwiania. + * + * Funkcja \c gg_debug() i pochodne mogÄ… być przechwytywane przez aplikacjÄ™ + * korzystajÄ…cÄ… z biblioteki, by wyĹ›wietlić je na ĹĽÄ…danie uĹĽytkownika lub + * zapisać do późniejszej analizy. JeĹ›li nie okreĹ›lono pliku, wybrane + * informacje bÄ™dÄ… wysyĹ‚ane do standardowego wyjĹ›cia bĹ‚Ä™du (\c stderr). + * + * \ingroup debug + */ +FILE *gg_debug_file = NULL; + +#ifndef GG_DEBUG_DISABLE + +/** + * \internal Przekazuje informacje odpluskwiania do odpowiedniej funkcji. + * + * JeĹ›li aplikacja ustawiĹ‚a odpowiedniÄ… funkcjÄ™ obsĹ‚ugi w + * \c gg_debug_handler_session lub \c gg_debug_handler, jest ona wywoĹ‚ywana. + * W przeciwnym wypadku wynik jest wysyĹ‚any do standardowego wyjĹ›cia bĹ‚Ä™du. + * + * \param sess Struktura sesji (moĹĽe być \c NULL) + * \param level Poziom informacji + * \param format Format wiadomoĹ›ci (zgodny z \c printf) + * \param ap Lista argumentĂłw (zgodna z \c printf) + */ +static void gg_debug_common(struct gg_session *sess, int level, const char *format, va_list ap) +{ + if (gg_debug_handler_session) + (*gg_debug_handler_session)(sess, level, format, ap); + else if (gg_debug_handler) + (*gg_debug_handler)(level, format, ap); + else if (gg_debug_level & level) + vfprintf(gg_debug_file ? gg_debug_file : stderr, format, ap); +} + + +/** + * \internal Przekazuje informacjÄ™ odpluskawiania. + * + * \param level Poziom wiadomoĹ›ci + * \param format Format wiadomoĹ›ci (zgodny z \c printf) + * + * \ingroup debug + */ +void gg_debug(int level, const char *format, ...) +{ + va_list ap; + int old_errno = errno; + va_start(ap, format); + gg_debug_common(NULL, level, format, ap); + va_end(ap); + errno = old_errno; +} + +/** + * \internal Przekazuje informacjÄ™ odpluskwiania zwiÄ…zanÄ… z sesjÄ…. + * + * \param sess Struktura sesji + * \param level Poziom wiadomoĹ›ci + * \param format Format wiadomoĹ›ci (zgodny z \c printf) + * + * \ingroup debug + */ +void gg_debug_session(struct gg_session *sess, int level, const char *format, ...) +{ + va_list ap; + int old_errno = errno; + va_start(ap, format); + gg_debug_common(sess, level, format, ap); + va_end(ap); + errno = old_errno; +} + +/** + * \internal Przekazuje informacjÄ™ odpluskwiania zwiÄ…zane z zawartoĹ›ciÄ… pamiÄ™ci. + * + * \param sess Struktura sesji + * \param buf Adres w pamiÄ™ci + * \param buf_length Ilość danych do wyĹ›wietlenia + * \param format Format wiadomoĹ›ci (zgodny z \c printf) + * + * \ingroup debug + */ +void gg_debug_dump_session(struct gg_session *sess, const void *buf, unsigned int buf_length, const char *format, ...) +{ + va_list ap; + + if ((gg_debug_level & GG_DEBUG_DUMP)) { + unsigned int i; + + va_start(ap, format); + gg_debug_common(sess, GG_DEBUG_DUMP, format, ap); + for (i = 0; i < buf_length; ++i) + gg_debug_session(sess, GG_DEBUG_DUMP, " %.2x", ((unsigned char*) buf)[i]); + gg_debug_session(sess, GG_DEBUG_DUMP, "\n"); + va_end(ap); + } +} + +#endif + +/** + * \internal Odpowiednik funkcji \c vsprintf alokujÄ…cy miejsce na wynik. + * + * Funkcja korzysta z funkcji \c vsnprintf, sprawdzajÄ…c czy dostÄ™pna funkcja + * systemowa jest zgodna ze standardem C99 czy wczeĹ›niejszymi. + * + * \param format Format wiadomoĹ›ci (zgodny z \c printf) + * \param ap Lista argumentĂłw (zgodna z \c printf) + * + * \return Zaalokowany bufor lub NULL, jeĹ›li zabrakĹ‚o pamiÄ™ci. + * + * \ingroup helper + */ +char *gg_vsaprintf(const char *format, va_list ap) +{ + int size = 0; + char *buf = NULL; + +#ifdef GG_CONFIG_HAVE_VA_COPY + va_list aq; + + va_copy(aq, ap); +#else +# ifdef GG_CONFIG_HAVE___VA_COPY + va_list aq; + + __va_copy(aq, ap); +# endif +#endif + +#ifndef GG_CONFIG_HAVE_C99_VSNPRINTF + { + int res; + char *tmp; + + size = 128; + do { + size *= 2; + if (!(tmp = realloc(buf, size))) { + free(buf); + return NULL; + } + buf = tmp; + res = vsnprintf(buf, size, format, ap); + } while (res == size - 1 || res == -1); + } +#else + { + char tmp[2]; + + /* libce Solarisa przy buforze NULL zawsze zwracajÄ… -1, wiÄ™c + * musimy podać coĹ› istniejÄ…cego jako cel printf()owania. */ + size = vsnprintf(tmp, sizeof(tmp), format, ap); + if (!(buf = malloc(size + 1))) + return NULL; + } +#endif + +#ifdef GG_CONFIG_HAVE_VA_COPY + vsnprintf(buf, size + 1, format, aq); + va_end(aq); +#else +# ifdef GG_CONFIG_HAVE___VA_COPY + vsnprintf(buf, size + 1, format, aq); + va_end(aq); +# else + vsnprintf(buf, size + 1, format, ap); +# endif +#endif + + return buf; +} + +/** + * \internal Odpowiednik funkcji \c sprintf alokujÄ…cy miejsce na wynik. + * + * Funkcja korzysta z funkcji \c vsnprintf, sprawdzajÄ…c czy dostÄ™pna funkcja + * systemowa jest zgodna ze standardem C99 czy wczeĹ›niejszymi. + * + * \param format Format wiadomoĹ›ci (zgodny z \c printf) + * + * \return Zaalokowany bufor lub NULL, jeĹ›li zabrakĹ‚o pamiÄ™ci. + * + * \ingroup helper + */ +char *gg_saprintf(const char *format, ...) +{ + va_list ap; + char *res; + + va_start(ap, format); + res = gg_vsaprintf(format, ap); + va_end(ap); + + return res; +} + +/** + * \internal Pobiera liniÄ™ tekstu z bufora. + * + * Funkcja niszczy bufor ĹşrĂłdĹ‚owy bezpowrotnie, dzielÄ…c go na kolejne ciÄ…gi + * znakĂłw i obcina znaki koĹ„ca linii. + * + * \param ptr WskaĹşnik do zmiennej, ktĂłra przechowuje aktualne poĹ‚oĹĽenie + * w analizowanym buforze + * + * \return WskaĹşnik do kolejnej linii tekstu lub NULL, jeĹ›li to juĹĽ koniec + * bufora. + */ +char *gg_get_line(char **ptr) +{ + char *foo, *res; + + if (!ptr || !*ptr || !strcmp(*ptr, "")) + return NULL; + + res = *ptr; + + if (!(foo = strchr(*ptr, '\n'))) + *ptr += strlen(*ptr); + else { + size_t len; + *ptr = foo + 1; + *foo = 0; + + len = strlen(res); + + if (len > 1 && res[len - 1] == '\r') + res[len - 1] = 0; + } + + return res; +} + +/** + * \internal Czyta liniÄ™ tekstu z gniazda. + * + * Funkcja czyta tekst znak po znaku, wiÄ™c nie jest efektywna, ale dziÄ™ki + * brakowi buforowania, nie koliduje z innymi funkcjami odczytu. + * + * \param sock Deskryptor gniazda + * \param buf WskaĹşnik do bufora + * \param length DĹ‚ugość bufora + * + * \return Zwraca \c buf jeĹ›li siÄ™ powiodĹ‚o, lub \c NULL w przypadku bĹ‚Ä™du. + */ +char *gg_read_line(SOCKET sock, char *buf, int length) +{ + int ret; + + if (!buf || length < 0) + return NULL; + + for (; length > 1; buf++, length--) { + do { + if ((ret = gg_sock_read(sock, buf, 1)) == -1 && errno != EINTR && errno != EAGAIN) { + gg_debug(GG_DEBUG_MISC, "// gg_read_line() error on read (errno=%d, %s)\n", errno, strerror(errno)); + *buf = 0; + return NULL; + } else if (ret == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_read_line() eof reached\n"); + *buf = 0; + return NULL; + } + } while (ret == -1 && (errno == EINTR || errno == EAGAIN)); + + if (*buf == '\n') { + buf++; + break; + } + } + + *buf = 0; + return buf; +} + +/** + * \internal NawiÄ…zuje poĹ‚Ä…czenie TCP. + * + * \param addr WskaĹşnik na strukturÄ™ \c in_addr z adresem serwera + * \param port Port serwera + * \param async Flaga asynchronicznego poĹ‚Ä…czenia + * + * \return Deskryptor gniazda lub -1 w przypadku bĹ‚Ä™du + * + * \ingroup helper + */ +#ifdef GG_CONFIG_MIRANDA +SOCKET gg_connect_internal(void *addr, int port, int async, SOCKET *gg_sock) +#else +SOCKET gg_connect(void *addr, int port, int async) +#endif +{ + SOCKET sock; + int one = 1, errno2; + struct sockaddr_in sin; + struct in_addr *a = addr; + struct sockaddr_in myaddr; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_connect(%s, %d, %d);\n", inet_ntoa(*a), port, async); + + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_connect() socket() failed (errno=%d, %s)\n", errno, strerror(errno)); + return -1; + } + + memset(&myaddr, 0, sizeof(myaddr)); + myaddr.sin_family = AF_INET; + + myaddr.sin_addr.s_addr = gg_local_ip; + + if (bind(sock, (struct sockaddr *) &myaddr, sizeof(myaddr)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_connect() bind() failed (errno=%d, %s)\n", errno, strerror(errno)); + errno2 = errno; + gg_sock_close(sock); + errno = errno2; + return -1; + } + +#ifdef GG_CONFIG_MIRANDA + if (gg_sock) *gg_sock = sock; +#endif + + if (async) { +#ifdef FIONBIO + if (ioctl(sock, FIONBIO, &one) == -1) { +#else + if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_connect() ioctl() failed (errno=%d, %s)\n", errno, strerror(errno)); + errno2 = errno; + gg_sock_close(sock); + errno = errno2; + return -1; + } + } + + sin.sin_port = htons((uint16_t)port); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = a->s_addr; + + errno = 0; + if (connect(sock, (struct sockaddr*) &sin, sizeof(sin)) == -1) { + if (errno && (!async || errno != EINPROGRESS)) { + gg_debug(GG_DEBUG_MISC, "// gg_connect() connect() failed (errno=%d, %s)\n", errno, strerror(errno)); + errno2 = errno; + gg_sock_close(sock); + errno = errno2; + return -1; + } + gg_debug(GG_DEBUG_MISC, "// gg_connect() connect() in progress\n"); + } + + return sock; +} + +#ifdef GG_CONFIG_MIRANDA +SOCKET gg_connect(void *addr, int port, int async) +{ + return gg_connect_internal(addr, port, async, 0); +} +#endif + +/** + * \internal Usuwa znaki koĹ„ca linii. + * + * Funkcja dziaĹ‚a bezpoĹ›rednio na buforze. + * + * \param line Bufor z tekstem + * + * \ingroup helper + */ +void gg_chomp(char *line) +{ + size_t len; + + if (!line) + return; + + len = strlen(line); + + if (len > 0 && line[len - 1] == '\n') + line[--len] = 0; + if (len > 0 && line[len - 1] == '\r') + line[--len] = 0; +} + +/** + * \internal Koduje ciÄ…g znakĂłw do postacji adresu HTTP. + * + * Zamienia znaki niedrukowalne, spoza ASCII i majÄ…ce specjalne znaczenie + * dla protokoĹ‚u HTTP na encje postaci \c %XX, gdzie \c XX jest szesnastkowÄ… + * wartoĹ›ciÄ… znaku. + * + * \param str CiÄ…g znakĂłw do zakodowania + * + * \return Zaalokowany bufor lub \c NULL w przypadku bĹ‚Ä™du. + * + * \ingroup helper + */ +char *gg_urlencode(const char *str) +{ + char *q, *buf, hex[] = "0123456789abcdef"; + const char *p; + unsigned int size = 0; + + if (!str) + str = ""; + + for (p = str; *p; p++, size++) { + if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || *p == ' ') || (*p == '@') || (*p == '.') || (*p == '-')) + size += 2; + } + + if (!(buf = malloc(size + 1))) + return NULL; + + for (p = str, q = buf; *p; p++, q++) { + if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') || (*p >= '0' && *p <= '9') || (*p == '@') || (*p == '.') || (*p == '-')) + *q = *p; + else { + if (*p == ' ') + *q = '+'; + else { + *q++ = '%'; + *q++ = hex[*p >> 4 & 15]; + *q = hex[*p & 15]; + } + } + } + + *q = 0; + + return buf; +} + +/** + * \internal Wyznacza skrĂłt dla usĹ‚ug HTTP. + * + * Funkcja jest wykorzystywana do wyznaczania skrĂłtu adresu e-mail, hasĹ‚a + * i innych wartoĹ›ci przekazywanych jako parametry usĹ‚ug HTTP. + * + * W parametrze \c format naleĹĽy umieĹ›cić znaki okreĹ›lajÄ…ce postać kolejnych + * parametrĂłw: \c 's' jeĹ›li parametr jest ciÄ…giem znakĂłw, \c 'u' jeĹ›li jest + * liczbÄ…. + * + * \param format Format kolejnych parametrĂłw (niezgodny z \c printf) + * + * \return Wartość skrĂłtu + */ +int gg_http_hash(const char *format, ...) +{ + unsigned int a, c, i, j; + va_list ap; + int b = -1; + + va_start(ap, format); + + for (j = 0; j < strlen(format); j++) { + char *arg, buf[16]; + + if (format[j] == 'u') { + snprintf(buf, sizeof(buf), "%d", va_arg(ap, uin_t)); + arg = buf; + } else { + if (!(arg = va_arg(ap, char*))) + arg = ""; + } + + i = 0; + while ((c = (unsigned char) arg[i++]) != 0) { + a = (c ^ b) + (c << 8); + b = (a >> 24) | (a << 8); + } + } + + va_end(ap); + + return (b < 0 ? -b : b); +} + +/** + * \internal Zestaw znakĂłw kodowania base64. + */ +static char gg_base64_charset[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +/** + * \internal Koduje ciÄ…g znakĂłw do base64. + * + * Wynik funkcji naleĹĽy zwolnić za pomocÄ… \c free. + * + * \param buf Bufor z danami do zakodowania + * + * \return Zaalokowany bufor z zakodowanymi danymi + * + * \ingroup helper + */ +char *gg_base64_encode(const char *buf) +{ + char *out, *res; + unsigned int i = 0, j = 0, k = 0, len = (unsigned int)strlen(buf); + + res = out = malloc((len / 3 + 1) * 4 + 2); + + if (!res) + return NULL; + + while (j <= len) { + switch (i % 4) { + case 0: + k = (buf[j] & 252) >> 2; + break; + case 1: + if (j < len) + k = ((buf[j] & 3) << 4) | ((buf[j + 1] & 240) >> 4); + else + k = (buf[j] & 3) << 4; + + j++; + break; + case 2: + if (j < len) + k = ((buf[j] & 15) << 2) | ((buf[j + 1] & 192) >> 6); + else + k = (buf[j] & 15) << 2; + + j++; + break; + case 3: + k = buf[j++] & 63; + break; + } + *out++ = gg_base64_charset[k]; + i++; + } + + if (i % 4) + for (j = 0; j < 4 - (i % 4); j++, out++) + *out = '='; + + *out = 0; + + return res; +} + +/** + * \internal Dekoduje ciÄ…g znakĂłw zapisany w base64. + * + * Wynik funkcji naleĹĽy zwolnić za pomocÄ… \c free. + * + * \param buf Bufor ĹşrĂłdĹ‚owy z danymi do zdekodowania + * + * \return Zaalokowany bufor ze zdekodowanymi danymi + * + * \ingroup helper + */ +char *gg_base64_decode(const char *buf) +{ + char *res, *save, *foo, val; + const char *end; + unsigned int index = 0; + + if (!buf) + return NULL; + + save = res = calloc(1, (strlen(buf) / 4 + 1) * 3 + 2); + + if (!save) + return NULL; + + end = buf + strlen(buf); + + while (*buf && buf < end) { + if (*buf == '\r' || *buf == '\n') { + buf++; + continue; + } + if (!(foo = strchr(gg_base64_charset, *buf))) + foo = gg_base64_charset; + val = (int)(foo - gg_base64_charset); + buf++; + switch (index) { + case 0: + *res |= val << 2; + break; + case 1: + *res++ |= val >> 4; + *res |= val << 4; + break; + case 2: + *res++ |= val >> 2; + *res |= val << 6; + break; + case 3: + *res++ |= val; + break; + } + index++; + index %= 4; + } + *res = 0; + + return save; +} + +/** + * \internal Tworzy nagĹ‚Ăłwek autoryzacji serwera poĹ›redniczÄ…cego. + * + * Dane pobiera ze zmiennych globalnych \c gg_proxy_username i + * \c gg_proxy_password. + * + * \return Zaalokowany bufor z tekstem lub NULL, jeĹ›li serwer poĹ›redniczÄ…cy + * nie jest uĹĽywany lub nie wymaga autoryzacji. + */ +char *gg_proxy_auth() +{ + char *tmp, *enc, *out; + size_t tmp_size; + + if (!gg_proxy_enabled || !gg_proxy_username || !gg_proxy_password) + return NULL; + + if (!(tmp = malloc((tmp_size = strlen(gg_proxy_username) + strlen(gg_proxy_password) + 2)))) + return NULL; + + snprintf(tmp, tmp_size, "%s:%s", gg_proxy_username, gg_proxy_password); + + if (!(enc = gg_base64_encode(tmp))) { + free(tmp); + return NULL; + } + + free(tmp); + + if (!(out = malloc(strlen(enc) + 40))) { + free(enc); + return NULL; + } + + snprintf(out, strlen(enc) + 40, "Proxy-Authorization: Basic %s\r\n", enc); + + free(enc); + + return out; +} + +/** + * \internal Tablica pomocnicza do wyznaczania sumy kontrolnej. + */ +static uint32_t gg_crc32_table[256]; + +/** + * \internal Flaga wypeĹ‚nienia tablicy pomocniczej do wyznaczania sumy + * kontrolnej. + */ +static int gg_crc32_initialized = 0; + +/** + * \internal Tworzy tablicÄ™ pomocniczÄ… do wyznaczania sumy kontrolnej. + */ +static void gg_crc32_make_table(void) +{ + uint32_t h = 1; + unsigned int i, j; + + memset(gg_crc32_table, 0, sizeof(gg_crc32_table)); + + for (i = 128; i; i >>= 1) { + h = (h >> 1) ^ ((h & 1) ? 0xedb88320L : 0); + + for (j = 0; j < 256; j += 2 * i) + gg_crc32_table[i + j] = gg_crc32_table[j] ^ h; + } + + gg_crc32_initialized = 1; +} + +/** + * Wyznacza sumÄ™ kontrolnÄ… CRC32. + * + * \param crc Suma kontrola poprzedniego bloku danych lub 0 jeĹ›li liczona + * jest suma kontrolna pierwszego bloku + * \param buf Bufor danych + * \param len DĹ‚ugość bufora danych + * + * \return Suma kontrolna. + */ +uint32_t gg_crc32(uint32_t crc, const unsigned char *buf, int len) +{ + if (!gg_crc32_initialized) + gg_crc32_make_table(); + + if (!buf || len < 0) + return crc; + + crc ^= 0xffffffffL; + + while (len--) + crc = (crc >> 8) ^ gg_crc32_table[(crc ^ *buf++) & 0xff]; + + return crc ^ 0xffffffffL; +} + +/** + * \internal Tablica konwersji miÄ™dzy CP1250 a UTF-8. + */ +static const uint16_t table_cp1250[] = { + 0x20ac, '?', 0x201a, '?', 0x201e, 0x2026, 0x2020, 0x2021, + '?', 0x2030, 0x0160, 0x2039, 0x015a, 0x0164, 0x017d, 0x0179, + '?', 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, + '?', 0x2122, 0x0161, 0x203a, 0x015b, 0x0165, 0x017e, 0x017a, + 0x00a0, 0x02c7, 0x02d8, 0x0141, 0x00a4, 0x0104, 0x00a6, 0x00a7, + 0x00a8, 0x00a9, 0x015e, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x017b, + 0x00b0, 0x00b1, 0x02db, 0x0142, 0x00b4, 0x00b5, 0x00b6, 0x00b7, + 0x00b8, 0x0105, 0x015f, 0x00bb, 0x013d, 0x02dd, 0x013e, 0x017c, + 0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7, + 0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e, + 0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7, + 0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df, + 0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7, + 0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f, + 0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7, + 0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9, +}; + +/** + * \internal Zamienia tekst kodowany CP1250 na UTF-8. + * + * \param b Tekst ĹşrĂłdĹ‚owy w CP1250. + * + * \return Zaalokowany bufor z tekstem w UTF-8. + */ +char *gg_cp_to_utf8(const char *b) +{ + unsigned char *buf = (unsigned char *) b; + char *newbuf; + int newlen = 0; + int i, j; + + for (i = 0; buf[i]; i++) { + uint16_t znak = (buf[i] < 0x80) ? buf[i] : table_cp1250[buf[i]-0x80]; + + if (znak < 0x80) newlen += 1; + else if (znak < 0x800) newlen += 2; + else newlen += 3; + } + + if (!(newbuf = malloc(newlen+1))) { + gg_debug(GG_DEBUG_MISC, "// gg_cp_to_utf8() not enough memory\n"); + return NULL; + } + + for (i = 0, j = 0; buf[i]; i++) { + uint16_t znak = (buf[i] < 0x80) ? buf[i] : table_cp1250[buf[i]-0x80]; + int count; + + if (znak < 0x80) count = 1; + else if (znak < 0x800) count = 2; + else count = 3; + + switch (count) { + case 3: newbuf[j+2] = 0x80 | (znak & 0x3f); znak = znak >> 6; znak |= 0x800; + case 2: newbuf[j+1] = 0x80 | (znak & 0x3f); znak = znak >> 6; znak |= 0xc0; + case 1: newbuf[j] = (char)znak; + } + j += count; + } + newbuf[j] = '\0'; + + return newbuf; +} + +/** + * \internal Dekoduje jeden znak UTF-8. + * + * \note Funkcja nie jest kompletnÄ… implementacjÄ… UTF-8, a wersjÄ… uproszczonÄ… + * do potrzeb kodowania CP1250. + * + * \param s Tekst ĹşrĂłdĹ‚owy. + * \param n DĹ‚ugość tekstu ĹşrĂłdĹ‚owego. + * \param ch WskaĹşnik na wynik dekodowania. + * + * \return DĹ‚ugość zdekodowanej sekwencji w bajtach lub wartość mniejsza + * od zera w przypadku bĹ‚Ä™du. + */ +static int gg_utf8_helper(unsigned char *s, int n, uint16_t *ch) +{ + unsigned char c = s[0]; + + if (c < 0x80) { + *ch = c; + return 1; + } + + if (c < 0xc2) + return -1; + + if (c < 0xe0) { + if (n < 2) + return -2; + if (!((s[1] ^ 0x80) < 0x40)) + return -1; + *ch = ((uint16_t) (c & 0x1f) << 6) | (uint16_t) (s[1] ^ 0x80); + return 2; + } + + if (c < 0xf0) { + if (n < 3) + return -2; + if (!((s[1] ^ 0x80) < 0x40 && (s[2] ^ 0x80) < 0x40 && (c >= 0xe1 || s[1] >= 0xa0))) + return -1; + *ch = ((uint16_t) (c & 0x0f) << 12) | ((uint16_t) (s[1] ^ 0x80) << 6) | (uint16_t) (s[2] ^ 0x80); + return 3; + } + + return -1; +} + +/** + * \internal Zamienia tekst kodowany UTF-8 na CP1250. + * + * \param b Tekst ĹşrĂłdĹ‚owy w UTF-8. + * + * \return Zaalokowany bufor z tekstem w CP1250. + */ +char *gg_utf8_to_cp(const char *b) +{ + unsigned char *buf = (unsigned char *) b; + char *newbuf; + int newlen = 0; + int len; + int i, j; + + len = (int)strlen(b); + + for (i = 0; i < len; newlen++) { + uint16_t discard; + int ret; + + ret = gg_utf8_helper(&buf[i], len - i, &discard); + + if (ret > 0) + i += ret; + else + i++; + } + + if (!(newbuf = malloc(newlen+1))) { + gg_debug(GG_DEBUG_MISC, "// gg_utf8_to_cp() not enough memory\n"); + return NULL; + } + + for (i = 0, j = 0; buf[i]; j++) { + uint16_t znak; + int ret, k; + + ret = gg_utf8_helper(&buf[i], len - i, &znak); + + if (ret > 0) { + i += ret; + } else { + znak = '?'; + i++; + } + + if (znak < 0x80) { + newbuf[j] = (char)znak; + continue; + } + + newbuf[j] = '?'; + + for (k = 0; k < (sizeof(table_cp1250)/sizeof(table_cp1250[0])); k++) { + if (table_cp1250[k] == znak) { + newbuf[j] = (0x80 | k); + break; + } + } + } + newbuf[j] = '\0'; + + return newbuf; +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/protocols/Gadu-Gadu/src/libgadu/compat.h b/protocols/Gadu-Gadu/src/libgadu/compat.h new file mode 100644 index 0000000000..1aa17d1e4a --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/compat.h @@ -0,0 +1,36 @@ +/* coding: UTF-8 */ +/* $Id: compat.h 11075 2009-12-20 15:01:43Z dezred $ */ + +/* + * (C) Copyright 2001-2002 Wojtek Kaniewski + * Robert J. WoĹşny + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +/** + * \file compat.h + * + * \brief Makra zapewniajÄ…ce kompatybilność API na różnych systemach + */ + +#ifndef __COMPAT_H +#define __COMPAT_H + +#ifdef sun +# define INADDR_NONE ((in_addr_t) 0xffffffff) +#endif + +#endif diff --git a/protocols/Gadu-Gadu/src/libgadu/dcc.c b/protocols/Gadu-Gadu/src/libgadu/dcc.c new file mode 100644 index 0000000000..2fbe1b430c --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/dcc.c @@ -0,0 +1,1363 @@ +/* coding: UTF-8 */ +/* $Id: dcc.c 12145 2010-07-07 23:49:04Z dezred $ */ + +/* + * (C) Copyright 2001-2008 Wojtek Kaniewski + * Tomasz ChiliĹ„ski + * Adam Wysocki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +/** + * \file dcc.c + * + * \brief ObsĹ‚uga poĹ‚Ä…czeĹ„ bezpoĹ›rednich do wersji Gadu-Gadu 6.x + */ + +#ifndef _WIN64 +#define _USE_32BIT_TIME_T +#endif + +#include +#include +#ifdef _WIN32 +#include "win32.h" +#else +#include +#include +#include +#include +#ifdef sun +# include +#endif +#endif /* _WIN32 */ + +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +#include "compat.h" +#include "libgadu.h" + +#ifdef _WIN32 +#undef small +#endif + +#ifndef GG_DEBUG_DISABLE + +/** + * \internal Przekazuje zawartość pakietu do odpluskwiania. + * + * \param prefix Prefiks informacji + * \param fd Deskryptor gniazda + * \param buf Bufor z danumi + * \param size Rozmiar bufora z danymi + */ +static void gg_dcc_debug_data(const char *prefix, SOCKET fd, const void *buf, unsigned int size) +{ + unsigned int i; + + gg_debug(GG_DEBUG_MISC, "++ gg_dcc %s (fd=%d,len=%d)", prefix, fd, size); + + for (i = 0; i < size; i++) + gg_debug(GG_DEBUG_MISC, " %.2x", ((unsigned char*) buf)[i]); + + gg_debug(GG_DEBUG_MISC, "\n"); +} +#else +#define gg_dcc_debug_data(a,b,c,d) do { } while (0) +#endif + +/** + * WysyĹ‚a ĹĽÄ…danie zwrotnego poĹ‚Ä…czenia bezpoĹ›redniego. + * + * FunkcjÄ™ wykorzystuje siÄ™, jeĹ›li nie ma moĹĽliwoĹ›ci poĹ‚Ä…czenia siÄ™ z odbiorcÄ… + * pliku lub rozmowy gĹ‚osowej. Po otrzymaniu ĹĽÄ…dania druga strona sprĂłbuje + * nawiÄ…zać zwrotne poĹ‚Ä…czenie bezpoĹ›rednie z nadawcÄ…. + * gg_dcc_request() + * + * \param sess Struktura sesji + * \param uin Numer odbiorcy + * + * \return Patrz \c gg_send_message_ctcp() + * + * \ingroup dcc6 + */ +int gg_dcc_request(struct gg_session *sess, uin_t uin) +{ + return gg_send_message_ctcp(sess, GG_CLASS_CTCP, uin, (unsigned char*) "\002", 1); +} + +/** + * \internal Zamienia znacznik czasu w postaci uniksowej na format API WIN32. + * + * \note Funkcja dziaĹ‚a jedynie gdy kompilator obsĹ‚uguje typ danych + * \c long \c long. + * + * \param ut Czas w postaci uniksowej + * \param ft Czas w postaci API WIN32 + */ +static void gg_dcc_fill_filetime(time_t ut, uint32_t *ft) +{ +#ifdef GG_CONFIG_HAVE_LONG_LONG + unsigned long long tmp; + + tmp = ut; + tmp += 11644473600LL; + tmp *= 10000000LL; + +#ifndef GG_CONFIG_BIGENDIAN + ft[0] = (uint32_t) tmp; + ft[1] = (uint32_t) (tmp >> 32); +#else + ft[0] = gg_fix32((uint32_t) (tmp >> 32)); + ft[1] = gg_fix32((uint32_t) tmp); +#endif + +#endif +} + +/** + * WypeĹ‚nia pola struktury \c gg_dcc niezbÄ™dne do wysĹ‚ania pliku. + * + * \note WiÄ™kszÄ… funkcjonalność zapewnia funkcja \c gg_dcc_fill_file_info2(). + * + * \param d Struktura poĹ‚Ä…czenia + * \param filename Nazwa pliku + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup dcc6 + */ +int gg_dcc_fill_file_info(struct gg_dcc *d, const char *filename) +{ + return gg_dcc_fill_file_info2(d, filename, filename); +} + +/** + * WypeĹ‚nia pola struktury \c gg_dcc niezbÄ™dne do wysĹ‚ania pliku. + * + * \param d Struktura poĹ‚Ä…czenia + * \param filename Nazwa pliku zapisywana w strukturze + * \param local_filename Nazwa pliku w lokalnym systemie plikĂłw + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup dcc6 + */ +int gg_dcc_fill_file_info2(struct gg_dcc *d, const char *filename, const char *local_filename) +{ + struct stat st; + const char *name, *ext, *p; + unsigned char *q; + int i, j; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_fill_file_info2(%p, \"%s\", \"%s\");\n", d, filename, local_filename); + + if (!d || d->type != GG_SESSION_DCC_SEND) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() invalid arguments\n"); + errno = EINVAL; + return -1; + } + + if (stat(local_filename, &st) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() stat() failed (%s)\n", strerror(errno)); + return -1; + } + + if ((st.st_mode & S_IFDIR)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() that's a directory\n"); + errno = EINVAL; + return -1; + } + + if ((d->file_fd = open(local_filename, O_RDONLY | O_BINARY)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() open() failed (%s)\n", strerror(errno)); + return -1; + } + + memset(&d->file_info, 0, sizeof(d->file_info)); + + if (!(st.st_mode & S_IWUSR)) + d->file_info.mode |= gg_fix32(GG_DCC_FILEATTR_READONLY); + + gg_dcc_fill_filetime(st.st_atime, d->file_info.atime); + gg_dcc_fill_filetime(st.st_mtime, d->file_info.mtime); + gg_dcc_fill_filetime(st.st_ctime, d->file_info.ctime); + + d->file_info.size = gg_fix32(st.st_size); + d->file_info.mode = gg_fix32(0x20); /* FILE_ATTRIBUTE_ARCHIVE */ + +#ifdef _WIN32 + if (!(name = strrchr(filename, '\\'))) +#else + if (!(name = strrchr(filename, '/'))) +#endif + name = filename; + else + name++; + + if (!(ext = strrchr(name, '.'))) + ext = name + strlen(name); + + for (i = 0, p = name; i < 8 && p < ext; i++, p++) + d->file_info.short_filename[i] = toupper(name[i]); + + if (i == 8 && p < ext) { + d->file_info.short_filename[6] = '~'; + d->file_info.short_filename[7] = '1'; + } + + if (strlen(ext) > 0) { + for (j = 0; *ext && j < 4; j++, p++) + d->file_info.short_filename[i + j] = toupper(ext[j]); + } + + for (q = d->file_info.short_filename; *q; q++) { + if (*q == 185) { + *q = 165; + } else if (*q == 230) { + *q = 198; + } else if (*q == 234) { + *q = 202; + } else if (*q == 179) { + *q = 163; + } else if (*q == 241) { + *q = 209; + } else if (*q == 243) { + *q = 211; + } else if (*q == 156) { + *q = 140; + } else if (*q == 159) { + *q = 143; + } else if (*q == 191) { + *q = 175; + } + } + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_fill_file_info2() short name \"%s\", dos name \"%s\"\n", name, d->file_info.short_filename); + strncpy((char*) d->file_info.filename, name, sizeof(d->file_info.filename) - 1); + + return 0; +} + +/** + * \internal Rozpoczyna poĹ‚Ä…czenie bezpoĹ›rednie z danym klientem. + * + * \param ip Adres IP odbiorcy + * \param port Port odbiorcy + * \param my_uin WĹ‚asny numer + * \param peer_uin Numer odbiorcy + * \param type Rodzaj poĹ‚Ä…czenia (\c GG_SESSION_DCC_SEND lub \c GG_SESSION_DCC_GET) + * + * \return Struktura \c gg_dcc lub \c NULL w przypadku bĹ‚Ä™du + */ +static struct gg_dcc *gg_dcc_transfer(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin, int type) +{ + struct gg_dcc *d = NULL; + struct in_addr addr; + + addr.s_addr = ip; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_transfer(%s, %d, %ld, %ld, %s);\n", inet_ntoa(addr), port, my_uin, peer_uin, (type == GG_SESSION_DCC_SEND) ? "SEND" : "GET"); + + if (!ip || ip == INADDR_NONE || !port || !my_uin || !peer_uin) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_transfer() invalid arguments\n"); + errno = EINVAL; + return NULL; + } + + if (!(d = (void*) calloc(1, sizeof(*d)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_transfer() not enough memory\n"); + return NULL; + } + + d->check = GG_CHECK_WRITE; + d->state = GG_STATE_CONNECTING; + d->type = type; + d->timeout = GG_DEFAULT_TIMEOUT; + d->file_fd = -1; + d->active = 1; + d->fd = -1; + d->uin = my_uin; + d->peer_uin = peer_uin; + + if ((d->fd = gg_connect(&addr, port, 1)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_transfer() connection failed\n"); + free(d); + return NULL; + } + + return d; +} + +/** + * Rozpoczyna odbieranie pliku przez zwrotne poĹ‚Ä…czenie bezpoĹ›rednie. + * + * \param ip Adres IP nadawcy + * \param port Port nadawcy + * \param my_uin WĹ‚asny numer + * \param peer_uin Numer nadawcy + * + * \return Struktura \c gg_dcc lub \c NULL w przypadku bĹ‚Ä™du + * + * \ingroup dcc6 + */ +struct gg_dcc *gg_dcc_get_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin) +{ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_get_file() handing over to gg_dcc_transfer()\n"); + + return gg_dcc_transfer(ip, port, my_uin, peer_uin, GG_SESSION_DCC_GET); +} + +/** + * Rozpoczyna wysyĹ‚anie pliku. + * + * \param ip Adres IP odbiorcy + * \param port Port odbiorcy + * \param my_uin WĹ‚asny numer + * \param peer_uin Numer odbiorcy + * + * \return Struktura \c gg_dcc lub \c NULL w przypadku bĹ‚Ä™du + * + * \ingroup dcc6 + */ +struct gg_dcc *gg_dcc_send_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin) +{ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_send_file() handing over to gg_dcc_transfer()\n"); + + return gg_dcc_transfer(ip, port, my_uin, peer_uin, GG_SESSION_DCC_SEND); +} + +/** + * Rozpoczyna poĹ‚Ä…czenie gĹ‚osowe. + * + * \param ip Adres IP odbiorcy + * \param port Port odbiorcy + * \param my_uin WĹ‚asny numer + * \param peer_uin Numer odbiorcy + * + * \return Struktura \c gg_dcc lub \c NULL w przypadku bĹ‚Ä™du + * + * \ingroup dcc6 + */ +struct gg_dcc *gg_dcc_voice_chat(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin) +{ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_chat() handing over to gg_dcc_transfer()\n"); + + return gg_dcc_transfer(ip, port, my_uin, peer_uin, GG_SESSION_DCC_VOICE); +} + +/** + * Ustawia typ przychodzÄ…cego poĹ‚Ä…czenia bezpoĹ›redniego. + * + * FunkcjÄ™ naleĹĽy wywoĹ‚ać po otrzymaniu zdarzenia \c GG_EVENT_DCC_CALLBACK. + * + * \param d Struktura poĹ‚Ä…czenia + * \param type Rodzaj poĹ‚Ä…czenia (\c GG_SESSION_DCC_SEND lub + * \c GG_SESSION_DCC_VOICE) + * + * \ingroup dcc6 + */ +void gg_dcc_set_type(struct gg_dcc *d, int type) +{ + d->type = type; + d->state = (type == GG_SESSION_DCC_SEND) ? GG_STATE_SENDING_FILE_INFO : GG_STATE_SENDING_VOICE_REQUEST; +} + +/** + * \internal Funkcja zwrotna poĹ‚Ä…czenia bezpoĹ›redniego. + * + * Pole \c callback struktury \c gg_dcc zawiera wskaĹşnik do tej funkcji. + * WywoĹ‚uje ona \c gg_watch_fd() i zachowuje wynik w polu \c event. + * + * \note Funkcjonalność funkcjo zwrotnej nie jest juĹĽ wspierana. + * + * \param d Struktura poĹ‚Ä…czenia + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +static int gg_dcc_callback(struct gg_dcc *d) +{ + struct gg_event *e = gg_dcc_watch_fd(d); + + d->event = e; + + return (e != NULL) ? 0 : -1; +} + +/** + * Tworzy gniazdo nasĹ‚uchujÄ…ce dla poĹ‚Ä…czeĹ„ bezpoĹ›rednich. + * + * Funkcja przywiÄ…zuje gniazdo do pierwszego wolnego portu TCP. + * + * \param uin WĹ‚asny numer + * \param port Preferowany port (jeĹ›li rĂłwny 0 lub -1, prĂłbuje siÄ™ domyĹ›lnego) + * + * \return Struktura \c gg_dcc lub \c NULL w przypadku bĹ‚Ä™du + * + * \ingroup dcc6 + */ +struct gg_dcc *gg_dcc_socket_create(uin_t uin, uint16_t port) +{ + struct gg_dcc *c; + struct sockaddr_in sin; + SOCKET sock; + int bound = 0, errno2; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_create_dcc_socket(%d, %d);\n", uin, port); + + if (!uin) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() invalid arguments\n"); + errno = EINVAL; + return NULL; + } + + if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() can't create socket (%s)\n", strerror(errno)); + return NULL; + } + + if (port == 0 || port == -1) + port = GG_DEFAULT_DCC_PORT; + + while (!bound) { + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = htons(port); + + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() trying port %d\n", port); + if (!bind(sock, (struct sockaddr*) &sin, sizeof(sin))) + bound = 1; + else { + if (++port == 65535) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() no free port found\n"); + gg_sock_close(sock); + return NULL; + } + } + } + + if (listen(sock, 10)) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() unable to listen (%s)\n", strerror(errno)); + errno2 = errno; + gg_sock_close(sock); + errno = errno2; + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() bound to port %d\n", port); + + if (!(c = malloc(sizeof(*c)))) { + gg_debug(GG_DEBUG_MISC, "// gg_create_dcc_socket() not enough memory for struct\n"); + gg_sock_close(sock); + return NULL; + } + memset(c, 0, sizeof(*c)); + + c->port = c->id = port; + c->fd = sock; + c->type = GG_SESSION_DCC_SOCKET; + c->uin = uin; + c->timeout = -1; + c->state = GG_STATE_LISTENING; + c->check = GG_CHECK_READ; + c->callback = gg_dcc_callback; + c->destroy = gg_dcc_free; + + return c; +} + +/** + * WysyĹ‚a ramkÄ™ danych poĹ‚Ä…czenia gĹ‚osowego. + * + * \param d Struktura poĹ‚Ä…czenia + * \param buf Bufor z danymi + * \param length DĹ‚ugość bufora z danymi + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup dcc6 + */ +int gg_dcc_voice_send(struct gg_dcc *d, char *buf, int length) +{ + struct packet_s { + uint8_t type; + uint32_t length; + } GG_PACKED; + struct packet_s packet; + + gg_debug(GG_DEBUG_FUNCTION, "++ gg_dcc_voice_send(%p, %p, %d);\n", d, buf, length); + if (!d || !buf || length < 0 || d->type != GG_SESSION_DCC_VOICE) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() invalid argument\n"); + errno = EINVAL; + return -1; + } + + packet.type = 0x03; /* XXX */ + packet.length = gg_fix32(length); + + if (gg_sock_write(d->fd, &packet, sizeof(packet)) < (signed)sizeof(packet)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() write() failed\n"); + return -1; + } + gg_dcc_debug_data("write", d->fd, &packet, sizeof(packet)); + + if (gg_sock_write(d->fd, buf, length) < length) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_voice_send() write() failed\n"); + return -1; + } + gg_dcc_debug_data("write", d->fd, buf, length); + + return 0; +} + +/** + * \internal Odbiera dane z poĹ‚Ä…czenia bezpoĹ›redniego z obsĹ‚ugÄ… bĹ‚Ä™dĂłw. + * + * \param fd Deskryptor gniazda + * \param buf Bufor na dane + * \param size Rozmiar bufora na dane + */ +#define gg_dcc_read(fd, buf, size) \ +{ \ + int tmp = gg_sock_read(fd, buf, size); \ + \ + if (tmp < (int) size) { \ + if (tmp == -1) { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed (errno=%d, %s)\n", errno, strerror(errno)); \ + } else if (tmp == 0) { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed, connection broken\n"); \ + } else { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed (%d bytes, %d needed)\n", tmp, size); \ + } \ + e->type = GG_EVENT_DCC_ERROR; \ + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; \ + return e; \ + } \ + gg_dcc_debug_data("read", fd, buf, size); \ +} + +/** + * \internal WysyĹ‚a dane do poĹ‚Ä…czenia bezpoĹ›redniego z obsĹ‚ugÄ… bĹ‚Ä™dĂłw. + * + * \param fd Deskryptor gniazda + * \param buf Bufor z danymi + * \param size Rozmiar bufora z danymi + */ +#define gg_dcc_write(fd, buf, size) \ +{ \ + int tmp; \ + gg_dcc_debug_data("write", fd, buf, size); \ + tmp = gg_sock_write(fd, buf, size); \ + if (tmp < (int) size) { \ + if (tmp == -1) { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (errno=%d, %s)\n", errno, strerror(errno)); \ + } else { \ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (%d needed, %d done)\n", size, tmp); \ + } \ + e->type = GG_EVENT_DCC_ERROR; \ + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; \ + return e; \ + } \ +} + +/** + * Funkcja wywoĹ‚ywana po zaobserwowaniu zmian na deskryptorze poĹ‚Ä…czenia. + * + * Funkcja zwraca strukturÄ™ zdarzenia \c gg_event. JeĹ›li rodzaj zdarzenia + * to \c GG_EVENT_NONE, nie wydarzyĹ‚o siÄ™ jeszcze nic wartego odnotowania. + * StrukturÄ™ zdarzenia naleĹĽy zwolnić funkcja \c gg_event_free. + * + * \param h Struktura poĹ‚Ä…czenia + * + * \return Struktura zdarzenia lub \c NULL jeĹ›li wystÄ…piĹ‚ bĹ‚Ä…d + * + * \ingroup dcc6 + */ +struct gg_event *gg_dcc_watch_fd(struct gg_dcc *h) +{ + struct gg_event *e; + int foo; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_watch_fd(%p);\n", h); + + if (!h || (h->type != GG_SESSION_DCC && h->type != GG_SESSION_DCC_SOCKET && h->type != GG_SESSION_DCC_SEND && h->type != GG_SESSION_DCC_GET && h->type != GG_SESSION_DCC_VOICE)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() invalid argument\n"); + errno = EINVAL; + return NULL; + } + + if (!(e = (void*) calloc(1, sizeof(*e)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() not enough memory\n"); + return NULL; + } + + e->type = GG_EVENT_NONE; + + if (h->type == GG_SESSION_DCC_SOCKET) { + struct sockaddr_in sin; + struct gg_dcc *c; + SOCKET fd; + int one = 1; + unsigned int sin_len = sizeof(sin); + + if ((fd = accept(h->fd, (struct sockaddr*) &sin, &sin_len)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() can't accept() new connection (errno=%d, %s)\n", errno, strerror(errno)); + return e; + } + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() new direct connection from %s:%d\n", inet_ntoa(sin.sin_addr), htons(sin.sin_port)); + +#ifdef FIONBIO + if (ioctl(fd, FIONBIO, &one) == -1) { +#else + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() can't set nonblocking (errno=%d, %s)\n", errno, strerror(errno)); + gg_sock_close(fd); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + return e; + } + + if (!(c = (void*) calloc(1, sizeof(*c)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() not enough memory for client data\n"); + + free(e); + gg_sock_close(fd); + return NULL; + } + + c->fd = fd; + c->check = GG_CHECK_READ; + c->state = GG_STATE_READING_UIN_1; + c->type = GG_SESSION_DCC; + c->timeout = GG_DEFAULT_TIMEOUT; + c->file_fd = -1; + c->remote_addr = sin.sin_addr.s_addr; + c->remote_port = ntohs(sin.sin_port); + + e->type = GG_EVENT_DCC_NEW; + e->event.dcc_new = c; + + return e; + } else { + struct gg_dcc_tiny_packet tiny; + struct gg_dcc_small_packet small; + struct gg_dcc_big_packet big; + int size, tmp, res; + unsigned int utmp, res_size = sizeof(res); + char buf[1024], ack[] = "UDAG"; + + struct gg_dcc_file_info_packet { + struct gg_dcc_big_packet big; + struct gg_file_info file_info; + } GG_PACKED; + struct gg_dcc_file_info_packet file_info_packet; + + switch (h->state) { + case GG_STATE_READING_UIN_1: + case GG_STATE_READING_UIN_2: + { + uin_t uin; + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_READING_UIN_%d\n", (h->state == GG_STATE_READING_UIN_1) ? 1 : 2); + + gg_dcc_read(h->fd, &uin, sizeof(uin)); + + if (h->state == GG_STATE_READING_UIN_1) { + h->state = GG_STATE_READING_UIN_2; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->peer_uin = gg_fix32(uin); + } else { + h->state = GG_STATE_SENDING_ACK; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + h->uin = gg_fix32(uin); + e->type = GG_EVENT_DCC_CLIENT_ACCEPT; + } + + return e; + } + + case GG_STATE_SENDING_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_SENDING_ACK\n"); + + gg_dcc_write(h->fd, ack, 4); + + h->state = GG_STATE_READING_TYPE; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_READING_TYPE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_TYPE\n"); + + gg_dcc_read(h->fd, &small, sizeof(small)); + + small.type = gg_fix32(small.type); + + switch (small.type) { + case 0x0003: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() callback\n"); + h->type = GG_SESSION_DCC_SEND; + h->state = GG_STATE_SENDING_FILE_INFO; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + e->type = GG_EVENT_DCC_CALLBACK; + + break; + + case 0x0002: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() dialin\n"); + h->type = GG_SESSION_DCC_GET; + h->state = GG_STATE_READING_REQUEST; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->incoming = 1; + + break; + + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() unknown dcc type (%.4x) from %ld\n", small.type, h->peer_uin); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + } + + return e; + + case GG_STATE_READING_REQUEST: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_REQUEST\n"); + + gg_dcc_read(h->fd, &small, sizeof(small)); + + small.type = gg_fix32(small.type); + + switch (small.type) { + case 0x0001: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() file transfer request\n"); + h->state = GG_STATE_READING_FILE_INFO; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + break; + + case 0x0003: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() voice chat request\n"); + h->state = GG_STATE_SENDING_VOICE_ACK; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DCC_TIMEOUT_VOICE_ACK; + h->type = GG_SESSION_DCC_VOICE; + e->type = GG_EVENT_DCC_NEED_VOICE_ACK; + + break; + + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() unknown dcc request (%.4x) from %ld\n", small.type, h->peer_uin); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + } + + return e; + + case GG_STATE_READING_FILE_INFO: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_FILE_INFO\n"); + + gg_dcc_read(h->fd, &file_info_packet, sizeof(file_info_packet)); + + memcpy(&h->file_info, &file_info_packet.file_info, sizeof(h->file_info)); + + h->file_info.mode = gg_fix32(h->file_info.mode); + h->file_info.size = gg_fix32(h->file_info.size); + + h->state = GG_STATE_SENDING_FILE_ACK; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DCC_TIMEOUT_FILE_ACK; + + e->type = GG_EVENT_DCC_NEED_FILE_ACK; + + return e; + + case GG_STATE_SENDING_FILE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE_ACK\n"); + + big.type = gg_fix32(0x0006); /* XXX */ + big.dunno1 = gg_fix32(h->offset); + big.dunno2 = 0; + + gg_dcc_write(h->fd, &big, sizeof(big)); + + h->state = GG_STATE_READING_FILE_HEADER; + h->chunk_size = sizeof(big); + h->chunk_offset = 0; + if (!(h->chunk_buf = malloc(sizeof(big)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory\n"); + free(e); + return NULL; + } + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_SENDING_VOICE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_VOICE_ACK\n"); + + tiny.type = 0x01; /* XXX */ + + gg_dcc_write(h->fd, &tiny, sizeof(tiny)); + + h->state = GG_STATE_READING_VOICE_HEADER; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + h->offset = 0; + + return e; + + case GG_STATE_READING_FILE_HEADER: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_FILE_HEADER\n"); + + tmp = gg_sock_read(h->fd, h->chunk_buf + h->chunk_offset, h->chunk_size - h->chunk_offset); + + if (tmp == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_watch_fd() read() failed (errno=%d, %s)\n", errno, strerror(errno)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + gg_dcc_debug_data("read", h->fd, h->chunk_buf + h->chunk_offset, h->chunk_size - h->chunk_offset); + + h->chunk_offset += tmp; + + if (h->chunk_offset < h->chunk_size) + return e; + + memcpy(&big, h->chunk_buf, sizeof(big)); + free(h->chunk_buf); + h->chunk_buf = NULL; + + big.type = gg_fix32(big.type); + h->chunk_size = gg_fix32(big.dunno1); + h->chunk_offset = 0; + + if (big.type == 0x0005) { /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() transfer refused\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_REFUSED; + return e; + } + + if (h->chunk_size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() empty chunk, EOF\n"); + e->type = GG_EVENT_DCC_DONE; + return e; + } + + h->state = GG_STATE_GETTING_FILE; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->established = 1; + + return e; + + case GG_STATE_READING_VOICE_HEADER: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_HEADER\n"); + + gg_dcc_read(h->fd, &tiny, sizeof(tiny)); + + switch (tiny.type) { + case 0x03: /* XXX */ + h->state = GG_STATE_READING_VOICE_SIZE; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + h->established = 1; + break; + case 0x04: /* XXX */ + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() peer breaking connection\n"); + /* XXX zwracać odpowiedni event */ + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() unknown request (%.2x)\n", tiny.type); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + } + + return e; + + case GG_STATE_READING_VOICE_SIZE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_SIZE\n"); + + gg_dcc_read(h->fd, &small, sizeof(small)); + + small.type = gg_fix32(small.type); + + if (small.type < 16 || small.type > sizeof(buf)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() invalid voice frame size (%d)\n", small.type); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + + return e; + } + + h->chunk_size = small.type; + h->chunk_offset = 0; + + if (!(h->voice_buf = malloc(h->chunk_size))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory for voice frame\n"); + free(e); + return NULL; + } + + h->state = GG_STATE_READING_VOICE_DATA; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_READING_VOICE_DATA: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_DATA\n"); + + tmp = gg_sock_read(h->fd, h->voice_buf + h->chunk_offset, h->chunk_size - h->chunk_offset); + if (tmp < 1) { + if (tmp == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed (errno=%d, %s)\n", errno, strerror(errno)); + } else { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed, connection broken\n"); + } + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + gg_dcc_debug_data("read", h->fd, h->voice_buf + h->chunk_offset, tmp); + + h->chunk_offset += tmp; + + if (h->chunk_offset >= h->chunk_size) { + e->type = GG_EVENT_DCC_VOICE_DATA; + e->event.dcc_voice_data.data = (unsigned char*) h->voice_buf; + e->event.dcc_voice_data.length = h->chunk_size; + h->state = GG_STATE_READING_VOICE_HEADER; + h->voice_buf = NULL; + } + + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_CONNECTING: + { + uin_t uins[2]; + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_CONNECTING\n"); + + res = 0; + if ((foo = gg_getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &res, &res_size)) || res) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() connection failed (fd=%d,errno=%d(%s),foo=%d,res=%d(%s))\n", h->fd, errno, strerror(errno), foo, res, strerror(res)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + return e; + } + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() connected, sending uins\n"); + + uins[0] = gg_fix32(h->uin); + uins[1] = gg_fix32(h->peer_uin); + + gg_dcc_write(h->fd, uins, sizeof(uins)); + + h->state = GG_STATE_READING_ACK; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + } + + case GG_STATE_READING_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_ACK\n"); + + gg_dcc_read(h->fd, buf, 4); + + if (strncmp(buf, ack, 4)) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() did't get ack\n"); + + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + return e; + } + + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + h->state = GG_STATE_SENDING_REQUEST; + + return e; + + case GG_STATE_SENDING_VOICE_REQUEST: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_VOICE_REQUEST\n"); + + small.type = gg_fix32(0x0003); + + gg_dcc_write(h->fd, &small, sizeof(small)); + + h->state = GG_STATE_READING_VOICE_ACK; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + return e; + + case GG_STATE_SENDING_REQUEST: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_REQUEST\n"); + + small.type = (h->type == GG_SESSION_DCC_GET) ? gg_fix32(0x0003) : gg_fix32(0x0002); /* XXX */ + + gg_dcc_write(h->fd, &small, sizeof(small)); + + switch (h->type) { + case GG_SESSION_DCC_GET: + h->state = GG_STATE_READING_REQUEST; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + break; + + case GG_SESSION_DCC_SEND: + h->state = GG_STATE_SENDING_FILE_INFO; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + if (h->file_fd == -1) + e->type = GG_EVENT_DCC_NEED_FILE_INFO; + break; + + case GG_SESSION_DCC_VOICE: + h->state = GG_STATE_SENDING_VOICE_REQUEST; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + return e; + + case GG_STATE_SENDING_FILE_INFO: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE_INFO\n"); + + if (h->file_fd == -1) { + e->type = GG_EVENT_DCC_NEED_FILE_INFO; + return e; + } + + small.type = gg_fix32(0x0001); /* XXX */ + + gg_dcc_write(h->fd, &small, sizeof(small)); + + file_info_packet.big.type = gg_fix32(0x0003); /* XXX */ + file_info_packet.big.dunno1 = 0; + file_info_packet.big.dunno2 = 0; + + memcpy(&file_info_packet.file_info, &h->file_info, sizeof(h->file_info)); + + /* zostajÄ… teraz u nas, wiÄ™c odwracamy z powrotem */ + h->file_info.size = gg_fix32(h->file_info.size); + h->file_info.mode = gg_fix32(h->file_info.mode); + + gg_dcc_write(h->fd, &file_info_packet, sizeof(file_info_packet)); + + h->state = GG_STATE_READING_FILE_ACK; + h->check = GG_CHECK_READ; + h->timeout = GG_DCC_TIMEOUT_FILE_ACK; + + return e; + + case GG_STATE_READING_FILE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_FILE_ACK\n"); + + gg_dcc_read(h->fd, &big, sizeof(big)); + + /* XXX sprawdzać wynik */ + h->offset = gg_fix32(big.dunno1); + + h->state = GG_STATE_SENDING_FILE_HEADER; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + e->type = GG_EVENT_DCC_ACK; + + return e; + + case GG_STATE_READING_VOICE_ACK: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_READING_VOICE_ACK\n"); + + gg_dcc_read(h->fd, &tiny, sizeof(tiny)); + + if (tiny.type != 0x01) { + gg_debug(GG_DEBUG_MISC, "// invalid reply (%.2x), connection refused\n", tiny.type); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_REFUSED; + return e; + } + + h->state = GG_STATE_READING_VOICE_HEADER; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + + e->type = GG_EVENT_DCC_ACK; + + return e; + + case GG_STATE_SENDING_FILE_HEADER: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE_HEADER\n"); + + h->chunk_offset = 0; + + if ((h->chunk_size = h->file_info.size - h->offset) > 4096) { + h->chunk_size = 4096; + big.type = gg_fix32(0x0003); /* XXX */ + } else + big.type = gg_fix32(0x0002); /* XXX */ + + big.dunno1 = gg_fix32(h->chunk_size); + big.dunno2 = 0; + + gg_dcc_write(h->fd, &big, sizeof(big)); + + h->state = GG_STATE_SENDING_FILE; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + h->established = 1; + + return e; + + case GG_STATE_SENDING_FILE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_SENDING_FILE\n"); + + if ((utmp = h->chunk_size - h->chunk_offset) > sizeof(buf)) + utmp = sizeof(buf); + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() offset=%d, size=%d\n", h->offset, h->file_info.size); + + /* koniec pliku? */ + if (h->file_info.size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() reached eof on empty file\n"); + e->type = GG_EVENT_DCC_DONE; + + return e; + } + + if (h->offset >= h->file_info.size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() offset >= size, finished\n"); + e->type = GG_EVENT_DCC_DONE; + return e; + } + + lseek(h->file_fd, h->offset, SEEK_SET); + + size = read(h->file_fd, buf, utmp); + + /* bĹ‚Ä…d */ + if (size == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed. (errno=%d, %s)\n", errno, strerror(errno)); + + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_FILE; + + return e; + } + + /* koniec pliku? */ + if (size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() reached eof\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_EOF; + + return e; + } + + /* jeĹ›li wczytaliĹ›my wiÄ™cej, utnijmy. */ + if (h->offset + size > h->file_info.size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() too much (read=%d, ofs=%d, size=%d)\n", size, h->offset, h->file_info.size); + size = h->file_info.size - h->offset; + + if (size < 1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() reached EOF after cutting\n"); + e->type = GG_EVENT_DCC_DONE; + return e; + } + } + + tmp = gg_sock_write(h->fd, buf, size); + + if (tmp == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (%s)\n", strerror(errno)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + if (tmp == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (connection reset)\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + h->offset += tmp; + + if (h->offset >= h->file_info.size) { + e->type = GG_EVENT_DCC_DONE; + return e; + } + + h->chunk_offset += tmp; + + if (h->chunk_offset >= h->chunk_size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() chunk finished\n"); + h->state = GG_STATE_SENDING_FILE_HEADER; + h->timeout = GG_DEFAULT_TIMEOUT; + } else { + h->state = GG_STATE_SENDING_FILE; + h->timeout = GG_DCC_TIMEOUT_SEND; + } + + h->check = GG_CHECK_WRITE; + + return e; + + case GG_STATE_GETTING_FILE: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_GETTING_FILE\n"); + + if ((utmp = h->chunk_size - h->chunk_offset) > sizeof(buf)) + utmp = sizeof(buf); + + if (h->offset >= h->file_info.size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() offset >= size, finished\n"); + e->type = GG_EVENT_DCC_DONE; + return e; + } + + size = gg_sock_read(h->fd, buf, utmp); + + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() ofs=%d, size=%d, read()=%d\n", h->offset, h->file_info.size, size); + + /* bĹ‚Ä…d */ + if (size == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() failed. (errno=%d, %s)\n", errno, strerror(errno)); + + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + + return e; + } + + /* koniec? */ + if (size == 0) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() read() reached eof\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_EOF; + + return e; + } + + tmp = write(h->file_fd, buf, size); + + if (tmp == -1 || tmp < size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() write() failed (%d:fd=%d:res=%d:%s)\n", tmp, h->file_fd, size, strerror(errno)); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_NET; + return e; + } + + h->offset += size; + + if (h->offset >= h->file_info.size) { + e->type = GG_EVENT_DCC_DONE; + return e; + } + + h->chunk_offset += size; + + if (h->chunk_offset >= h->chunk_size) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() chunk finished\n"); + h->state = GG_STATE_READING_FILE_HEADER; + h->timeout = GG_DEFAULT_TIMEOUT; + h->chunk_offset = 0; + h->chunk_size = sizeof(big); + if (!(h->chunk_buf = malloc(sizeof(big)))) { + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() out of memory\n"); + free(e); + return NULL; + } + } else { + h->state = GG_STATE_GETTING_FILE; + h->timeout = GG_DCC_TIMEOUT_GET; + } + + h->check = GG_CHECK_READ; + + return e; + + default: + gg_debug(GG_DEBUG_MISC, "// gg_dcc_watch_fd() GG_STATE_???\n"); + e->type = GG_EVENT_DCC_ERROR; + e->event.dcc_error = GG_ERROR_DCC_HANDSHAKE; + + return e; + } + } + + return e; +} + +/** + * Zwalnia zasoby uĹĽywane przez poĹ‚Ä…czenie bezpoĹ›rednie. + * + * \param d Struktura poĹ‚Ä…czenia + * + * \ingroup dcc6 + */ +void gg_dcc_free(struct gg_dcc *d) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_dcc_free(%p);\n", d); + + if (!d) + return; + + if (d->fd != -1) + gg_sock_close(d->fd); + + free(d->chunk_buf); + free(d); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/protocols/Gadu-Gadu/src/libgadu/dcc7.c b/protocols/Gadu-Gadu/src/libgadu/dcc7.c new file mode 100644 index 0000000000..f3813e6e83 --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/dcc7.c @@ -0,0 +1,1655 @@ +/* coding: UTF-8 */ +/* $Id: dcc7.c,v 1.2 2007-07-20 23:00:49 wojtekka Exp $ */ + +/* + * (C) Copyright 2001-2010 Wojtek Kaniewski + * Tomasz ChiliĹ„ski + * Adam Wysocki + * BartĹ‚omiej ZimoĹ„ + * + * Thanks to Jakub Zawadzki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, + * USA. + */ + +/** + * \file dcc7.c + * + * \brief ObsĹ‚uga poĹ‚Ä…czeĹ„ bezpoĹ›rednich od wersji Gadu-Gadu 7.x + */ + +#ifndef _WIN64 +#define _USE_32BIT_TIME_T +#endif + +#include +#include +#ifdef _WIN32 +#include "win32.h" +#else +#include +#include +#include +#include +#ifdef sun +# include +#endif +#endif /* _WIN32 */ +#include + +#include +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif + +#include "compat.h" +#include "libgadu.h" +#include "protocol.h" +#include "resolver.h" +#include "internal.h" + +/** + * \internal Dodaje poĹ‚Ä…czenie bezpoĹ›rednie do sesji. + * + * \param sess Struktura sesji + * \param dcc Struktura poĹ‚Ä…czenia + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +static int gg_dcc7_session_add(struct gg_session *sess, struct gg_dcc7 *dcc) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_session_add(%p, %p)\n", sess, dcc); + + if (!sess || !dcc || dcc->next) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_session_add() invalid parameters\n"); + errno = EINVAL; + return -1; + } + + dcc->next = sess->dcc7_list; + sess->dcc7_list = dcc; + + return 0; +} + +/** + * \internal Usuwa poĹ‚Ä…czenie bezpoĹ›rednie z sesji. + * + * \param sess Struktura sesji + * \param dcc Struktura poĹ‚Ä…czenia + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +static int gg_dcc7_session_remove(struct gg_session *sess, struct gg_dcc7 *dcc) +{ + struct gg_dcc7 *tmp; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_session_remove(%p, %p)\n", sess, dcc); + + if (sess == NULL || dcc == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_session_remove() invalid parameters\n"); + errno = EINVAL; + return -1; + } + + if (sess->dcc7_list == dcc) { + sess->dcc7_list = dcc->next; + dcc->next = NULL; + return 0; + } + + for (tmp = sess->dcc7_list; tmp != NULL; tmp = tmp->next) { + if (tmp->next == dcc) { + tmp->next = dcc->next; + dcc->next = NULL; + return 0; + } + } + + errno = ENOENT; + return -1; +} + +/** + * \internal Zwraca strukturÄ™ poĹ‚Ä…czenia o danym identyfikatorze. + * + * \param sess Struktura sesji + * \param id Identyfikator poĹ‚Ä…czenia + * \param uin Numer nadawcy lub odbiorcy + * + * \return Struktura poĹ‚Ä…czenia lub \c NULL jeĹ›li nie znaleziono + */ +static struct gg_dcc7 *gg_dcc7_session_find(struct gg_session *sess, gg_dcc7_id_t id, uin_t uin) +{ + struct gg_dcc7 *tmp; + int empty; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_session_find(%p, ..., %d)\n", sess, (int) uin); + + empty = !memcmp(&id, "\0\0\0\0\0\0\0\0", 8); + + for (tmp = sess->dcc7_list; tmp; tmp = tmp->next) { + if (empty) { + if (tmp->peer_uin == uin && !tmp->state == GG_STATE_WAITING_FOR_ACCEPT) + return tmp; + } else { + if (!memcmp(&tmp->cid, &id, sizeof(id))) + return tmp; + } + } + + return NULL; +} + +/** + * \internal Rozpoczyna proces pobierania adresu + * + * \param dcc Struktura poĹ‚Ä…czenia + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +static int gg_dcc7_get_relay_addr(struct gg_dcc7 *dcc) +{ + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_get_relay_addr(%p)\n", dcc); + + if (dcc == NULL || dcc->sess == NULL) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_get_relay_addr() invalid parameters\n"); + errno = EINVAL; + return -1; + } + + if (dcc->sess->resolver_start(&dcc->fd, &dcc->resolver, GG_RELAY_HOST) == -1) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_get_relay_addr() resolving failed (errno=%d, %s)\n", errno, strerror(errno)); + return -1; + } + + dcc->state = GG_STATE_RESOLVING_RELAY; + dcc->check = GG_CHECK_READ; + dcc->timeout = GG_DEFAULT_TIMEOUT; + + return 0; +} + +/** + * \internal NawiÄ…zuje poĹ‚Ä…czenie bezpoĹ›rednie + * + * \param dcc Struktura poĹ‚Ä…czenia + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +static int gg_dcc7_connect(struct gg_dcc7 *dcc) +{ + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_connect(%p)\n", dcc); + + if (dcc == NULL) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_connect() invalid parameters\n"); + errno = EINVAL; + return -1; + } + + if ((dcc->fd = gg_connect(&dcc->remote_addr, dcc->remote_port, 1)) == -1) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_connect() connection failed\n"); + return -1; + } + + dcc->state = GG_STATE_CONNECTING; + dcc->check = GG_CHECK_WRITE; + dcc->timeout = GG_DCC7_TIMEOUT_CONNECT; + dcc->soft_timeout = 1; + + return 0; +} + +/** + * \internal Tworzy gniazdo nasĹ‚uchujÄ…ce dla poĹ‚Ä…czenia bezpoĹ›redniego + * + * \param dcc Struktura poĹ‚Ä…czenia + * \param port Preferowany port (jeĹ›li rĂłwny 0 lub -1, prĂłbuje siÄ™ domyĹ›lnego) + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +static int gg_dcc7_listen(struct gg_dcc7 *dcc, uint16_t port) +{ + struct sockaddr_in sin; + SOCKET fd; + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_listen(%p, %d)\n", dcc, port); + + if (!dcc) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_listen() invalid parameters\n"); + errno = EINVAL; + return -1; + } + + if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_listen() can't create socket (%s)\n", strerror(errno)); + return -1; + } + + // XXX losować porty? + + if (!port) + port = GG_DEFAULT_DCC_PORT; + + while (1) { + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = htons(port); + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_listen() trying port %d\n", port); + + if (!bind(fd, (struct sockaddr*) &sin, sizeof(sin))) + break; + + if (port++ == 65535) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_listen() no free port found\n"); + gg_sock_close(fd); + errno = ENOENT; + return -1; + } + } + + if (listen(fd, 1)) { + int errsv = errno; + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_listen() unable to listen (%s)\n", strerror(errno)); + gg_sock_close(fd); + errno = errsv; + return -1; + } + + dcc->fd = fd; + dcc->local_port = port; + + dcc->state = GG_STATE_LISTENING; + dcc->check = GG_CHECK_READ; + dcc->timeout = GG_DCC7_TIMEOUT_FILE_ACK; + + return 0; +} + +/** + * \internal Tworzy gniazdo nasĹ‚uchujÄ…ce i wysyĹ‚a jego parametry + * + * \param dcc Struktura poĹ‚Ä…czenia + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +static int gg_dcc7_listen_and_send_info(struct gg_dcc7 *dcc) +{ + struct gg_dcc7_info pkt; + uint16_t external_port; + uint16_t local_port; + uint32_t count; + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_listen_and_send_info(%p)\n", dcc); + + if (!dcc->sess->client_port) + local_port = dcc->sess->external_port; + else + local_port = dcc->sess->client_port; + + if (gg_dcc7_listen(dcc, local_port) == -1) + return -1; + + if (!dcc->sess->external_port || dcc->local_port != local_port) + external_port = dcc->local_port; + else + external_port = dcc->sess->external_port; + + if (!dcc->sess->external_addr || dcc->local_port != local_port) + dcc->local_addr = dcc->sess->client_addr; + else + dcc->local_addr = dcc->sess->external_addr; + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// dcc7_listen_and_send_info() sending IP address %s and port %d\n", inet_ntoa(*((struct in_addr*) &dcc->local_addr)), external_port); + + memset(&pkt, 0, sizeof(pkt)); + pkt.uin = gg_fix32(dcc->peer_uin); + pkt.type = GG_DCC7_TYPE_P2P; + pkt.id = dcc->cid; + snprintf((char*) pkt.info, sizeof(pkt.info), "%s %d", inet_ntoa(*((struct in_addr*) &dcc->local_addr)), external_port); + // TODO: implement hash count + // we MUST fill hash to recive from server request for server connection + //snprintf((char*) pkt.hash, sizeof(pkt.hash), "0"); + count = dcc->local_addr + external_port * rand(); + snprintf((char*) pkt.hash, sizeof(pkt.hash), "%d", count); + + return gg_send_packet(dcc->sess, GG_DCC7_INFO, &pkt, sizeof(pkt), NULL); +} + +/** + * \internal Odwraca poĹ‚Ä…czenie po nieudanym connect() + * + * \param dcc Struktura poĹ‚Ä…czenia + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +static int gg_dcc7_reverse_connect(struct gg_dcc7 *dcc) +{ + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_reverse_connect(%p)\n", dcc); + + if (dcc->reverse) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_reverse_connect() already reverse connection\n"); + return -1; + } + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_reverse_connect() timeout, trying reverse connection\n"); + gg_sock_close(dcc->fd); + dcc->fd = -1; + dcc->reverse = 1; + + return gg_dcc7_listen_and_send_info(dcc); +} + +/** + * \internal WysyĹ‚a do serwera ĹĽÄ…danie nadania identyfikatora sesji + * + * \param sess Struktura sesji + * \param type Rodzaj poĹ‚Ä…czenia (\c GG_DCC7_TYPE_FILE lub \c GG_DCC7_TYPE_VOICE) + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +static int gg_dcc7_request_id(struct gg_session *sess, uint32_t type) +{ + struct gg_dcc7_id_request pkt; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_request_id(%p, %d)\n", sess, type); + + if (!sess) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_request_id() invalid parameters\n"); + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_request_id() not connected\n"); + errno = ENOTCONN; + return -1; + } + + if (type != GG_DCC7_TYPE_VOICE && type != GG_DCC7_TYPE_FILE) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_request_id() invalid transfer type (%d)\n", type); + errno = EINVAL; + return -1; + } + + memset(&pkt, 0, sizeof(pkt)); + pkt.type = gg_fix32(type); + + return gg_send_packet(sess, GG_DCC7_ID_REQUEST, &pkt, sizeof(pkt), NULL); +} + +/** + * \internal Rozpoczyna wysyĹ‚anie pliku. + * + * Funkcja jest wykorzystywana przez \c gg_dcc7_send_file() oraz + * \c gg_dcc_send_file_fd(). + * + * \param sess Struktura sesji + * \param rcpt Numer odbiorcy + * \param fd Deskryptor pliku + * \param size Rozmiar pliku + * \param filename1250 Nazwa pliku w kodowaniu CP-1250 + * \param hash SkrĂłt SHA-1 pliku + * \param seek Flaga mĂłwiÄ…ca, czy moĹĽna uĹĽywać lseek() + * + * \return Struktura \c gg_dcc7 lub \c NULL w przypadku bĹ‚Ä™du + * + * \ingroup dcc7 + */ +static struct gg_dcc7 *gg_dcc7_send_file_common(struct gg_session *sess, uin_t rcpt, int fd, size_t size, const char *filename1250, const char *hash, int seek) +{ + struct gg_dcc7 *dcc = NULL; + + if (!sess || !rcpt || !filename1250 || !hash || fd == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_send_file_common() invalid parameters\n"); + errno = EINVAL; + goto fail; + } + + if (!(dcc = malloc(sizeof(struct gg_dcc7)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_send_file_common() not enough memory\n"); + goto fail; + } + + if (gg_dcc7_request_id(sess, GG_DCC7_TYPE_FILE) == -1) + goto fail; + + memset(dcc, 0, sizeof(struct gg_dcc7)); + dcc->type = GG_SESSION_DCC7_SEND; + dcc->dcc_type = GG_DCC7_TYPE_FILE; + dcc->state = GG_STATE_REQUESTING_ID; + dcc->timeout = GG_DEFAULT_TIMEOUT; + dcc->sess = sess; + dcc->fd = -1; + dcc->uin = sess->uin; + dcc->peer_uin = rcpt; + dcc->file_fd = fd; + dcc->size = (unsigned int)size; + dcc->seek = seek; + + strncpy((char*) dcc->filename, filename1250, GG_DCC7_FILENAME_LEN - 1); + dcc->filename[GG_DCC7_FILENAME_LEN] = 0; + + memcpy(dcc->hash, hash, GG_DCC7_HASH_LEN); + + if (gg_dcc7_session_add(sess, dcc) == -1) + goto fail; + + return dcc; + +fail: + free(dcc); + return NULL; +} + +/** + * Rozpoczyna wysyĹ‚anie pliku o danej nazwie. + * + * \param sess Struktura sesji + * \param rcpt Numer odbiorcy + * \param filename Nazwa pliku w lokalnym systemie plikĂłw + * \param filename1250 Nazwa pliku w kodowaniu CP-1250 + * \param hash SkrĂłt SHA-1 pliku (lub \c NULL jeĹ›li ma być wyznaczony) + * + * \return Struktura \c gg_dcc7 lub \c NULL w przypadku bĹ‚Ä™du + * + * \ingroup dcc7 + */ +struct gg_dcc7 *gg_dcc7_send_file(struct gg_session *sess, uin_t rcpt, const char *filename, const char *filename1250, const char *hash) +{ + struct gg_dcc7 *dcc = NULL; + const char *tmp; + char hash_buf[GG_DCC7_HASH_LEN]; + struct stat st; + int fd = -1; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_send_file(%p, %d, \"%s\", %p)\n", sess, rcpt, filename, hash); + + if (!sess || !rcpt || !filename) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_send_file() invalid parameters\n"); + errno = EINVAL; + goto fail; + } + + if (!filename1250) + filename1250 = filename; + + if (stat(filename, &st) == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_send_file() stat() failed (%s)\n", strerror(errno)); + goto fail; + } + + if ((st.st_mode & S_IFDIR)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_send_file() that's a directory\n"); + errno = EINVAL; + goto fail; + } + + if ((fd = open(filename, O_RDONLY | O_BINARY)) == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_send_file() open() failed (%s)\n", strerror(errno)); + goto fail; + } + + if (!hash) { + if (gg_file_hash_sha1(fd, (uint8_t*) hash_buf) == -1) + goto fail; + + hash = hash_buf; + } + +#ifdef _WIN32 + if ((tmp = strrchr(filename1250, '\\'))) +#else + if ((tmp = strrchr(filename1250, '/'))) +#endif + filename1250 = tmp + 1; + + if (!(dcc = gg_dcc7_send_file_common(sess, rcpt, fd, st.st_size, filename1250, hash, 1))) + goto fail; + + return dcc; + +fail: + if (fd != -1) { + int errsv = errno; + close(fd); + errno = errsv; + } + + free(dcc); + return NULL; +} + +/** + * \internal Rozpoczyna wysyĹ‚anie pliku o danym deskryptorze. + * + * \note WysyĹ‚anie pliku nie bÄ™dzie dziaĹ‚ać poprawnie, jeĹ›li deskryptor + * ĹşrĂłdĹ‚owy jest w trybie nieblokujÄ…cym i w pewnym momencie zabraknie danych. + * + * \param sess Struktura sesji + * \param rcpt Numer odbiorcy + * \param fd Deskryptor pliku + * \param size Rozmiar pliku + * \param filename1250 Nazwa pliku w kodowaniu CP-1250 + * \param hash SkrĂłt SHA-1 pliku + * + * \return Struktura \c gg_dcc7 lub \c NULL w przypadku bĹ‚Ä™du + * + * \ingroup dcc7 + */ +struct gg_dcc7 *gg_dcc7_send_file_fd(struct gg_session *sess, uin_t rcpt, int fd, size_t size, const char *filename1250, const char *hash) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_send_file_fd(%p, %d, %d, %u, \"%s\", %p)\n", sess, rcpt, fd, size, filename1250, hash); + + return gg_dcc7_send_file_common(sess, rcpt, fd, size, filename1250, hash, 0); +} + + +/** + * Potwierdza chęć odebrania pliku. + * + * \param dcc Struktura poĹ‚Ä…czenia + * \param offset PoczÄ…tkowy offset przy wznawianiu przesyĹ‚ania pliku + * + * \note Biblioteka nie zmienia poĹ‚oĹĽenia w odbieranych plikach. JeĹ›li offset + * poczÄ…tkowy jest różny od zera, naleĹĽy ustawić go funkcjÄ… \c lseek() lub + * podobnÄ…. + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup dcc7 + */ +int gg_dcc7_accept(struct gg_dcc7 *dcc, unsigned int offset) +{ + struct gg_dcc7_accept pkt; + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_accept(%p, %d)\n", dcc, offset); + + if (!dcc || !dcc->sess) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_accept() invalid parameters\n"); + errno = EFAULT; + return -1; + } + + memset(&pkt, 0, sizeof(pkt)); + pkt.uin = gg_fix32(dcc->peer_uin); + pkt.id = dcc->cid; + pkt.offset = gg_fix32(offset); + + if (gg_send_packet(dcc->sess, GG_DCC7_ACCEPT, &pkt, sizeof(pkt), NULL) == -1) + return -1; + + dcc->offset = offset; + + return gg_dcc7_listen_and_send_info(dcc); +} + +/** + * Odrzuca prĂłbÄ™ przesĹ‚ania pliku. + * + * \param dcc Struktura poĹ‚Ä…czenia + * \param reason PowĂłd odrzucenia + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup dcc7 + */ +int gg_dcc7_reject(struct gg_dcc7 *dcc, int reason) +{ + struct gg_dcc7_reject pkt; + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_reject(%p, %d)\n", dcc, reason); + + if (!dcc || !dcc->sess) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_reject() invalid parameters\n"); + errno = EFAULT; + return -1; + } + + memset(&pkt, 0, sizeof(pkt)); + pkt.uin = gg_fix32(dcc->peer_uin); + pkt.id = dcc->cid; + pkt.reason = gg_fix32(reason); + + return gg_send_packet(dcc->sess, GG_DCC7_REJECT, &pkt, sizeof(pkt), NULL); +} + +/** + * Przerwanie ĹĽÄ…dania przesĹ‚ania pliku. + * + * \param dcc Struktura poĹ‚Ä…czenia + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup dcc7 + */ +int gg_dcc7_abort(struct gg_dcc7 *dcc) +{ + struct gg_dcc7_abort pkt; + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_abort(%p)\n", dcc); + + if (!dcc || !dcc->sess) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_abort() invalid parameters\n"); + errno = EFAULT; + return -1; + } + + memset(&pkt, 0, sizeof(pkt)); + pkt.id = dcc->cid; + pkt.uin_from = gg_fix32(dcc->uin); + pkt.uin_to = gg_fix32(dcc->peer_uin); + + return gg_send_packet(dcc->sess, GG_DCC7_ABORT, &pkt, sizeof(pkt), NULL); +} + +/** + * \internal ObsĹ‚uguje pakiet identyfikatora poĹ‚Ä…czenia bezpoĹ›redniego. + * + * \param sess Struktura sesji + * \param e Struktura zdarzenia + * \param payload Treść pakietu + * \param len DĹ‚ugość pakietu + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +int gg_dcc7_handle_id(struct gg_session *sess, struct gg_event *e, void *payload, int len) +{ + struct gg_dcc7_id_reply *p = payload; + struct gg_dcc7 *tmp; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_id(%p, %p, %p, %d)\n", sess, e, payload, len); + + for (tmp = sess->dcc7_list; tmp; tmp = tmp->next) { + gg_debug_session(sess, GG_DEBUG_MISC, "// checking dcc %p, state %d, type %d\n", tmp, tmp->state, tmp->dcc_type); + + if (tmp->state != GG_STATE_REQUESTING_ID || tmp->dcc_type != (int)gg_fix32(p->type)) + continue; + + tmp->cid = p->id; + + switch (tmp->dcc_type) { + case GG_DCC7_TYPE_FILE: + { + struct gg_dcc7_new s; + + memset(&s, 0, sizeof(s)); + s.id = tmp->cid; + s.type = gg_fix32(GG_DCC7_TYPE_FILE); + s.uin_from = gg_fix32(tmp->uin); + s.uin_to = gg_fix32(tmp->peer_uin); + s.size = gg_fix32(tmp->size); + + strncpy((char*) s.filename, (char*) tmp->filename, GG_DCC7_FILENAME_LEN); + + tmp->state = GG_STATE_WAITING_FOR_ACCEPT; + tmp->timeout = GG_DCC7_TIMEOUT_FILE_ACK; + + return gg_send_packet(sess, GG_DCC7_NEW, &s, sizeof(s), NULL); + } + } + } + + return 0; +} + +/** + * \internal ObsĹ‚uguje pakiet akceptacji poĹ‚Ä…czenia bezpoĹ›redniego. + * + * \param sess Struktura sesji + * \param e Struktura zdarzenia + * \param payload Treść pakietu + * \param len DĹ‚ugość pakietu + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +int gg_dcc7_handle_accept(struct gg_session *sess, struct gg_event *e, void *payload, int len) +{ + struct gg_dcc7_accept *p = payload; + struct gg_dcc7 *dcc; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_accept(%p, %p, %p, %d)\n", sess, e, payload, len); + + if (!(dcc = gg_dcc7_session_find(sess, p->id, gg_fix32(p->uin)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_accept() unknown dcc session\n"); + // XXX wysĹ‚ać reject? + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + e->event.dcc7_error_ex.dcc7 = dcc; + return 0; + } + + if (dcc->state != GG_STATE_WAITING_FOR_ACCEPT) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_accept() invalid state\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + e->event.dcc7_error_ex.dcc7 = dcc; + return 0; + } + + // XXX czy dla odwrotnego poĹ‚Ä…czenia powinniĹ›my wywoĹ‚ać juĹĽ zdarzenie GG_DCC7_ACCEPT? + + dcc->offset = gg_fix32(p->offset); + dcc->state = GG_STATE_WAITING_FOR_INFO; + + return 0; +} + +/** + * \internal ObsĹ‚uguje pakiet informacji o poĹ‚Ä…czeniu bezpoĹ›rednim. + * + * \param sess Struktura sesji + * \param e Struktura zdarzenia + * \param payload Treść pakietu + * \param len DĹ‚ugość pakietu + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +int gg_dcc7_handle_info(struct gg_session *sess, struct gg_event *e, void *payload, int len) +{ + struct gg_dcc7_info *p = payload; + struct gg_dcc7 *dcc; + char *tmp; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_info(%p, %p, %p, %d)\n", sess, e, payload, len); + gg_debug_session(sess, GG_DEBUG_FUNCTION, "// gg_dcc7_handle_info() received address: %s, hash: %s\n", p->info, p->hash); + + if (!(dcc = gg_dcc7_session_find(sess, p->id, gg_fix32(p->uin)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unknown dcc session\n"); + return 0; + } + + if (dcc->state == GG_STATE_CONNECTED) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() state is already connected\n"); + return 0; + } + + switch (p->type) + { + case GG_DCC7_TYPE_P2P: + if ((dcc->remote_addr = inet_addr(p->info)) == INADDR_NONE) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid IP address\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + e->event.dcc7_error_ex.dcc7 = dcc; + return 0; + } + + if (!(tmp = strchr(p->info, ' ')) || !(dcc->remote_port = atoi(tmp + 1))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid IP port\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + e->event.dcc7_error_ex.dcc7 = dcc; + return 0; + } + + if (dcc->state == GG_STATE_WAITING_FOR_INFO) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() wainting for info so send one\n"); + gg_dcc7_listen_and_send_info(dcc); + return 0; + } + + break; + + case GG_DCC7_TYPE_SERVER: + if (!(tmp = strstr(p->info, "GG"))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unknown info packet\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + e->event.dcc7_error_ex.dcc7 = dcc; + return 0; + } + +#if defined(GG_CONFIG_HAVE_UINT64_T) && defined(GG_CONFIG_HAVE_STRTOULL) + { + uint64_t cid; + + cid = strtoull(tmp + 2, NULL, 0); + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() info.str=%s, info.id=%llu, sess.id=%llu\n", tmp + 2, cid, *((unsigned long long*) &dcc->cid)); + + cid = gg_fix64(cid); + + if (memcmp(&dcc->cid, &cid, sizeof(cid)) != 0) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid session id\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + e->event.dcc7_error_ex.dcc7 = dcc; + return 0; + } + } +#endif + + if (gg_dcc7_get_relay_addr(dcc) == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unable to retrieve relay address\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_RELAY; + e->event.dcc7_error_ex.dcc7 = dcc; + return 0; + } + + // XXX wysyĹ‚ać dopiero jeĹ›li uda siÄ™ poĹ‚Ä…czyć z serwerem? + + gg_send_packet(dcc->sess, GG_DCC7_INFO, payload, len, NULL); + + break; + + default: + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() unhandled transfer type (%d)\n", p->type); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + e->event.dcc7_error_ex.dcc7 = dcc; + return 0; + } + + // jeĹ›li nadal czekamy na poĹ‚Ä…czenie przychodzÄ…ce, a druga strona nie + // daje rady i oferuje namiary na siebie, bierzemy co dajÄ…. + +// if (dcc->state != GG_STATE_WAITING_FOR_INFO && (dcc->state != GG_STATE_LISTENING || dcc->reverse)) { +// gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_info() invalid state\n"); +// e->type = GG_EVENT_DCC7_ERROR; +// e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; +// e->event.dcc7_error_ex.dcc7 = dcc; +// return 0; +// } + + if (dcc->state == GG_STATE_LISTENING) { + gg_sock_close(dcc->fd); + dcc->fd = -1; + dcc->reverse = 1; + } + + if (dcc->type == GG_SESSION_DCC7_SEND) { + e->type = GG_EVENT_DCC7_ACCEPT; + e->event.dcc7_accept.dcc7 = dcc; + e->event.dcc7_accept.type = gg_fix32(p->type); + e->event.dcc7_accept.remote_ip = dcc->remote_addr; + e->event.dcc7_accept.remote_port = dcc->remote_port; + } else { + e->type = GG_EVENT_DCC7_PENDING; + e->event.dcc7_pending.dcc7 = dcc; + } + + if (dcc->state == GG_STATE_RESOLVING_RELAY) + return 0; + + if (gg_dcc7_connect(dcc) == -1) { + if (gg_dcc7_reverse_connect(dcc) == -1) { + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_NET; + e->event.dcc7_error_ex.dcc7 = dcc; + return 0; + } + } + + return 0; +} + +/** + * \internal ObsĹ‚uguje pakiet odrzucenia poĹ‚Ä…czenia bezpoĹ›redniego. + * + * \param sess Struktura sesji + * \param e Struktura zdarzenia + * \param payload Treść pakietu + * \param len DĹ‚ugość pakietu + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +int gg_dcc7_handle_reject(struct gg_session *sess, struct gg_event *e, void *payload, int len) +{ + struct gg_dcc7_reject *p = payload; + struct gg_dcc7 *dcc; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_reject(%p, %p, %p, %d)\n", sess, e, payload, len); + + if (!(dcc = gg_dcc7_session_find(sess, p->id, gg_fix32(p->uin)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_reject() unknown dcc session\n"); + return 0; + } + + if (dcc->state != GG_STATE_WAITING_FOR_ACCEPT) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_reject() invalid state\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + e->event.dcc7_error_ex.dcc7 = dcc; + return 0; + } + + e->type = GG_EVENT_DCC7_REJECT; + e->event.dcc7_reject.dcc7 = dcc; + e->event.dcc7_reject.reason = gg_fix32(p->reason); + + // XXX ustawić state na rejected? + + return 0; +} + +/** + * \internal ObsĹ‚uguje pakiet przerwania ĹĽÄ…dania poĹ‚Ä…czenia bezpoĹ›redniego. + * + * \param sess Struktura sesji + * \param e Struktura zdarzenia + * \param payload Treść pakietu + * \param len DĹ‚ugość pakietu + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +int gg_dcc7_handle_abort(struct gg_session *sess, struct gg_event *e, void *payload, int len) +{ + struct gg_dcc7_aborted *p = payload; + struct gg_dcc7 *dcc; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_abort(%p, %p, %p, %d)\n", sess, e, payload, len); + + if (!(dcc = gg_dcc7_session_find(sess, p->id, gg_fix32(0)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_abort() unknown dcc session\n"); + return 0; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_abort() state %d\n", dcc->state); + + if (dcc->state != GG_STATE_IDLE) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_abort() invalid state\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + e->event.dcc7_error_ex.dcc7 = dcc; + return 0; + } + + e->type = GG_EVENT_DCC7_REJECT; + e->event.dcc7_reject.dcc7 = dcc; + e->event.dcc7_reject.reason = gg_fix32(GG_DCC7_REJECT_USER); + + // XXX ustawić state na rejected? + + return 0; +} + +/** + * \internal ObsĹ‚uguje pakiet nowego poĹ‚Ä…czenia bezpoĹ›redniego. + * + * \param sess Struktura sesji + * \param e Struktura zdarzenia + * \param payload Treść pakietu + * \param len DĹ‚ugość pakietu + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +int gg_dcc7_handle_new(struct gg_session *sess, struct gg_event *e, void *payload, int len) +{ + struct gg_dcc7_new *p = payload; + struct gg_dcc7 *dcc; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_dcc7_handle_new(%p, %p, %p, %d)\n", sess, e, payload, len); + + switch (gg_fix32(p->type)) { + case GG_DCC7_TYPE_FILE: + if (!(dcc = malloc(sizeof(struct gg_dcc7)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_new() not enough memory\n"); + return -1; + } + + memset(dcc, 0, sizeof(struct gg_dcc7)); + dcc->type = GG_SESSION_DCC7_GET; + dcc->dcc_type = GG_DCC7_TYPE_FILE; + dcc->fd = -1; + dcc->file_fd = -1; + dcc->uin = sess->uin; + dcc->peer_uin = gg_fix32(p->uin_from); + dcc->cid = p->id; + dcc->sess = sess; + + if (gg_dcc7_session_add(sess, dcc) == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_new() unable to add to session\n"); + gg_dcc7_free(dcc); + return -1; + } + + dcc->size = gg_fix32(p->size); + strncpy((char*) dcc->filename, (char*) p->filename, GG_DCC7_FILENAME_LEN - 1); + dcc->filename[GG_DCC7_FILENAME_LEN] = 0; + memcpy(dcc->hash, p->hash, GG_DCC7_HASH_LEN); + + e->type = GG_EVENT_DCC7_NEW; + e->event.dcc7_new = dcc; + + break; + + case GG_DCC7_TYPE_VOICE: + if (!(dcc = malloc(sizeof(struct gg_dcc7)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_packet() not enough memory\n"); + return -1; + } + + memset(dcc, 0, sizeof(struct gg_dcc7)); + + dcc->type = GG_SESSION_DCC7_VOICE; + dcc->dcc_type = GG_DCC7_TYPE_VOICE; + dcc->fd = -1; + dcc->file_fd = -1; + dcc->uin = sess->uin; + dcc->peer_uin = gg_fix32(p->uin_from); + dcc->cid = p->id; + dcc->sess = sess; + + if (gg_dcc7_session_add(sess, dcc) == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_new() unable to add to session\n"); + gg_dcc7_free(dcc); + return -1; + } + + e->type = GG_EVENT_DCC7_NEW; + e->event.dcc7_new = dcc; + + break; + + default: + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_dcc7_handle_new() unknown dcc type (%d) from %ld\n", gg_fix32(p->type), gg_fix32(p->uin_from)); + + break; + } + + return 0; +} + +/** + * \internal Ustawia odpowiednie stany wewnÄ™trzne w zaleĹĽnoĹ›ci od rodzaju + * poĹ‚Ä…czenia. + * + * \param dcc Struktura poĹ‚Ä…czenia + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du. + */ +static int gg_dcc7_postauth_fixup(struct gg_dcc7 *dcc) +{ + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_postauth_fixup(%p)\n", dcc); + + if (!dcc) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_postauth_fixup() invalid parameters\n"); + errno = EINVAL; + return -1; + } + + switch (dcc->type) { + case GG_SESSION_DCC7_GET: + dcc->state = GG_STATE_GETTING_FILE; + dcc->check = GG_CHECK_READ; + return 0; + + case GG_SESSION_DCC7_SEND: + dcc->state = GG_STATE_SENDING_FILE; + dcc->check = GG_CHECK_WRITE; + return 0; + + case GG_SESSION_DCC7_VOICE: + dcc->state = GG_STATE_READING_VOICE_DATA; + dcc->check = GG_CHECK_READ; + return 0; + } + + errno = EINVAL; + + return -1; +} + +/** + * Funkcja wywoĹ‚ywana po zaobserwowaniu zmian na deskryptorze poĹ‚Ä…czenia. + * + * Funkcja zwraca strukturÄ™ zdarzenia \c gg_event. JeĹ›li rodzaj zdarzenia + * to \c GG_EVENT_NONE, nie wydarzyĹ‚o siÄ™ jeszcze nic wartego odnotowania. + * StrukturÄ™ zdarzenia naleĹĽy zwolnić funkcja \c gg_event_free(). + * + * \param dcc Struktura poĹ‚Ä…czenia + * + * \return Struktura zdarzenia lub \c NULL jeĹ›li wystÄ…piĹ‚ bĹ‚Ä…d + * + * \ingroup dcc7 + */ +struct gg_event *gg_dcc7_watch_fd(struct gg_dcc7 *dcc) +{ + struct gg_event *e; + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_watch_fd(%p)\n", dcc); + + if (!dcc || (dcc->type != GG_SESSION_DCC7_SEND && dcc->type != GG_SESSION_DCC7_GET && dcc->type != GG_SESSION_DCC7_VOICE)) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() invalid parameters\n"); + errno = EINVAL; + return NULL; + } + + if (!(e = malloc(sizeof(struct gg_event)))) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() not enough memory\n"); + return NULL; + } + + memset(e, 0, sizeof(struct gg_event)); + e->type = GG_EVENT_NONE; + + switch (dcc->state) { + case GG_STATE_LISTENING: + { + struct sockaddr_in sin; + SOCKET fd; + int one = 1; + unsigned int sin_len = sizeof(sin); + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_LISTENING\n"); + + if ((fd = accept(dcc->fd, (struct sockaddr*) &sin, &sin_len)) == -1) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() accept() failed (%s)\n", strerror(errno)); + return e; + } + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection from %s:%d\n", inet_ntoa(sin.sin_addr), htons(sin.sin_port)); + +#ifdef FIONBIO + if (ioctl(fd, FIONBIO, &one) == -1) { +#else + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { +#endif + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() can't set nonblocking (%s)\n", strerror(errno)); + gg_sock_close(fd); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + + gg_sock_close(dcc->fd); + dcc->fd = fd; + + dcc->state = GG_STATE_READING_ID; + dcc->check = GG_CHECK_READ; + dcc->timeout = GG_DEFAULT_TIMEOUT; + dcc->incoming = 1; + + dcc->remote_port = ntohs(sin.sin_port); + dcc->remote_addr = sin.sin_addr.s_addr; + + e->type = GG_EVENT_DCC7_CONNECTED; + e->event.dcc7_connected.dcc7 = dcc; + + return e; + } + + case GG_STATE_CONNECTING: + { + int res = 0, error = 0; + unsigned int error_size = sizeof(error); + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_CONNECTING\n"); + + dcc->soft_timeout = 0; + + if (dcc->timeout == 0) + error = ETIMEDOUT; + + if (error || (res = gg_getsockopt(dcc->fd, SOL_SOCKET, SO_ERROR, &error, &error_size)) == -1 || error != 0) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection failed (%s)\n", (res == -1) ? strerror(errno) : strerror(error)); + + if (dcc->relay) { + for (dcc->relay_index++; dcc->relay_index < dcc->relay_count; dcc->relay_index++) { + dcc->remote_addr = dcc->relay_list[dcc->relay_index].addr; + dcc->remote_port = dcc->relay_list[dcc->relay_index].port; + + if (gg_dcc7_connect(dcc) == 0) + break; + } + + if (dcc->relay_index >= dcc->relay_count) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() no relay available"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_RELAY; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + } else { + if (gg_dcc7_reverse_connect(dcc) != -1) { + e->type = GG_EVENT_DCC7_PENDING; + e->event.dcc7_pending.dcc7 = dcc; + } else { + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_NET; + e->event.dcc7_error_ex.dcc7 = dcc; + } + + return e; + } + } + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connected, sending id\n"); + + dcc->state = GG_STATE_SENDING_ID; + dcc->check = GG_CHECK_WRITE; + dcc->timeout = GG_DEFAULT_TIMEOUT; + dcc->incoming = 0; + + return e; + } + + case GG_STATE_READING_ID: + { + int res; + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_READING_ID\n"); + + if (!dcc->relay) { + struct gg_dcc7_welcome_p2p welcome, welcome_ok; + welcome_ok.id = dcc->cid; + + if ((res = gg_sock_read(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (%d, %s)\n", res, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + + if (memcmp(&welcome, &welcome_ok, sizeof(welcome))) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() invalid id\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + } else { + struct gg_dcc7_welcome_server welcome, welcome_ok; + welcome_ok.magic = GG_DCC7_WELCOME_SERVER; + welcome_ok.id = dcc->cid; + + if ((res = gg_sock_read(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (%d, %s)\n", res, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + + if (memcmp(&welcome, &welcome_ok, sizeof(welcome)) != 0) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() invalid id\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + } + + if (dcc->incoming) { + dcc->state = GG_STATE_SENDING_ID; + dcc->check = GG_CHECK_WRITE; + dcc->timeout = GG_DEFAULT_TIMEOUT; + } else { + gg_dcc7_postauth_fixup(dcc); + dcc->timeout = GG_DEFAULT_TIMEOUT; + } + + return e; + } + + case GG_STATE_SENDING_ID: + { + int res; + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_SENDING_ID\n"); + + if (!dcc->relay) { + struct gg_dcc7_welcome_p2p welcome; + + welcome.id = dcc->cid; + + if ((res = gg_sock_write(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (%d, %s)", res, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + } else { + struct gg_dcc7_welcome_server welcome; + + welcome.magic = gg_fix32(GG_DCC7_WELCOME_SERVER); + welcome.id = dcc->cid; + + if ((res = gg_sock_write(dcc->fd, &welcome, sizeof(welcome))) != sizeof(welcome)) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (%d, %s)\n", res, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + } + + if (dcc->incoming) { + gg_dcc7_postauth_fixup(dcc); + dcc->timeout = GG_DEFAULT_TIMEOUT; + } else { + dcc->state = GG_STATE_READING_ID; + dcc->check = GG_CHECK_READ; + dcc->timeout = GG_DEFAULT_TIMEOUT; + } + + return e; + } + + case GG_STATE_SENDING_FILE: + { + char buf[1024]; + int chunk, res; + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_SENDING_FILE (offset=%d, size=%d)\n", dcc->offset, dcc->size); + + if (dcc->offset >= dcc->size) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() offset >= size, finished\n"); + e->type = GG_EVENT_DCC7_DONE; + e->event.dcc7_done.dcc7 = dcc; + return e; + } + + if (dcc->seek && lseek(dcc->file_fd, dcc->offset, SEEK_SET) == (off_t) -1) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() lseek() failed (%s)\n", strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_FILE; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + + if ((chunk = dcc->size - dcc->offset) > sizeof(buf)) + chunk = sizeof(buf); + + if ((res = read(dcc->file_fd, buf, chunk)) < 1) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (res=%d, %s)\n", res, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = (res == -1) ? GG_ERROR_DCC7_FILE : GG_ERROR_DCC7_EOF; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + + if ((res = gg_sock_write(dcc->fd, buf, res)) == -1) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (%s)\n", strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_NET; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + + dcc->offset += res; + + if (dcc->offset >= dcc->size) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() finished\n"); + e->type = GG_EVENT_DCC7_DONE; + e->event.dcc7_done.dcc7 = dcc; + return e; + } + + dcc->state = GG_STATE_SENDING_FILE; + dcc->check = GG_CHECK_WRITE; + dcc->timeout = GG_DCC7_TIMEOUT_SEND; + + return e; + } + + case GG_STATE_GETTING_FILE: + { + char buf[1024]; + int res, wres; + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_GETTING_FILE (offset=%d, size=%d)\n", dcc->offset, dcc->size); + + if (dcc->offset >= dcc->size) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() finished\n"); + e->type = GG_EVENT_DCC7_DONE; + e->event.dcc7_done.dcc7 = dcc; + return e; + } + + if ((res = gg_sock_read(dcc->fd, buf, sizeof(buf))) < 1) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (fd=%d, res=%d, %s)\n", dcc->fd, res, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = (res == -1) ? GG_ERROR_DCC7_NET : GG_ERROR_DCC7_EOF; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + + // XXX zapisywać do skutku? + + if ((wres = write(dcc->file_fd, buf, res)) < res) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() write() failed (fd=%d, res=%d, %s)\n", dcc->file_fd, wres, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_FILE; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + + dcc->offset += res; + + if (dcc->offset >= dcc->size) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() finished\n"); + e->type = GG_EVENT_DCC7_DONE; + e->event.dcc7_done.dcc7 = dcc; + return e; + } + + dcc->state = GG_STATE_GETTING_FILE; + dcc->check = GG_CHECK_READ; + dcc->timeout = GG_DCC7_TIMEOUT_GET; + + return e; + } + + case GG_STATE_RESOLVING_RELAY: + { + struct in_addr addr; + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_RESOLVING_RELAY\n"); + + if (gg_sock_read(dcc->fd, &addr, sizeof(addr)) < sizeof(addr) || addr.s_addr == INADDR_NONE) { + int errno_save = errno; + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() resolving failed\n"); + gg_sock_close(dcc->fd); + dcc->fd = -1; + dcc->sess->resolver_cleanup(&dcc->resolver, 0); + errno = errno_save; + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_RELAY; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() resolved, connecting to %s:%d\n", inet_ntoa(addr), GG_RELAY_PORT); + + if ((dcc->fd = gg_connect(&addr, GG_RELAY_PORT, 1)) == -1) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection failed (errno=%d, %s), critical\n", errno, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_RELAY; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + + dcc->state = GG_STATE_CONNECTING_RELAY; + dcc->check = GG_CHECK_WRITE; + dcc->timeout = GG_DEFAULT_TIMEOUT; + + return e; + } + + case GG_STATE_CONNECTING_RELAY: + { + int res; + unsigned int res_size = sizeof(res); + struct gg_dcc7_relay_req pkt; + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_CONNECTING_RELAY\n"); + + if (gg_getsockopt(dcc->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) != 0 || res != 0) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection failed (errno=%d, %s)\n", res, strerror(res)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_RELAY; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + + memset(&pkt, 0, sizeof(pkt)); + pkt.magic = gg_fix32(GG_DCC7_RELAY_REQUEST); + pkt.len = gg_fix32(sizeof(pkt)); + pkt.id = dcc->cid; + pkt.type = gg_fix16(GG_DCC7_RELAY_TYPE_SERVER); + pkt.dunno1 = gg_fix16(GG_DCC7_RELAY_DUNNO1); + + gg_debug_dump_session((dcc) ? (dcc)->sess : NULL, &pkt, sizeof(pkt), "// gg_dcc7_watch_fd() send pkt(0x%.2x)\n", gg_fix32(pkt.magic)); + + if ((res = gg_sock_write(dcc->fd, &pkt, sizeof(pkt))) != sizeof(pkt)) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() sending failed\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_RELAY; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + + dcc->state = GG_STATE_READING_RELAY; + dcc->check = GG_CHECK_READ; + dcc->timeout = GG_DEFAULT_TIMEOUT; + + return e; + } + + case GG_STATE_READING_RELAY: + { + char buf[256]; + struct gg_dcc7_relay_reply *pkt; + struct gg_dcc7_relay_reply_server srv; + int res; + int i; + + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_READING_RELAY\n"); + + if ((res = gg_sock_read(dcc->fd, buf, sizeof(buf))) < sizeof(*pkt)) { + if (res == 0) + errno = ECONNRESET; + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() read() failed (%d, %s)\n", res, strerror(errno)); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_RELAY; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + + pkt = (struct gg_dcc7_relay_reply*) buf; + + if (gg_fix32(pkt->magic) != GG_DCC7_RELAY_REPLY || gg_fix32(pkt->rcount) < 1 || gg_fix32(pkt->rcount > 256) || gg_fix32(pkt->len) < sizeof(*pkt) + gg_fix32(pkt->rcount) * sizeof(srv)) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_wathc_fd() invalid reply\n"); + errno = EINVAL; + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_RELAY; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + + gg_debug_dump_session((dcc) ? (dcc)->sess : NULL, buf, res, "// gg_dcc7_get_relay() read pkt(0x%.2x)\n", gg_fix32(pkt->magic)); + + free(dcc->relay_list); + + dcc->relay_index = 0; + dcc->relay_count = gg_fix32(pkt->rcount); + dcc->relay_list = malloc(dcc->relay_count * sizeof(gg_dcc7_relay_t)); + + if (dcc->relay_list == NULL) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() not enough memory"); + dcc->relay_count = 0; + free(e); + return NULL; + } + + for (i = 0; i < dcc->relay_count; i++) { + struct in_addr addr; + + memcpy(&srv, buf + sizeof(*pkt) + i * sizeof(srv), sizeof(srv)); + dcc->relay_list[i].addr = srv.addr; + dcc->relay_list[i].port = gg_fix16(srv.port); + dcc->relay_list[i].family = srv.family; + + addr.s_addr = srv.addr; + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// %s %d %d\n", inet_ntoa(addr), gg_fix16(srv.port), srv.family); + } + + dcc->relay = 1; + + for (; dcc->relay_index < dcc->relay_count; dcc->relay_index++) { + dcc->remote_addr = dcc->relay_list[dcc->relay_index].addr; + dcc->remote_port = dcc->relay_list[dcc->relay_index].port; + + if (gg_dcc7_connect(dcc) == 0) + break; + } + + if (dcc->relay_index >= dcc->relay_count) { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() no relay available"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_RELAY; + e->event.dcc7_error_ex.dcc7 = dcc; + return e; + } + + return e; + } + + default: + { + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() GG_STATE_???\n"); + e->type = GG_EVENT_DCC7_ERROR; + e->event.dcc7_error = GG_ERROR_DCC7_HANDSHAKE; + e->event.dcc7_error_ex.dcc7 = dcc; + + return e; + } + } + + return e; +} + +/** + * Zwalnia zasoby uĹĽywane przez poĹ‚Ä…czenie bezpoĹ›rednie. + * + * \param dcc Struktura poĹ‚Ä…czenia + * + * \ingroup dcc7 + */ +void gg_dcc7_free(struct gg_dcc7 *dcc) +{ + gg_debug_session((dcc) ? (dcc)->sess : NULL, GG_DEBUG_FUNCTION, "** gg_dcc7_free(%p)\n", dcc); + + if (!dcc) + return; + + if (dcc->fd != -1) + gg_sock_close(dcc->fd); + + if (dcc->file_fd != -1) + close(dcc->file_fd); + + if (dcc->sess) + gg_dcc7_session_remove(dcc->sess, dcc); + + free(dcc->relay_list); + + free(dcc); +} + diff --git a/protocols/Gadu-Gadu/src/libgadu/events.c b/protocols/Gadu-Gadu/src/libgadu/events.c new file mode 100644 index 0000000000..a730e9d620 --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/events.c @@ -0,0 +1,2864 @@ +/* coding: UTF-8 */ +/* $Id: events.c 13583 2011-04-12 12:51:18Z dezred $ */ + +/* + * (C) Copyright 2001-2006 Wojtek Kaniewski + * Robert J. WoĹşny + * Arkadiusz MiĹ›kiewicz + * Adam Wysocki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +/** + * \file events.c + * + * \brief ObsĹ‚uga zdarzeĹ„ + */ + +#ifndef _WIN64 +#define _USE_32BIT_TIME_T +#endif + +#include +#ifdef _WIN32 +#include "win32.h" +#else +#include +#include +#include +#include +#endif /* _WIN32 */ + +#include "compat.h" +#include "libgadu.h" +#include "protocol.h" +#include "internal.h" + +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#include +#endif /* _WIN32 */ +#ifndef GG_CONFIG_MIRANDA +#ifdef GG_CONFIG_HAVE_OPENSSL +# include +# include +#endif +#endif + +/** + * Zwalnia pamięć zajmowanÄ… przez informacjÄ™ o zdarzeniu. + * + * FunkcjÄ™ naleĹĽy wywoĹ‚ywać za kaĹĽdym razem gdy funkcja biblioteki zwrĂłci + * strukturÄ™ \c gg_event. + * + * \param e Struktura zdarzenia + * + * \ingroup events + */ +void gg_event_free(struct gg_event *e) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_event_free(%p);\n", e); + + if (!e) + return; + + switch (e->type) { + case GG_EVENT_MSG: + case GG_EVENT_MULTILOGON_MSG: + free(e->event.msg.message); + free(e->event.msg.formats); + free(e->event.msg.recipients); + free(e->event.msg.xhtml_message); + break; + + case GG_EVENT_NOTIFY: + free(e->event.notify); + break; + + case GG_EVENT_NOTIFY60: + { + int i; + + for (i = 0; e->event.notify60[i].uin; i++) + free(e->event.notify60[i].descr); + + free(e->event.notify60); + + break; + } + + case GG_EVENT_STATUS60: + free(e->event.status60.descr); + break; + + case GG_EVENT_STATUS: + free(e->event.status.descr); + break; + + case GG_EVENT_NOTIFY_DESCR: + free(e->event.notify_descr.notify); + free(e->event.notify_descr.descr); + break; + + case GG_EVENT_DCC_VOICE_DATA: + free(e->event.dcc_voice_data.data); + break; + + case GG_EVENT_PUBDIR50_SEARCH_REPLY: + case GG_EVENT_PUBDIR50_READ: + case GG_EVENT_PUBDIR50_WRITE: + gg_pubdir50_free(e->event.pubdir50); + break; + + case GG_EVENT_USERLIST: + free(e->event.userlist.reply); + break; + + case GG_EVENT_IMAGE_REPLY: + free(e->event.image_reply.filename); + free(e->event.image_reply.image); + break; + + case GG_EVENT_XML_EVENT: + free(e->event.xml_event.data); + break; + + case GG_EVENT_XML_ACTION: + free(e->event.xml_action.data); + break; + + case GG_EVENT_USER_DATA: + { + unsigned i, j; + + for (i = 0; i < e->event.user_data.user_count; i++) { + for (j = 0; j < e->event.user_data.users[i].attr_count; j++) { + free(e->event.user_data.users[i].attrs[j].key); + free(e->event.user_data.users[i].attrs[j].value); + } + + free(e->event.user_data.users[i].attrs); + } + + free(e->event.user_data.users); + + break; + } + + case GG_EVENT_MULTILOGON_INFO: + { + int i; + + for (i = 0; i < e->event.multilogon_info.count; i++) + free(e->event.multilogon_info.sessions[i].name); + + free(e->event.multilogon_info.sessions); + + break; + } + } + + free(e); +} + +/** \cond internal */ + +/** + * \internal Usuwa obrazek z kolejki do wysĹ‚ania. + * + * \param s Struktura sesji + * \param q Struktura obrazka + * \param freeq Flaga zwolnienia elementu kolejki + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 jeĹ›li wystÄ…piĹ‚ bĹ‚Ä…d + */ +int gg_image_queue_remove(struct gg_session *s, struct gg_image_queue *q, int freeq) +{ + if (!s || !q) { + errno = EFAULT; + return -1; + } + + if (s->images == q) + s->images = q->next; + else { + struct gg_image_queue *qq; + + for (qq = s->images; qq; qq = qq->next) { + if (qq->next == q) { + qq->next = q->next; + break; + } + } + } + + if (freeq) { + free(q->image); + free(q->filename); + free(q); + } + + return 0; +} + +/** + * \internal Analizuje przychodzÄ…cy pakiet z obrazkiem. + * + * \param e Struktura zdarzenia + * \param p Bufor z danymi + * \param len DĹ‚ugość bufora + * \param sess Struktura sesji + * \param sender Numer nadawcy + * \param size Rozmiar pliku (z nagĹ‚Ăłwka) + * \param crc32 Suma kontrolna (z nagĹ‚Ăłwka) + */ +static void gg_image_queue_parse(struct gg_event *e, char *p, unsigned int len, struct gg_session *sess, uin_t sender, uint32_t size, uint32_t crc32) +{ + struct gg_image_queue *q, *qq; + + if (!p || !sess || !e) { + errno = EFAULT; + return; + } + + /* znajdĹş dany obrazek w kolejce danej sesji */ + + for (qq = sess->images, q = NULL; qq; qq = qq->next) { + if (sender == qq->sender && size == qq->size && crc32 == qq->crc32) { + q = qq; + break; + } + } + + if (!q) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() unknown image from %d, size=%d, crc32=%.8x\n", sender, size, crc32); + return; + } + + if (p[0] == 0x05) { + q->done = 0; + + len -= sizeof(struct gg_msg_image_reply); + p += sizeof(struct gg_msg_image_reply); + + if (memchr(p, 0, len) == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() malformed packet from %d, unlimited filename\n", sender); + return; + } + + if (!(q->filename = strdup(p))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_queue_parse() not enough memory for filename\n"); + return; + } + + len -= (unsigned int)strlen(p) + 1; + p += strlen(p) + 1; + } else { + len -= sizeof(struct gg_msg_image_reply); + p += sizeof(struct gg_msg_image_reply); + } + + if (q->done + len > q->size) + len = q->size - q->done; + + memcpy(q->image + q->done, p, len); + q->done += len; + + /* jeĹ›li skoĹ„czono odbierać obrazek, wygeneruj zdarzenie */ + + if (q->done >= q->size) { + e->type = GG_EVENT_IMAGE_REPLY; + e->event.image_reply.sender = sender; + e->event.image_reply.size = q->size; + e->event.image_reply.crc32 = q->crc32; + e->event.image_reply.filename = q->filename; + e->event.image_reply.image = q->image; + + gg_image_queue_remove(sess, q, 0); + + free(q); + } +} + +/** + * \internal Analizuje informacje rozszerzone wiadomoĹ›ci. + * + * \param sess Struktura sesji. + * \param e Struktura zdarzenia. + * \param sender Numer nadawcy. + * \param p WskaĹşnik na dane rozszerzone. + * \param packet_end WskaĹşnik na koniec pakietu. + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 jeĹ›li wiadomość obsĹ‚uĹĽono i wynik ma + * zostać przekazany aplikacji, -2 jeĹ›li wystÄ…piĹ‚ bĹ‚Ä…d ogĂłlny, -3 jeĹ›li + * wiadomość jest niepoprawna. + */ +static int gg_handle_recv_msg_options(struct gg_session *sess, struct gg_event *e, uin_t sender, char *p, char *packet_end) +{ + while (p < packet_end) { + switch (*p) { + case 0x01: /* konferencja */ + { + struct gg_msg_recipients *m = (void*) p; + uint32_t i, count; + + if (p + sizeof(*m) > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (1)\n"); + goto malformed; + } + + memcpy(&count, &m->count, sizeof(count)); + count = gg_fix32(count); + p += sizeof(*m); + + if (p + count * sizeof(uin_t) > packet_end || p + count * sizeof(uin_t) < p || count > 0xffff) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (1.5)\n"); + goto malformed; + } + + if (e->event.msg.recipients != NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() e->event.msg.recipients already exist\n"); + goto malformed; + } + + e->event.msg.recipients = malloc(count * sizeof(uin_t)); + + if (e->event.msg.recipients == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() not enough memory for recipients data\n"); + goto fail; + } + + memcpy(e->event.msg.recipients, p, count * sizeof(uin_t)); + p += count * sizeof(uin_t); + + for (i = 0; i < count; i++) + e->event.msg.recipients[i] = gg_fix32(e->event.msg.recipients[i]); + + e->event.msg.recipients_count = count; + + break; + } + + case 0x02: /* richtext */ + { + uint16_t len; + char *buf; + + if (p + 3 > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (2)\n"); + goto malformed; + } + + memcpy(&len, p + 1, sizeof(uint16_t)); + len = gg_fix16(len); + + if (e->event.msg.formats != NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() e->event.msg.formats already exist\n"); + goto malformed; + } + + buf = malloc(len); + + if (buf == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() not enough memory for richtext data\n"); + goto fail; + } + + p += 3; + + if (p + len > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (3)\n"); + free(buf); + goto malformed; + } + + memcpy(buf, p, len); + + e->event.msg.formats = buf; + e->event.msg.formats_length = len; + + p += len; + + break; + } + + case 0x04: /* image_request */ + { + struct gg_msg_image_request *i = (void*) p; + + if (p + sizeof(*i) > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (3.5)\n"); + goto malformed; + } + + if (e->event.msg.formats != NULL || e->event.msg.recipients != NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() mixed options (1)\n"); + goto malformed; + } + + memcpy(&e->event.image_request.size, &i->size, sizeof(i->size)); + memcpy(&e->event.image_request.crc32, &i->crc32, sizeof(i->crc32)); + + e->event.image_request.sender = sender; + e->event.image_request.size = gg_fix32(e->event.image_request.size); + e->event.image_request.crc32 = gg_fix32(e->event.image_request.crc32); + + e->type = GG_EVENT_IMAGE_REQUEST; + + goto handled; + } + + case 0x05: /* image_reply */ + case 0x06: + { + struct gg_msg_image_reply *rep = (void*) p; + uint32_t size; + uint32_t crc32; + + if (e->event.msg.formats != NULL || e->event.msg.recipients != NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() mixed options (2)\n"); + goto malformed; + } + + if (p + sizeof(struct gg_msg_image_reply) + 1 > packet_end) { + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() packet out of bounds (4)\n"); + goto malformed; + } + + memcpy(&size, &rep->size, sizeof(size)); + memcpy(&crc32, &rep->crc32, sizeof(crc32)); + size = gg_fix32(size); + crc32 = gg_fix32(crc32); + + if (p + sizeof(struct gg_msg_image_reply) == packet_end) { + /* pusta odpowiedĹş - klient po drugiej stronie nie ma ĹĽÄ…danego obrazka */ + + e->type = GG_EVENT_IMAGE_REPLY; + e->event.image_reply.sender = sender; + e->event.image_reply.size = 0; + e->event.image_reply.crc32 = crc32; + e->event.image_reply.filename = NULL; + e->event.image_reply.image = NULL; + goto handled; + + } + + gg_image_queue_parse(e, p, (unsigned int)(packet_end - p), sess, sender, size, crc32); + + goto handled; + } + + default: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg_options() unknown payload 0x%.2x\n", *p); + p = packet_end; + } + } + } + + return 0; + +handled: + return -1; + +fail: + return -2; + +malformed: + return -3; +} + +/** + * \internal Analizuje przychodzÄ…cy pakiet z wiadomoĹ›ciÄ…. + * + * Rozbija pakiet na poszczegĂłlne skĹ‚adniki -- tekst, informacje + * o konferencjach, formatowani itd. + * + * \param h WskaĹşnik do odebranego pakietu + * \param e Struktura zdarzenia + * \param sess Struktura sesji + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +static int gg_handle_recv_msg(struct gg_header *h, struct gg_event *e, struct gg_session *sess) +{ + struct gg_recv_msg *r = (struct gg_recv_msg*) ((char*) h + sizeof(struct gg_header)); + char *p, *packet_end = (char*) r + h->length; + int ctcp = 0; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_handle_recv_msg(%p, %p);\n", h, e); + + if (!r->seq && !r->msgclass) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() oops, silently ignoring the bait\n"); + e->type = GG_EVENT_NONE; + return 0; + } + + /* znajdĹş \0 */ + for (p = (char*) r + sizeof(*r); ; p++) { + if (p >= packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() malformed packet, message out of bounds (0)\n"); + goto malformed; + } + + if (*p == 0x02 && p == packet_end - 1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg() received ctcp packet\n"); + ctcp = 1; + break; + } + + if (!*p) + break; + } + + p++; + + switch (gg_handle_recv_msg_options(sess, e, gg_fix32(r->sender), p, packet_end)) { + case -1: // handled + return 0; + + case -2: // failed + goto fail; + + case -3: // malformed + goto malformed; + } + + e->type = GG_EVENT_MSG; + e->event.msg.msgclass = gg_fix32(r->msgclass); + e->event.msg.sender = gg_fix32(r->sender); + e->event.msg.time = gg_fix32(r->time); + e->event.msg.seq = gg_fix32(r->seq); + if (ctcp) + e->event.msg.message = (unsigned char*) strdup("\x02"); + else + e->event.msg.message = (unsigned char*) strdup((char*) r + sizeof(*r)); + + + return 0; + +malformed: + e->type = GG_EVENT_NONE; + free(e->event.msg.message); + free(e->event.msg.recipients); + free(e->event.msg.formats); + + return 0; + +fail: + free(e->event.msg.message); + free(e->event.msg.recipients); + free(e->event.msg.formats); + return -1; +} + +/** + * \internal Zamienia tekst w formacie HTML na czysty tekst. + * + * \param dst Bufor wynikowy (moĹĽe być \c NULL) + * \param html Tekst ĹşrĂłdĹ‚owy + * + * \note Dokleja \c \\0 na koĹ„cu bufora wynikowego. + * + * \return DĹ‚ugość tekstu wynikowego bez \c \\0 (nawet jeĹ›li \c dst to \c NULL). + */ +static int gg_convert_from_html(char *dst, const char *html) +{ + const char *src, *entity, *tag; + int len, in_tag, in_entity; + + len = 0; + in_tag = 0; + tag = NULL; + in_entity = 0; + entity = NULL; + + for (src = html; *src != 0; src++) { + if (*src == '<') { + tag = src; + in_tag = 1; + continue; + } + + if (in_tag && (*src == '>')) { + if (strncmp(tag, "seq && !r->msgclass) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() oops, silently ignoring the bait\n"); + goto malformed; + } + + offset_plain = gg_fix32(r->offset_plain); + offset_attr = gg_fix32(r->offset_attr); + + if (offset_plain < sizeof(struct gg_recv_msg80) || offset_plain >= h->length) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, message out of bounds (0)\n"); + goto malformed; + } + + if (offset_attr < sizeof(struct gg_recv_msg80) || offset_attr > h->length) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, attr out of bounds (1)\n"); + offset_attr = 0; /* nie parsuj attr. */ + /* goto ignore; */ + } + + /* Normalna sytuacja, wiÄ™c nie podpada pod powyĹĽszy warunek. */ + if (offset_attr == h->length) + offset_attr = 0; + + if (memchr(packet + offset_plain, 0, h->length - offset_plain) == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, message out of bounds (2)\n"); + goto malformed; + } + + if (offset_plain > sizeof(struct gg_recv_msg80) && memchr(packet + sizeof(struct gg_recv_msg80), 0, offset_plain - sizeof(struct gg_recv_msg80)) == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_recv_msg80() malformed packet, message out of bounds (3)\n"); + goto malformed; + } + + e->type = event; + e->event.msg.msgclass = gg_fix32(r->msgclass); + e->event.msg.sender = gg_fix32(r->sender); + e->event.msg.time = gg_fix32(r->time); + e->event.msg.seq = gg_fix32(r->seq); + + if (offset_attr != 0) { + switch (gg_handle_recv_msg_options(sess, e, gg_fix32(r->sender), packet + offset_attr, packet + h->length)) { + case -1: // handled + return 0; + + case -2: // failed + goto fail; + + case -3: // malformed + goto malformed; + } + } + + if (sess->encoding == GG_ENCODING_CP1250) { + e->event.msg.message = (unsigned char*) strdup(packet + offset_plain); + } else { + if (offset_plain > sizeof(struct gg_recv_msg80)) { + int len; + + len = gg_convert_from_html(NULL, packet + sizeof(struct gg_recv_msg80)); + + e->event.msg.message = malloc(len + 1); + + if (e->event.msg.message == NULL) + goto fail; + + gg_convert_from_html((char*) e->event.msg.message, packet + sizeof(struct gg_recv_msg80)); + } else { + e->event.msg.message = (unsigned char*) gg_cp_to_utf8(packet + offset_plain); + } + } + + if (offset_plain > sizeof(struct gg_recv_msg80)) { + if (sess->encoding == GG_ENCODING_UTF8) + e->event.msg.xhtml_message = strdup(packet + sizeof(struct gg_recv_msg80)); + else + e->event.msg.xhtml_message = gg_utf8_to_cp(packet + sizeof(struct gg_recv_msg80)); + } else { + e->event.msg.xhtml_message = NULL; + } + + return 0; + +fail: + free(e->event.msg.message); + free(e->event.msg.xhtml_message); + free(e->event.msg.recipients); + free(e->event.msg.formats); + return -1; + +malformed: + e->type = GG_EVENT_NONE; + free(e->event.msg.message); + free(e->event.msg.xhtml_message); + free(e->event.msg.recipients); + free(e->event.msg.formats); + return 0; +} + +/** + * \internal WysyĹ‚a potwierdzenie odebrania wiadomoĹ›ci. + * + * \param sess Struktura sesji + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 jeĹ›li wystÄ…piĹ‚ bĹ‚Ä…d + */ +static int gg_handle_recv_msg_ack(struct gg_header *h, struct gg_session *sess) +{ + char *packet = (char*) h + sizeof(struct gg_header); + struct gg_recv_msg80 *r = (struct gg_recv_msg80*) packet; + struct gg_recv_msg_ack pkt; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_handle_recv_msg_ack(%p);\n", sess); + + if ((sess->protocol_features & GG_FEATURE_MSG_ACK) == 0) + return 0; + + pkt.seq = gg_fix32(r->seq); + + return gg_send_packet(sess, GG_RECV_MSG_ACK, &pkt, sizeof(pkt), NULL); +} + +/** + * \internal Analizuje przychodzÄ…cy pakiet z danymi kontaktĂłw. + * + * \param sess Struktura sesji + * \param e Struktura zdarzenia + * \param payload Treść pakietu + * \param len DĹ‚ugość pakietu + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +static int gg_handle_user_data(struct gg_session *sess, struct gg_event *e, void *packet, size_t len) +{ + struct gg_user_data d; + char *p = (char*) packet; + char *packet_end = (char*) packet + len; + struct gg_event_user_data_user *users; + unsigned i, j; + int res = 0; + + gg_debug_session(sess, GG_DEBUG_MISC, "** gg_handle_user_data(%p, %p, %p, %d);\n", sess, e, packet, len); + + e->event.user_data.user_count = 0; + e->event.user_data.users = NULL; + + if (p + sizeof(d) > packet_end) + goto malformed; + + memcpy(&d, p, sizeof(d)); + p += sizeof(d); + + d.type = gg_fix32(d.type); + d.user_count = gg_fix32(d.user_count); + + if (d.user_count > 0xffff) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() malformed packet (1)\n"); + goto malformed; + } + + if (d.user_count > 0) { + users = calloc(d.user_count, sizeof(struct gg_event_user_data_user)); + + if (users == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() out of memory (%d*%d)\n", d.user_count, sizeof(struct gg_event_user_data_user)); + goto fail; + } + } else { + users = NULL; + } + + e->type = GG_EVENT_USER_DATA; + e->event.user_data.type = d.type; + e->event.user_data.user_count = d.user_count; + e->event.user_data.users = users; + + gg_debug_session(sess, GG_DEBUG_DUMP, "type=%d, count=%d\n", d.type, d.user_count); + + for (i = 0; i < d.user_count; i++) { + struct gg_user_data_user u; + struct gg_event_user_data_attr *attrs; + + if (p + sizeof(u) > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() malformed packet (2)\n"); + goto malformed; + } + + memcpy(&u, p, sizeof(u)); + p += sizeof(u); + + u.uin = gg_fix32(u.uin); + u.attr_count = gg_fix32(u.attr_count); + + if (u.attr_count > 0xffff) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() malformed packet (2)\n"); + goto malformed; + } + + if (u.attr_count > 0) { + attrs = calloc(u.attr_count, sizeof(struct gg_event_user_data_attr)); + + if (attrs == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() out of memory (%d*%d)\n", u.attr_count, sizeof(struct gg_event_user_data_attr)); + goto fail; + } + } else { + attrs = NULL; + } + + users[i].uin = u.uin; + users[i].attr_count = u.attr_count; + users[i].attrs = attrs; + + gg_debug_session(sess, GG_DEBUG_DUMP, " uin=%d, count=%d\n", u.uin, u.attr_count); + + for (j = 0; j < u.attr_count; j++) { + uint32_t key_size; + uint32_t attr_type; + uint32_t value_size; + char *key; + char *value; + + if (p + sizeof(key_size) > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() malformed packet (3)\n"); + goto malformed; + } + + memcpy(&key_size, p, sizeof(key_size)); + p += sizeof(key_size); + + key_size = gg_fix32(key_size); + + if (key_size > 0xffff || p + key_size > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() malformed packet (3)\n"); + goto malformed; + } + + key = malloc(key_size + 1); + + if (key == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() out of memory (%d)\n", key_size + 1); + goto fail; + } + + memcpy(key, p, key_size); + p += key_size; + + key[key_size] = 0; + + attrs[j].key = key; + + if (p + sizeof(attr_type) + sizeof(value_size) > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() malformed packet (4)\n"); + goto malformed; + } + + memcpy(&attr_type, p, sizeof(attr_type)); + p += sizeof(attr_type); + memcpy(&value_size, p, sizeof(value_size)); + p += sizeof(value_size); + + attrs[j].type = gg_fix32(attr_type); + value_size = gg_fix32(value_size); + + if (value_size > 0xffff || p + value_size > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() malformed packet (5)\n"); + goto malformed; + } + + value = malloc(value_size + 1); + + if (value == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_user_data() out of memory (%d)\n", value_size + 1); + goto fail; + } + + memcpy(value, p, value_size); + p += value_size; + + value[value_size] = 0; + + attrs[j].value = value; + + gg_debug_session(sess, GG_DEBUG_DUMP, " key=\"%s\", type=%d, value=\"%s\"\n", key, attr_type, value); + } + } + + return 0; + +fail: + res = -1; + +malformed: + e->type = GG_EVENT_NONE; + + for (i = 0; i < e->event.user_data.user_count; i++) { + for (j = 0; j < e->event.user_data.users[i].attr_count; j++) { + free(e->event.user_data.users[i].attrs[j].key); + free(e->event.user_data.users[i].attrs[j].value); + } + + free(e->event.user_data.users[i].attrs); + } + + free(e->event.user_data.users); + + return res; +} + +/** + * \internal Analizuje przychodzÄ…cy pakiet z listÄ… sesji multilogowania. + * + * \param sess Struktura sesji + * \param e Struktura zdarzenia + * \param payload Treść pakietu + * \param len DĹ‚ugość pakietu + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +static int gg_handle_multilogon_info(struct gg_session *sess, struct gg_event *e, void *packet, size_t len) +{ + char *packet_end = (char*) packet + len; + struct gg_multilogon_info *info = (struct gg_multilogon_info*) packet; + char *p = (char*) packet + sizeof(*info); + struct gg_multilogon_session *sessions = NULL; + size_t count; + size_t i; + int res = 0; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_handle_multilogon_info(%p, %p, %p, %d);\n", sess, e, packet, len); + + count = gg_fix32(info->count); + + if (count > 0xffff) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_multilogon_info() malformed packet (1)\n"); + goto malformed; + } + + sessions = calloc(count, sizeof(struct gg_multilogon_session)); + + if (sessions == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_multilogon_info() out of memory (%d*%d)\n", count, sizeof(struct gg_multilogon_session)); + return -1; + } + + e->type = GG_EVENT_MULTILOGON_INFO; + e->event.multilogon_info.count = (int)count; + e->event.multilogon_info.sessions = sessions; + + for (i = 0; i < count; i++) { + struct gg_multilogon_info_item item; + size_t name_size; + + if (p + sizeof(item) > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_multilogon_info() malformed packet (2)\n"); + goto malformed; + } + + memcpy(&item, p, sizeof(item)); + + sessions[i].id = item.conn_id; + sessions[i].remote_addr = item.addr; + sessions[i].status_flags = gg_fix32(item.flags); + sessions[i].protocol_features = gg_fix32(item.features); + sessions[i].logon_time = gg_fix32(item.logon_time); + + p += sizeof(item); + + name_size = gg_fix32(item.name_size); + + if (name_size > 0xffff || p + name_size > packet_end) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_multilogon_info() malformed packet (3)\n"); + goto malformed; + } + + sessions[i].name = malloc(name_size + 1); + + if (sessions[i].name == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_handle_multilogon_info() out of memory (%d)\n", name_size); + goto fail; + } + + memcpy(sessions[i].name, p, name_size); + sessions[i].name[name_size] = 0; + + p += name_size; + } + + return 0; + +fail: + res = -1; + +malformed: + e->type = GG_EVENT_NONE; + + for (i = 0; i < (size_t)e->event.multilogon_info.count; i++) + free(e->event.multilogon_info.sessions[i].name); + + free(e->event.multilogon_info.sessions); + + return res; +} + +/** + * \internal Odbiera pakiet od serwera. + * + * Analizuje pakiet i wypeĹ‚nia strukturÄ™ zdarzenia. + * + * \param sess Struktura sesji + * \param e Struktura zdarzenia + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 jeĹ›li wystÄ…piĹ‚ bĹ‚Ä…d + */ +static int gg_watch_fd_connected(struct gg_session *sess, struct gg_event *e) +{ + struct gg_header *h = NULL; + char *p; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_watch_fd_connected(%p, %p);\n", sess, e); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (!(h = gg_recv_packet(sess))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() gg_recv_packet failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + + p = (char*) h + sizeof(struct gg_header); + + switch (h->type) { + case GG_RECV_MSG: + { + if (h->length >= sizeof(struct gg_recv_msg)) { + if (gg_handle_recv_msg(h, e, sess) != -1) + gg_handle_recv_msg_ack(h, sess); + else + goto fail; + } + + break; + } + + case GG_RECV_MSG80: + { + if (h->length >= sizeof(struct gg_recv_msg80)) { + if (gg_handle_recv_msg80(h, e, sess, GG_EVENT_MSG) != -1) + gg_handle_recv_msg_ack(h, sess); + else + goto fail; + } + + break; + } + + case GG_RECV_OWN_MSG: + { + if (h->length >= sizeof(struct gg_recv_msg80)) { + if (gg_handle_recv_msg80(h, e, sess, GG_EVENT_MULTILOGON_MSG) == -1) + goto fail; + } + + break; + } + + case GG_NOTIFY_REPLY: + { + struct gg_notify_reply *n = (void*) p; + unsigned int count, i; + char *tmp; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); + + if (h->length < sizeof(*n)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() incomplete packet\n"); + errno = EINVAL; + goto fail; + } + + if (gg_fix32(n->status) == GG_STATUS_BUSY_DESCR || gg_fix32(n->status) == GG_STATUS_NOT_AVAIL_DESCR || gg_fix32(n->status) == GG_STATUS_AVAIL_DESCR) { + e->type = GG_EVENT_NOTIFY_DESCR; + + if (!(e->event.notify_descr.notify = (void*) malloc(sizeof(*n) * 2))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + e->event.notify_descr.notify[1].uin = 0; + memcpy(e->event.notify_descr.notify, p, sizeof(*n)); + e->event.notify_descr.notify[0].uin = gg_fix32(e->event.notify_descr.notify[0].uin); + e->event.notify_descr.notify[0].status = gg_fix32(e->event.notify_descr.notify[0].status); + e->event.notify_descr.notify[0].remote_port = gg_fix16(e->event.notify_descr.notify[0].remote_port); + e->event.notify_descr.notify[0].version = gg_fix32(e->event.notify_descr.notify[0].version); + + count = h->length - sizeof(*n); + if (!(tmp = malloc(count + 1))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + memcpy(tmp, p + sizeof(*n), count); + tmp[count] = 0; + e->event.notify_descr.descr = tmp; + + } else { + e->type = GG_EVENT_NOTIFY; + + if (!(e->event.notify = (void*) malloc(h->length + 2 * sizeof(*n)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + memcpy(e->event.notify, p, h->length); + count = h->length / sizeof(*n); + e->event.notify[count].uin = 0; + + for (i = 0; i < count; i++) { + e->event.notify[i].uin = gg_fix32(e->event.notify[i].uin); + e->event.notify[i].status = gg_fix32(e->event.notify[i].status); + e->event.notify[i].remote_port = gg_fix16(e->event.notify[i].remote_port); + e->event.notify[i].version = gg_fix32(e->event.notify[i].version); + } + } + + break; + } + + case GG_STATUS: + { + struct gg_status *s = (void*) p; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); + + if (h->length >= sizeof(*s)) { + e->type = GG_EVENT_STATUS; + memcpy(&e->event.status, p, sizeof(*s)); + e->event.status.uin = gg_fix32(e->event.status.uin); + e->event.status.status = gg_fix32(e->event.status.status); + if (h->length > sizeof(*s)) { + int len = h->length - sizeof(*s); + char *buf = malloc(len + 1); + if (buf) { + memcpy(buf, p + sizeof(*s), len); + buf[len] = 0; + } + e->event.status.descr = buf; + } else + e->event.status.descr = NULL; + } + + break; + } + + case GG_NOTIFY_REPLY77: + case GG_NOTIFY_REPLY80BETA: + { + struct gg_notify_reply77 *n = (void*) p; + unsigned int length = h->length, i = 0; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); + + e->type = GG_EVENT_NOTIFY60; + e->event.notify60 = malloc(sizeof(*e->event.notify60)); + + if (!e->event.notify60) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + e->event.notify60[0].uin = 0; + + while (length >= sizeof(struct gg_notify_reply77)) { + uin_t uin = gg_fix32(n->uin); + char *tmp; + + e->event.notify60[i].uin = uin & 0x00ffffff; + e->event.notify60[i].status = n->status; + e->event.notify60[i].remote_ip = n->remote_ip; + e->event.notify60[i].remote_port = gg_fix16(n->remote_port); + e->event.notify60[i].version = n->version; + e->event.notify60[i].image_size = n->image_size; + e->event.notify60[i].descr = NULL; + e->event.notify60[i].time = 0; + + if (uin & 0x40000000) + e->event.notify60[i].version |= GG_HAS_AUDIO_MASK; + if (uin & 0x20000000) + e->event.notify60[i].version |= GG_HAS_AUDIO7_MASK; + if (uin & 0x08000000) + e->event.notify60[i].version |= GG_ERA_OMNIX_MASK; + + if (GG_S_D(n->status)) { + unsigned char descr_len = *((char*) n + sizeof(struct gg_notify_reply77)); + + if (sizeof(struct gg_notify_reply77) + descr_len <= length) { + char *descr; + + if (!(descr = malloc(descr_len + 1))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + memcpy(descr, (char*) n + sizeof(struct gg_notify_reply77) + 1, descr_len); + descr[descr_len] = 0; + + if (h->type == GG_NOTIFY_REPLY80BETA && sess->encoding != GG_ENCODING_UTF8) { + char *cp_descr = gg_utf8_to_cp(descr); + + if (!cp_descr) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + free(descr); + goto fail; + } + + free(descr); + descr = cp_descr; + } + + e->event.notify60[i].descr = descr; + + /* XXX czas */ + + length -= sizeof(struct gg_notify_reply77) + descr_len + 1; + n = (void*) ((char*) n + sizeof(struct gg_notify_reply77) + descr_len + 1); + } else { + length = 0; + } + + } else { + length -= sizeof(struct gg_notify_reply77); + n = (void*) ((char*) n + sizeof(struct gg_notify_reply77)); + } + + if (!(tmp = realloc(e->event.notify60, (i + 2) * sizeof(*e->event.notify60)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + free(e->event.notify60); + goto fail; + } + + e->event.notify60 = (void*) tmp; + e->event.notify60[++i].uin = 0; + } + + break; + } + + case GG_STATUS77: + case GG_STATUS80BETA: + { + struct gg_status77 *s = (void*) p; + uint32_t uin; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); + + if (h->length < sizeof(*s)) + break; + + uin = gg_fix32(s->uin); + + e->type = GG_EVENT_STATUS60; + e->event.status60.uin = uin & 0x00ffffff; + e->event.status60.status = s->status; + e->event.status60.remote_ip = s->remote_ip; + e->event.status60.remote_port = gg_fix16(s->remote_port); + e->event.status60.version = s->version; + e->event.status60.image_size = s->image_size; + e->event.status60.descr = NULL; + e->event.status60.time = 0; + + if (uin & 0x40000000) + e->event.status60.version |= GG_HAS_AUDIO_MASK; + if (uin & 0x20000000) + e->event.status60.version |= GG_HAS_AUDIO7_MASK; + if (uin & 0x08000000) + e->event.status60.version |= GG_ERA_OMNIX_MASK; + + if (h->length > sizeof(*s)) { + int len = h->length - sizeof(*s); + char *buf = malloc(len + 1); + + /* XXX, jesli malloc() sie nie uda to robic tak samo jak przy GG_NOTIFY_REPLY* ? + * - goto fail; (?) + */ + if (buf) { + memcpy(buf, (char*) p + sizeof(*s), len); + buf[len] = 0; + + if (h->type == GG_STATUS80BETA && sess->encoding != GG_ENCODING_UTF8) { + char *cp_buf = gg_utf8_to_cp(buf); + free(buf); + buf = cp_buf; + } + } + + e->event.status60.descr = buf; + + if (len > 4 && p[h->length - 5] == 0) { + uint32_t t; + memcpy(&t, p + h->length - 4, sizeof(uint32_t)); + e->event.status60.time = gg_fix32(t); + } + } + + break; + } + + case GG_NOTIFY_REPLY60: + { + struct gg_notify_reply60 *n = (void*) p; + unsigned int length = h->length, i = 0; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); + + e->type = GG_EVENT_NOTIFY60; + e->event.notify60 = malloc(sizeof(*e->event.notify60)); + + if (!e->event.notify60) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + e->event.notify60[0].uin = 0; + + while (length >= sizeof(struct gg_notify_reply60)) { + uin_t uin = gg_fix32(n->uin); + char *tmp; + + e->event.notify60[i].uin = uin & 0x00ffffff; + e->event.notify60[i].status = n->status; + e->event.notify60[i].remote_ip = n->remote_ip; + e->event.notify60[i].remote_port = gg_fix16(n->remote_port); + e->event.notify60[i].version = n->version; + e->event.notify60[i].image_size = n->image_size; + e->event.notify60[i].descr = NULL; + e->event.notify60[i].time = 0; + + if (uin & 0x40000000) + e->event.notify60[i].version |= GG_HAS_AUDIO_MASK; + if (uin & 0x08000000) + e->event.notify60[i].version |= GG_ERA_OMNIX_MASK; + + if (GG_S_D(n->status)) { + unsigned char descr_len = *((char*) n + sizeof(struct gg_notify_reply60)); + + if (sizeof(struct gg_notify_reply60) + descr_len <= length) { + if (!(e->event.notify60[i].descr = malloc(descr_len + 1))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + memcpy(e->event.notify60[i].descr, (char*) n + sizeof(struct gg_notify_reply60) + 1, descr_len); + e->event.notify60[i].descr[descr_len] = 0; + + /* XXX czas */ + + length -= sizeof(struct gg_notify_reply60) + descr_len + 1; + n = (void*) ((char*) n + sizeof(struct gg_notify_reply60) + descr_len + 1); + } else { + length = 0; + } + + } else { + length -= sizeof(struct gg_notify_reply60); + n = (void*) ((char*) n + sizeof(struct gg_notify_reply60)); + } + + if (!(tmp = realloc(e->event.notify60, (i + 2) * sizeof(*e->event.notify60)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + free(e->event.notify60); + goto fail; + } + + e->event.notify60 = (void*) tmp; + e->event.notify60[++i].uin = 0; + } + + break; + } + + case GG_STATUS60: + { + struct gg_status60 *s = (void*) p; + uint32_t uin; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); + + if (h->length < sizeof(*s)) + break; + + uin = gg_fix32(s->uin); + + e->type = GG_EVENT_STATUS60; + e->event.status60.uin = uin & 0x00ffffff; + e->event.status60.status = s->status; + e->event.status60.remote_ip = s->remote_ip; + e->event.status60.remote_port = gg_fix16(s->remote_port); + e->event.status60.version = s->version; + e->event.status60.image_size = s->image_size; + e->event.status60.descr = NULL; + e->event.status60.time = 0; + + if (uin & 0x40000000) + e->event.status60.version |= GG_HAS_AUDIO_MASK; + if (uin & 0x08000000) + e->event.status60.version |= GG_ERA_OMNIX_MASK; + + if (h->length > sizeof(*s)) { + int len = h->length - sizeof(*s); + char *buf = malloc(len + 1); + + if (buf) { + memcpy(buf, (char*) p + sizeof(*s), len); + buf[len] = 0; + } + + e->event.status60.descr = buf; + + if (len > 4 && p[h->length - 5] == 0) { + uint32_t t; + memcpy(&t, p + h->length - 4, sizeof(uint32_t)); + e->event.status60.time = gg_fix32(t); + } + } + + break; + } + + case GG_STATUS80: + { + struct gg_notify_reply80 *s = (void*) p; + uint32_t descr_len; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a status change\n"); + + if (h->length < sizeof(*s)) + break; + + e->type = GG_EVENT_STATUS60; + e->event.status60.uin = gg_fix32(s->uin); + e->event.status60.status = gg_fix32(s->status); + e->event.status60.remote_ip = s->remote_ip; + e->event.status60.remote_port = gg_fix16(s->remote_port); + e->event.status60.image_size = s->image_size; + e->event.status60.descr = NULL; + e->event.status60.version = 0x00; /* not-supported */ + e->event.status60.time = 0; /* not-supported */ + + descr_len = gg_fix32(s->descr_len); + + if (descr_len > 0 && h->length-sizeof(*s) >= descr_len) { + char *buf = malloc(descr_len + 1); + + if (buf) { + memcpy(buf, (char*) p + sizeof(*s), descr_len); + buf[descr_len] = 0; + + if (sess->encoding != GG_ENCODING_UTF8) { + char *cp_buf = gg_utf8_to_cp(buf); + free(buf); + buf = cp_buf; + } + } + + e->event.status60.descr = buf; + } + break; + } + + case GG_NOTIFY_REPLY80: + { + struct gg_notify_reply80 *n = (void*) p; + unsigned int length = h->length, i = 0; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); + + e->type = GG_EVENT_NOTIFY60; + e->event.notify60 = malloc(sizeof(*e->event.notify60)); + + if (!e->event.notify60) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + e->event.notify60[0].uin = 0; + + while (length >= sizeof(struct gg_notify_reply80)) { + uint32_t descr_len; + char *tmp; + + e->event.notify60[i].uin = gg_fix32(n->uin); + e->event.notify60[i].status = gg_fix32(n->status); + e->event.notify60[i].remote_ip = n->remote_ip; + e->event.notify60[i].remote_port= gg_fix16(n->remote_port); + e->event.notify60[i].image_size = n->image_size; + e->event.notify60[i].descr = NULL; + e->event.notify60[i].version = 0x00; /* not-supported */ + e->event.notify60[i].time = 0; /* not-supported */ + + descr_len = gg_fix32(n->descr_len); + + length -= sizeof(struct gg_notify_reply80); + n = (void*) ((char*) n + sizeof(struct gg_notify_reply80)); + + if (descr_len) { + if (length >= descr_len) { + /* XXX, GG_S_D(n->status) */ + char *descr; + + if (!(descr = malloc(descr_len + 1))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + goto fail; + } + + memcpy(descr, n, descr_len); + descr[descr_len] = 0; + + if (sess->encoding != GG_ENCODING_UTF8) { + char *cp_descr = gg_utf8_to_cp(descr); + + if (!cp_descr) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + free(descr); + goto fail; + } + + free(descr); + descr = cp_descr; + } + e->event.notify60[i].descr = descr; + + length -= descr_len; + n = (void*) ((char*) n + descr_len); + } else + length = 0; + } + + if (!(tmp = realloc(e->event.notify60, (i + 2) * sizeof(*e->event.notify60)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); + free(e->event.notify60); + goto fail; + } + + e->event.notify60 = (void*) tmp; + e->event.notify60[++i].uin = 0; + } + break; + } + + case GG_SEND_MSG_ACK: + { + struct gg_send_msg_ack *s = (void*) p; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a message ack\n"); + + if (h->length < sizeof(*s)) + break; + + e->type = GG_EVENT_ACK; + e->event.ack.status = gg_fix32(s->status); + e->event.ack.recipient = gg_fix32(s->recipient); + e->event.ack.seq = gg_fix32(s->seq); + + break; + } + + case GG_PONG: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a pong\n"); + + e->type = GG_EVENT_PONG; + sess->last_pong = (int)time(NULL); + + break; + } + + case GG_DISCONNECTING: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received disconnection warning\n"); + e->type = GG_EVENT_DISCONNECT; + break; + } + + case GG_DISCONNECT_ACK: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received disconnection acknowledge\n"); + e->type = GG_EVENT_DISCONNECT_ACK; + break; + } + + case GG_XML_EVENT: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received XML event\n"); + e->type = GG_EVENT_XML_EVENT; + if (!(e->event.xml_event.data = (char *) malloc(h->length + 1))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for XML event data\n"); + goto fail; + } + memcpy(e->event.xml_event.data, p, h->length); + e->event.xml_event.data[h->length] = 0; + break; + } + + case GG_XML_ACTION: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received XML action\n"); + e->type = GG_EVENT_XML_ACTION; + if (!(e->event.xml_action.data = (char *) malloc(h->length + 1))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for XML action data\n"); + goto fail; + } + memcpy(e->event.xml_action.data, p, h->length); + e->event.xml_action.data[h->length] = 0; + break; + } + + case GG_PUBDIR50_REPLY: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received pubdir/search reply\n"); + if (gg_pubdir50_handle_reply_sess(sess, e, p, h->length) == -1) + goto fail; + break; + } + + case GG_USERLIST_REPLY: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received userlist reply\n"); + + if (h->length < 1) + break; + + /* jeĹ›li odpowiedĹş na eksport, wywoĹ‚aj zdarzenie tylko + * gdy otrzymano wszystkie odpowiedzi */ + if (p[0] == GG_USERLIST_PUT_REPLY || p[0] == GG_USERLIST_PUT_MORE_REPLY) { + if (--sess->userlist_blocks) + break; + + p[0] = GG_USERLIST_PUT_REPLY; + } + + if (h->length > 1) { + char *tmp; + unsigned int len = (sess->userlist_reply) ? (unsigned int)strlen(sess->userlist_reply) : 0; + + gg_debug_session(sess, GG_DEBUG_MISC, "userlist_reply=%p, len=%d\n", sess->userlist_reply, len); + + if (!(tmp = realloc(sess->userlist_reply, len + h->length))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for userlist reply\n"); + free(sess->userlist_reply); + sess->userlist_reply = NULL; + goto fail; + } + + sess->userlist_reply = tmp; + sess->userlist_reply[len + h->length - 1] = 0; + memcpy(sess->userlist_reply + len, p + 1, h->length - 1); + } + + if (p[0] == GG_USERLIST_GET_MORE_REPLY) + break; + + e->type = GG_EVENT_USERLIST; + e->event.userlist.type = p[0]; + e->event.userlist.reply = sess->userlist_reply; + sess->userlist_reply = NULL; + + break; + } + + case GG_DCC7_ID_REPLY: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 id packet\n"); + + if (h->length < sizeof(struct gg_dcc7_id_reply)) + break; + + if (gg_dcc7_handle_id(sess, e, p, h->length) == -1) + goto fail; + + break; + } + + case GG_DCC7_ACCEPT: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 accept\n"); + + if (h->length < sizeof(struct gg_dcc7_accept)) + break; + + if (gg_dcc7_handle_accept(sess, e, p, h->length) == -1) + goto fail; + + break; + } + + case GG_DCC7_NEW: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 request\n"); + + if (h->length < sizeof(struct gg_dcc7_new)) + break; + + if (gg_dcc7_handle_new(sess, e, p, h->length) == -1) + goto fail; + + break; + } + + case GG_DCC7_REJECT: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 reject\n"); + + if (h->length < sizeof(struct gg_dcc7_reject)) + break; + + if (gg_dcc7_handle_reject(sess, e, p, h->length) == -1) + goto fail; + + break; + } + + case GG_DCC7_ABORT: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 abort\n"); + + if (h->length < sizeof(struct gg_dcc7_aborted)) + break; + + if (gg_dcc7_handle_abort(sess, e, p, h->length) == -1) + goto fail; + + break; + } + + case GG_DCC7_INFO: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received dcc7 info\n"); + + if (h->length < sizeof(struct gg_dcc7_info)) + break; + + if (gg_dcc7_handle_info(sess, e, p, h->length) == -1) + goto fail; + + break; + } + + case GG_USER_DATA: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received user data\n"); + + if (h->length < sizeof(struct gg_user_data)) + break; + + if (gg_handle_user_data(sess, e, p, h->length) == -1) + goto fail; + + break; + } + + case GG_TYPING_NOTIFICATION: + { + struct gg_typing_notification *n = (void*) p; + uin_t uin; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received typing notification\n"); + + if (h->length < sizeof(*n)) + break; + + memcpy(&uin, &n->uin, sizeof(uin_t)); + + e->type = GG_EVENT_TYPING_NOTIFICATION; + e->event.typing_notification.uin = gg_fix32(uin); + e->event.typing_notification.length = gg_fix16(n->length); + + break; + } + + case GG_MULTILOGON_INFO: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received multilogon info\n"); + + if (h->length < sizeof(struct gg_multilogon_info)) + break; + + if (gg_handle_multilogon_info(sess, e, p, h->length) == -1) + goto fail; + + break; + } + + default: + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received unknown packet 0x%.2x\n", h->type); + } + + free(h); + return 0; + +fail: + free(h); + return -1; +} + +/** \endcond */ + +/** + * Funkcja wywoĹ‚ywana po zaobserwowaniu zmian na deskryptorze sesji. + * + * Funkcja zwraca strukturÄ™ zdarzenia \c gg_event. JeĹ›li rodzaj zdarzenia + * to \c GG_EVENT_NONE, nie wydarzyĹ‚o siÄ™ jeszcze nic wartego odnotowania. + * StrukturÄ™ zdarzenia naleĹĽy zwolnić funkcja \c gg_event_free(). + * + * \param sess Struktura sesji + * + * \return Struktura zdarzenia lub \c NULL jeĹ›li wystÄ…piĹ‚ bĹ‚Ä…d + * + * \ingroup events + */ +struct gg_event *gg_watch_fd(struct gg_session *sess) +{ + struct gg_event *e; + int res = 0; + int port = 0; + int errno2 = 0; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_watch_fd(%p);\n", sess); + + if (!sess) { + errno = EFAULT; + return NULL; + } + + if (!(e = (void*) calloc(1, sizeof(*e)))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() not enough memory for event data\n"); + return NULL; + } + + e->type = GG_EVENT_NONE; + + if (sess->send_buf && (sess->state == GG_STATE_READING_REPLY || sess->state == GG_STATE_CONNECTED)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending %d bytes of queued data\n", sess->send_left); + + res = gg_sock_write(sess->fd, sess->send_buf, sess->send_left); + + if (res == -1 && errno != EAGAIN) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() write() failed (errno=%d, %s)\n", errno, strerror(errno)); + + if (sess->state == GG_STATE_READING_REPLY) + goto fail_connecting; + else + goto done; + } + + if (res == sess->send_left) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sent all queued data\n"); + free(sess->send_buf); + sess->send_buf = NULL; + sess->send_left = 0; + } else if (res > 0) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sent %d bytes of queued data, %d bytes left\n", res, sess->send_left - res); + + memmove(sess->send_buf, sess->send_buf + res, sess->send_left - res); + sess->send_left -= res; + } + + res = 0; + } + + switch (sess->state) { + case GG_STATE_RESOLVING: + { + struct in_addr addr; + int failed = 0; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_RESOLVING\n"); + + if (gg_sock_read(sess->fd, &addr, sizeof(addr)) < (signed)sizeof(addr) || addr.s_addr == INADDR_NONE) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() resolving failed\n"); + failed = 1; + errno2 = errno; + } + + gg_sock_close(sess->fd); + sess->fd = -1; + + sess->resolver_cleanup(&sess->resolver, 0); + + if (failed) { + errno = errno2; + goto fail_resolving; + } + + /* jeĹ›li jesteĹ›my w resolverze i mamy ustawiony port + * proxy, znaczy, ĹĽe resolvowaliĹ›my proxy. zatem + * wpiszmy jego adres. */ + if (sess->proxy_port) + sess->proxy_addr = addr.s_addr; + + /* zapiszmy sobie adres huba i adres serwera (do + * bezpoĹ›redniego poĹ‚Ä…czenia, jeĹ›li hub leĹĽy) + * z resolvera. */ + if (sess->proxy_addr && sess->proxy_port) + port = sess->proxy_port; + else { + sess->server_addr = sess->hub_addr = addr.s_addr; + port = GG_APPMSG_PORT; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() resolved, connecting to %s:%d\n", inet_ntoa(addr), port); + + /* Ĺ‚Ä…czymy siÄ™ albo z hubem, albo z proxy, zaleĹĽnie + * od tego, co resolvowaliĹ›my. */ + if ((sess->fd = gg_connect(&addr, port, sess->async)) == -1) { + /* jeĹ›li w trybie asynchronicznym gg_connect() + * zwrĂłci bĹ‚Ä…d, nie ma sensu prĂłbować dalej. */ + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), critical\n", errno, strerror(errno)); + goto fail_connecting; + } + + /* jeĹ›li podano serwer i Ĺ‚Ä…czmy siÄ™ przez proxy, + * jest to bezpoĹ›rednie poĹ‚Ä…czenie, inaczej jest + * do huba. */ + + if (sess->proxy_addr && sess->proxy_port && sess->server_addr) { + sess->state = GG_STATE_CONNECTING_GG; + sess->soft_timeout = 1; + } else + sess->state = GG_STATE_CONNECTING_HUB; + + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } + + case GG_STATE_CONNECTING_HUB: + { + char buf[1024], *client, *auth; + int res = 0; + unsigned int res_size = sizeof(res); + const char *host; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTING_HUB\n"); + + /* jeĹ›li asynchroniczne, sprawdzamy, czy nie wystÄ…piĹ‚ + * przypadkiem jakiĹ› bĹ‚Ä…d. */ + if (sess->async && (gg_getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { + if (sess->proxy_addr && sess->proxy_port) + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", res, strerror(res)); + else + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection to hub failed (errno=%d, %s)\n", res, strerror(res)); + + goto fail_connecting; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connected to hub, sending query\n"); + + if (!(client = gg_urlencode((sess->client_version) ? sess->client_version : GG_DEFAULT_CLIENT_VERSION))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory for client version\n"); + goto fail_connecting; + } + + if (!gg_proxy_http_only && sess->proxy_addr && sess->proxy_port) + host = "http://" GG_APPMSG_HOST; + else + host = ""; + + auth = gg_proxy_auth(); + +#ifdef GG_CONFIG_MIRANDA + if (sess->tls) { + snprintf(buf, sizeof(buf) - 1, + "GET %s/appsvc/appmsg_ver10.asp?fmnumber=%u&fmt=2&lastmsg=%d&version=%s&age=2&gender=1 HTTP/1.0\r\n" + "Connection: close\r\n" + "Host: " GG_APPMSG_HOST "\r\n" + "%s" + "\r\n", host, sess->uin, sess->last_sysmsg, client, (auth) ? auth : ""); + } else +#elif GG_CONFIG_HAVE_OPENSSL + if (sess->ssl != NULL) { + snprintf(buf, sizeof(buf) - 1, + "GET %s/appsvc/appmsg_ver10.asp?fmnumber=%u&fmt=2&lastmsg=%d&version=%s&age=2&gender=1 HTTP/1.0\r\n" + "Connection: close\r\n" + "Host: " GG_APPMSG_HOST "\r\n" + "%s" + "\r\n", host, sess->uin, sess->last_sysmsg, client, (auth) ? auth : ""); + } else +#endif + { + snprintf(buf, sizeof(buf) - 1, + "GET %s/appsvc/appmsg_ver8.asp?fmnumber=%u&fmt=2&lastmsg=%d&version=%s HTTP/1.0\r\n" + "Host: " GG_APPMSG_HOST "\r\n" + "%s" + "\r\n", host, sess->uin, sess->last_sysmsg, client, (auth) ? auth : ""); + } + + free(auth); + free(client); + + /* zwolnij pamięć po wersji klienta. */ + if (sess->client_version) { + free(sess->client_version); + sess->client_version = NULL; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", buf); + + /* zapytanie jest krĂłtkie, wiÄ™c zawsze zmieĹ›ci siÄ™ + * do bufora gniazda. jeĹ›li write() zwrĂłci mniej, + * staĹ‚o siÄ™ coĹ› zĹ‚ego. */ + if (gg_sock_write(sess->fd, buf, (int)strlen(buf)) < (signed)strlen(buf)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() sending query failed\n"); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_WRITING; + sess->state = GG_STATE_IDLE; + gg_sock_close(sess->fd); + sess->fd = -1; + break; + } + + sess->state = GG_STATE_READING_DATA; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } + + case GG_STATE_READING_DATA: + { + char buf[1024], *tmp, *host; + int port = GG_DEFAULT_PORT; + struct in_addr addr; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_DATA\n"); + + /* czytamy liniÄ™ z gniazda i obcinamy \r\n. */ + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() received http header (%s)\n", buf); + + /* sprawdzamy, czy wszystko w porzÄ…dku. */ + if (strncmp(buf, "HTTP/1.", 7) || strncmp(buf + 9, "200", 3)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid http reply, connection failed\n"); + goto fail_connecting; + } + + /* ignorujemy resztÄ™ nagĹ‚Ăłwka. */ + while (strcmp(buf, "\r\n") && strcmp(buf, "")) + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + + /* czytamy pierwszÄ… liniÄ™ danych. */ + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + + /* jeĹ›li pierwsza liczba w linii nie jest rĂłwna zeru, + * oznacza to, ĹĽe mamy wiadomość systemowÄ…. */ + if (atoi(buf)) { + char tmp[1024], *foo, *sysmsg_buf = NULL; + int len = 0; + + while (gg_read_line(sess->fd, tmp, sizeof(tmp) - 1)) { + if (!(foo = realloc(sysmsg_buf, len + strlen(tmp) + 2))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() out of memory for system message, ignoring\n"); + break; + } + + sysmsg_buf = foo; + + if (!len) + strcpy(sysmsg_buf, tmp); + else + strcat(sysmsg_buf, tmp); + + len += (int)strlen(tmp); + } + + e->type = GG_EVENT_MSG; + e->event.msg.msgclass = atoi(buf); + e->event.msg.sender = 0; + e->event.msg.message = (unsigned char*) sysmsg_buf; + } + + gg_sock_close(sess->fd); + + gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() received http data (%s)\n", buf); + + /* analizujemy otrzymane dane. */ + tmp = buf; + + while (*tmp && *tmp != ' ') + tmp++; + while (*tmp && *tmp == ' ') + tmp++; + while (*tmp && *tmp != ' ') + tmp++; + while (*tmp && *tmp == ' ') + tmp++; + host = tmp; + while (*tmp && *tmp != ' ') + tmp++; + *tmp = 0; + + if ((tmp = strchr(host, ':'))) { + *tmp = 0; + port = atoi(tmp + 1); + } + + if (!strcmp(host, "notoperating")) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() service unavailable\n", errno, strerror(errno)); + sess->fd = -1; + goto fail_unavailable; + } + + addr.s_addr = inet_addr(host); + sess->server_addr = addr.s_addr; + + if (!gg_proxy_http_only && sess->proxy_addr && sess->proxy_port) { + /* jeĹ›li mamy proxy, Ĺ‚Ä…czymy siÄ™ z nim. */ + if ((sess->fd = gg_connect(&sess->proxy_addr, sess->proxy_port, sess->async)) == -1) { + /* nie wyszĹ‚o? trudno. */ + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + sess->soft_timeout = 1; + break; + } + + sess->port = port; + + /* JeĹ›li podano nazwÄ™, nie adres serwera... */ + if (sess->server_addr == INADDR_NONE) { + if (sess->resolver_start(&sess->fd, &sess->resolver, host) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() resolving failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_resolving; + } + + sess->state = GG_STATE_RESOLVING_GG; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + break; + } + + /* Ĺ‚Ä…czymy siÄ™ z wĹ‚aĹ›ciwym serwerem. */ + if ((sess->fd = gg_connect(&addr, sess->port, sess->async)) == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno)); + + sess->port = GG_HTTPS_PORT; + + /* nie wyszĹ‚o? prĂłbujemy portu 443. */ + if ((sess->fd = gg_connect(&addr, GG_HTTPS_PORT, sess->async)) == -1) { + /* ostatnia deska ratunku zawiodĹ‚a? + * w takim razie zwijamy manatki. */ + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + sess->soft_timeout = 1; + + break; + } + + case GG_STATE_RESOLVING_GG: + { + struct in_addr addr; + int failed = 0; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_RESOLVING_GG\n"); + + if (gg_sock_read(sess->fd, &addr, sizeof(addr)) < (signed)sizeof(addr) || addr.s_addr == INADDR_NONE) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() resolving failed\n"); + failed = 1; + errno2 = errno; + } + + gg_sock_close(sess->fd); + sess->fd = -1; + + sess->resolver_cleanup(&sess->resolver, 0); + + if (failed) { + errno = errno2; + goto fail_resolving; + } + + sess->server_addr = addr.s_addr; + + /* Ĺ‚Ä…czymy siÄ™ z wĹ‚aĹ›ciwym serwerem. */ + if ((sess->fd = gg_connect(&addr, sess->port, sess->async)) == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", errno, strerror(errno)); + + sess->port = GG_HTTPS_PORT; + + /* nie wyszĹ‚o? prĂłbujemy portu 443. */ + if ((sess->fd = gg_connect(&addr, GG_HTTPS_PORT, sess->async)) == -1) { + /* ostatnia deska ratunku zawiodĹ‚a? + * w takim razie zwijamy manatki. */ + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + sess->soft_timeout = 1; + + break; + } + + case GG_STATE_CONNECTING_GG: + { + int res = 0; + unsigned int res_size = sizeof(res); + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTING_GG\n"); + + sess->soft_timeout = 0; + + /* jeĹ›li wystÄ…piĹ‚ bĹ‚Ä…d podczas Ĺ‚Ä…czenia siÄ™... */ + if (sess->async && (sess->timeout == 0 || gg_getsockopt(sess->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { + /* jeĹ›li nie udaĹ‚o siÄ™ poĹ‚Ä…czenie z proxy, + * nie mamy czego prĂłbować wiÄ™cej. */ + if (sess->proxy_addr && sess->proxy_port) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection to proxy failed (errno=%d, %s)\n", res, strerror(res)); + goto fail_connecting; + } + + gg_sock_close(sess->fd); + sess->fd = -1; + +#ifdef ETIMEDOUT + if (sess->timeout == 0) + errno = ETIMEDOUT; +#endif + +#ifdef GG_CONFIG_HAVE_OPENSSL + /* jeĹ›li logujemy siÄ™ po TLS, nie prĂłbujemy + * siÄ™ Ĺ‚Ä…czyć juĹĽ z niczym innym w przypadku + * bĹ‚Ä™du. nie dość, ĹĽe nie ma sensu, to i + * trzeba by siÄ™ bawić w tworzenie na nowo + * SSL i SSL_CTX. */ + + if (sess->ssl) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", res, strerror(res)); + goto fail_connecting; + } +#endif + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s), trying https\n", res, strerror(res)); + + if (sess->port == GG_HTTPS_PORT) + goto fail_connecting; + + sess->port = GG_HTTPS_PORT; + + /* prĂłbujemy na port 443. */ + if ((sess->fd = gg_connect(&sess->server_addr, sess->port, sess->async)) == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail_connecting; + } + + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + sess->soft_timeout = 1; + + break; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() connected\n"); + + if (gg_proxy_http_only) + sess->proxy_port = 0; + + /* jeĹ›li mamy proxy, wyĹ›lijmy zapytanie. */ + if (sess->proxy_addr && sess->proxy_port) { + char buf[100], *auth = gg_proxy_auth(); + struct in_addr addr; + + if (sess->server_addr) + addr.s_addr = sess->server_addr; + else + addr.s_addr = sess->hub_addr; + + snprintf(buf, sizeof(buf), "CONNECT %s:%d HTTP/1.0\r\n", inet_ntoa(addr), sess->port); + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() proxy request:\n// %s", buf); + + /* wysyĹ‚amy zapytanie. jest ono na tyle krĂłtkie, + * ĹĽe musi siÄ™ zmieĹ›cić w buforze gniazda. jeĹ›li + * write() zawiedzie, staĹ‚o siÄ™ coĹ› zĹ‚ego. */ + if (gg_sock_write(sess->fd, buf, (int)strlen(buf)) < (signed)strlen(buf)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n"); + free(auth); + goto fail_connecting; + } + + if (auth) { + gg_debug_session(sess, GG_DEBUG_MISC, "// %s", auth); + if (gg_sock_write(sess->fd, auth, (int)strlen(auth)) < (signed)strlen(auth)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n"); + free(auth); + goto fail_connecting; + } + + free(auth); + } + + if (gg_sock_write(sess->fd, "\r\n", 2) < 2) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() can't send proxy request\n"); + goto fail_connecting; + } + } + +#ifdef GG_CONFIG_MIRANDA + if (sess->tls) { + sess->state = GG_STATE_TLS_NEGOTIATION; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } +#elif GG_CONFIG_HAVE_OPENSSL + if (sess->ssl != NULL) { + SSL_set_fd(sess->ssl, (int)sess->fd); + + sess->state = GG_STATE_TLS_NEGOTIATION; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } +#endif + + sess->state = GG_STATE_READING_KEY; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } + +#ifdef GG_CONFIG_MIRANDA + case GG_STATE_TLS_NEGOTIATION: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_TLS_NEGOTIATION\n"); + + sess->ssl = si.connect(sess->fd, 0, 0); + + if (sess->ssl == NULL) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation failed\n"); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_TLS; + sess->state = GG_STATE_IDLE; + gg_sock_close(sess->fd); + sess->fd = -1; + break; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation succeded\n"); + + sess->state = GG_STATE_READING_KEY; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } +#elif GG_CONFIG_HAVE_OPENSSL + case GG_STATE_TLS_NEGOTIATION: + { + int res; + X509 *peer; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_TLS_NEGOTIATION\n"); + + if ((res = SSL_connect(sess->ssl)) <= 0) { + int err = SSL_get_error(sess->ssl, res); + + if (res == 0) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() disconnected during TLS negotiation\n"); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_TLS; + sess->state = GG_STATE_IDLE; + gg_sock_close(sess->fd); + sess->fd = -1; + break; + } + + if (err == SSL_ERROR_WANT_READ) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to read\n"); + + sess->state = GG_STATE_TLS_NEGOTIATION; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } else if (err == SSL_ERROR_WANT_WRITE) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() wants to write\n"); + + sess->state = GG_STATE_TLS_NEGOTIATION; + sess->check = GG_CHECK_WRITE; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } else { + char buf[256]; + + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() SSL_connect() bailed out: %s\n", buf); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_TLS; + sess->state = GG_STATE_IDLE; + gg_sock_close(sess->fd); + sess->fd = -1; + break; + } + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() TLS negotiation succeded:\n// cipher: %s\n", SSL_get_cipher_name(sess->ssl)); + + peer = SSL_get_peer_certificate(sess->ssl); + + if (!peer) + gg_debug_session(sess, GG_DEBUG_MISC, "// WARNING! unable to get peer certificate!\n"); + else { + char buf[256]; + + X509_NAME_oneline(X509_get_subject_name(peer), buf, sizeof(buf)); + gg_debug_session(sess, GG_DEBUG_MISC, "// cert subject: %s\n", buf); + + X509_NAME_oneline(X509_get_issuer_name(peer), buf, sizeof(buf)); + gg_debug_session(sess, GG_DEBUG_MISC, "// cert issuer: %s\n", buf); + } + + sess->state = GG_STATE_READING_KEY; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + + break; + } +#endif + + case GG_STATE_READING_KEY: + { + struct gg_header *h; + struct gg_welcome *w; + unsigned char *password = (unsigned char*) sess->password; + int ret; + uint8_t login_hash[64]; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_KEY\n"); + + memset(login_hash, 0, sizeof(login_hash)); + + /* XXX bardzo, bardzo, bardzo gĹ‚upi pomysĹ‚ na pozbycie + * siÄ™ tekstu wrzucanego przez proxy. */ + if (sess->proxy_addr && sess->proxy_port) { + char buf[100]; + + strcpy(buf, ""); + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() proxy response:\n// %s\n", buf); + + while (strcmp(buf, "")) { + gg_read_line(sess->fd, buf, sizeof(buf) - 1); + gg_chomp(buf); + if (strcmp(buf, "")) + gg_debug_session(sess, GG_DEBUG_MISC, "// %s\n", buf); + } + + /* XXX niech czeka jeszcze raz w tej samej + * fazie. gĹ‚upio, ale dziaĹ‚a. */ + sess->proxy_port = 0; + + break; + } + + /* czytaj pierwszy pakiet. */ + if (!(h = gg_recv_packet(sess))) { + if (errno == EAGAIN) { + sess->check = GG_CHECK_READ; + break; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() didn't receive packet (errno=%d, %s)\n", errno, strerror(errno)); + + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_READING; + sess->state = GG_STATE_IDLE; + errno2 = errno; + gg_sock_close(sess->fd); + errno = errno2; + sess->fd = -1; + break; + } + + if (h->type != GG_WELCOME) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid packet received\n"); + free(h); + gg_sock_close(sess->fd); + sess->fd = -1; + errno = EINVAL; + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_INVALID; + sess->state = GG_STATE_IDLE; + break; + } + + w = (struct gg_welcome*) ((char*) h + sizeof(struct gg_header)); + w->key = gg_fix32(w->key); + + switch (sess->hash_type) { + case GG_LOGIN_HASH_GG32: + { + unsigned int hash; + + hash = gg_fix32(gg_login_hash(password, w->key)); + gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_watch_fd() challenge %.4x --> GG32 hash %.8x\n", w->key, hash); + memcpy(login_hash, &hash, sizeof(hash)); + + break; + } + + case GG_LOGIN_HASH_SHA1: + { + char tmp[41]; + int i; + + gg_login_hash_sha1((char*) password, w->key, login_hash); + for (i = 0; i < 40; i += 2) + snprintf(tmp + i, sizeof(tmp) - i, "%02x", login_hash[i / 2]); + + gg_debug_session(sess, GG_DEBUG_DUMP, "// gg_watch_fd() challenge %.4x --> SHA1 hash: %s\n", w->key, tmp); + + break; + } + } + + free(h); + free(sess->password); + sess->password = NULL; + + if (gg_dcc_ip == (unsigned long) inet_addr("255.255.255.255")) { + struct sockaddr_in sin; + unsigned int sin_len = sizeof(sin); + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() detecting address\n"); + + if (!getsockname(sess->fd, (struct sockaddr*) &sin, &sin_len)) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() detected address to %s\n", inet_ntoa(sin.sin_addr)); + sess->client_addr = sin.sin_addr.s_addr; + } else { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() unable to detect address\n"); + sess->client_addr = 0; + } + } else + sess->client_addr = gg_dcc_ip; + + if (sess->protocol_version >= 0x2e) { + struct gg_login80 l; + const char *version = (sess->client_version) ? sess->client_version : GG_DEFAULT_CLIENT_VERSION; + uint32_t tmp_version_len = gg_fix32((uint32_t)strlen(GG8_VERSION) + (uint32_t)strlen(version)); + uint32_t tmp_descr_len = gg_fix32((sess->initial_descr) ? (uint32_t)strlen(sess->initial_descr) : 0); + + memset(&l, 0, sizeof(l)); + l.uin = gg_fix32(sess->uin); + memcpy(l.language, GG8_LANG, sizeof(l.language)); + l.hash_type = sess->hash_type; + memcpy(l.hash, login_hash, sizeof(login_hash)); + l.status = gg_fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL); + l.flags = gg_fix32(sess->status_flags); + l.features = gg_fix32(sess->protocol_features); + l.image_size = sess->image_size; + l.dunno2 = 0x64; + + gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending GG_LOGIN80 packet\n"); + ret = gg_send_packet(sess, GG_LOGIN80, + &l, sizeof(l), + &tmp_version_len, sizeof(uint32_t), GG8_VERSION, strlen(GG8_VERSION), version, strlen(version), + &tmp_descr_len, sizeof(uint32_t), sess->initial_descr, (sess->initial_descr) ? strlen(sess->initial_descr) : 0, + NULL); + + } else if (sess->protocol_version == 0x2d) { + struct gg_login70 l; + + memset(&l, 0, sizeof(l)); + l.uin = gg_fix32(sess->uin); + l.local_ip = (sess->external_addr) ? sess->external_addr : sess->client_addr; + l.local_port = gg_fix16((uint16_t)((sess->external_port > 1023) ? sess->external_port : gg_dcc_port)); + l.hash_type = sess->hash_type; + memcpy(l.hash, login_hash, sizeof(login_hash)); + l.image_size = sess->image_size; + l.dunno2 = 0x64; + l.status = gg_fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL); + l.version = gg_fix32(sess->protocol_version | sess->protocol_flags); + + gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending GG_LOGIN80BETA packet\n"); + ret = gg_send_packet(sess, GG_LOGIN80BETA, + &l, sizeof(l), + sess->initial_descr, (sess->initial_descr) ? strlen(sess->initial_descr) : 0, + (sess->initial_descr) ? "\0" : NULL, (sess->initial_descr) ? 1 : 0, + NULL); + } else { + struct gg_login70 l; + + memset(&l, 0, sizeof(l)); + l.local_ip = (sess->external_addr) ? sess->external_addr : sess->client_addr; + l.uin = gg_fix32(sess->uin); + l.local_port = gg_fix16((uint16_t)((sess->external_port > 1023) ? sess->external_port : gg_dcc_port)); + l.hash_type = sess->hash_type; + memcpy(l.hash, login_hash, sizeof(login_hash)); + l.image_size = sess->image_size; + l.dunno2 = 0xbe; + l.status = gg_fix32(sess->initial_status ? sess->initial_status : GG_STATUS_AVAIL); + l.version = gg_fix32(sess->protocol_version | sess->protocol_flags); + + gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending GG_LOGIN70 packet\n"); + ret = gg_send_packet(sess, GG_LOGIN70, + &l, sizeof(l), + sess->initial_descr, (sess->initial_descr) ? strlen(sess->initial_descr) : 0, + NULL); + } + + free(sess->initial_descr); + sess->initial_descr = NULL; + + if (ret == -1) { + gg_debug_session(sess, GG_DEBUG_TRAFFIC, "// gg_watch_fd() sending packet failed. (errno=%d, %s)\n", errno, strerror(errno)); + errno2 = errno; + gg_sock_close(sess->fd); + errno = errno2; + sess->fd = -1; + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_WRITING; + sess->state = GG_STATE_IDLE; + break; + } + + sess->state = GG_STATE_READING_REPLY; + sess->check = GG_CHECK_READ; + + break; + } + + case GG_STATE_READING_REPLY: + { + struct gg_header *h; + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_READING_REPLY\n"); + + if (!(h = gg_recv_packet(sess))) { + if (errno == EAGAIN) { + sess->check = GG_CHECK_READ; + break; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() didn't receive packet (errno=%d, %s)\n", errno, strerror(errno)); + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_READING; + sess->state = GG_STATE_IDLE; + errno2 = errno; + gg_sock_close(sess->fd); + errno = errno2; + sess->fd = -1; + break; + } + + if (h->type == GG_LOGIN_OK || h->type == GG_NEED_EMAIL || h->type == GG_LOGIN80_OK) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() login succeded\n"); + e->type = GG_EVENT_CONN_SUCCESS; + sess->state = GG_STATE_CONNECTED; + sess->check = GG_CHECK_READ; + sess->timeout = -1; + sess->status = (sess->initial_status) ? sess->initial_status : GG_STATUS_AVAIL; + free(h); + break; + } + + if (h->type == GG_LOGIN_FAILED || h->type == GG_LOGIN80_FAILED) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() login failed\n"); + e->event.failure = GG_FAILURE_PASSWORD; + errno = EACCES; + } else if (h->type == GG_DISCONNECTING) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() too many incorrect password attempts\n"); + e->event.failure = GG_FAILURE_INTRUDER; + errno = EACCES; + } else { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() invalid packet\n"); + e->event.failure = GG_FAILURE_INVALID; + errno = EINVAL; + } + + e->type = GG_EVENT_CONN_FAILED; + sess->state = GG_STATE_IDLE; + errno2 = errno; + gg_sock_close(sess->fd); + errno = errno2; + sess->fd = -1; + free(h); + + break; + } + + case GG_STATE_CONNECTED: + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() GG_STATE_CONNECTED\n"); + + sess->last_event = (int)time(NULL); + + if ((res = gg_watch_fd_connected(sess, e)) == -1) { + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd() watch_fd_connected failed (errno=%d, %s)\n", errno, strerror(errno)); + + if (errno == EAGAIN) { + e->type = GG_EVENT_NONE; + res = 0; + } else + res = -1; + } + + sess->check = GG_CHECK_READ; + + break; + } + } + +done: + if (res == -1) { + free(e); + e = NULL; + } else { + if (sess->send_buf && (sess->state == GG_STATE_READING_REPLY || sess->state == GG_STATE_CONNECTED)) + sess->check |= GG_CHECK_WRITE; + } + + return e; + +fail_connecting: + if (sess->fd != -1) { + errno2 = errno; + gg_sock_close(sess->fd); + errno = errno2; + sess->fd = -1; + } + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_CONNECTING; + sess->state = GG_STATE_IDLE; + goto done; + +fail_resolving: + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_RESOLVING; + sess->state = GG_STATE_IDLE; + goto done; + +fail_unavailable: + e->type = GG_EVENT_CONN_FAILED; + e->event.failure = GG_FAILURE_UNAVAILABLE; + sess->state = GG_STATE_IDLE; + goto done; +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/protocols/Gadu-Gadu/src/libgadu/http.c b/protocols/Gadu-Gadu/src/libgadu/http.c new file mode 100644 index 0000000000..c070b3ee53 --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/http.c @@ -0,0 +1,544 @@ +/* coding: UTF-8 */ +/* $Id: http.c 11370 2010-03-13 16:17:54Z dezred $ */ + +/* + * (C) Copyright 2001-2002 Wojtek Kaniewski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +/** + * \file http.c + * + * \brief ObsĹ‚uga poĹ‚Ä…czeĹ„ HTTP + */ + +#include +#ifdef _WIN32 +#include "win32.h" +#else +#include +#include +#include +#endif /* _WIN32 */ + +#include "compat.h" +#include "libgadu.h" +#include "resolver.h" + +#include +#include +#ifndef _WIN32 +#include +#endif /* _WIN32 */ +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif /* _WIN32 */ + +/** + * Rozpoczyna poĹ‚Ä…czenie HTTP. + * + * Funkcja przeprowadza poĹ‚Ä…czenie HTTP przy poĹ‚Ä…czeniu synchronicznym, + * zwracajÄ…c wynik w polach struktury \c gg_http, lub bĹ‚Ä…d, gdy sesja siÄ™ + * nie powiedzie. + * + * Przy poĹ‚Ä…czeniu asynchronicznym, funkcja rozpoczyna poĹ‚Ä…czenie, a dalsze + * etapy bÄ™dÄ… przeprowadzane po wykryciu zmian (\c watch) na obserwowanym + * deskryptorze (\c fd) i wywoĹ‚aniu funkcji \c gg_http_watch_fd(). + * + * Po zakoĹ„czeniu, naleĹĽy zwolnić strukturÄ™ za pomocÄ… funkcji + * \c gg_http_free(). PoĹ‚Ä…czenie asynchroniczne moĹĽna zatrzymać w kaĹĽdej + * chwili za pomocÄ… \c gg_http_stop(). + * + * \param hostname Adres serwera + * \param port Port serwera + * \param async Flaga asynchronicznego poĹ‚Ä…czenia + * \param method Metoda HTTP + * \param path ĹšcieĹĽka do zasobu (musi być poprzedzona znakiem '/') + * \param header NagĹ‚Ăłwek zapytania plus ewentualne dane dla POST + * + * \return Zaalokowana struktura \c gg_http lub NULL, jeĹ›li wystÄ…piĹ‚ bĹ‚Ä…d. + * + * \ingroup http + */ +struct gg_http *gg_http_connect(const char *hostname, int port, int async, const char *method, const char *path, const char *header) +{ + struct gg_http *h; + + if (!hostname || !port || !method || !path || !header) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() invalid arguments\n"); + errno = EFAULT; + return NULL; + } + + if (!(h = malloc(sizeof(*h)))) + return NULL; + memset(h, 0, sizeof(*h)); + + h->async = async; + h->port = port; + h->fd = -1; + h->type = GG_SESSION_HTTP; + + gg_http_set_resolver(h, GG_RESOLVER_DEFAULT); + + if (gg_proxy_enabled) { + char *auth = gg_proxy_auth(); + + h->query = gg_saprintf("%s http://%s:%d%s HTTP/1.0\r\n%s%s", + method, hostname, port, path, (auth) ? auth : + "", header); + hostname = gg_proxy_host; + h->port = port = gg_proxy_port; + free(auth); + + } else { + h->query = gg_saprintf("%s %s HTTP/1.0\r\n%s", + method, path, header); + } + + if (!h->query) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() not enough memory for query\n"); + free(h); + errno = ENOMEM; + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-QUERY-----\n%s\n=> -----END-HTTP-QUERY-----\n", h->query); + + if (async) { + if (h->resolver_start(&h->fd, &h->resolver, hostname) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() resolver failed\n"); + gg_http_free(h); + errno = ENOENT; + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() resolver = %p\n", h->resolver); + + h->state = GG_STATE_RESOLVING; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + } else { + struct in_addr addr; + + if (gg_gethostbyname_real(hostname, &addr, 0) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() host not found\n"); + gg_http_free(h); + errno = ENOENT; + return NULL; + } + + if ((h->fd = gg_connect(&addr, port, 0)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + gg_http_free(h); + return NULL; + } + + h->state = GG_STATE_CONNECTING; + + while (h->state != GG_STATE_ERROR && h->state != GG_STATE_PARSING) { + if (gg_http_watch_fd(h) == -1) + break; + } + + if (h->state != GG_STATE_PARSING) { + gg_debug(GG_DEBUG_MISC, "// gg_http_connect() some strange error\n"); + gg_http_free(h); + return NULL; + } + } + + h->callback = gg_http_watch_fd; + h->destroy = gg_http_free; + + return h; +} + +#ifndef DOXYGEN + +#define gg_http_error(x) \ + gg_sock_close(h->fd); \ + h->fd = -1; \ + h->state = GG_STATE_ERROR; \ + h->error = x; \ + return 0; + +#endif /* DOXYGEN */ + +/** + * Funkcja wywoĹ‚ywana po zaobserwowaniu zmian na deskryptorze poĹ‚Ä…czenia. + * + * Operacja bÄ™dzie zakoĹ„czona, gdy pole \c state bÄ™dzie rĂłwne + * \c GG_STATE_PARSING. W tym miejscu dziaĹ‚anie przejmuje zwykle funkcja + * korzystajÄ…ca z \c gg_http_watch_fd(). W przypadku bĹ‚Ä™du poĹ‚Ä…czenia, + * pole \c state bÄ™dzie rĂłwne \c GG_STATE_ERROR, a kod bĹ‚Ä™du znajdzie siÄ™ + * w polu \c error. + * + * \param h Struktura poĹ‚Ä…czenia + * + * \return \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup http + */ +int gg_http_watch_fd(struct gg_http *h) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_http_watch_fd(%p);\n", h); + + if (!h) { + gg_debug(GG_DEBUG_MISC, "// gg_http_watch_fd() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + if (h->state == GG_STATE_RESOLVING) { + struct in_addr a; + + gg_debug(GG_DEBUG_MISC, "=> http, resolving done\n"); + + if (gg_sock_read(h->fd, &a, sizeof(a)) < (signed)sizeof(a) || a.s_addr == INADDR_NONE) { + gg_debug(GG_DEBUG_MISC, "=> http, resolver thread failed\n"); + gg_http_error(GG_ERROR_RESOLVING); + } + + gg_sock_close(h->fd); + h->fd = -1; + + h->resolver_cleanup(&h->resolver, 0); + + gg_debug(GG_DEBUG_MISC, "=> http, connecting to %s:%d\n", inet_ntoa(a), h->port); + + if ((h->fd = gg_connect(&a, h->port, h->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "=> http, connection failed (errno=%d, %s)\n", errno, strerror(errno)); + gg_http_error(GG_ERROR_CONNECTING); + } + + h->state = GG_STATE_CONNECTING; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + + return 0; + } + + if (h->state == GG_STATE_CONNECTING) { + int res = 0; + unsigned int res_size = sizeof(res); + + if (h->async && (gg_getsockopt(h->fd, SOL_SOCKET, SO_ERROR, &res, &res_size) || res)) { + gg_debug(GG_DEBUG_MISC, "=> http, async connection failed (errno=%d, %s)\n", (res) ? res : errno , strerror((res) ? res : errno)); + gg_sock_close(h->fd); + h->fd = -1; + h->state = GG_STATE_ERROR; + h->error = GG_ERROR_CONNECTING; + if (res) + errno = res; + return 0; + } + + gg_debug(GG_DEBUG_MISC, "=> http, connected, sending request\n"); + + h->state = GG_STATE_SENDING_QUERY; + } + + if (h->state == GG_STATE_SENDING_QUERY) { + int res; + + if ((res = gg_sock_write(h->fd, h->query, (int)strlen(h->query))) < 1) { + gg_debug(GG_DEBUG_MISC, "=> http, write() failed (len=%d, res=%d, errno=%d)\n", strlen(h->query), res, errno); + gg_http_error(GG_ERROR_WRITING); + } + + if (res < (int)strlen(h->query)) { + gg_debug(GG_DEBUG_MISC, "=> http, partial header sent (led=%d, sent=%d)\n", strlen(h->query), res); + + memmove(h->query, h->query + res, strlen(h->query) - res + 1); + h->state = GG_STATE_SENDING_QUERY; + h->check = GG_CHECK_WRITE; + h->timeout = GG_DEFAULT_TIMEOUT; + } else { + gg_debug(GG_DEBUG_MISC, "=> http, request sent (len=%d)\n", strlen(h->query)); + free(h->query); + h->query = NULL; + + h->state = GG_STATE_READING_HEADER; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + } + + return 0; + } + + if (h->state == GG_STATE_READING_HEADER) { + char buf[1024], *tmp; + int res; + + if ((res = gg_sock_read(h->fd, buf, sizeof(buf))) == -1) { + gg_debug(GG_DEBUG_MISC, "=> http, reading header failed (errno=%d)\n", errno); + if (h->header) { + free(h->header); + h->header = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + if (!res) { + gg_debug(GG_DEBUG_MISC, "=> http, connection reset by peer\n"); + if (h->header) { + free(h->header); + h->header = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + gg_debug(GG_DEBUG_MISC, "=> http, read %d bytes of header\n", res); + + if (!(tmp = realloc(h->header, h->header_size + res + 1))) { + gg_debug(GG_DEBUG_MISC, "=> http, not enough memory for header\n"); + free(h->header); + h->header = NULL; + gg_http_error(GG_ERROR_READING); + } + + h->header = tmp; + + memcpy(h->header + h->header_size, buf, res); + h->header_size += res; + + gg_debug(GG_DEBUG_MISC, "=> http, header_buf=%p, header_size=%d\n", h->header, h->header_size); + + h->header[h->header_size] = 0; + + if ((tmp = strstr(h->header, "\r\n\r\n")) || (tmp = strstr(h->header, "\n\n"))) { + int sep_len = (*tmp == '\r') ? 4 : 2; + unsigned int left; + char *line; + + left = h->header_size - ((long)(tmp) - (long)(h->header) + sep_len); + + gg_debug(GG_DEBUG_MISC, "=> http, got all header (%d bytes, %d left)\n", h->header_size - left, left); + + /* HTTP/1.1 200 OK */ + if (strlen(h->header) < 16 || strncmp(h->header + 9, "200", 3)) { + gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-HEADER-----\n%s\n=> -----END-HTTP-HEADER-----\n", h->header); + + gg_debug(GG_DEBUG_MISC, "=> http, didn't get 200 OK -- no results\n"); + free(h->header); + h->header = NULL; + gg_http_error(GG_ERROR_CONNECTING); + } + + h->body_size = 0; + line = h->header; + *tmp = 0; + + gg_debug(GG_DEBUG_MISC, "=> -----BEGIN-HTTP-HEADER-----\n%s\n=> -----END-HTTP-HEADER-----\n", h->header); + + while (line) { + if (!strncasecmp(line, "Content-length: ", 16)) { + h->body_size = atoi(line + 16); + } + line = strchr(line, '\n'); + if (line) + line++; + } + + if (h->body_size <= 0) { + gg_debug(GG_DEBUG_MISC, "=> http, content-length not found\n"); + h->body_size = left; + } + + if (left > h->body_size) { + gg_debug(GG_DEBUG_MISC, "=> http, oversized reply (%d bytes needed, %d bytes left)\n", h->body_size, left); + h->body_size = left; + } + + gg_debug(GG_DEBUG_MISC, "=> http, body_size=%d\n", h->body_size); + + if (!(h->body = malloc(h->body_size + 1))) { + gg_debug(GG_DEBUG_MISC, "=> http, not enough memory (%d bytes for body_buf)\n", h->body_size + 1); + free(h->header); + h->header = NULL; + gg_http_error(GG_ERROR_READING); + } + + if (left) { + memcpy(h->body, tmp + sep_len, left); + h->body_done = left; + } + + h->body[left] = 0; + + h->state = GG_STATE_READING_DATA; + h->check = GG_CHECK_READ; + h->timeout = GG_DEFAULT_TIMEOUT; + } + + return 0; + } + + if (h->state == GG_STATE_READING_DATA) { + char buf[1024]; + int res; + + if ((res = gg_sock_read(h->fd, buf, sizeof(buf))) == -1) { + gg_debug(GG_DEBUG_MISC, "=> http, reading body failed (errno=%d)\n", errno); + if (h->body) { + free(h->body); + h->body = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + if (!res) { + if (h->body_done >= h->body_size) { + gg_debug(GG_DEBUG_MISC, "=> http, we're done, closing socket\n"); + h->state = GG_STATE_PARSING; + gg_sock_close(h->fd); + h->fd = -1; + } else { + gg_debug(GG_DEBUG_MISC, "=> http, connection closed while reading (have %d, need %d)\n", h->body_done, h->body_size); + if (h->body) { + free(h->body); + h->body = NULL; + } + gg_http_error(GG_ERROR_READING); + } + + return 0; + } + + gg_debug(GG_DEBUG_MISC, "=> http, read %d bytes of body\n", res); + + if (h->body_done + res > h->body_size) { + char *tmp; + + gg_debug(GG_DEBUG_MISC, "=> http, too much data (%d bytes, %d needed), enlarging buffer\n", h->body_done + res, h->body_size); + + if (!(tmp = realloc(h->body, h->body_done + res + 1))) { + gg_debug(GG_DEBUG_MISC, "=> http, not enough memory for data (%d needed)\n", h->body_done + res + 1); + free(h->body); + h->body = NULL; + gg_http_error(GG_ERROR_READING); + } + + h->body = tmp; + h->body_size = h->body_done + res; + } + + h->body[h->body_done + res] = 0; + memcpy(h->body + h->body_done, buf, res); + h->body_done += res; + + gg_debug(GG_DEBUG_MISC, "=> body_done=%d, body_size=%d\n", h->body_done, h->body_size); + + return 0; + } + + if (h->fd != -1) + gg_sock_close(h->fd); + + h->fd = -1; + h->state = GG_STATE_ERROR; + h->error = 0; + + return -1; +} + +/** + * KoĹ„czy asynchroniczne poĹ‚Ä…czenie HTTP. + * + * Po zatrzymaniu naleĹĽy zwolnić zasoby funkcjÄ… \c gg_http_free(). + * + * \param h Struktura poĹ‚Ä…czenia + * + * \ingroup http + */ +void gg_http_stop(struct gg_http *h) +{ + if (!h) + return; + + if (h->state == GG_STATE_ERROR || h->state == GG_STATE_DONE) + return; + + if (h->fd != -1) { + gg_sock_close(h->fd); + h->fd = -1; + } + + h->resolver_cleanup(&h->resolver, 1); +} + +/** + * \internal Zwalnia pola struktury \c gg_http. + * + * Funkcja zwalnia same pola, nie zwalnia struktury. + * + * \param h Struktura poĹ‚Ä…czenia + */ +void gg_http_free_fields(struct gg_http *h) +{ + if (!h) + return; + + if (h->body) { + free(h->body); + h->body = NULL; + } + + if (h->query) { + free(h->query); + h->query = NULL; + } + + if (h->header) { + free(h->header); + h->header = NULL; + } +} + +/** + * Zwalnia zasoby po poĹ‚Ä…czeniu HTTP. + * + * JeĹ›li poĹ‚Ä…czenie nie zostaĹ‚o jeszcze zakoĹ„czone, jest przerywane. + * + * \param h Struktura poĹ‚Ä…czenia + * + * \ingroup http + */ +void gg_http_free(struct gg_http *h) +{ + if (!h) + return; + + gg_http_stop(h); + gg_http_free_fields(h); + free(h); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/protocols/Gadu-Gadu/src/libgadu/internal.h b/protocols/Gadu-Gadu/src/libgadu/internal.h new file mode 100644 index 0000000000..0ffd39fdd5 --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/internal.h @@ -0,0 +1,48 @@ +/* coding: UTF-8 */ +/* $Id$ */ + +/* + * (C) Copyright 2009 Jakub Zawadzki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +#ifndef LIBGADU_INTERNAL_H +#define LIBGADU_INTERNAL_H + +#include "libgadu.h" + +struct gg_dcc7_relay { + uint32_t addr; + uint16_t port; + uint8_t family; +}; + +typedef struct gg_dcc7_relay gg_dcc7_relay_t; + +char *gg_cp_to_utf8(const char *b); +char *gg_utf8_to_cp(const char *b); +int gg_pubdir50_handle_reply_sess(struct gg_session *sess, struct gg_event *e, const char *packet, int length); +void gg_debug_dump_session(struct gg_session *sess, const void *buf, unsigned int buf_length, const char *format, ...); + +int gg_resolve(int *fd, int *pid, const char *hostname); +int gg_resolve_pthread(int *fd, void **resolver, const char *hostname); +void gg_resolve_pthread_cleanup(void *resolver, int kill); + +#ifdef GG_CONFIG_HAVE_UINT64_T +uint64_t gg_fix64(uint64_t x); +#endif + +#endif /* LIBGADU_INTERNAL_H */ diff --git a/protocols/Gadu-Gadu/src/libgadu/libgadu.c b/protocols/Gadu-Gadu/src/libgadu/libgadu.c new file mode 100644 index 0000000000..f06f8a5b84 --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/libgadu.c @@ -0,0 +1,2353 @@ +/* coding: UTF-8 */ +/* $Id: libgadu.c 13762 2011-08-09 12:35:16Z dezred $ */ + +/* + * (C) Copyright 2001-2010 Wojtek Kaniewski + * Robert J. WoĹşny + * Arkadiusz MiĹ›kiewicz + * Tomasz ChiliĹ„ski + * Adam Wysocki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +/** + * \file libgadu.c + * + * \brief GĹ‚Ăłwny moduĹ‚ biblioteki + */ + +#ifndef _WIN64 +#define _USE_32BIT_TIME_T +#endif + +#include +#ifdef _WIN32 +#include "win32.h" +#else +#include +#include +#include +#ifdef sun +# include +#endif +#endif /* _WIN32 */ + +#include "compat.h" +#include "libgadu.h" +#include "protocol.h" +#include "resolver.h" +#include "internal.h" + +#include +#ifndef _WIN32 +#include +#endif /* _WIN32 */ +#include +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif /* _WIN32 */ +#if !defined(GG_CONFIG_MIRANDA) && defined(GG_CONFIG_HAVE_OPENSSL) +# include +# include +#endif + +/** + * Poziom rejestracji informacji odpluskwiajÄ…cych. Zmienna jest maskÄ… bitowÄ… + * skĹ‚adajÄ…cÄ… siÄ™ ze staĹ‚ych \c GG_DEBUG_... + * + * \ingroup debug + */ +int gg_debug_level = 0; + +/** + * Funkcja, do ktĂłrej sÄ… przekazywane informacje odpluskwiajÄ…ce. JeĹ›li zarĂłwno + * ten \c gg_debug_handler, jak i \c gg_debug_handler_session, sÄ… rĂłwne + * \c NULL, informacje sÄ… wysyĹ‚ane do standardowego wyjĹ›cia bĹ‚Ä™du (\c stderr). + * + * \param level Poziom rejestracji + * \param format Format wiadomoĹ›ci (zgodny z \c printf) + * \param ap Lista argumentĂłw (zgodna z \c printf) + * + * \note Funkcja jest przesĹ‚aniana przez \c gg_debug_handler_session. + * + * \ingroup debug + */ +void (*gg_debug_handler)(int level, const char *format, va_list ap) = NULL; + +/** + * Funkcja, do ktĂłrej sÄ… przekazywane informacje odpluskwiajÄ…ce. JeĹ›li zarĂłwno + * ten \c gg_debug_handler, jak i \c gg_debug_handler_session, sÄ… rĂłwne + * \c NULL, informacje sÄ… wysyĹ‚ane do standardowego wyjĹ›cia bĹ‚Ä™du. + * + * \param sess Sesja ktĂłrej dotyczy informacja lub \c NULL + * \param level Poziom rejestracji + * \param format Format wiadomoĹ›ci (zgodny z \c printf) + * \param ap Lista argumentĂłw (zgodna z \c printf) + * + * \note Funkcja przesĹ‚ania przez \c gg_debug_handler_session. + * + * \ingroup debug + */ +void (*gg_debug_handler_session)(struct gg_session *sess, int level, const char *format, va_list ap) = NULL; + +/** + * Port gniazda nasĹ‚uchujÄ…cego dla poĹ‚Ä…czeĹ„ bezpoĹ›rednich. + * + * \ingroup ip + */ +int gg_dcc_port = 0; + +/** + * Adres IP gniazda nasĹ‚uchujÄ…cego dla poĹ‚Ä…czeĹ„ bezpoĹ›rednich. + * + * \ingroup ip + */ +unsigned long gg_dcc_ip = 0; + +/** + * Adres lokalnego interfejsu IP, z ktĂłrego wywoĹ‚ywane sÄ… wszystkie poĹ‚Ä…czenia. + * + * \ingroup ip + */ +unsigned long gg_local_ip = 0; + +/** + * Flaga wĹ‚Ä…czenia poĹ‚Ä…czeĹ„ przez serwer poĹ›redniczÄ…cy. + * + * \ingroup proxy + */ +int gg_proxy_enabled = 0; + +/** + * Adres serwera poĹ›redniczÄ…cego. + * + * \ingroup proxy + */ +char *gg_proxy_host = NULL; + +/** + * Port serwera poĹ›redniczÄ…cego. + * + * \ingroup proxy + */ +int gg_proxy_port = 0; + +/** + * Flaga uĹĽywania serwera poĹ›redniczÄ…cego jedynie dla usĹ‚ug HTTP. + * + * \ingroup proxy + */ +int gg_proxy_http_only = 0; + +/** + * Nazwa uĹĽytkownika do autoryzacji serwera poĹ›redniczÄ…cego. + * + * \ingroup proxy + */ +char *gg_proxy_username = NULL; + +/** + * HasĹ‚o uĹĽytkownika do autoryzacji serwera poĹ›redniczÄ…cego. + * + * \ingroup proxy + */ +char *gg_proxy_password = NULL; + +#ifndef DOXYGEN + +#ifndef lint +static char rcsid[] +#ifdef __GNUC__ +__attribute__ ((unused)) +#endif += "$Id: libgadu.c 13762 2011-08-09 12:35:16Z dezred $"; +#endif + +#endif /* DOXYGEN */ + +/** + * Zwraca wersjÄ™ biblioteki. + * + * \return WskaĹşnik na statyczny bufor z wersjÄ… biblioteki. + * + * \ingroup version + */ +const char *gg_libgadu_version() +{ + return GG_LIBGADU_VERSION; +} + +#ifdef GG_CONFIG_HAVE_UINT64_T +/** + * \internal Zamienia kolejność bajtĂłw w 64-bitowym sĹ‚owie. + * + * Ze wzglÄ™du na little-endianowość protokoĹ‚u Gadu-Gadu, na maszynach + * big-endianowych odwraca kolejność bajtĂłw w sĹ‚owie. + * + * \param x Liczba do zamiany + * + * \return Liczba z odpowiedniÄ… kolejnoĹ›ciÄ… bajtĂłw + * + * \ingroup helper + */ +uint64_t gg_fix64(uint64_t x) +{ +#ifndef GG_CONFIG_BIGENDIAN + return x; +#else + return (uint64_t) + (((x & (uint64_t) 0x00000000000000ffULL) << 56) | + ((x & (uint64_t) 0x000000000000ff00ULL) << 40) | + ((x & (uint64_t) 0x0000000000ff0000ULL) << 24) | + ((x & (uint64_t) 0x00000000ff000000ULL) << 8) | + ((x & (uint64_t) 0x000000ff00000000ULL) >> 8) | + ((x & (uint64_t) 0x0000ff0000000000ULL) >> 24) | + ((x & (uint64_t) 0x00ff000000000000ULL) >> 40) | + ((x & (uint64_t) 0xff00000000000000ULL) >> 56)); +#endif +} +#endif /* GG_CONFIG_HAVE_UINT64_T */ + +/** + * \internal Zamienia kolejność bajtĂłw w 32-bitowym sĹ‚owie. + * + * Ze wzglÄ™du na little-endianowość protokoĹ‚u Gadu-Gadu, na maszynach + * big-endianowych odwraca kolejność bajtĂłw w sĹ‚owie. + * + * \param x Liczba do zamiany + * + * \return Liczba z odpowiedniÄ… kolejnoĹ›ciÄ… bajtĂłw + * + * \ingroup helper + */ +uint32_t gg_fix32(uint32_t x) +{ +#ifndef GG_CONFIG_BIGENDIAN + return x; +#else + return (uint32_t) + (((x & (uint32_t) 0x000000ffU) << 24) | + ((x & (uint32_t) 0x0000ff00U) << 8) | + ((x & (uint32_t) 0x00ff0000U) >> 8) | + ((x & (uint32_t) 0xff000000U) >> 24)); +#endif +} + +/** + * \internal Zamienia kolejność bajtĂłw w 16-bitowym sĹ‚owie. + * + * Ze wzglÄ™du na little-endianowość protokoĹ‚u Gadu-Gadu, na maszynach + * big-endianowych zamienia kolejność bajtĂłw w sĹ‚owie. + * + * \param x Liczba do zamiany + * + * \return Liczba z odpowiedniÄ… kolejnoĹ›ciÄ… bajtĂłw + * + * \ingroup helper + */ +uint16_t gg_fix16(uint16_t x) +{ +#ifndef GG_CONFIG_BIGENDIAN + return x; +#else + return (uint16_t) + (((x & (uint16_t) 0x00ffU) << 8) | + ((x & (uint16_t) 0xff00U) >> 8)); +#endif +} + +/** + * \internal Liczy skrĂłt z hasĹ‚a i ziarna. + * + * \param password HasĹ‚o + * \param seed Ziarno podane przez serwer + * + * \return Wartość skrĂłtu + */ +unsigned int gg_login_hash(const unsigned char *password, unsigned int seed) +{ + unsigned int x, y, z; + + y = seed; + + for (x = 0; *password; password++) { + x = (x & 0xffffff00) | *password; + y ^= x; + y += x; + x <<= 8; + y ^= x; + x <<= 8; + y -= x; + x <<= 8; + y ^= x; + + z = y & 0x1F; + y = (y << z) | (y >> (32 - z)); + } + + return y; +} + +/** + * \internal Odbiera od serwera dane binarne. + * + * Funkcja odbiera dane od serwera zajmujÄ…c siÄ™ SSL/TLS w razie koniecznoĹ›ci. + * ObsĹ‚uguje EINTR, wiÄ™c uĹĽytkownik nie musi siÄ™ przejmować przerwanymi + * wywoĹ‚aniami systemowymi. + * + * \param sess Struktura sesji + * \param buf Bufor na danymi + * \param length DĹ‚ugość bufora + * + * \return To samo co funkcja systemowa \c read + */ +int gg_read(struct gg_session *sess, char *buf, int length) +{ +#ifdef GG_CONFIG_MIRANDA + if (sess->ssl != NULL) + return si.read(sess->ssl, buf, length, 0); +#elif GG_CONFIG_HAVE_OPENSSL + if (sess->ssl != NULL) { + for (;;) { + int res, err; + + res = SSL_read(sess->ssl, buf, length); + + if (res < 0) { + err = SSL_get_error(sess->ssl, res); + + if (err == SSL_ERROR_SYSCALL && errno == EINTR) + continue; + + if (err == SSL_ERROR_WANT_READ) + errno = EAGAIN; + else if (err != SSL_ERROR_SYSCALL) + errno = EINVAL; + + return -1; + } + + return res; + } + } +#endif + + return gg_sock_read(sess->fd, buf, length); +} + +/** + * \internal WysyĹ‚a do serwera dane binarne. + * + * Funkcja wysyĹ‚a dane do serwera zajmujÄ…c siÄ™ SSL/TLS w razie koniecznoĹ›ci. + * ObsĹ‚uguje EINTR, wiÄ™c uĹĽytkownik nie musi siÄ™ przejmować przerwanymi + * wywoĹ‚aniami systemowymi. + * + * \note Funkcja nie zajmuje siÄ™ buforowaniem wysyĹ‚anych danych (patrz + * gg_write()). + * + * \param sess Struktura sesji + * \param buf Bufor z danymi + * \param length DĹ‚ugość bufora + * + * \return To samo co funkcja systemowa \c write + */ +static int gg_write_common(struct gg_session *sess, const char *buf, int length) +{ +#ifdef GG_CONFIG_MIRANDA + if (sess->ssl != NULL) + return si.write(sess->ssl, buf, length); +#elif GG_CONFIG_HAVE_OPENSSL + if (sess->ssl != NULL) { + for (;;) { + int res, err; + + res = SSL_write(sess->ssl, (void *)buf, length); + + if (res < 0) { + err = SSL_get_error(sess->ssl, res); + + if (err == SSL_ERROR_SYSCALL && errno == EINTR) + continue; + + if (err == SSL_ERROR_WANT_WRITE) + errno = EAGAIN; + else if (err != SSL_ERROR_SYSCALL) + errno = EINVAL; + + return -1; + } + + return res; + } + } +#endif + + return gg_sock_write(sess->fd, buf, length); +} + + + +/** + * \internal WysyĹ‚a do serwera dane binarne. + * + * Funkcja wysyĹ‚a dane do serwera zajmujÄ…c siÄ™ TLS w razie koniecznoĹ›ci. + * + * \param sess Struktura sesji + * \param buf Bufor z danymi + * \param length DĹ‚ugość bufora + * + * \return To samo co funkcja systemowa \c write + */ +int gg_write(struct gg_session *sess, const char *buf, int length) +{ + int res = 0; + + if (!sess->async) { + int written = 0; + + while (written < length) { + res = gg_write_common(sess, buf + written, length - written); + + if (res == -1) + return -1; + + written += res; + res = written; + } + } else { + res = 0; + + if (sess->send_buf == NULL) { + res = gg_write_common(sess, buf, length); + + if (res == -1) + return -1; + } + + if (res < length) { + char *tmp; + + if (!(tmp = realloc(sess->send_buf, sess->send_left + length - res))) { + errno = ENOMEM; + return -1; + } + + sess->send_buf = tmp; + + memcpy(sess->send_buf + sess->send_left, buf + res, length - res); + + sess->send_left += length - res; + } + } + + return res; +} + +/** + * \internal Odbiera pakiet od serwera. + * + * Funkcja odczytuje nagĹ‚Ăłwek pakietu, a nastÄ™pnie jego zawartość i zwraca + * w zaalokowanym buforze. + * + * Przy poĹ‚Ä…czeniach asynchronicznych, funkcja moĹĽe nie być w stanie + * skompletować caĹ‚ego pakietu -- w takim przypadku zwrĂłci -1, a kodem bĹ‚Ä™du + * bÄ™dzie \c EAGAIN. + * + * \param sess Struktura sesji + * + * \return WskaĹşnik do zaalokowanego bufora + */ +void *gg_recv_packet(struct gg_session *sess) +{ + struct gg_header h; + char *buf = NULL; + int ret = 0; + unsigned int offset, size = 0; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_recv_packet(%p);\n", sess); + + if (!sess) { + errno = EFAULT; + return NULL; + } + + if (sess->recv_left < 1) { + if (sess->header_buf) { + memcpy(&h, sess->header_buf, sess->header_done); + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv: resuming last read (%d bytes left)\n", sizeof(h) - sess->header_done); + free(sess->header_buf); + sess->header_buf = NULL; + } else + sess->header_done = 0; + + while (sess->header_done < sizeof(h)) { + ret = gg_read(sess, (char*) &h + sess->header_done, sizeof(h) - sess->header_done); + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv(%d,%p,%d) = %d\n", sess->fd, &h + sess->header_done, sizeof(h) - sess->header_done, ret); + + if (!ret) { + errno = ECONNRESET; + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: connection broken\n"); + return NULL; + } + + if (ret == -1) { + if (errno == EAGAIN) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() incomplete header received\n"); + + if (!(sess->header_buf = malloc(sess->header_done))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() not enough memory\n"); + return NULL; + } + + memcpy(sess->header_buf, &h, sess->header_done); + + errno = EAGAIN; + + return NULL; + } + + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() header recv() failed: errno=%d, %s\n", errno, strerror(errno)); + + return NULL; + } + + sess->header_done += ret; + + } + + h.type = gg_fix32(h.type); + h.length = gg_fix32(h.length); + } else + memcpy(&h, sess->recv_buf, sizeof(h)); + + /* jakieĹ› sensowne limity na rozmiar pakietu */ + if (h.length > 65535) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() invalid packet length (%d)\n", h.length); + errno = ERANGE; + return NULL; + } + + if (sess->recv_left > 0) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() resuming last gg_recv_packet()\n"); + size = sess->recv_left; + offset = sess->recv_done; + buf = sess->recv_buf; + } else { + if (!(buf = malloc(sizeof(h) + h.length + 1))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() not enough memory for packet data\n"); + return NULL; + } + + memcpy(buf, &h, sizeof(h)); + + offset = 0; + size = h.length; + } + + while (size > 0) { + ret = gg_read(sess, buf + sizeof(h) + offset, size); + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv(%d,%p,%d) = %d\n", sess->fd, buf + sizeof(h) + offset, size, ret); + if (!ret) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed: connection broken\n"); + errno = ECONNRESET; + return NULL; + } + if (ret > -1 && ret <= (int)size) { + offset += ret; + size -= ret; + } else if (ret == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() body recv() failed (errno=%d, %s)\n", errno, strerror(errno)); + + if (errno == EAGAIN) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_recv_packet() %d bytes received, %d left\n", offset, size); + sess->recv_buf = buf; + sess->recv_left = size; + sess->recv_done = offset; + return NULL; + } + + free(buf); + return NULL; + } + } + + sess->recv_left = 0; + + gg_debug_dump_session(sess, buf, sizeof(h) + h.length, "// gg_recv_packet(0x%.2x)", h.type); + + return buf; +} + +/** + * \internal WysyĹ‚a pakiet do serwera. + * + * Funkcja konstruuje pakiet do wysĹ‚ania z dowolnej liczby fragmentĂłw. JeĹ›li + * rozmiar pakietu jest za duĹĽy, by mĂłc go wysĹ‚ać za jednym razem, pozostaĹ‚a + * część zostanie zakolejkowana i wysĹ‚ana, gdy bÄ™dzie to moĹĽliwe. + * + * \param sess Struktura sesji + * \param type Rodzaj pakietu + * \param ... Lista kolejnych części pakietu (wskaĹşnik na bufor i dĹ‚ugość + * typu \c int) zakoĹ„czona \c NULL + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +int gg_send_packet(struct gg_session *sess, int type, ...) +{ + struct gg_header *h; + char *tmp; + unsigned int tmp_length; + void *payload; + unsigned int payload_length; + va_list ap; + int res; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_packet(%p, 0x%.2x, ...);\n", sess, type); + + tmp_length = sizeof(struct gg_header); + + if (!(tmp = malloc(tmp_length))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() not enough memory for packet header\n"); + return -1; + } + + va_start(ap, type); + + payload = va_arg(ap, void *); + + while (payload) { + char *tmp2; + + payload_length = va_arg(ap, unsigned int); + + if (!(tmp2 = realloc(tmp, tmp_length + payload_length))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() not enough memory for payload\n"); + free(tmp); + va_end(ap); + return -1; + } + + tmp = tmp2; + + memcpy(tmp + tmp_length, payload, payload_length); + tmp_length += payload_length; + + payload = va_arg(ap, void *); + } + + va_end(ap); + + h = (struct gg_header*) tmp; + h->type = gg_fix32(type); + h->length = gg_fix32(tmp_length - sizeof(struct gg_header)); + + gg_debug_dump_session(sess, tmp, tmp_length, "// gg_send_packet(0x%.2x)", gg_fix32(h->type)); + + res = gg_write(sess, tmp, tmp_length); + + free(tmp); + + if (res == -1) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() write() failed. res = %d, errno = %d (%s)\n", res, errno, strerror(errno)); + return -1; + } + + if (sess->async) + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_send_packet() partial write(), %d sent, %d left, %d total left\n", res, tmp_length - res, sess->send_left); + + if (sess->send_buf) + sess->check |= GG_CHECK_WRITE; + + return 0; +} + +/** + * \internal Funkcja zwrotna sesji. + * + * Pole \c callback struktury \c gg_session zawiera wskaĹşnik do tej funkcji. + * WywoĹ‚uje ona \c gg_watch_fd i zachowuje wynik w polu \c event. + * + * \note Korzystanie z tej funkcjonalnoĹ›ci nie jest juĹĽ zalecane. + * + * \param sess Struktura sesji + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +static int gg_session_callback(struct gg_session *sess) +{ + if (!sess) { + errno = EFAULT; + return -1; + } + + return ((sess->event = gg_watch_fd(sess)) != NULL) ? 0 : -1; +} + +/** + * ĹÄ…czy siÄ™ z serwerem Gadu-Gadu. + * + * Przy poĹ‚Ä…czeniu synchronicznym funkcja zakoĹ„czy dziaĹ‚anie po nawiÄ…zaniu + * poĹ‚Ä…czenia lub gdy wystÄ…pi bĹ‚Ä…d. Po udanym poĹ‚Ä…czeniu naleĹĽy wywoĹ‚ywać + * funkcjÄ™ \c gg_watch_fd(), ktĂłra odbiera informacje od serwera i zwraca + * informacje o zdarzeniach. + * + * Przy poĹ‚Ä…czeniu asynchronicznym funkcja rozpocznie procedurÄ™ poĹ‚Ä…czenia + * i zwrĂłci zaalokowanÄ… strukturÄ™. Pole \c fd struktury \c gg_session zawiera + * deskryptor, ktĂłry naleĹĽy obserwować funkcjÄ… \c select, \c poll lub za + * pomocÄ… mechanizmĂłw uĹĽytej pÄ™tli zdarzeĹ„ (Glib, Qt itp.). Pole \c check + * jest maskÄ… bitowÄ… mĂłwiÄ…cÄ…, czy biblioteka chce być informowana o moĹĽliwoĹ›ci + * odczytu danych (\c GG_CHECK_READ) czy zapisu danych (\c GG_CHECK_WRITE). + * Po zaobserwowaniu zmian na deskryptorze naleĹĽy wywoĹ‚ać funkcjÄ™ + * \c gg_watch_fd(). Podczas korzystania z poĹ‚Ä…czeĹ„ asynchronicznych, w trakcie + * poĹ‚Ä…czenia moĹĽe zostać stworzony dodatkowy proces rozwiÄ…zujÄ…cy nazwÄ™ + * serwera -- z tego powodu program musi poprawnie obsĹ‚uĹĽyć sygnaĹ‚ SIGCHLD. + * + * \note Po nawiÄ…zaniu poĹ‚Ä…czenia z serwerem naleĹĽy wysĹ‚ać listÄ™ kontaktĂłw + * za pomocÄ… funkcji \c gg_notify() lub \c gg_notify_ex(). + * + * \param p Struktura opisujÄ…ca parametry poĹ‚Ä…czenia. Wymagane pola: uin, + * password, async. + * + * \return WskaĹşnik do zaalokowanej struktury sesji \c gg_session lub NULL + * w przypadku bĹ‚Ä™du. + * + * \ingroup login + */ +#ifdef GG_CONFIG_MIRANDA +struct gg_session *gg_login(const struct gg_login_params *p, SOCKET *gg_sock, int *gg_failno) +#else +struct gg_session *gg_login(const struct gg_login_params *p) +#endif +{ + struct gg_session *sess = NULL; + char *hostname; + int port; + + if (!p) { + gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%p);\n", p); + errno = EFAULT; + return NULL; + } + + gg_debug(GG_DEBUG_FUNCTION, "** gg_login(%p: [uin=%u, async=%d, ...]);\n", p, p->uin, p->async); + + if (!(sess = malloc(sizeof(struct gg_session)))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for session data\n"); + goto fail; + } + + memset(sess, 0, sizeof(struct gg_session)); + + if (!p->password || !p->uin) { + gg_debug(GG_DEBUG_MISC, "// gg_login() invalid arguments. uin and password needed\n"); + errno = EFAULT; + goto fail; + } + + if (!(sess->password = strdup(p->password))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for password\n"); + goto fail; + } + + if (p->hash_type < 0 || p->hash_type > GG_LOGIN_HASH_SHA1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() invalid arguments. unknown hash type (%d)\n", p->hash_type); + errno = EFAULT; + goto fail; + } + + sess->uin = p->uin; + sess->state = GG_STATE_RESOLVING; + sess->check = GG_CHECK_READ; + sess->timeout = GG_DEFAULT_TIMEOUT; + sess->async = p->async; + sess->type = GG_SESSION_GG; + sess->initial_status = p->status; + sess->callback = gg_session_callback; + sess->destroy = gg_free_session; + sess->port = (p->server_port) ? p->server_port : ((gg_proxy_enabled) ? GG_HTTPS_PORT : GG_DEFAULT_PORT); + sess->server_addr = p->server_addr; + sess->external_port = p->external_port; + sess->external_addr = p->external_addr; + sess->client_port = p->client_port; + + if (p->protocol_features == 0) { + sess->protocol_features = GG_FEATURE_MSG80 | GG_FEATURE_STATUS80 | GG_FEATURE_DND_FFC | GG_FEATURE_IMAGE_DESCR | GG_FEATURE_UNKNOWN_100 | GG_FEATURE_USER_DATA | GG_FEATURE_MSG_ACK | GG_FEATURE_TYPING_NOTIFICATION; + } else { + sess->protocol_features = (p->protocol_features & ~(GG_FEATURE_STATUS77 | GG_FEATURE_MSG77)); + + if (!(p->protocol_features & GG_FEATURE_STATUS77)) + sess->protocol_features |= GG_FEATURE_STATUS80; + + if (!(p->protocol_features & GG_FEATURE_MSG77)) + sess->protocol_features |= GG_FEATURE_MSG80; + } + + if (!(sess->status_flags = p->status_flags)) + sess->status_flags = GG_STATUS_FLAG_UNKNOWN | GG_STATUS_FLAG_SPAM; + + sess->protocol_version = (p->protocol_version) ? p->protocol_version : GG_DEFAULT_PROTOCOL_VERSION; + + if (p->era_omnix) + sess->protocol_flags |= GG_ERA_OMNIX_MASK; + if (p->has_audio) + sess->protocol_flags |= GG_HAS_AUDIO_MASK; + sess->client_version = (p->client_version) ? strdup(p->client_version) : NULL; + sess->last_sysmsg = p->last_sysmsg; + sess->image_size = p->image_size; + sess->pid = -1; + sess->encoding = p->encoding; + + if (gg_session_set_resolver(sess, p->resolver) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() invalid arguments. unsupported resolver type (%d)\n", p->resolver); + errno = EFAULT; + goto fail; + } + + if (p->status_descr) { + int max_length; + + if (sess->protocol_version >= 0x2d) + max_length = GG_STATUS_DESCR_MAXSIZE; + else + max_length = GG_STATUS_DESCR_MAXSIZE_PRE_8_0; + + if (sess->protocol_version >= 0x2d && p->encoding != GG_ENCODING_UTF8) + sess->initial_descr = gg_cp_to_utf8(p->status_descr); + else + sess->initial_descr = strdup(p->status_descr); + + if (!sess->initial_descr) { + gg_debug(GG_DEBUG_MISC, "// gg_login() not enough memory for status\n"); + goto fail; + } + + // XXX pamiÄ™tać, ĹĽeby nie ciąć w Ĺ›rodku znaku utf-8 + + if ((signed)strlen(sess->initial_descr) > max_length) + sess->initial_descr[max_length] = 0; + } + +#ifdef GG_CONFIG_MIRANDA + sess->tls = p->tls; +#endif + if (p->tls == 1) { +#ifdef GG_CONFIG_HAVE_OPENSSL + char buf[1024]; + + OpenSSL_add_ssl_algorithms(); + + if (!RAND_status()) { + char rdata[1024]; + struct { + time_t time; + void *ptr; + } rstruct; + + time(&rstruct.time); + rstruct.ptr = (void *) &rstruct; + + RAND_seed((void *) rdata, sizeof(rdata)); + RAND_seed((void *) &rstruct, sizeof(rstruct)); + } + + sess->ssl_ctx = SSL_CTX_new(SSLv3_client_method()); + + if (!sess->ssl_ctx) { + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// gg_login() SSL_CTX_new() failed: %s\n", buf); + goto fail; + } + + SSL_CTX_set_verify(sess->ssl_ctx, SSL_VERIFY_NONE, NULL); + + sess->ssl = SSL_new(sess->ssl_ctx); + + if (!sess->ssl) { + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + gg_debug(GG_DEBUG_MISC, "// gg_login() SSL_new() failed: %s\n", buf); + goto fail; + } +#elif !defined(GG_CONFIG_MIRANDA) + gg_debug(GG_DEBUG_MISC, "// gg_login() client requested TLS but no support compiled in\n"); +#endif + } + + if (gg_proxy_enabled) { + hostname = gg_proxy_host; + sess->proxy_port = port = gg_proxy_port; + } else { + hostname = GG_APPMSG_HOST; + port = GG_APPMSG_PORT; + } + + if (p->hash_type) + sess->hash_type = p->hash_type; + else + sess->hash_type = GG_LOGIN_HASH_SHA1; + + if (!p->async) { + struct in_addr addr; + + if (!sess->server_addr) { + if ((addr.s_addr = inet_addr(hostname)) == INADDR_NONE) { + if (gg_gethostbyname_real(hostname, &addr, 0) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() host \"%s\" not found\n", hostname); +#ifdef GG_CONFIG_MIRANDA + errno = EACCES; + *gg_failno = GG_FAILURE_RESOLVING; +#endif + goto fail; + } + } + } else { + addr.s_addr = sess->server_addr; + port = sess->port; + } + + sess->hub_addr = addr.s_addr; + + if (gg_proxy_enabled) + sess->proxy_addr = addr.s_addr; + +#ifdef GG_CONFIG_MIRANDA + if ((sess->fd = gg_connect_internal(&addr, port, 0, gg_sock)) == -1) { +#else + if ((sess->fd = gg_connect(&addr, port, 0)) == -1) { +#endif + gg_debug(GG_DEBUG_MISC, "// gg_login() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + + /* nie wyszĹ‚o? prĂłbujemy portu 443. */ + if (sess->server_addr) { + sess->port = GG_HTTPS_PORT; + +#ifdef GG_CONFIG_MIRANDA + if ((sess->fd = gg_connect_internal(&addr, GG_HTTPS_PORT, 0, gg_sock)) == -1) { +#else + if ((sess->fd = gg_connect(&addr, GG_HTTPS_PORT, 0)) == -1) { +#endif + /* ostatnia deska ratunku zawiodĹ‚a? + * w takim razie zwijamy manatki. */ + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_login() connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + } else { + goto fail; + } + } + + if (sess->server_addr) + sess->state = GG_STATE_CONNECTING_GG; + else + sess->state = GG_STATE_CONNECTING_HUB; + + while (sess->state != GG_STATE_CONNECTED) { + struct gg_event *e; + + if (!(e = gg_watch_fd(sess))) { + gg_debug(GG_DEBUG_MISC, "// gg_login() critical error in gg_watch_fd()\n"); + goto fail; + } + + if (e->type == GG_EVENT_CONN_FAILED) { + errno = EACCES; +#ifdef GG_CONFIG_MIRANDA + *gg_failno = e->event.failure; +#endif + gg_debug(GG_DEBUG_MISC, "// gg_login() could not login\n"); + gg_event_free(e); + goto fail; + } + + gg_event_free(e); + } + + return sess; + } + + if (!sess->server_addr || gg_proxy_enabled) { + if (sess->resolver_start(&sess->fd, &sess->resolver, hostname) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() resolving failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + } else { + if ((sess->fd = gg_connect(&sess->server_addr, sess->port, sess->async)) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_login() direct connection failed (errno=%d, %s)\n", errno, strerror(errno)); + goto fail; + } + sess->state = GG_STATE_CONNECTING_GG; + sess->check = GG_CHECK_WRITE; + sess->soft_timeout = 1; + } + + return sess; + +fail: + gg_free_session(sess); + + return NULL; +} + +/** + * WysyĹ‚a do serwera pakiet utrzymania poĹ‚Ä…czenia. + * + * Klient powinien regularnie co minutÄ™ wysyĹ‚ać pakiet utrzymania poĹ‚Ä…czenia, + * inaczej serwer uzna, ĹĽe klient straciĹ‚ Ĺ‚Ä…czność z sieciÄ… i zerwie + * poĹ‚Ä…czenie. + * + * \param sess Struktura sesji + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup login + */ +int gg_ping(struct gg_session *sess) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_ping(%p);\n", sess); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + return gg_send_packet(sess, GG_PING, NULL); +} + +/** + * KoĹ„czy poĹ‚Ä…czenie z serwerem. + * + * Funkcja nie zwalnia zasobĂłw, wiÄ™c po jej wywoĹ‚aniu naleĹĽy uĹĽyć + * \c gg_free_session(). JeĹ›li chce siÄ™ ustawić opis niedostÄ™pnoĹ›ci, naleĹĽy + * wczeĹ›niej wywoĹ‚ać funkcjÄ™ \c gg_change_status_descr() lub + * \c gg_change_status_descr_time(). + * + * \note JeĹ›li w buforze nadawczym poĹ‚Ä…czenia z serwerem znajdujÄ… siÄ™ jeszcze + * dane (np. z powodu strat pakietĂłw na Ĺ‚Ä…czu), prawdopodobnie zostanÄ… one + * utracone przy zrywaniu poĹ‚Ä…czenia. Aby mieć pewność, ĹĽe opis statusu + * zostanie zachowany, naleĹĽy ustawić stan \c GG_STATUS_NOT_AVAIL_DESCR + * za pomocÄ… funkcji \c gg_change_status_descr() i poczekać na zdarzenie + * \c GG_EVENT_DISCONNECT_ACK. + * + * \param sess Struktura sesji + * + * \ingroup login + */ +void gg_logoff(struct gg_session *sess) +{ + if (!sess) + return; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_logoff(%p);\n", sess); + +#ifdef GG_CONFIG_MIRANDA + if (sess->ssl != NULL) + si.shutdown(sess->ssl); +#elif GG_CONFIG_HAVE_OPENSSL + if (sess->ssl != NULL) + SSL_shutdown(sess->ssl); +#endif + + sess->resolver_cleanup(&sess->resolver, 1); + + if (sess->fd != -1) { + shutdown(sess->fd, SHUT_RDWR); + gg_sock_close(sess->fd); + sess->fd = -1; + } + + if (sess->send_buf) { + free(sess->send_buf); + sess->send_buf = NULL; + sess->send_left = 0; + } +} + +/** + * Zwalnia zasoby uĹĽywane przez poĹ‚Ä…czenie z serwerem. FunkcjÄ™ naleĹĽy wywoĹ‚ać + * po zamkniÄ™ciu poĹ‚Ä…czenia z serwerem, by nie doprowadzić do wycieku zasobĂłw + * systemowych. + * + * \param sess Struktura sesji + * + * \ingroup login + */ +void gg_free_session(struct gg_session *sess) +{ + struct gg_dcc7 *dcc; + + if (!sess) + return; + + /* XXX dopisać zwalnianie i zamykanie wszystkiego, co mogĹ‚o zostać */ + + free(sess->password); + free(sess->initial_descr); + free(sess->client_version); + free(sess->header_buf); + +#ifdef GG_CONFIG_MIRANDA + if (sess->ssl != NULL) + si.sfree(sess->ssl); +#elif GG_CONFIG_HAVE_OPENSSL + if (sess->ssl != NULL) + SSL_free(sess->ssl); + + if (sess->ssl_ctx) + SSL_CTX_free(sess->ssl_ctx); +#endif + + sess->resolver_cleanup(&sess->resolver, 1); + + if (sess->fd != -1) + gg_sock_close(sess->fd); + + while (sess->images) + gg_image_queue_remove(sess, sess->images, 1); + + free(sess->send_buf); + + for (dcc = sess->dcc7_list; dcc; dcc = dcc->next) + dcc->sess = NULL; + + free(sess); +} + +#ifndef DOXYGEN + +/** + * \internal Funkcja wysyĹ‚ajÄ…ca pakiet zmiany statusu uĹĽytkownika. + * + * \param sess Struktura sesji + * \param status Nowy status uĹĽytkownika + * \param descr Opis statusu uĹĽytkownika (lub \c NULL) + * \param time Czas powrotu w postaci uniksowego znacznika czasu (lub 0) + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup status + */ +static int gg_change_status_common(struct gg_session *sess, int status, const char *descr, int time) +{ + char *new_descr = NULL; + uint32_t new_time; + int descr_len = 0; + int descr_len_max; + int packet_type; + int append_null = 0; + int res; + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + /* XXX, obcinać stany ktĂłrych stary protokół niezna (czyt. dnd->aw; ffc->av) */ + + /* dodaj flagÄ™ obsĹ‚ugi poĹ‚Ä…czeĹ„ gĹ‚osowych zgodnÄ… z GG 7.x */ + if ((sess->protocol_version >= 0x2a) && (sess->protocol_version < 0x2d /* ? */ ) && (sess->protocol_flags & GG_HAS_AUDIO_MASK) && !GG_S_I(status)) + status |= GG_STATUS_VOICE_MASK; + + sess->status = status; + + if (sess->protocol_version >= 0x2d) { + if (descr != NULL && sess->encoding != GG_ENCODING_UTF8) { + new_descr = gg_cp_to_utf8(descr); + + if (!new_descr) + return -1; + } + + if (sess->protocol_version >= 0x2e) + packet_type = GG_NEW_STATUS80; + else /* sess->protocol_version == 0x2d */ + packet_type = GG_NEW_STATUS80BETA; + descr_len_max = GG_STATUS_DESCR_MAXSIZE; + append_null = 1; + + } else { + packet_type = GG_NEW_STATUS; + descr_len_max = GG_STATUS_DESCR_MAXSIZE_PRE_8_0; + + if (time != 0) + append_null = 1; + } + + if (descr) { + descr_len = (int)strlen((new_descr) ? new_descr : descr); + + if (descr_len > descr_len_max) + descr_len = descr_len_max; + + // XXX pamiÄ™tać o tym, ĹĽeby nie ucinać w Ĺ›rodku znaku utf-8 + } + + if (time) + new_time = gg_fix32(time); + + if (packet_type == GG_NEW_STATUS80) { + struct gg_new_status80 p; + + p.status = gg_fix32(status); + p.flags = gg_fix32(sess->status_flags); + p.description_size = gg_fix32(descr_len); + res = gg_send_packet(sess, + packet_type, + &p, + sizeof(p), + (new_descr) ? new_descr : descr, + descr_len, + NULL); + + } else { + struct gg_new_status p; + + p.status = gg_fix32(status); + res = gg_send_packet(sess, + packet_type, + &p, + sizeof(p), + (new_descr) ? new_descr : descr, + descr_len, + (append_null) ? "\0" : NULL, + (append_null) ? 1 : 0, + (time) ? &new_time : NULL, + (time) ? sizeof(new_time) : 0, + NULL); + } + + free(new_descr); + return res; +} + +#endif /* DOXYGEN */ + +/** + * Zmienia status uĹĽytkownika. + * + * \param sess Struktura sesji + * \param status Nowy status uĹĽytkownika + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup status + */ +int gg_change_status(struct gg_session *sess, int status) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status(%p, %d);\n", sess, status); + + return gg_change_status_common(sess, status, NULL, 0); +} + +/** + * Zmienia status uĹĽytkownika na status opisowy. + * + * \param sess Struktura sesji + * \param status Nowy status uĹĽytkownika + * \param descr Opis statusu uĹĽytkownika + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup status + */ +int gg_change_status_descr(struct gg_session *sess, int status, const char *descr) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_descr(%p, %d, \"%s\");\n", sess, status, descr); + + return gg_change_status_common(sess, status, descr, 0); +} + +/** + * Zmienia status uĹĽytkownika na status opisowy z podanym czasem powrotu. + * + * \param sess Struktura sesji + * \param status Nowy status uĹĽytkownika + * \param descr Opis statusu uĹĽytkownika + * \param time Czas powrotu w postaci uniksowego znacznika czasu + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup status + */ +int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int time) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_descr_time(%p, %d, \"%s\", %d);\n", sess, status, descr, time); + + return gg_change_status_common(sess, status, descr, time); +} + +/** + * Funkcja zmieniajÄ…ca flagi statusu. + * + * \param sess Struktura sesji + * \param flags Nowe flagi statusu + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \note Aby zmiany weszĹ‚y w ĹĽycie, naleĹĽy ponownie ustawić status za pomocÄ… + * funkcji z rodziny \c gg_change_status(). + * + * \ingroup status + */ +int gg_change_status_flags(struct gg_session *sess, int flags) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_change_status_flags(%p, 0x%08x);\n", sess, flags); + + if (sess == NULL) { + errno = EFAULT; + return -1; + } + + sess->status_flags = flags; + + return 0; +} + +/** + * WysyĹ‚a wiadomość do uĹĽytkownika. + * + * Zwraca losowy numer sekwencyjny, ktĂłry moĹĽna zignorować albo wykorzystać + * do potwierdzenia. + * + * \param sess Struktura sesji + * \param msgclass Klasa wiadomoĹ›ci + * \param recipient Numer adresata + * \param message Treść wiadomoĹ›ci + * + * \return Numer sekwencyjny wiadomoĹ›ci lub -1 w przypadku bĹ‚Ä™du. + * + * \ingroup messages + */ +int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message(%p, %d, %u, %p)\n", sess, msgclass, recipient, message); + + return gg_send_message_confer_richtext(sess, msgclass, 1, &recipient, message, NULL, 0); +} + +/** + * WysyĹ‚a wiadomość formatowanÄ…. + * + * Zwraca losowy numer sekwencyjny, ktĂłry moĹĽna zignorować albo wykorzystać + * do potwierdzenia. + * + * \param sess Struktura sesji + * \param msgclass Klasa wiadomoĹ›ci + * \param recipient Numer adresata + * \param message Treść wiadomoĹ›ci + * \param format Informacje o formatowaniu + * \param formatlen DĹ‚ugość informacji o formatowaniu + * + * \return Numer sekwencyjny wiadomoĹ›ci lub -1 w przypadku bĹ‚Ä™du. + * + * \ingroup messages + */ +int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, const unsigned char *format, int formatlen) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_richtext(%p, %d, %u, %p, %p, %d);\n", sess, msgclass, recipient, message, format, formatlen); + + return gg_send_message_confer_richtext(sess, msgclass, 1, &recipient, message, format, formatlen); +} + +/** + * WysyĹ‚a wiadomość w ramach konferencji. + * + * Zwraca losowy numer sekwencyjny, ktĂłry moĹĽna zignorować albo wykorzystać + * do potwierdzenia. + * + * \param sess Struktura sesji + * \param msgclass Klasa wiadomoĹ›ci + * \param recipients_count Liczba adresatĂłw + * \param recipients WskaĹşnik do tablicy z numerami adresatĂłw + * \param message Treść wiadomoĹ›ci + * + * \return Numer sekwencyjny wiadomoĹ›ci lub -1 w przypadku bĹ‚Ä™du. + * + * \ingroup messages + */ +int gg_send_message_confer(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message) +{ + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_confer(%p, %d, %d, %p, %p);\n", sess, msgclass, recipients_count, recipients, message); + + return gg_send_message_confer_richtext(sess, msgclass, recipients_count, recipients, message, NULL, 0); +} + +/** + * \internal Dodaje tekst na koniec bufora. + * + * \param dst WskaĹşnik na bufor roboczy + * \param pos WskaĹşnik na aktualne poĹ‚oĹĽenie w buforze roboczym + * \param src Dodawany tekst + * \param len DĹ‚ugość dodawanego tekstu + */ +static void gg_append(char *dst, int *pos, const void *src, int len) +{ + if (dst != NULL) + memcpy(&dst[*pos], src, len); + + *pos += len; +} + +/** + * \internal Zamienia tekst z formatowaniem Gadu-Gadu na HTML. + * + * \param dst Bufor wynikowy (moĹĽe być \c NULL) + * \param src Tekst ĹşrĂłdĹ‚owy w UTF-8 + * \param format Atrybuty tekstu ĹşrĂłdĹ‚owego + * \param format_len DĹ‚ugość bloku atrybutĂłw tekstu ĹşrĂłdĹ‚owego + * + * \note Wynikowy tekst nie jest idealnym kodem HTML, poniewaĹĽ ma jak + * dokĹ‚adniej odzwierciedlać to, co wygenerowaĹ‚by oryginalny klient. + * + * \note Dokleja \c \\0 na koĹ„cu bufora wynikowego. + * + * \return DĹ‚ugość tekstu wynikowego bez \c \\0 (nawet jeĹ›li \c dst to \c NULL). + */ +static int gg_convert_to_html(char *dst, const char *src, const unsigned char *format, int format_len) +{ + const char span_fmt[] = ""; + const int span_len = 75; + const char img_fmt[] = ""; + const int img_len = 29; + int char_pos = 0; + int format_idx = 0; + unsigned char old_attr = 0; + const unsigned char *color = (const unsigned char*) "\x00\x00\x00"; + int len, i; + const unsigned char *format_ = (const unsigned char*) format; + + len = 0; + + /* Nie mamy atrybutĂłw dla pierwsze znaku, a tekst nie jest pusty, wiÄ™c + * tak czy inaczej trzeba otworzyć . */ + + if (src[0] != 0 && (format_idx + 3 > format_len || (format_[format_idx] | (format_[format_idx + 1] << 8)) != 0)) { + if (dst != NULL) + sprintf(&dst[len], span_fmt, 0, 0, 0); + + len += span_len; + } + + /* PÄ™tla przechodzi teĹĽ przez koĹ„czÄ…ce \0, ĹĽeby mĂłc dokleić obrazek + * na koĹ„cu tekstu. */ + + for (i = 0; ; i++) { + /* Analizuj atrybuty tak dĹ‚ugo jak dotyczÄ… aktualnego znaku. */ + for (;;) { + unsigned char attr; + int attr_pos; + + if (format_idx + 3 > format_len) + break; + + attr_pos = format_[format_idx] | (format_[format_idx + 1] << 8); + + if (attr_pos != char_pos) + break; + + attr = format_[format_idx + 2]; + + /* Nie doklejaj atrybutĂłw na koĹ„cu, co najwyĹĽej obrazki. */ + + if (src[i] == 0) + attr &= ~(GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR); + + format_idx += 3; + + if ((attr & (GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR)) != 0 || (attr == 0 && old_attr != 0)) { + if (char_pos != 0) { + if ((old_attr & GG_FONT_UNDERLINE) != 0) + gg_append(dst, &len, "", 4); + + if ((old_attr & GG_FONT_ITALIC) != 0) + gg_append(dst, &len, "", 4); + + if ((old_attr & GG_FONT_BOLD) != 0) + gg_append(dst, &len, "", 4); + + if (src[i] != 0) + gg_append(dst, &len, "", 7); + } + + if (((attr & GG_FONT_COLOR) != 0) && (format_idx + 3 <= format_len)) { + color = &format_[format_idx]; + format_idx += 3; + } else { + color = (unsigned char*) "\x00\x00\x00"; + } + + if (src[i] != 0) { + if (dst != NULL) + sprintf(&dst[len], span_fmt, color[0], color[1], color[2]); + len += span_len; + } + } else if (char_pos == 0 && src[0] != 0) { + if (dst != NULL) + sprintf(&dst[len], span_fmt, 0, 0, 0); + len += span_len; + } + + if ((attr & GG_FONT_BOLD) != 0) + gg_append(dst, &len, "", 3); + + if ((attr & GG_FONT_ITALIC) != 0) + gg_append(dst, &len, "", 3); + + if ((attr & GG_FONT_UNDERLINE) != 0) + gg_append(dst, &len, "", 3); + + if (((attr & GG_FONT_IMAGE) != 0) && (format_idx + 10 <= format_len)) { + if (dst != NULL) { + sprintf(&dst[len], img_fmt, + format_[format_idx + 9], + format_[format_idx + 8], + format_[format_idx + 7], + format_[format_idx + 6], + format_[format_idx + 5], + format_[format_idx + 4], + format_[format_idx + 3], + format_[format_idx + 2]); + } + + len += img_len; + format_idx += 10; + } + + old_attr = attr; + } + + /* Doklej znak zachowujÄ…c htmlowe escapowanie. */ + + switch (src[i]) { + case '&': + gg_append(dst, &len, "&", 5); + break; + case '<': + gg_append(dst, &len, "<", 4); + break; + case '>': + gg_append(dst, &len, ">", 4); + break; + case '\'': + gg_append(dst, &len, "'", 6); + break; + case '\"': + gg_append(dst, &len, """, 6); + break; + case '\n': + gg_append(dst, &len, "
", 4); + break; + case '\r': + case 0: + break; + default: + if (dst != NULL) + dst[len] = src[i]; + len++; + } + + /* SprawdĹş, czy bajt nie jest kontynuacjÄ… znaku unikodowego. */ + + if ((src[i] & 0xc0) != 0xc0) + char_pos++; + + if (src[i] == 0) + break; + } + + /* Zamknij tagi. */ + + if ((old_attr & GG_FONT_UNDERLINE) != 0) + gg_append(dst, &len, "
", 4); + + if ((old_attr & GG_FONT_ITALIC) != 0) + gg_append(dst, &len, "
", 4); + + if ((old_attr & GG_FONT_BOLD) != 0) + gg_append(dst, &len, "
", 4); + + if (src[0] != 0) + gg_append(dst, &len, "
", 7); + + if (dst != NULL) + dst[len] = 0; + + return len; +} + +/** + * WysyĹ‚a wiadomość formatowanÄ… w ramach konferencji. + * + * Zwraca losowy numer sekwencyjny, ktĂłry moĹĽna zignorować albo wykorzystać + * do potwierdzenia. + * + * \param sess Struktura sesji + * \param msgclass Klasa wiadomoĹ›ci + * \param recipients_count Liczba adresatĂłw + * \param recipients WskaĹşnik do tablicy z numerami adresatĂłw + * \param message Treść wiadomoĹ›ci + * \param format Informacje o formatowaniu + * \param formatlen DĹ‚ugość informacji o formatowaniu + * + * \return Numer sekwencyjny wiadomoĹ›ci lub -1 w przypadku bĹ‚Ä™du. + * + * \ingroup messages + */ +int gg_send_message_confer_richtext(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message, const unsigned char *format, int formatlen) +{ + struct gg_send_msg s; + struct gg_send_msg80 s80; + struct gg_msg_recipients r; + char *cp_msg = NULL; + char *utf_msg = NULL; + char *html_msg = NULL; + int seq_no; + int i, j, k; + uin_t *recps; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_confer_richtext(%p, %d, %d, %p, %p, %p, %d);\n", sess, msgclass, recipients_count, recipients, message, format, formatlen); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (message == NULL || recipients_count <= 0 || recipients_count > 0xffff || (recipients_count != 1 && recipients == NULL)) { + errno = EINVAL; + return -1; + } + + if (sess->encoding == GG_ENCODING_UTF8) { + if (!(cp_msg = gg_utf8_to_cp((const char *) message))) + return -1; + + utf_msg = (char*) message; + } else { + if (sess->protocol_version >= 0x2d) { + if (!(utf_msg = gg_cp_to_utf8((const char *) message))) + return -1; + } + + cp_msg = (char*) message; + } + + if (sess->protocol_version < 0x2d) { + if (!sess->seq) + sess->seq = 0x01740000 | (rand() & 0xffff); + seq_no = sess->seq; + sess->seq += (rand() % 0x300) + 0x300; + + s.msgclass = gg_fix32(msgclass); + s.seq = gg_fix32(seq_no); + } else { + int len; + + // Drobne odchylenie od protokoĹ‚u. JeĹ›li wysyĹ‚amy kilka + // wiadomoĹ›ci w ciÄ…gu jednej sekundy, zwiÄ™kszamy poprzedniÄ… + // wartość, ĹĽeby kaĹĽda wiadomość miaĹ‚a unikalny numer. + + seq_no = (int)time(NULL); + + if (seq_no <= sess->seq) + seq_no = sess->seq + 1; + + sess->seq = seq_no; + + if (format == NULL || formatlen < 3) { + format = (unsigned char*) "\x02\x06\x00\x00\x00\x08\x00\x00\x00"; + formatlen = 9; + } + + len = gg_convert_to_html(NULL, utf_msg, format + 3, formatlen - 3); + + html_msg = malloc(len + 1); + + if (html_msg == NULL) { + seq_no = -1; + goto cleanup; + } + + gg_convert_to_html(html_msg, utf_msg, format + 3, formatlen - 3); + + s80.seq = gg_fix32(seq_no); + s80.msgclass = gg_fix32(msgclass); + s80.offset_plain = gg_fix32(sizeof(s80) + (uint32_t)strlen(html_msg) + 1); + s80.offset_attr = gg_fix32(sizeof(s80) + (uint32_t)strlen(html_msg) + 1 + (uint32_t)strlen(cp_msg) + 1); + } + + if (recipients_count > 1) { + r.flag = 0x01; + r.count = gg_fix32(recipients_count - 1); + + recps = malloc(sizeof(uin_t) * recipients_count); + + if (!recps) { + seq_no = -1; + goto cleanup; + } + + for (i = 0; i < recipients_count; i++) { + for (j = 0, k = 0; j < recipients_count; j++) { + if (recipients[j] != recipients[i]) { + recps[k] = gg_fix32(recipients[j]); + k++; + } + } + + if (sess->protocol_version < 0x2d) { + s.recipient = gg_fix32(recipients[i]); + + if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), cp_msg, strlen(cp_msg) + 1, &r, sizeof(r), recps, (recipients_count - 1) * sizeof(uin_t), format, formatlen, NULL) == -1) + seq_no = -1; + } else { + s80.recipient = gg_fix32(recipients[i]); + + if (gg_send_packet(sess, GG_SEND_MSG80, &s80, sizeof(s80), html_msg, strlen(html_msg) + 1, cp_msg, strlen(cp_msg) + 1, &r, sizeof(r), recps, (recipients_count - 1) * sizeof(uin_t), format, formatlen, NULL) == -1) + seq_no = -1; + } + } + + free(recps); + } else { + if (sess->protocol_version < 0x2d) { + s.recipient = gg_fix32(recipients[0]); + + if (gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), cp_msg, strlen(cp_msg) + 1, format, formatlen, NULL) == -1) + seq_no = -1; + } else { + s80.recipient = gg_fix32(recipients[0]); + + if (gg_send_packet(sess, GG_SEND_MSG80, &s80, sizeof(s80), html_msg, strlen(html_msg) + 1, cp_msg, strlen(cp_msg) + 1, format, formatlen, NULL) == -1) + seq_no = -1; + } + } + +cleanup: + if (cp_msg != (char*) message) + free(cp_msg); + + if (utf_msg != (char*) message) + free(utf_msg); + + free(html_msg); + + return seq_no; +} + +/** + * WysyĹ‚a wiadomość binarnÄ… przeznaczonÄ… dla klienta. + * + * WiadomoĹ›ci miÄ™dzy klientami przesyĹ‚a siÄ™ np. w celu wywoĹ‚ania zwrotnego + * poĹ‚Ä…czenia bezpoĹ›redniego. Funkcja zwraca losowy numer sekwencyjny, + * ktĂłry moĹĽna zignorować albo wykorzystać do potwierdzenia. + * + * \param sess Struktura sesji + * \param msgclass Klasa wiadomoĹ›ci + * \param recipient Numer adresata + * \param message Treść wiadomoĹ›ci + * \param message_len DĹ‚ugość wiadomoĹ›ci + * + * \return Numer sekwencyjny wiadomoĹ›ci lub -1 w przypadku bĹ‚Ä™du. + * + * \ingroup messages + */ +int gg_send_message_ctcp(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, int message_len) +{ + struct gg_send_msg s; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_send_message_ctcp(%p, %d, %u, ...);\n", sess, msgclass, recipient); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + s.recipient = gg_fix32(recipient); + s.seq = gg_fix32(0); + s.msgclass = gg_fix32(msgclass); + + return gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), message, message_len, NULL); +} + +/** + * WysyĹ‚a ĹĽÄ…danie obrazka o podanych parametrach. + * + * WiadomoĹ›ci obrazkowe nie zawierajÄ… samych obrazkĂłw, a tylko ich rozmiary + * i sumy kontrolne. Odbiorca najpierw szuka obrazkĂłw w swojej pamiÄ™ci + * podrÄ™cznej i dopiero gdy ich nie znajdzie, wysyĹ‚a ĹĽÄ…danie do nadawcy. + * Wynik zostanie przekazany zdarzeniem \c GG_EVENT_IMAGE_REPLY. + * + * \param sess Struktura sesji + * \param recipient Numer adresata + * \param size Rozmiar obrazka w bajtach + * \param crc32 Suma kontrola obrazka + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup messages + */ +int gg_image_request(struct gg_session *sess, uin_t recipient, int size, uint32_t crc32) +{ + struct gg_send_msg s; + struct gg_msg_image_request r; + char dummy = 0; + int res; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_image_request(%p, %d, %u, 0x%.4x);\n", sess, recipient, size, crc32); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (size < 0) { + errno = EINVAL; + return -1; + } + + s.recipient = gg_fix32(recipient); + s.seq = gg_fix32(0); + s.msgclass = gg_fix32(GG_CLASS_MSG); + + r.flag = 0x04; + r.size = gg_fix32(size); + r.crc32 = gg_fix32(crc32); + + res = gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), &dummy, 1, &r, sizeof(r), NULL); + + if (!res) { + struct gg_image_queue *q = malloc(sizeof(*q)); + char *buf; + + if (!q) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_request() not enough memory for image queue\n"); + return -1; + } + + buf = malloc(size); + if (size && !buf) + { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_image_request() not enough memory for image\n"); + free(q); + return -1; + } + + memset(q, 0, sizeof(*q)); + + q->sender = recipient; + q->size = size; + q->crc32 = crc32; + q->image = buf; + + if (!sess->images) + sess->images = q; + else { + struct gg_image_queue *qq; + + for (qq = sess->images; qq->next; qq = qq->next) + ; + + qq->next = q; + } + } + + return res; +} + +/** + * WysyĹ‚a ĹĽÄ…dany obrazek. + * + * \param sess Struktura sesji + * \param recipient Numer adresata + * \param filename Nazwa pliku + * \param image Bufor z obrazkiem + * \param size Rozmiar obrazka + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup messages + */ +int gg_image_reply(struct gg_session *sess, uin_t recipient, const char *filename, const char *image, int size) +{ + struct gg_msg_image_reply *r; + struct gg_send_msg s; + const char *tmp; + char buf[1910]; + int res = -1; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_image_reply(%p, %d, \"%s\", %p, %d);\n", sess, recipient, filename, image, size); + + if (!sess || !filename || !image) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (size < 0) { + errno = EINVAL; + return -1; + } + + /* wytnij Ĺ›cieĹĽki, zostaw tylko nazwÄ™ pliku */ + while ((tmp = strrchr(filename, '/')) || (tmp = strrchr(filename, '\\'))) + filename = tmp + 1; + + if (strlen(filename) < 1 || strlen(filename) > 1024) { + errno = EINVAL; + return -1; + } + + s.recipient = gg_fix32(recipient); + s.seq = gg_fix32(0); + s.msgclass = gg_fix32(GG_CLASS_MSG); + + buf[0] = 0; + r = (void*) &buf[1]; + + r->flag = 0x05; + r->size = gg_fix32(size); + r->crc32 = gg_fix32(gg_crc32(0, (unsigned char*) image, size)); + + while (size > 0) { + int buflen, chunklen; + + /* \0 + struct gg_msg_image_reply */ + buflen = sizeof(struct gg_msg_image_reply) + 1; + + /* w pierwszym kawaĹ‚ku jest nazwa pliku */ + if (r->flag == 0x05) { + strcpy(buf + buflen, filename); + buflen += (int)strlen(filename) + 1; + } + + chunklen = (size >= (int)sizeof(buf) - buflen) ? ((int)sizeof(buf) - buflen) : size; + + memcpy(buf + buflen, image, chunklen); + size -= chunklen; + image += chunklen; + + res = gg_send_packet(sess, GG_SEND_MSG, &s, sizeof(s), buf, buflen + chunklen, NULL); + + if (res == -1) + break; + + r->flag = 0x06; + } + + return res; +} + +/** + * WysyĹ‚a do serwera listÄ™ kontaktĂłw. + * + * Funkcja informuje serwer o liĹ›cie kontaktĂłw, ktĂłrych statusy bÄ™dÄ… + * obserwowane lub kontaktĂłw, ktĂłre bedÄ… blokowane. Dla kaĹĽdego z \c count + * kontaktĂłw tablica \c userlist zawiera numer, a tablica \c types rodzaj + * kontaktu (\c GG_USER_NORMAL, \c GG_USER_OFFLINE, \c GG_USER_BLOCKED). + * + * ListÄ™ kontaktĂłw naleĹĽy \b zawsze wysyĹ‚ać po poĹ‚Ä…czeniu, nawet jeĹ›li + * jest pusta. + * + * \param sess Struktura sesji + * \param userlist WskaĹşnik do tablicy numerĂłw kontaktĂłw + * \param types WskaĹşnik do tablicy rodzajĂłw kontaktĂłw + * \param count Liczba kontaktĂłw + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup contacts + */ +int gg_notify_ex(struct gg_session *sess, uin_t *userlist, char *types, int count) +{ + struct gg_notify *n; + uin_t *u; + char *t; + int i, res = 0; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_notify_ex(%p, %p, %p, %d);\n", sess, userlist, types, count); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!userlist || !count) + return gg_send_packet(sess, GG_LIST_EMPTY, NULL); + + while (count > 0) { + int part_count, packet_type; + + if (count > 400) { + part_count = 400; + packet_type = GG_NOTIFY_FIRST; + } else { + part_count = count; + packet_type = GG_NOTIFY_LAST; + } + + if (!(n = (struct gg_notify*) malloc(sizeof(*n) * part_count))) + return -1; + + for (u = userlist, t = types, i = 0; i < part_count; u++, t++, i++) { + n[i].uin = gg_fix32(*u); + n[i].dunno1 = *t; + } + + if (gg_send_packet(sess, packet_type, n, sizeof(*n) * part_count, NULL) == -1) { + free(n); + res = -1; + break; + } + + count -= part_count; + userlist += part_count; + types += part_count; + + free(n); + } + + return res; +} + +/** + * WysyĹ‚a do serwera listÄ™ kontaktĂłw. + * + * Funkcja jest odpowiednikiem \c gg_notify_ex(), gdzie wszystkie kontakty + * sÄ… rodzaju \c GG_USER_NORMAL. + * + * \param sess Struktura sesji + * \param userlist WskaĹşnik do tablicy numerĂłw kontaktĂłw + * \param count Liczba kontaktĂłw + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup contacts + */ +int gg_notify(struct gg_session *sess, uin_t *userlist, int count) +{ + struct gg_notify *n; + uin_t *u; + int i, res = 0; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_notify(%p, %p, %d);\n", sess, userlist, count); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!userlist || !count) + return gg_send_packet(sess, GG_LIST_EMPTY, NULL); + + while (count > 0) { + int part_count, packet_type; + + if (count > 400) { + part_count = 400; + packet_type = GG_NOTIFY_FIRST; + } else { + part_count = count; + packet_type = GG_NOTIFY_LAST; + } + + if (!(n = (struct gg_notify*) malloc(sizeof(*n) * part_count))) + return -1; + + for (u = userlist, i = 0; i < part_count; u++, i++) { + n[i].uin = gg_fix32(*u); + n[i].dunno1 = GG_USER_NORMAL; + } + + if (gg_send_packet(sess, packet_type, n, sizeof(*n) * part_count, NULL) == -1) { + res = -1; + free(n); + break; + } + + free(n); + + userlist += part_count; + count -= part_count; + } + + return res; +} + +/** + * Dodaje kontakt. + * + * Dodaje do listy kontaktĂłw dany numer w trakcie poĹ‚Ä…czenia. Aby zmienić + * rodzaj kontaktu (np. z normalnego na zablokowany), naleĹĽy najpierw usunąć + * poprzedni rodzaj, poniewaĹĽ serwer operuje na maskach bitowych. + * + * \param sess Struktura sesji + * \param uin Numer kontaktu + * \param type Rodzaj kontaktu + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup contacts + */ +int gg_add_notify_ex(struct gg_session *sess, uin_t uin, char type) +{ + struct gg_add_remove a; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_add_notify_ex(%p, %u, %d);\n", sess, uin, type); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + a.uin = gg_fix32(uin); + a.dunno1 = type; + + return gg_send_packet(sess, GG_ADD_NOTIFY, &a, sizeof(a), NULL); +} + +/** + * Dodaje kontakt. + * + * Funkcja jest odpowiednikiem \c gg_add_notify_ex(), gdzie rodzaj wszystkich + * kontaktĂłw to \c GG_USER_NORMAL. + * + * \param sess Struktura sesji + * \param uin Numer kontaktu + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup contacts + */ +int gg_add_notify(struct gg_session *sess, uin_t uin) +{ + return gg_add_notify_ex(sess, uin, GG_USER_NORMAL); +} + +/** + * Usuwa kontakt. + * + * Usuwa z listy kontaktĂłw dany numer w trakcie poĹ‚Ä…czenia. + * + * \param sess Struktura sesji + * \param uin Numer kontaktu + * \param type Rodzaj kontaktu + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup contacts + */ +int gg_remove_notify_ex(struct gg_session *sess, uin_t uin, char type) +{ + struct gg_add_remove a; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_remove_notify_ex(%p, %u, %d);\n", sess, uin, type); + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + a.uin = gg_fix32(uin); + a.dunno1 = type; + + return gg_send_packet(sess, GG_REMOVE_NOTIFY, &a, sizeof(a), NULL); +} + +/** + * Usuwa kontakt. + * + * Funkcja jest odpowiednikiem \c gg_add_notify_ex(), gdzie rodzaj wszystkich + * kontaktĂłw to \c GG_USER_NORMAL. + * + * \param sess Struktura sesji + * \param uin Numer kontaktu + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup contacts + */ +int gg_remove_notify(struct gg_session *sess, uin_t uin) +{ + return gg_remove_notify_ex(sess, uin, GG_USER_NORMAL); +} + +/** + * WysyĹ‚a do serwera zapytanie dotyczÄ…ce listy kontaktĂłw. + * + * Funkcja sĹ‚uĹĽy do importu lub eksportu listy kontaktĂłw do serwera. + * W odróżnieniu od funkcji \c gg_notify(), ta lista kontaktĂłw jest przez + * serwer jedynie przechowywana i nie ma wpĹ‚ywu na poĹ‚Ä…czenie. Format + * listy kontaktĂłw jest ignorowany przez serwer, ale ze wzglÄ™du na + * kompatybilność z innymi klientami, naleĹĽy przechowywać dane w tym samym + * formacie co oryginalny klient Gadu-Gadu. + * + * Program nie musi siÄ™ przejmować fragmentacjÄ… listy kontaktĂłw wynikajÄ…cÄ… + * z protokoĹ‚u -- wysyĹ‚a i odbiera kompletnÄ… listÄ™. + * + * \param sess Struktura sesji + * \param type Rodzaj zapytania + * \param request Treść zapytania (moĹĽe być rĂłwne NULL) + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup importexport + */ +int gg_userlist_request(struct gg_session *sess, char type, const char *request) +{ + int len; + + if (!sess) { + errno = EFAULT; + return -1; + } + + if (sess->state != GG_STATE_CONNECTED) { + errno = ENOTCONN; + return -1; + } + + if (!request) { + sess->userlist_blocks = 1; + return gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), NULL); + } + + len = (int)strlen(request); + + sess->userlist_blocks = 0; + + while (len > 2047) { + sess->userlist_blocks++; + + if (gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), request, 2047, NULL) == -1) + return -1; + + if (type == GG_USERLIST_PUT) + type = GG_USERLIST_PUT_MORE; + + request += 2047; + len -= 2047; + } + + sess->userlist_blocks++; + + return gg_send_packet(sess, GG_USERLIST_REQUEST, &type, sizeof(type), request, len, NULL); +} + +/** + * Informuje rozmĂłwcÄ™ o pisaniu wiadomoĹ›ci. + * + * \param sess Struktura sesji + * \param recipient Numer adresata + * \param length DĹ‚ugość wiadomoĹ›ci lub 0 jeĹ›li jest pusta + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup messages + */ +int gg_typing_notification(struct gg_session *sess, uin_t recipient, int length){ + struct gg_typing_notification pkt; + uin_t uin; + + pkt.length = gg_fix16((uint16_t)length); + uin = gg_fix32(recipient); + memcpy(&pkt.uin, &uin, sizeof(uin_t)); + + return gg_send_packet(sess, GG_TYPING_NOTIFICATION, &pkt, sizeof(pkt), NULL); +} + +/** + * RozĹ‚Ä…cza innÄ… sesjÄ™ multilogowania. + * + * \param gs Struktura sesji + * \param conn_id Sesja do rozĹ‚Ä…czenia + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup login + */ +int gg_multilogon_disconnect(struct gg_session *gs, gg_multilogon_id_t conn_id) +{ + struct gg_multilogon_disconnect pkt; + + pkt.conn_id = conn_id; + + return gg_send_packet(gs, GG_MULTILOGON_DISCONNECT, &pkt, sizeof(pkt), NULL); +} + +/* @} */ + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/protocols/Gadu-Gadu/src/libgadu/libgadu.h b/protocols/Gadu-Gadu/src/libgadu/libgadu.h new file mode 100644 index 0000000000..d273d998e1 --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/libgadu.h @@ -0,0 +1,2311 @@ +/* coding: UTF-8 */ +/* $Id: libgadu.h 13762 2011-08-09 12:35:16Z dezred $ */ + +/* + * (C) Copyright 2001-2009 Wojtek Kaniewski + * Robert J. WoĹşny + * Arkadiusz MiĹ›kiewicz + * Tomasz ChiliĹ„ski + * Piotr Wysocki + * Dawid Jarosz + * Jakub Zawadzki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +/** + * \file libgadu.h + * + * \brief GĹ‚Ăłwny plik nagĹ‚Ăłwkowy biblioteki + */ + +#ifndef __GG_LIBGADU_H +#define __GG_LIBGADU_H + +/* Defined if libgadu should be compatible with Miranda. */ +#define GG_CONFIG_MIRANDA + +#ifdef GG_CONFIG_MIRANDA +#include +#endif + +#if defined(__cplusplus) || defined(_WIN32) +#pragma pack(push, 1) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/** \cond ignore */ + +/* Defined if libgadu was compiled for bigendian machine. */ +#undef GG_CONFIG_BIGENDIAN + +/* Defined if this machine has gethostbyname_r(). */ +#undef GG_CONFIG_HAVE_GETHOSTBYNAME_R + +/* Defined if libgadu was compiled and linked with pthread support. */ +#define GG_CONFIG_HAVE_PTHREAD + +/* Defined if pthread resolver is the default one. */ +#define GG_CONFIG_PTHREAD_DEFAULT + +/* Defined if this machine has C99-compiliant vsnprintf(). */ +#undef GG_CONFIG_HAVE_C99_VSNPRINTF + +/* Defined if this machine has va_copy(). */ +#undef GG_CONFIG_HAVE_VA_COPY + +/* Defined if this machine has __va_copy(). */ +#undef GG_CONFIG_HAVE___VA_COPY + +/* Defined if this machine supports long long. */ +/* Visual C++ 6.0 has no long long */ +#if !defined(_MSC_VER) || (_MSC_VER >= 1300) +#define GG_CONFIG_HAVE_LONG_LONG +#endif + +/* Defined if libgadu was compiled and linked with OpenSSL support. */ +#undef GG_CONFIG_HAVE_OPENSSL + +/* Defined if uintX_t types are defined in . */ +#undef GG_CONFIG_HAVE_STDINT_H + +/* Defined if uintX_t types are defined in . */ +#undef GG_CONFIG_HAVE_INTTYPES_H + +/* Defined if uintX_t types are defined in . */ +#undef GG_CONFIG_HAVE_SYS_INTTYPES_H + +/* Defined if uintX_t types are defined in . */ +#undef GG_CONFIG_HAVE_SYS_INT_TYPES_H + +/* Defined if uintX_t types are defined in . */ +#undef GG_CONFIG_HAVE_SYS_TYPES_H + +/* MSC have no va_copy */ +#ifndef _MSC_VER +#define GG_CONFIG_HAVE_VA_COPY +#define GG_CONFIG_HAVE___VA_COPY +#endif + +#if defined(GG_CONFIG_HAVE_OPENSSL) && !defined(GG_CONFIG_MIRANDA) +#include +#endif + +#ifdef GG_CONFIG_HAVE_STDINT_H +#include +#else +# ifdef GG_CONFIG_HAVE_INTTYPES_H +# include +# else +# ifdef GG_CONFIG_HAVE_SYS_INTTYPES_H +# include +# else +# ifdef GG_CONFIG_HAVE_SYS_INT_TYPES_H +# include +# else +# ifdef GG_CONFIG_HAVE_SYS_TYPES_H +# include +# else + +#ifndef __AC_STDINT_H +#define __AC_STDINT_H + +/* ISO C 9X: 7.18 Integer types */ + +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +typedef unsigned int uint32_t; +#ifdef GG_CONFIG_HAVE_LONG_LONG +typedef unsigned long long uint64_t; +#define GG_CONFIG_HAVE_UINT64_T +#endif + +#ifndef __CYGWIN__ +#define __int8_t_defined +typedef signed char int8_t; +typedef signed short int16_t; +typedef signed int int32_t; +#endif + +#endif /* __AC_STDINT_H */ + +# endif +# endif +# endif +# endif +#endif + +#ifdef _WIN32 +# define kill(pid,sig) +# ifdef _MSC_VER +# define vsnprintf _vsnprintf +# define stat _stat +# ifndef strdup +# define strdup _strdup +# endif +# define strncasecmp _strnicmp +# define vsnprintf _vsnprintf +# define snprintf _snprintf +# define strcasecmp _stricmp +# define GG_CONFIG_HAVE_STRTOULL +# define strtoull _strtoui64 +# endif +# define gg_sock_write(sock,buf,len) send(sock,(void *)(buf),len,0) +# define gg_sock_read(sock,buf,len) recv(sock,(void *)(buf),len,0) +# define gg_sock_close(sock) closesocket(sock) +# define gg_getsockopt(sock,level,name,val,len) getsockopt(sock,level,name,(char *)val,len) +#else + typedef int SOCKET; +# define gg_sock_write write +# define gg_sock_read read +# define gg_sock_close close +# define gg_getsockopt getsockopt +#endif + +/** \endcond */ + +/** + * Numer Gadu-Gadu. + */ +typedef uint32_t uin_t; + +/** + * Identyfikator poĹ‚Ä…czenia bezpoĹ›redniego Gadu-Gadu 7.x. + */ +typedef struct { + uint8_t id[8]; +} gg_dcc7_id_t; + +/** + * Identyfikator sesji multilogowania. + */ +typedef struct { + uint8_t id[8]; +} gg_multilogon_id_t; + +/** + * Makro deklarujÄ…ce pola wspĂłlne dla struktur sesji. + */ +#define gg_common_head(x) \ + SOCKET fd; /**< Obserwowany deskryptor */ \ + int check; /**< Informacja o ĹĽÄ…daniu odczytu/zapisu (patrz \ref gg_check_t) */ \ + int state; /**< Aktualny stan poĹ‚Ä…czenia (patrz \ref gg_state_t) */ \ + int error; /**< Kod bĹ‚Ä™du dla \c GG_STATE_ERROR (patrz \ref gg_error_t) */ \ + int type; /**< Rodzaj sesji (patrz \ref gg_session_t) */ \ + int id; /**< Identyfikator sesji */ \ + int timeout; /**< Czas pozostaĹ‚y do zakoĹ„czenia stanu */ \ + int (*callback)(x*); /**< Funkcja zwrotna */ \ + void (*destroy)(x*); /**< Funkcja zwalniania zasobĂłw */ + +/** + * Struktura wspĂłlna dla wszystkich sesji i poĹ‚Ä…czeĹ„. Pozwala na proste + * rzutowanie niezaleĹĽne od rodzaju poĹ‚Ä…czenia. + */ +struct gg_common { + gg_common_head(struct gg_common) +}; + +struct gg_image_queue; + +struct gg_dcc7; + +struct gg_dcc7_relay; + +/** + * SposĂłb rozwiÄ…zywania nazw serwerĂłw. + */ +typedef enum { + GG_RESOLVER_DEFAULT = 0, /**< DomyĹ›lny sposĂłb rozwiÄ…zywania nazw (jeden z poniĹĽszych) */ + GG_RESOLVER_FORK, /**< RozwiÄ…zywanie nazw bazujÄ…ce na procesach */ + GG_RESOLVER_PTHREAD, /**< RozwiÄ…zywanie nazw bazujÄ…ce na wÄ…tkach */ + GG_RESOLVER_CUSTOM, /**< Funkcje rozwiÄ…zywania nazw dostarczone przed aplikacjÄ™ */ + GG_RESOLVER_INVALID = -1 /**< NieprawidĹ‚owy sposĂłb rozwiÄ…zywania nazw (wynik \c gg_session_get_resolver) */ +} gg_resolver_t; + +/** + * Rodzaj kodowania znakĂłw. + */ +typedef enum { + GG_ENCODING_CP1250 = 0, /**< Kodowanie CP1250 */ + GG_ENCODING_UTF8, /**< Kodowanie UTF-8 */ + GG_ENCODING_INVALID = -1 /**< NieprawidĹ‚owe kodowanie */ +} gg_encoding_t; + +/** + * Sesja Gadu-Gadu. + * + * Tworzona przez funkcjÄ™ \c gg_login(), zwalniana przez \c gg_free_session(). + * + * \ingroup login + */ +struct gg_session { + gg_common_head(struct gg_session) + + int async; /**< Flaga poĹ‚Ä…czenia asynchronicznego */ + int pid; /**< Numer procesu rozwiÄ…zujÄ…cego nazwÄ™ serwera */ + int port; /**< Port serwera */ + int seq; /**< Numer sekwencyjny ostatniej wiadomoĹ›ci */ + int last_pong; /**< Czas otrzymania ostatniej ramki utrzymaniowej */ + int last_event; /**< Czas otrzymania ostatniego pakietu */ + + struct gg_event *event; /**< Zdarzenie po wywoĹ‚aniu \c callback */ + + uint32_t proxy_addr; /**< Adres serwera poĹ›redniczÄ…cego */ + uint16_t proxy_port; /**< Port serwera poĹ›redniczÄ…cego */ + + uint32_t hub_addr; /**< Adres huba po rozwiÄ…zaniu nazwy */ + uint32_t server_addr; /**< Adres serwera otrzymany od huba */ + + uint32_t client_addr; /**< Adres gniazda dla poĹ‚Ä…czeĹ„ bezpoĹ›rednich do wersji Gadu-Gadu 6.x */ + uint16_t client_port; /**< Port gniazda dla poĹ‚Ä…czeĹ„ bezpoĹ›rednich do wersji Gadu-Gadu 6.x */ + + uint32_t external_addr; /**< Publiczny adres dla poĹ‚Ä…czeĹ„ bezpoĹ›rednich do wersji Gadu-Gadu 6.x */ + uint16_t external_port; /**< Publiczny port dla poĹ‚Ä…czeĹ„ bezpoĹ›rednich do wersji Gadu-Gadu 6.x */ + + uin_t uin; /**< WĹ‚asny numer Gadu-Gadu */ + char *password; /**< HasĹ‚o (zwalniane po uĹĽyciu) */ + + int initial_status; /**< PoczÄ…tkowy status */ + int status; /**< Aktualny status */ + + char *recv_buf; /**< Bufor na odbierany pakiety */ + int recv_done; /**< Liczba wczytanych bajtĂłw pakietu */ + int recv_left; /**< Liczba pozostaĹ‚ych do wczytania bajtĂłw pakietu */ + + int protocol_version; /**< Wersja protokoĹ‚u (bez flag) */ + char *client_version; /**< Wersja klienta */ + int last_sysmsg; /**< Numer ostatniej wiadomoĹ›ci systemowej */ + + char *initial_descr; /**< PoczÄ…tkowy opis statusu */ + + void *resolver; /**< Dane prywatne procesu lub wÄ…tku rozwiÄ…zujÄ…cego nazwÄ™ serwera */ + + char *header_buf; /**< Bufor na poczÄ…tek nagĹ‚Ăłwka pakietu */ + unsigned int header_done; /**< Liczba wczytanych bajtĂłw nagĹ‚Ăłwka pakietu */ + +#ifdef GG_CONFIG_MIRANDA + HSSL ssl; + int tls; /**< Flaga poĹ‚Ä…czenia szyfrowanego */ +#elif GG_CONFIG_HAVE_OPENSSL + SSL *ssl; /**< Struktura TLS */ + SSL_CTX *ssl_ctx; /**< Kontekst sesji TLS */ +#else + void *ssl; /**< Struktura TLS */ + void *ssl_ctx; /**< Kontekst sesji TLS */ +#endif + + int image_size; /**< Maksymalny rozmiar obsĹ‚ugiwanych obrazkĂłw w KiB */ + + char *userlist_reply; /**< Bufor z odbieranÄ… listÄ… kontaktĂłw */ + + int userlist_blocks; /**< Liczba części listy kontaktĂłw */ + + struct gg_image_queue *images; /**< Lista wczytywanych obrazkĂłw */ + + int hash_type; /**< Rodzaj funkcji skrĂłtu hasĹ‚a (\c GG_LOGIN_HASH_GG32 lub \c GG_LOGIN_HASH_SHA1) */ + + char *send_buf; /**< Bufor z danymi do wysĹ‚ania */ + int send_left; /**< Liczba bajtĂłw do wysĹ‚ania */ + + struct gg_dcc7 *dcc7_list; /**< Lista poĹ‚Ä…czeĹ„ bezpoĹ›rednich skojarzonych z sesjÄ… */ + + int soft_timeout; /**< Flaga mĂłwiÄ…ca, ĹĽe po przekroczeniu \c timeout naleĹĽy wywoĹ‚ać \c gg_watch_fd() */ + + int protocol_flags; /**< Flagi protokoĹ‚u */ + + gg_encoding_t encoding; /**< Rodzaj kodowania znakĂłw */ + + gg_resolver_t resolver_type; /**< SposĂłb rozwiÄ…zywania nazw serwerĂłw */ + int (*resolver_start)(SOCKET *fd, void **private_data, const char *hostname); /**< Funkcja rozpoczynajÄ…ca rozwiÄ…zywanie nazwy */ + void (*resolver_cleanup)(void **private_data, int force); /**< Funkcja zwalniajÄ…ca zasoby po rozwiÄ…zaniu nazwy */ + + int protocol_features; /**< Opcje protokoĹ‚u */ + int status_flags; /**< Flagi statusu */ +}; + +/** + * PoĹ‚Ä…czenie HTTP. + * + * Tworzone przez \c gg_http_connect(), zwalniane przez \c gg_http_free(). + * + * \ingroup http + */ +struct gg_http { + gg_common_head(struct gg_http) + + int async; /**< Flaga poĹ‚Ä…czenia asynchronicznego */ + int pid; /**< Identyfikator procesu rozwiÄ…zujÄ…cego nazwÄ™ serwera */ + int port; /**< Port */ + + char *query; /**< Zapytanie HTTP */ + char *header; /**< Odebrany nagĹ‚Ăłwek */ + int header_size; /**< Rozmiar wczytanego nagĹ‚Ăłwka */ + char *body; /**< Odebrana strona */ + unsigned int body_size; /**< Rozmiar strony */ + + void *data; /**< Dane prywatne usĹ‚ugi HTTP */ + + char *user_data; /**< Dane prywatne uĹĽytkownika (nie sÄ… zwalniane) */ + + void *resolver; /**< Dane prywatne procesu lub wÄ…tku rozwiÄ…zujÄ…cego nazwÄ™ */ + + unsigned int body_done; /**< Liczba odebranych bajtĂłw strony */ + + gg_resolver_t resolver_type; /**< SposĂłb rozwiÄ…zywania nazw serwerĂłw */ + int (*resolver_start)(SOCKET *fd, void **private_data, const char *hostname); /**< Funkcja rozpoczynajÄ…ca rozwiÄ…zywanie nazwy */ + void (*resolver_cleanup)(void **private_data, int force); /**< Funkcja zwalniajÄ…ca zasoby po rozwiÄ…zaniu nazwy */ +}; + +/** \cond ignore */ + +#ifdef __GNUC__ +#define GG_PACKED __attribute__ ((packed)) +#ifndef GG_IGNORE_DEPRECATED +#define GG_DEPRECATED __attribute__ ((deprecated)) +#else +#define GG_DEPRECATED +#endif +#else +#define GG_PACKED +#define GG_DEPRECATED +#endif + +/** \endcond */ + +#define GG_MAX_PATH 276 /**< Maksymalny rozmiar nazwy pliku w strukturze \c gg_file_info */ + +/** + * Odpowiednik struktury WIN32_FIND_DATA z API WIN32. + * + * Wykorzystywana przy poĹ‚Ä…czeniach bezpoĹ›rednich do wersji Gadu-Gadu 6.x. + */ +struct gg_file_info { + uint32_t mode; /**< dwFileAttributes */ + uint32_t ctime[2]; /**< ftCreationTime */ + uint32_t atime[2]; /**< ftLastAccessTime */ + uint32_t mtime[2]; /**< ftLastWriteTime */ + uint32_t size_hi; /**< nFileSizeHigh */ + uint32_t size; /**< nFileSizeLow */ + uint32_t reserved0; /**< dwReserved0 */ + uint32_t reserved1; /**< dwReserved1 */ + unsigned char filename[GG_MAX_PATH - 14]; /**< cFileName */ + unsigned char short_filename[14]; /**< cAlternateFileName */ +} /** \cond ignore */ GG_PACKED /** \endcond */; + +/** + * PoĹ‚Ä…czenie bezpoĹ›rednie do wersji Gadu-Gadu 6.x. + * + * Tworzone przez \c gg_dcc_socket_create(), \c gg_dcc_get_file(), + * \c gg_dcc_send_file() lub \c gg_dcc_voice_chat(), zwalniane przez + * \c gg_dcc_free(). + * + * \ingroup dcc6 + */ +struct gg_dcc { + gg_common_head(struct gg_dcc) + + struct gg_event *event; /**< Zdarzenie po wywoĹ‚aniu \c callback */ + + int active; /**< Flaga poĹ‚Ä…czenia aktywnego (nieuĹĽywana) */ + int port; /**< Port gniazda nasĹ‚uchujÄ…cego */ + uin_t uin; /**< WĹ‚asny numer Gadu-Gadu */ + uin_t peer_uin; /**< Numer Gadu-Gadu drugiej strony poĹ‚Ä…czenia */ + int file_fd; /**< deskryptor pliku */ + unsigned int offset; /**< PoĹ‚oĹĽenie w pliku */ + unsigned int chunk_size; + /**< Rozmiar kawaĹ‚ka pliku */ + unsigned int chunk_offset; + /**< PoĹ‚oĹĽenie w aktualnym kawaĹ‚ku pliku */ + struct gg_file_info file_info; + /**< Informacje o pliku */ + int established; /**< Flaga ustanowienia poĹ‚Ä…czenia */ + char *voice_buf; /**< Bufor na pakiet poĹ‚Ä…czenia gĹ‚osowego */ + int incoming; /**< Flaga poĹ‚Ä…czenia przychodzÄ…cego */ + char *chunk_buf; /**< Bufor na fragment danych */ + uint32_t remote_addr; /**< Adres drugiej strony */ + uint16_t remote_port; /**< Port drugiej strony */ + +#ifdef GG_CONFIG_MIRANDA + void *contact; + char *folder; + uint32_t tick; +#endif +}; + +#define GG_DCC7_HASH_LEN 20 /**< Maksymalny rozmiar skrĂłtu pliku w poĹ‚Ä…czeniach bezpoĹ›renich */ +#define GG_DCC7_FILENAME_LEN 255 /**< Maksymalny rozmiar nazwy pliku w poĹ‚Ä…czeniach bezpoĹ›rednich */ +#define GG_DCC7_INFO_LEN 32 /**< Maksymalny rozmiar informacji o poĹ‚Ä…czeniach bezpoĹ›rednich */ +#define GG_DCC7_INFO_HASH_LEN 32 /**< Maksymalny rozmiar skrĂłtu ip informacji o poĹ‚Ä…czeniach bezpoĹ›rednich */ + +/** + * PoĹ‚Ä…czenie bezpoĹ›rednie od wersji Gadu-Gadu 7.x. + * + * \ingroup dcc7 + */ +struct gg_dcc7 { + gg_common_head(struct gg_dcc7) + + gg_dcc7_id_t cid; /**< Identyfikator poĹ‚Ä…czenia */ + + struct gg_event *event; /**< Struktura zdarzenia */ + + uin_t uin; /**< WĹ‚asny numer Gadu-Gadu */ + uin_t peer_uin; /**< Numer Gadu-Gadu drugiej strony poĹ‚Ä…czenia */ + + int file_fd; /**< Deskryptor przesyĹ‚anego pliku */ + unsigned int offset; /**< Aktualne poĹ‚oĹĽenie w przesyĹ‚anym pliku */ + unsigned int size; /**< Rozmiar przesyĹ‚anego pliku */ + unsigned char filename[GG_DCC7_FILENAME_LEN + 1]; + /**< Nazwa przesyĹ‚anego pliku */ + unsigned char hash[GG_DCC7_HASH_LEN]; + /**< SkrĂłt SHA1 przesyĹ‚anego pliku */ + + int dcc_type; /**< Rodzaj poĹ‚Ä…czenia bezpoĹ›redniego */ + int established; /**< Flaga ustanowienia poĹ‚Ä…czenia */ + int incoming; /**< Flaga poĹ‚Ä…czenia przychodzÄ…cego */ + int reverse; /**< Flaga poĹ‚Ä…czenia zwrotnego */ + + uint32_t local_addr; /**< Adres lokalny */ + uint16_t local_port; /**< Port lokalny */ + + uint32_t remote_addr; /**< Adres drugiej strony */ + uint16_t remote_port; /**< Port drugiej strony */ + + struct gg_session *sess; + /**< Sesja do ktĂłrej przypisano poĹ‚Ä…czenie */ + struct gg_dcc7 *next; /**< NastÄ™pne poĹ‚Ä…czenie w liĹ›cie */ + + int soft_timeout; /**< Flaga mĂłwiÄ…ca, ĹĽe po przekroczeniu \c timeout naleĹĽy wywoĹ‚ać \c gg_dcc7_watch_fd() */ + int seek; /**< Flaga mĂłwiÄ…ca, ĹĽe moĹĽna zmieniać poĹ‚oĹĽenie w wysyĹ‚anym pliku */ + + void *resolver; /**< Dane prywatne procesu lub wÄ…tku rozwiÄ…zujÄ…cego nazwÄ™ serwera */ + + int relay; /**< Flaga mĂłwiÄ…ca, ĹĽe laczymy sie przez serwer */ + int relay_index; /**< Numer serwera poĹ›redniczÄ…cego, do ktĂłrego siÄ™ Ĺ‚Ä…czymy */ + int relay_count; /**< Rozmiar listy serwerĂłw poĹ›redniczÄ…cych */ + struct gg_dcc7_relay *relay_list; /**< Lista serwerĂłw poĹ›redniczÄ…cych */ + +#ifdef GG_CONFIG_MIRANDA + void *contact; + char *folder; + uint32_t tick; +#endif +}; + +/** + * Rodzaj sesji. + */ +enum gg_session_t { + GG_SESSION_GG = 1, /**< PoĹ‚Ä…czenie z serwerem Gadu-Gadu */ + GG_SESSION_HTTP, /**< PoĹ‚Ä…czenie HTTP */ + GG_SESSION_SEARCH, /**< Wyszukiwanie w katalogu publicznym (nieaktualne) */ + GG_SESSION_REGISTER, /**< Rejestracja nowego konta */ + GG_SESSION_REMIND, /**< Przypominanie hasĹ‚a */ + GG_SESSION_PASSWD, /**< Zmiana hasĹ‚a */ + GG_SESSION_CHANGE, /**< Zmiana informacji w katalogu publicznym (nieaktualne) */ + GG_SESSION_DCC, /**< PoĹ‚Ä…czenie bezpoĹ›rednie (do wersji 6.x) */ + GG_SESSION_DCC_SOCKET, /**< Gniazdo nasĹ‚uchujÄ…ce (do wersji 6.x) */ + GG_SESSION_DCC_SEND, /**< WysyĹ‚anie pliku (do wersji 6.x) */ + GG_SESSION_DCC_GET, /**< Odbieranie pliku (do wersji 6.x) */ + GG_SESSION_DCC_VOICE, /**< Rozmowa gĹ‚osowa (do wersji 6.x) */ + GG_SESSION_USERLIST_GET, /**< Import listy kontaktĂłw z serwera (nieaktualne) */ + GG_SESSION_USERLIST_PUT, /**< Eksport listy kontaktĂłw do serwera (nieaktualne) */ + GG_SESSION_UNREGISTER, /**< Usuwanie konta */ + GG_SESSION_USERLIST_REMOVE, /**< Usuwanie listy kontaktĂłw z serwera (nieaktualne) */ + GG_SESSION_TOKEN, /**< Pobieranie tokenu */ + GG_SESSION_DCC7_SOCKET, /**< Gniazdo nasĹ‚uchujÄ…ce (od wersji 7.x) */ + GG_SESSION_DCC7_SEND, /**< WysyĹ‚anie pliku (od wersji 7.x) */ + GG_SESSION_DCC7_GET, /**< Odbieranie pliku (od wersji 7.x) */ + GG_SESSION_DCC7_VOICE, /**< Rozmowa gĹ‚osowa (od wersji 7.x) */ + + GG_SESSION_USER0 = 256, /**< Rodzaj zadeklarowany dla uĹĽytkownika */ + GG_SESSION_USER1, /**< Rodzaj zadeklarowany dla uĹĽytkownika */ + GG_SESSION_USER2, /**< Rodzaj zadeklarowany dla uĹĽytkownika */ + GG_SESSION_USER3, /**< Rodzaj zadeklarowany dla uĹĽytkownika */ + GG_SESSION_USER4, /**< Rodzaj zadeklarowany dla uĹĽytkownika */ + GG_SESSION_USER5, /**< Rodzaj zadeklarowany dla uĹĽytkownika */ + GG_SESSION_USER6, /**< Rodzaj zadeklarowany dla uĹĽytkownika */ + GG_SESSION_USER7 /**< Rodzaj zadeklarowany dla uĹĽytkownika */ +}; + +/** + * Aktualny stan sesji. + */ +enum gg_state_t { + /* wspĂłlne */ + GG_STATE_IDLE = 0, /**< Nie dzieje siÄ™ nic */ + GG_STATE_RESOLVING, /**< Oczekiwanie na rozwiÄ…zanie nazwy serwera */ + GG_STATE_CONNECTING, /**< Oczekiwanie na poĹ‚Ä…czenie */ + GG_STATE_READING_DATA, /**< Oczekiwanie na dane */ + GG_STATE_ERROR, /**< Kod bĹ‚Ä™du w polu \c error */ + + /* gg_session */ + GG_STATE_CONNECTING_HUB, /**< Oczekiwanie na poĹ‚Ä…czenie z hubem */ + GG_STATE_CONNECTING_GG, /**< Oczekiwanie na poĹ‚Ä…czenie z serwerem */ + GG_STATE_READING_KEY, /**< Oczekiwanie na klucz */ + GG_STATE_READING_REPLY, /**< Oczekiwanie na odpowiedĹş serwera */ + GG_STATE_CONNECTED, /**< PoĹ‚Ä…czono z serwerem */ + + /* gg_http */ + GG_STATE_SENDING_QUERY, /**< WysĹ‚ano zapytanie HTTP */ + GG_STATE_READING_HEADER, /**< Oczekiwanie na nagĹ‚Ăłwek HTTP */ + GG_STATE_PARSING, /**< Przetwarzanie danych */ + GG_STATE_DONE, /**< PoĹ‚Ä…czenie zakoĹ„czone */ + + /* gg_dcc */ + GG_STATE_LISTENING, /* czeka na poĹ‚Ä…czenia */ + GG_STATE_READING_UIN_1, /* czeka na uin peera */ + GG_STATE_READING_UIN_2, /* czeka na swĂłj uin */ + GG_STATE_SENDING_ACK, /* wysyĹ‚a potwierdzenie dcc */ + GG_STATE_READING_ACK, /* czeka na potwierdzenie dcc */ + GG_STATE_READING_REQUEST, /* czeka na komendÄ™ */ + GG_STATE_SENDING_REQUEST, /* wysyĹ‚a komendÄ™ */ + GG_STATE_SENDING_FILE_INFO, /* wysyĹ‚a informacje o pliku */ + GG_STATE_READING_PRE_FILE_INFO, /* czeka na pakiet przed file_info */ + GG_STATE_READING_FILE_INFO, /* czeka na informacje o pliku */ + GG_STATE_SENDING_FILE_ACK, /* wysyĹ‚a potwierdzenie pliku */ + GG_STATE_READING_FILE_ACK, /* czeka na potwierdzenie pliku */ + GG_STATE_SENDING_FILE_HEADER, /* wysyĹ‚a nagĹ‚Ăłwek pliku */ + GG_STATE_READING_FILE_HEADER, /* czeka na nagĹ‚Ăłwek */ + GG_STATE_GETTING_FILE, /* odbiera plik */ + GG_STATE_SENDING_FILE, /* wysyĹ‚a plik */ + GG_STATE_READING_VOICE_ACK, /* czeka na potwierdzenie voip */ + GG_STATE_READING_VOICE_HEADER, /* czeka na rodzaj bloku voip */ + GG_STATE_READING_VOICE_SIZE, /* czeka na rozmiar bloku voip */ + GG_STATE_READING_VOICE_DATA, /* czeka na dane voip */ + GG_STATE_SENDING_VOICE_ACK, /* wysyĹ‚a potwierdzenie voip */ + GG_STATE_SENDING_VOICE_REQUEST, /* wysyĹ‚a ĹĽÄ…danie voip */ + GG_STATE_READING_TYPE, /* czeka na typ poĹ‚Ä…czenia */ + + /* nowe. bez sensu jest to API. */ + GG_STATE_TLS_NEGOTIATION, /**< Negocjacja poĹ‚Ä…czenia szyfrowanego */ + + GG_STATE_REQUESTING_ID, /**< Oczekiwanie na nadanie identyfikatora poĹ‚Ä…czenia bezpoĹ›redniego */ + GG_STATE_WAITING_FOR_ACCEPT, /**< Oczekiwanie na potwierdzenie lub odrzucenie poĹ‚Ä…czenia bezpoĹ›redniego */ + GG_STATE_WAITING_FOR_INFO, /**< Oczekiwanie na informacje o poĹ‚Ä…czeniu bezpoĹ›rednim */ + + GG_STATE_READING_ID, /**< Odebranie identyfikatora poĹ‚Ä…czenia bezpoĹ›redniego */ + GG_STATE_SENDING_ID, /**< WysĹ‚ano identyfikator poĹ‚Ä…czenia bezpoĹ›redniego */ + GG_STATE_RESOLVING_GG, /**< Oczekiwanie na rozwiÄ…zanie nazwy serwera Gadu-Gadu */ + + GG_STATE_RESOLVING_RELAY, /**< Oczekiwanie na rozwiÄ…zanie nazwy serwera poĹ›redniczÄ…cego */ + GG_STATE_CONNECTING_RELAY, /**< Oczekiwanie na poĹ‚Ä…czenie z serwerem poĹ›redniczÄ…cym */ + GG_STATE_READING_RELAY /**< Odbieranie danych */ +}; + +/** + * Informacja o tym, czy biblioteka chce zapisywać i/lub czytać + * z deskryptora. Maska bitowa. + * + * \ingroup events + */ +enum gg_check_t { + GG_CHECK_NONE = 0, /**< Nie sprawdzaj niczego */ + GG_CHECK_WRITE = 1, /**< SprawdĹş moĹĽliwość zapisu */ + GG_CHECK_READ = 2 /**< SprawdĹş moĹĽliwość odczytu */ +}; + +/** + * Parametry poĹ‚Ä…czenia z serwerem Gadu-Gadu. Parametry zostaĹ‚y przeniesione + * do struktury, by uniknąć zmian API po rozszerzeniu protokoĹ‚u i dodaniu + * kolejnych opcji poĹ‚Ä…czenia. Część parametrĂłw, ktĂłre nie sÄ… juĹĽ aktualne + * lub nie majÄ… znaczenia, zostaĹ‚a usuniÄ™ta z dokumentacji. + * + * \ingroup login + */ +struct gg_login_params { + uin_t uin; /**< Numer Gadu-Gadu */ + char *password; /**< HasĹ‚o */ + int async; /**< Flaga asynchronicznego poĹ‚Ä…czenia (domyĹ›lnie nie) */ + int status; /**< PoczÄ…tkowy status uĹĽytkownika (domyĹ›lnie \c GG_STATUS_AVAIL) */ + char *status_descr; /**< PoczÄ…tkowy opis uĹĽytkownika (domyĹ›lnie brak) */ + uint32_t server_addr; /**< Adres serwera Gadu-Gadu (domyĹ›lnie pobierany automatycznie) */ + uint16_t server_port; /**< Port serwera Gadu-Gadu (domyĹ›lnie pobierany automatycznie) */ +#ifndef DOXYGEN + uint32_t client_addr; /**< Adres poĹ‚Ä…czeĹ„ bezpoĹ›rednich (nieaktualne) */ + uint16_t client_port; /**< Port poĹ‚Ä…czeĹ„ bezpoĹ›rednich (nieaktualne) */ +#endif + int protocol_version; /**< Wersja protokoĹ‚u wysyĹ‚ana do serwera (domyĹ›lnie najnowsza obsĹ‚ugiwana) */ + char *client_version; /**< Wersja klienta wysyĹ‚ana do serwera (domyĹ›lnie najnowsza znana) */ + int has_audio; /**< Flaga obsĹ‚ugi poĹ‚Ä…czeĹ„ gĹ‚osowych */ + int last_sysmsg; /**< Numer ostatnio odebranej wiadomoĹ›ci systemowej */ + uint32_t external_addr; /**< Adres publiczny dla poĹ‚Ä…czeĹ„ bezpoĹ›rednich (6.x) */ + uint16_t external_port; /**< Port publiczny dla poĹ‚Ä…czeĹ„ bezpoĹ›rednich (6.x) */ +#ifndef DOXYGEN + int tls; /**< Flaga poĹ‚Ä…czenia szyfrowanego (nieaktualna) */ +#endif + int image_size; /**< Maksymalny rozmiar obsĹ‚ugiwanych obrazkĂłw w kilobajtach */ +#ifndef DOXYGEN + int era_omnix; /**< Flaga udawania klienta Era Omnix (nieaktualna) */ +#endif + int hash_type; /**< Rodzaj skrĂłtu hasĹ‚a (\c GG_LOGIN_HASH_GG32 lub \c GG_LOGIN_HASH_SHA1, domyĹ›lnie SHA1) */ + gg_encoding_t encoding; /**< Rodzaj kodowania uĹĽywanego w sesji (domyĹ›lnie CP1250) */ + gg_resolver_t resolver; /**< SposĂłb rozwiÄ…zywania nazw (patrz \ref build-resolver) */ + int protocol_features; /**< Opcje protokoĹ‚u (flagi GG_FEATURE_*). */ + int status_flags; /**< Flagi statusu (flagi GG_STATUS_FLAG_*, patrz \ref status). */ + +#ifndef DOXYGEN + char dummy[1 * sizeof(int)]; /**< \internal Miejsce na kilka kolejnych + parametrĂłw, ĹĽeby wraz z dodawaniem kolejnych + parametrĂłw nie zmieniaĹ‚ siÄ™ rozmiar struktury */ +#endif + +}; + +#ifdef GG_CONFIG_MIRANDA +struct gg_session *gg_login(const struct gg_login_params *p, SOCKET *gg_sock, int *gg_failno); +#else +struct gg_session *gg_login(const struct gg_login_params *p); +#endif +void gg_free_session(struct gg_session *sess); +void gg_logoff(struct gg_session *sess); +int gg_change_status(struct gg_session *sess, int status); +int gg_change_status_descr(struct gg_session *sess, int status, const char *descr); +int gg_change_status_descr_time(struct gg_session *sess, int status, const char *descr, int time); +int gg_change_status_flags(struct gg_session *sess, int flags); +int gg_send_message(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message); +int gg_send_message_richtext(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, const unsigned char *format, int formatlen); +int gg_send_message_confer(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message); +int gg_send_message_confer_richtext(struct gg_session *sess, int msgclass, int recipients_count, uin_t *recipients, const unsigned char *message, const unsigned char *format, int formatlen); +int gg_send_message_ctcp(struct gg_session *sess, int msgclass, uin_t recipient, const unsigned char *message, int message_len); +int gg_ping(struct gg_session *sess); +int gg_userlist_request(struct gg_session *sess, char type, const char *request); +int gg_image_request(struct gg_session *sess, uin_t recipient, int size, uint32_t crc32); +int gg_image_reply(struct gg_session *sess, uin_t recipient, const TCHAR *filename, const char *image, int size); +int gg_typing_notification(struct gg_session *sess, uin_t recipient, int length); + +uint32_t gg_crc32(uint32_t crc, const unsigned char *buf, int len); + +int gg_session_set_resolver(struct gg_session *gs, gg_resolver_t type); +gg_resolver_t gg_session_get_resolver(struct gg_session *gs); +int gg_session_set_custom_resolver(struct gg_session *gs, int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int)); + +int gg_http_set_resolver(struct gg_http *gh, gg_resolver_t type); +gg_resolver_t gg_http_get_resolver(struct gg_http *gh); +int gg_http_set_custom_resolver(struct gg_http *gh, int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int)); + +int gg_global_set_resolver(gg_resolver_t type); +gg_resolver_t gg_global_get_resolver(void); +int gg_global_set_custom_resolver(int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int)); + +int gg_multilogon_disconnect(struct gg_session *gs, gg_multilogon_id_t conn_id); + +/** + * Rodzaj zdarzenia. + * + * \ingroup events + */ +enum gg_event_t { + GG_EVENT_NONE = 0, /**< Nie wydarzyĹ‚o siÄ™ nic wartego uwagi */ + GG_EVENT_MSG, /**< \brief Otrzymano wiadomość. Przekazuje rĂłwnieĹĽ wiadomoĹ›ci systemowe od numeru 0. */ + GG_EVENT_NOTIFY, /**< \brief Informacja o statusach osĂłb z listy kontaktĂłw (przed 6.0). Zdarzenie naleĹĽy obsĹ‚ugiwać, jeĹ›li planuje siÄ™ uĹĽywać protokoĹ‚u w wersji starszej niĹĽ domyĹ›lna. Ostatni element tablicy zawiera uin rĂłwny 0, a pozostaĹ‚e pola sÄ… niezainicjowane. */ + GG_EVENT_NOTIFY_DESCR, /**< \brief Informacja o statusie opisowym osoby z listy kontaktĂłw (przed 6.0). Zdarzenie naleĹĽy obsĹ‚ugiwać, jeĹ›li planuje siÄ™ uĹĽywać protokoĹ‚u w wersji starszej niĹĽ domyĹ›lna. */ + GG_EVENT_STATUS, /**< \brief Zmiana statusu osoby z listy kontaktĂłw (przed 6.0). Zdarzenie naleĹĽy obsĹ‚ugiwać, jeĹ›li planuje siÄ™ uĹĽywać protokoĹ‚u w wersji starszej niĹĽ domyĹ›lna. */ + GG_EVENT_ACK, /**< Potwierdzenie dorÄ™czenia wiadomoĹ›ci */ + GG_EVENT_PONG, /**< \brief Utrzymanie poĹ‚Ä…czenia. Obecnie serwer nie wysyĹ‚a juĹĽ do klienta ramek utrzymania poĹ‚Ä…czenia, polega wyĹ‚Ä…cznie na wysyĹ‚aniu ramek przez klienta. */ + GG_EVENT_CONN_FAILED, /**< \brief Nie udaĹ‚o siÄ™ poĹ‚Ä…czyć */ + GG_EVENT_CONN_SUCCESS, /**< \brief PoĹ‚Ä…czono z serwerem. PierwszÄ… rzeczÄ…, jakÄ… naleĹĽy zrobić jest wysĹ‚anie listy kontaktĂłw. */ + GG_EVENT_DISCONNECT, /**< \brief Serwer zrywa poĹ‚Ä…czenie. Zdarza siÄ™, gdy rĂłwnolegle do serwera podĹ‚Ä…czy siÄ™ druga sesja i trzeba zerwać poĹ‚Ä…czenie z pierwszÄ…. */ + + GG_EVENT_DCC_NEW, /**< Nowe poĹ‚Ä…czenie bezpoĹ›rednie (6.x) */ + GG_EVENT_DCC_ERROR, /**< BĹ‚Ä…d poĹ‚Ä…czenia bezpoĹ›redniego (6.x) */ + GG_EVENT_DCC_DONE, /**< ZakoĹ„czono poĹ‚Ä…czenie bezpoĹ›rednie (6.x) */ + GG_EVENT_DCC_CLIENT_ACCEPT, /**< Moment akceptacji klienta w poĹ‚Ä…czeniu bezpoĹ›rednim (6.x) */ + GG_EVENT_DCC_CALLBACK, /**< Zwrotne poĹ‚Ä…czenie bezpoĹ›rednie (6.x) */ + GG_EVENT_DCC_NEED_FILE_INFO, /**< NaleĹĽy wypeĹ‚nić \c file_info dla poĹ‚Ä…czenia bezpoĹ›redniego (6.x) */ + GG_EVENT_DCC_NEED_FILE_ACK, /**< Czeka na potwierdzenie pliku w poĹ‚Ä…czeniu bezpoĹ›rednim (6.x) */ + GG_EVENT_DCC_NEED_VOICE_ACK, /**< Czeka na potwierdzenie rozmowy w poĹ‚Ä…czeniu bezpoĹ›rednim (6.x) */ + GG_EVENT_DCC_VOICE_DATA, /**< Dane bezpoĹ›redniego poĹ‚Ä…czenia gĹ‚osowego (6.x) */ + + GG_EVENT_PUBDIR50_SEARCH_REPLY, /**< OdpowiedĹş katalogu publicznego */ + GG_EVENT_PUBDIR50_READ, /**< Odczytano wĹ‚asne dane z katalogu publicznego */ + GG_EVENT_PUBDIR50_WRITE, /**< Zmieniono wĹ‚asne dane w katalogu publicznym */ + + GG_EVENT_STATUS60, /**< Zmiana statusu osoby z listy kontaktĂłw */ + GG_EVENT_NOTIFY60, /**< Informacja o statusach osĂłb z listy kontaktĂłw. Ostatni element tablicy zawiera uin rĂłwny 0, a pozostaĹ‚e pola sÄ… niezainicjowane. */ + GG_EVENT_USERLIST, /**< Wynik importu lub eksportu listy kontaktĂłw */ + GG_EVENT_IMAGE_REQUEST, /**< Żądanie przesĹ‚ania obrazka z wiadomoĹ›ci */ + GG_EVENT_IMAGE_REPLY, /**< PrzysĹ‚ano obrazek z wiadomoĹ›ci */ + GG_EVENT_DCC_ACK, /**< Potwierdzenie transmisji w poĹ‚Ä…czeniu bezpoĹ›rednim (6.x) */ + + GG_EVENT_DCC7_NEW, /**< Nowe poĹ‚Ä…czenie bezpoĹ›rednie (7.x) */ + GG_EVENT_DCC7_ACCEPT, /**< Zaakceptowano poĹ‚Ä…czenie bezpoĹ›rednie (7.x), nowy deskryptor */ + GG_EVENT_DCC7_REJECT, /**< Odrzucono poĹ‚Ä…czenie bezpoĹ›rednie (7.x) */ + GG_EVENT_DCC7_CONNECTED, /**< Zestawiono poĹ‚Ä…czenie bezpoĹ›rednie (7.x), nowy deskryptor */ + GG_EVENT_DCC7_ERROR, /**< BĹ‚Ä…d poĹ‚Ä…czenia bezpoĹ›redniego (7.x) */ + GG_EVENT_DCC7_DONE, /**< ZakoĹ„czono poĹ‚Ä…czenie bezpoĹ›rednie (7.x) */ + GG_EVENT_DCC7_PENDING, /**< Trwa prĂłba poĹ‚Ä…czenia bezpoĹ›redniego (7.x), nowy deskryptor */ + + GG_EVENT_XML_EVENT, /**< Otrzymano komunikat systemowy (7.7) */ + GG_EVENT_DISCONNECT_ACK, /**< \brief Potwierdzenie zakoĹ„czenia sesji. Informuje o tym, ĹĽe zmiana stanu na niedostÄ™pny z opisem dotarĹ‚a do serwera i moĹĽna zakoĹ„czyć poĹ‚Ä…czenie TCP. */ + GG_EVENT_XML_ACTION, + GG_EVENT_TYPING_NOTIFICATION, /**< Powiadomienie o pisaniu */ + GG_EVENT_USER_DATA, /**< Informacja o kontaktach */ + GG_EVENT_MULTILOGON_MSG, /**< Wiadomość wysĹ‚ana z innej sesji multilogowania */ + GG_EVENT_MULTILOGON_INFO /**< Informacja o innych sesjach multilogowania */ +}; + +#define GG_EVENT_SEARCH50_REPLY GG_EVENT_PUBDIR50_SEARCH_REPLY + +/** + * PowĂłd nieudanego poĹ‚Ä…czenia. + */ +enum gg_failure_t { + GG_FAILURE_RESOLVING = 1, /**< Nie znaleziono serwera */ + GG_FAILURE_CONNECTING, /**< BĹ‚Ä…d poĹ‚Ä…czenia */ + GG_FAILURE_INVALID, /**< Serwer zwrĂłciĹ‚ nieprawidĹ‚owe dane */ + GG_FAILURE_READING, /**< Zerwano poĹ‚Ä…czenie podczas odczytu */ + GG_FAILURE_WRITING, /**< Zerwano poĹ‚Ä…czenie podczas zapisu */ + GG_FAILURE_PASSWORD, /**< NieprawidĹ‚owe hasĹ‚o */ + GG_FAILURE_404, /**< NieuĹĽywane */ + GG_FAILURE_TLS, /**< BĹ‚Ä…d negocjacji szyfrowanego poĹ‚Ä…czenia */ + GG_FAILURE_NEED_EMAIL, /**< Serwer rozĹ‚Ä…czyĹ‚ nas z proĹ›bÄ… o zmianÄ™ adresu e-mail */ + GG_FAILURE_INTRUDER, /**< Zbyt wiele prĂłb poĹ‚Ä…czenia z nieprawidĹ‚owym hasĹ‚em */ + GG_FAILURE_UNAVAILABLE /**< Serwery sÄ… wyĹ‚Ä…czone */ +}; + +/** + * Kod bĹ‚Ä™du danej operacji. + * + * Nie zawiera przesadnie szczegółowych informacji o powodach bĹ‚Ä™dĂłw, by nie + * komplikować ich obsĹ‚ugi. JeĹ›li wymagana jest wiÄ™ksza dokĹ‚adność, naleĹĽy + * sprawdzić zawartość zmiennej systemowej \c errno. + */ +enum gg_error_t { + GG_ERROR_RESOLVING = 1, /**< Nie znaleziono hosta */ + GG_ERROR_CONNECTING, /**< BĹ‚Ä…d poĹ‚Ä…czenia */ + GG_ERROR_READING, /**< BĹ‚Ä…d odczytu/odbierania */ + GG_ERROR_WRITING, /**< BĹ‚Ä…d zapisu/wysyĹ‚ania */ + + GG_ERROR_DCC_HANDSHAKE, /**< BĹ‚Ä…d negocjacji */ + GG_ERROR_DCC_FILE, /**< BĹ‚Ä…d odczytu/zapisu pliku */ + GG_ERROR_DCC_EOF, /**< Przedwczesny koniec pliku */ + GG_ERROR_DCC_NET, /**< BĹ‚Ä…d wysyĹ‚ania/odbierania */ + GG_ERROR_DCC_REFUSED, /**< PoĹ‚Ä…czenie odrzucone */ + + GG_ERROR_DCC7_HANDSHAKE, /**< BĹ‚Ä…d negocjacji */ + GG_ERROR_DCC7_FILE, /**< BĹ‚Ä…d odczytu/zapisu pliku */ + GG_ERROR_DCC7_EOF, /**< Przedwczesny koniec pliku */ + GG_ERROR_DCC7_NET, /**< BĹ‚Ä…d wysyĹ‚ania/odbierania */ + GG_ERROR_DCC7_REFUSED, /**< PoĹ‚Ä…czenie odrzucone */ + GG_ERROR_DCC7_RELAY /**< Problem z serwerem poĹ›redniczÄ…cym */ +}; + +/** + * Pole zapytania lub odpowiedzi katalogu publicznego. + */ +struct gg_pubdir50_entry { + int num; /**< Numer wyniku */ + char *field; /**< Nazwa pola */ + char *value; /**< Wartość pola */ +} /* GG_DEPRECATED */; + +/** + * Zapytanie lub odpowiedĹş katalogu publicznego. + * + * Patrz \c gg_pubdir50_t. + */ +struct gg_pubdir50_s { + int count; /**< Liczba wynikĂłw odpowiedzi */ + uin_t next; /**< Numer poczÄ…tkowy nastÄ™pnego zapytania */ + int type; /**< Rodzaj zapytania */ + uint32_t seq; /**< Numer sekwencyjny */ + struct gg_pubdir50_entry *entries; /**< Pola zapytania lub odpowiedzi */ + int entries_count; /**< Liczba pĂłl */ +} /* GG_DEPRECATED */; + +/** + * Zapytanie lub odpowiedĹş katalogu publicznego. + * + * Do pĂłl nie naleĹĽy siÄ™ odwoĹ‚ywać bezpoĹ›rednio -- wszystkie niezbÄ™dne + * informacje sÄ… dostÄ™pne za pomocÄ… funkcji \c gg_pubdir50_* + */ +typedef struct gg_pubdir50_s *gg_pubdir50_t; + +/** + * Opis zdarzeĹ„ \c GG_EVENT_MSG i \c GG_EVENT_MULTILOGON_MSG. + */ +struct gg_event_msg { + uin_t sender; /**< Numer nadawcy/odbiorcy */ + int msgclass; /**< Klasa wiadomoĹ›ci */ + time_t time; /**< Czas nadania */ + char *message; /**< Treść wiadomoĹ›ci */ + + int recipients_count; /**< Liczba odbiorcĂłw konferencji */ + uin_t *recipients; /**< Odbiorcy konferencji */ + + int formats_length; /**< DĹ‚ugość informacji o formatowaniu tekstu */ + void *formats; /**< Informacje o formatowaniu tekstu */ + uint32_t seq; /**< Numer sekwencyjny wiadomoĹ›ci */ + + char *xhtml_message; /**< Treść wiadomoĹ›ci w formacie XHTML (moĹĽe być rĂłwne \c NULL, jeĹ›li wiadomość nie zawiera treĹ›ci XHTML) */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_NOTIFY_DESCR. + */ +struct gg_event_notify_descr { + struct gg_notify_reply *notify; /**< Informacje o liĹ›cie kontaktĂłw */ + char *descr; /**< Opis status */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_STATUS. + */ +struct gg_event_status { + uin_t uin; /**< Numer Gadu-Gadu */ + uint32_t status; /**< Nowy status */ + char *descr; /**< Opis */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_STATUS60. + */ +struct gg_event_status60 { + uin_t uin; /**< Numer Gadu-Gadu */ + int status; /**< Nowy status */ + uint32_t remote_ip; /**< Adres IP dla poĹ‚Ä…czeĹ„ bezpoĹ›rednich */ + uint16_t remote_port; /**< Port dla poĹ‚Ä…czeĹ„ bezpoĹ›rednich */ + int version; /**< Wersja protokoĹ‚u */ + int image_size; /**< Maksymalny rozmiar obsĹ‚ugiwanych obrazkĂłw w KiB */ + char *descr; /**< Opis statusu */ + time_t time; /**< Czas powrotu */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_NOTIFY_REPLY60. + */ +struct gg_event_notify60 { + uin_t uin; /**< Numer Gadu-Gadu. W ostatnim elemencie jest rĂłwny 0, a pozostaĹ‚e pola sÄ… niezainicjowane. */ + int status; /**< Nowy status */ + uint32_t remote_ip; /**< Adres IP dla poĹ‚Ä…czeĹ„ bezpoĹ›rednich */ + uint16_t remote_port; /**< Port dla poĹ‚Ä…czeĹ„ bezpoĹ›rednich */ + int version; /**< Wersja protokoĹ‚u */ + int image_size; /**< Maksymalny rozmiar obsĹ‚ugiwanych obrazkĂłw w KiB */ + char *descr; /**< Opis statusu */ + time_t time; /**< Czas powrotu */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_ACK. + */ +struct gg_event_ack { + uin_t recipient; /**< Numer odbiorcy */ + int status; /**< Status dorÄ™czenia */ + int seq; /**< Numer sekwencyjny wiadomoĹ›ci */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_USERLIST. + */ +struct gg_event_userlist { + char type; /**< Rodzaj odpowiedzi */ + char *reply; /**< Treść odpowiedzi */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_DCC_VOICE_DATA. + */ +struct gg_event_dcc_voice_data { + uint8_t *data; /**< Dane dĹşwiÄ™kowe */ + int length; /**< Rozmiar danych dĹşwiÄ™kowych */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_IMAGE_REQUEST. + */ +struct gg_event_image_request { + uin_t sender; /**< Nadawca ĹĽÄ…dania */ + uint32_t size; /**< Rozmiar obrazka */ + uint32_t crc32; /**< Suma kontrolna CRC32 */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_IMAGE_REPLY. + */ +struct gg_event_image_reply { + uin_t sender; /**< Nadawca obrazka */ + uint32_t size; /**< Rozmiar obrazka */ + uint32_t crc32; /**< Suma kontrolna CRC32 */ + char *filename; /**< Nazwa pliku */ + char *image; /**< Bufor z obrazkiem */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_XML_EVENT. + */ +struct gg_event_xml_event { + char *data; /**< Bufor z komunikatem */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_XML_ACTION. + */ +struct gg_event_xml_action { + char *data; /**< Bufor z komunikatem */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_DCC7_CONNECTED. + */ +struct gg_event_dcc7_connected { + struct gg_dcc7 *dcc7; /**< Struktura poĹ‚Ä…czenia */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_DCC7_PENDING. + */ +struct gg_event_dcc7_pending { + struct gg_dcc7 *dcc7; /**< Struktura poĹ‚Ä…czenia */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_DCC7_REJECT. + */ +struct gg_event_dcc7_reject { + struct gg_dcc7 *dcc7; /**< Struktura poĹ‚Ä…czenia */ + int reason; /**< powĂłd odrzucenia */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_DCC7_ACCEPT. + */ +struct gg_event_dcc7_accept { + struct gg_dcc7 *dcc7; /**< Struktura poĹ‚Ä…czenia */ + int type; /**< SposĂłb poĹ‚Ä…czenia (P2P, przez serwer) */ + uint32_t remote_ip; /**< Adres zdalnego klienta */ + uint16_t remote_port; /**< Port zdalnego klienta */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_DCC7_DONE. + */ +struct gg_event_dcc7_done { + struct gg_dcc7 *dcc7; /**< Struktura poĹ‚Ä…czenia */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_DCC7_ERROR. + * + * \note Odwrotna kolejność pĂłl ma na celu zachowanie ABI. + */ +struct gg_event_dcc7_error { + enum gg_error_t error; /**< Kod bĹ‚Ä™du */ + struct gg_dcc7 *dcc7; /**< Struktura poĹ‚Ä…czenia */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_TYPING_NOTIFICATION. + */ +struct gg_event_typing_notification { + uin_t uin; /**< Numer rozmĂłwcy */ + int length; /**< DĹ‚ugość tekstu */ +}; + +/** + * Atrybut uĹĽytkownika. + */ +struct gg_event_user_data_attr { + int type; /**< Typ atrybutu */ + char *key; /**< Klucz */ + char *value; /**< Wartość */ +}; + +/** + * Struktura opisujÄ…ca kontakt w zdarzeniu GG_EVENT_USER_DATA. + */ +struct gg_event_user_data_user { + uin_t uin; /**< Numer kontaktu */ + size_t attr_count; /**< Liczba atrybutĂłw */ + struct gg_event_user_data_attr *attrs; /**< Lista atrybutĂłw */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_USER_DATA. + */ +struct gg_event_user_data { + int type; /**< Rodzaj informacji o kontaktach */ + size_t user_count; /**< Liczba kontaktĂłw */ + struct gg_event_user_data_user *users; /**< Lista kontaktĂłw */ +}; + +/** + * Struktura opisujÄ…ca sesjÄ™ multilogowania. + */ +struct gg_multilogon_session { + gg_multilogon_id_t id; /**< Identyfikator sesji */ + char *name; /**< Nazwa sesji (podana w \c gg_login_params.client_version) */ + uint32_t remote_addr; /**< Adres sesji */ + int status_flags; /**< Flagi statusu sesji */ + int protocol_features; /**< Opcje protokolu sesji */ + time_t logon_time; /**< Czas zalogowania */ +}; + +/** + * Opis zdarzenia \c GG_EVENT_MULTILOGON_INFO. + */ +struct gg_event_multilogon_info { + int count; /**< Liczba sesji */ + struct gg_multilogon_session *sessions; /** Lista sesji */ +}; + +/** + * Unia wszystkich zdarzeĹ„ zwracanych przez funkcje \c gg_watch_fd(), + * \c gg_dcc_watch_fd() i \c gg_dcc7_watch_fd(). + * + * \ingroup events + */ +union gg_event_union { + enum gg_failure_t failure; /**< BĹ‚Ä…d poĹ‚Ä…czenia (\c GG_EVENT_CONN_FAILED) */ + struct gg_notify_reply *notify; /**< Zmiana statusu kontaktĂłw (\c GG_EVENT_NOTIFY) */ + struct gg_event_notify_descr notify_descr; /**< Zmiana statusu kontaktĂłw (\c GG_EVENT_NOTIFY_DESCR) */ + struct gg_event_status status; /**< Zmiana statusu kontaktĂłw (\c GG_EVENT_STATUS) */ + struct gg_event_status60 status60; /**< Zmiana statusu kontaktĂłw (\c GG_EVENT_STATUS60) */ + struct gg_event_notify60 *notify60; /**< Zmiana statusu kontaktĂłw (\c GG_EVENT_NOTIFY60) */ + struct gg_event_msg msg; /**< Otrzymano wiadomość (\c GG_EVENT_MSG) */ + struct gg_event_ack ack; /**< Potwierdzenie wiadomoĹ›ci (\c GG_EVENT_ACK) */ + struct gg_event_image_request image_request; /**< Żądanie wysĹ‚ania obrazka (\c GG_EVENT_IMAGE_REQUEST) */ + struct gg_event_image_reply image_reply; /**< OdpowiedĹş z obrazkiem (\c GG_EVENT_IMAGE_REPLY) */ + struct gg_event_userlist userlist; /**< OdpowiedĹş listy kontaktĂłw (\c GG_EVENT_USERLIST) */ + gg_pubdir50_t pubdir50; /**< OdpowiedĹş katalogu publicznego (\c GG_EVENT_PUBDIR50_*) */ + struct gg_event_xml_event xml_event; /**< Zdarzenie systemowe (\c GG_EVENT_XML_EVENT) */ + struct gg_event_xml_action xml_action; /**< Zdarzenie XML (\c GG_EVENT_XML_ACTION) */ + struct gg_dcc *dcc_new; /**< Nowe poĹ‚Ä…czenie bezpoĹ›rednie (\c GG_EVENT_DCC_NEW) */ + enum gg_error_t dcc_error; /**< BĹ‚Ä…d poĹ‚Ä…czenia bezpoĹ›redniego (\c GG_EVENT_DCC_ERROR) */ + struct gg_event_dcc_voice_data dcc_voice_data; /**< Dane poĹ‚Ä…czenia gĹ‚osowego (\c GG_EVENT_DCC_VOICE_DATA) */ + struct gg_dcc7 *dcc7_new; /**< Nowe poĹ‚Ä…czenie bezpoĹ›rednie (\c GG_EVENT_DCC7_NEW) */ + enum gg_error_t dcc7_error; /**< BĹ‚Ä…d poĹ‚Ä…czenia bezpoĹ›redniego (\c GG_EVENT_DCC7_ERROR) */ + struct gg_event_dcc7_error dcc7_error_ex; /**< BĹ‚Ä…d poĹ‚Ä…czenia bezpoĹ›redniego ze wskaĹşnikiem na strukturÄ™ poĹ‚Ä…czenia (\c GG_EVENT_DCC7_ERROR) */ + struct gg_event_dcc7_connected dcc7_connected; /**< Informacja o zestawieniu poĹ‚Ä…czenia bezpoĹ›redniego (\c GG_EVENT_DCC7_CONNECTED) */ + struct gg_event_dcc7_pending dcc7_pending; /**< Trwa prĂłba poĹ‚Ä…czenia bezpoĹ›redniego (\c GG_EVENT_DCC7_PENDING) */ + struct gg_event_dcc7_reject dcc7_reject; /**< Odrzucono poĹ‚Ä…czenia bezpoĹ›redniego (\c GG_EVENT_DCC7_REJECT) */ + struct gg_event_dcc7_accept dcc7_accept; /**< Zaakceptowano poĹ‚Ä…czenie bezpoĹ›rednie (\c GG_EVENT_DCC7_ACCEPT) */ + struct gg_event_dcc7_done dcc7_done; /**< ZakoĹ„czono poĹ‚Ä…czenie bezpoĹ›rednie (\c GG_EVENT_DCC7_DONE) */ + struct gg_event_typing_notification typing_notification; /**< Powiadomienie o pisaniu (\c GG_EVENT_TYPING_NOTIFICATION) */ + struct gg_event_user_data user_data; /**< Informacje o kontaktach */ + struct gg_event_msg multilogon_msg; /**< Inna sesja wysĹ‚aĹ‚a wiadomość (\c GG_EVENT_MULTILOGON_MSG) */ + struct gg_event_multilogon_info multilogon_info; /**< Informacja o innych sesjach multilogowania (\c GG_EVENT_MULTILOGON_INFO) */ +}; + +/** + * Opis zdarzenia. + * + * Zwracany przez funkcje \c gg_watch_fd(), \c gg_dcc_watch_fd() + * i \c gg_dcc7_watch_fd(). Po przeanalizowaniu naleĹĽy zwolnić + * za pomocÄ… \c gg_event_free(). + * + * \ingroup events + */ +struct gg_event { + int type; /**< Rodzaj zdarzenia */ + union gg_event_union event; /**< Informacja o zdarzeniu */ +}; + +struct gg_event *gg_watch_fd(struct gg_session *sess); +void gg_event_free(struct gg_event *e); + +int gg_notify_ex(struct gg_session *sess, uin_t *userlist, char *types, int count); +int gg_notify(struct gg_session *sess, uin_t *userlist, int count); +int gg_add_notify_ex(struct gg_session *sess, uin_t uin, char type); +int gg_add_notify(struct gg_session *sess, uin_t uin); +int gg_remove_notify_ex(struct gg_session *sess, uin_t uin, char type); +int gg_remove_notify(struct gg_session *sess, uin_t uin); + +struct gg_http *gg_http_connect(const char *hostname, int port, int async, const char *method, const char *path, const char *header); +int gg_http_watch_fd(struct gg_http *h); +void gg_http_stop(struct gg_http *h); +void gg_http_free(struct gg_http *h); + +uint32_t gg_pubdir50(struct gg_session *sess, gg_pubdir50_t req); +gg_pubdir50_t gg_pubdir50_new(int type); +int gg_pubdir50_add(gg_pubdir50_t req, const char *field, const char *value); +int gg_pubdir50_seq_set(gg_pubdir50_t req, uint32_t seq); +const char *gg_pubdir50_get(gg_pubdir50_t res, int num, const char *field); +int gg_pubdir50_type(gg_pubdir50_t res); +int gg_pubdir50_count(gg_pubdir50_t res); +uin_t gg_pubdir50_next(gg_pubdir50_t res); +uint32_t gg_pubdir50_seq(gg_pubdir50_t res); +void gg_pubdir50_free(gg_pubdir50_t res); + +#ifndef DOXYGEN + +#define GG_PUBDIR50_UIN "FmNumber" +#define GG_PUBDIR50_STATUS "FmStatus" +#define GG_PUBDIR50_FIRSTNAME "firstname" +#define GG_PUBDIR50_LASTNAME "lastname" +#define GG_PUBDIR50_NICKNAME "nickname" +#define GG_PUBDIR50_BIRTHYEAR "birthyear" +#define GG_PUBDIR50_CITY "city" +#define GG_PUBDIR50_GENDER "gender" +#define GG_PUBDIR50_GENDER_FEMALE "1" +#define GG_PUBDIR50_GENDER_MALE "2" +#define GG_PUBDIR50_GENDER_SET_FEMALE "2" +#define GG_PUBDIR50_GENDER_SET_MALE "1" +#define GG_PUBDIR50_ACTIVE "ActiveOnly" +#define GG_PUBDIR50_ACTIVE_TRUE "1" +#define GG_PUBDIR50_START "fmstart" +#define GG_PUBDIR50_FAMILYNAME "familyname" +#define GG_PUBDIR50_FAMILYCITY "familycity" + +#else + +/** + * \ingroup pubdir50 + * + * Rodzaj pola zapytania. + */ +enum { + GG_PUBDIR50_UIN, /**< Numer Gadu-Gadu */ + GG_PUBDIR50_STATUS, /**< Status (tylko wynik wyszukiwania) */ + GG_PUBDIR50_FIRSTNAME, /**< ImiÄ™ */ + GG_PUBDIR50_LASTNAME, /**< Nazwisko */ + GG_PUBDIR50_NICKNAME, /**< Pseudonim */ + GG_PUBDIR50_BIRTHYEAR, /**< Rok urodzenia lub przedziaĹ‚ lat oddzielony spacjÄ… */ + GG_PUBDIR50_CITY, /**< Miejscowość */ + GG_PUBDIR50_GENDER, /**< PĹ‚eć */ + GG_PUBDIR50_ACTIVE, /**< Osoba dostÄ™pna (tylko wyszukiwanie) */ + GG_PUBDIR50_START, /**< Numer poczÄ…tkowy wyszukiwania (tylko wyszukiwanie) */ + GG_PUBDIR50_FAMILYNAME, /**< Nazwisko rodowe (tylko wysyĹ‚anie informacji o sobie) */ + GG_PUBDIR50_FAMILYCITY, /**< Miejscowość pochodzenia (tylko wysyĹ‚anie informacji o sobie) */ +}; + +/** + * \ingroup pubdir50 + * + * Wartość pola GG_PUBDIR50_GENDER przy wyszukiwaniu. Brak pola oznacza dowolnÄ… pĹ‚eć. + */ +enum { + GG_PUBDIR50_GENDER_FEMALE, /**< Kobieta */ + GG_PUBDIR50_GENDER_MALE, /**< Mężczyzna */ +}; + +/** + * \ingroup pubdir50 + * + * Wartość pola GG_PUBDIR50_GENDER przy wysyĹ‚aniu informacji o sobie. + */ +enum { + GG_PUBDIR50_GENDER_SET_FEMALE, /**< Kobieta */ + GG_PUBDIR50_GENDER_SET_MALE, /**< Mężczyzna */ +}; + +/** + * \ingroup pubdir50 + * + * Wartość pola GG_PUBDIR50_ACTIVE. + */ +enum { + GG_PUBDIR50_ACTIVE_TRUE, /**< Wyszukaj tylko osoby dostÄ™pne */ +}; + +#endif /* DOXYGEN */ + +/** + * Wynik operacji na katalogu publicznym. + * + * \ingroup http + */ +struct gg_pubdir { + int success; /**< Flaga powodzenia operacji */ + uin_t uin; /**< Otrzymany numer lub 0 w przypadku bĹ‚Ä™du */ +}; + +int gg_pubdir_watch_fd(struct gg_http *f); +void gg_pubdir_free(struct gg_http *f); + +/** + * Token autoryzacji niektĂłrych operacji HTTP. + * + * \ingroup token + */ +struct gg_token { + int width; /**< Szerokość obrazka */ + int height; /**< Wysokość obrazka */ + int length; /**< Liczba znakĂłw w tokenie */ + char *tokenid; /**< Identyfikator tokenu */ +}; + +struct gg_http *gg_token(int async); +int gg_token_watch_fd(struct gg_http *h); +void gg_token_free(struct gg_http *h); + +struct gg_http *gg_register3(const char *email, const char *password, const char *tokenid, const char *tokenval, int async); +#ifndef DOXYGEN +#define gg_register_watch_fd gg_pubdir_watch_fd +#define gg_register_free gg_pubdir_free +#endif + +struct gg_http *gg_unregister3(uin_t uin, const char *password, const char *tokenid, const char *tokenval, int async); +#ifndef DOXYGEN +#define gg_unregister_watch_fd gg_pubdir_watch_fd +#define gg_unregister_free gg_pubdir_free +#endif + +struct gg_http *gg_remind_passwd3(uin_t uin, const char *email, const char *tokenid, const char *tokenval, int async); +#ifndef DOXYGEN +#define gg_remind_passwd_watch_fd gg_pubdir_watch_fd +#define gg_remind_passwd_free gg_pubdir_free +#endif + +struct gg_http *gg_change_passwd4(uin_t uin, const char *email, const char *passwd, const char *newpasswd, const char *tokenid, const char *tokenval, int async); +#ifndef DOXYGEN +#define gg_change_passwd_watch_fd gg_pubdir_watch_fd +#define gg_change_passwd_free gg_pubdir_free +#endif + +extern int gg_dcc_port; +extern unsigned long gg_dcc_ip; + +int gg_dcc_request(struct gg_session *sess, uin_t uin); + +struct gg_dcc *gg_dcc_send_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin); +struct gg_dcc *gg_dcc_get_file(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin); +struct gg_dcc *gg_dcc_voice_chat(uint32_t ip, uint16_t port, uin_t my_uin, uin_t peer_uin); +void gg_dcc_set_type(struct gg_dcc *d, int type); +int gg_dcc_fill_file_info(struct gg_dcc *d, const char *filename); +int gg_dcc_fill_file_info2(struct gg_dcc *d, const char *filename, const char *local_filename); +int gg_dcc_voice_send(struct gg_dcc *d, char *buf, int length); + +#define GG_DCC_VOICE_FRAME_LENGTH 195 /**< Rozmiar pakietu gĹ‚osowego przed wersjÄ… Gadu-Gadu 5.0.5 */ +#define GG_DCC_VOICE_FRAME_LENGTH_505 326 /**< Rozmiar pakietu gĹ‚osowego od wersji Gadu-Gadu 5.0.5 */ + +struct gg_dcc *gg_dcc_socket_create(uin_t uin, uint16_t port); +#ifndef DOXYGEN +#define gg_dcc_socket_free gg_dcc_free +#define gg_dcc_socket_watch_fd gg_dcc_watch_fd +#endif + +struct gg_event *gg_dcc_watch_fd(struct gg_dcc *d); + +void gg_dcc_free(struct gg_dcc *c); + +struct gg_event *gg_dcc7_watch_fd(struct gg_dcc7 *d); +struct gg_dcc7 *gg_dcc7_send_file(struct gg_session *sess, uin_t rcpt, const char *filename, const char *filename1250, const char *hash); +struct gg_dcc7 *gg_dcc7_send_file_fd(struct gg_session *sess, uin_t rcpt, int fd, size_t size, const char *filename1250, const char *hash); +int gg_dcc7_accept(struct gg_dcc7 *dcc, unsigned int offset); +int gg_dcc7_reject(struct gg_dcc7 *dcc, int reason); +int gg_dcc7_abort(struct gg_dcc7 *dcc); +void gg_dcc7_free(struct gg_dcc7 *d); + +extern int gg_debug_level; + +extern void (*gg_debug_handler)(int level, const char *format, va_list ap); +extern void (*gg_debug_handler_session)(struct gg_session *sess, int level, const char *format, va_list ap); + +extern FILE *gg_debug_file; + +/** + * \ingroup debug + * @{ + */ +#define GG_DEBUG_NET 1 /**< Rejestracja zdarzeĹ„ zwiÄ…zanych z sieciÄ… */ +#define GG_DEBUG_TRAFFIC 2 /**< Rejestracja ruchu sieciowego */ +#define GG_DEBUG_DUMP 4 /**< Rejestracja zawartoĹ›ci pakietĂłw */ +#define GG_DEBUG_FUNCTION 8 /**< Rejestracja wywoĹ‚aĹ„ funkcji */ +#define GG_DEBUG_MISC 16 /**< Rejestracja różnych informacji */ +/** @} */ + +#ifdef GG_DEBUG_DISABLE +#define gg_debug(x, y...) do { } while(0) +#define gg_debug_session(z, x, y...) do { } while(0) +#else +void gg_debug(int level, const char *format, ...); +void gg_debug_session(struct gg_session *sess, int level, const char *format, ...); +#endif + +const char *gg_libgadu_version(void); + +extern int gg_proxy_enabled; +extern char *gg_proxy_host; +extern int gg_proxy_port; +extern char *gg_proxy_username; +extern char *gg_proxy_password; +extern int gg_proxy_http_only; + +extern unsigned long gg_local_ip; + +#define GG_LOGIN_HASH_GG32 0x01 /**< Algorytm Gadu-Gadu */ +#define GG_LOGIN_HASH_SHA1 0x02 /**< Algorytm SHA1 */ + +#ifndef DOXYGEN + +#define GG_PUBDIR50_WRITE 0x01 +#define GG_PUBDIR50_READ 0x02 +#define GG_PUBDIR50_SEARCH 0x03 +#define GG_PUBDIR50_SEARCH_REQUEST GG_PUBDIR50_SEARCH +#define GG_PUBDIR50_SEARCH_REPLY 0x05 + +#else + +/** + * \ingroup pubdir50 + * + * Rodzaj zapytania lub odpowiedzi katalogu publicznego. + */ +enum { + GG_PUBDIR50_WRITE, /**< WysĹ‚anie do serwera informacji o sobie */ + GG_PUBDIR50_READ, /**< Pobranie z serwera informacji o sobie */ + GG_PUBDIR50_SEARCH, /**< Wyszukiwanie w katalogu publicznym */ + GG_PUBDIR50_SEARCH_REPLY, /**< Wynik wyszukiwania w katalogu publicznym */ +}; + +#endif /* DOXYGEN */ + +/** \cond obsolete */ + +#define gg_free_event gg_event_free +#define gg_free_http gg_http_free +#define gg_free_pubdir gg_pubdir_free +#define gg_free_register gg_pubdir_free +#define gg_free_remind_passwd gg_pubdir_free +#define gg_free_dcc gg_dcc_free +#define gg_free_change_passwd gg_pubdir_free + +struct gg_search_request { + int active; + unsigned int start; + char *nickname; + char *first_name; + char *last_name; + char *city; + int gender; + int min_birth; + int max_birth; + char *email; + char *phone; + uin_t uin; +} /* GG_DEPRECATED */; + +struct gg_search { + int count; + struct gg_search_result *results; +} GG_DEPRECATED; + +struct gg_search_result { + uin_t uin; + char *first_name; + char *last_name; + char *nickname; + int born; + int gender; + char *city; + int active; +} GG_DEPRECATED; + +#define GG_GENDER_NONE 0 +#define GG_GENDER_FEMALE 1 +#define GG_GENDER_MALE 2 + +struct gg_http *gg_search(const struct gg_search_request *r, int async) GG_DEPRECATED; +int gg_search_watch_fd(struct gg_http *f) GG_DEPRECATED; +void gg_free_search(struct gg_http *f) GG_DEPRECATED; +#define gg_search_free gg_free_search + +const struct gg_search_request *gg_search_request_mode_0(char *nickname, char *first_name, char *last_name, char *city, int gender, int min_birth, int max_birth, int active, int start) GG_DEPRECATED; +const struct gg_search_request *gg_search_request_mode_1(char *email, int active, int start) GG_DEPRECATED; +const struct gg_search_request *gg_search_request_mode_2(char *phone, int active, int start) GG_DEPRECATED; +const struct gg_search_request *gg_search_request_mode_3(uin_t uin, int active, int start) GG_DEPRECATED; +void gg_search_request_free(struct gg_search_request *r) GG_DEPRECATED; + +struct gg_http *gg_register(const char *email, const char *password, int async) GG_DEPRECATED; +struct gg_http *gg_register2(const char *email, const char *password, const char *qa, int async) GG_DEPRECATED; + +struct gg_http *gg_unregister(uin_t uin, const char *password, const char *email, int async) GG_DEPRECATED; +struct gg_http *gg_unregister2(uin_t uin, const char *password, const char *qa, int async) GG_DEPRECATED; + +struct gg_http *gg_remind_passwd(uin_t uin, int async) GG_DEPRECATED; +struct gg_http *gg_remind_passwd2(uin_t uin, const char *tokenid, const char *tokenval, int async) GG_DEPRECATED; + +struct gg_http *gg_change_passwd(uin_t uin, const char *passwd, const char *newpasswd, const char *newemail, int async) GG_DEPRECATED; +struct gg_http *gg_change_passwd2(uin_t uin, const char *passwd, const char *newpasswd, const char *email, const char *newemail, int async) GG_DEPRECATED; +struct gg_http *gg_change_passwd3(uin_t uin, const char *passwd, const char *newpasswd, const char *qa, int async) GG_DEPRECATED; + +struct gg_change_info_request { + char *first_name; + char *last_name; + char *nickname; + char *email; + int born; + int gender; + char *city; +} /* GG_DEPRECATED */; + +struct gg_change_info_request *gg_change_info_request_new(const char *first_name, const char *last_name, const char *nickname, const char *email, int born, int gender, const char *city) GG_DEPRECATED; +void gg_change_info_request_free(struct gg_change_info_request *r) GG_DEPRECATED; + +struct gg_http *gg_change_info(uin_t uin, const char *passwd, const struct gg_change_info_request *request, int async) GG_DEPRECATED; +#define gg_change_pubdir_watch_fd gg_pubdir_watch_fd +#define gg_change_pubdir_free gg_pubdir_free +#define gg_free_change_pubdir gg_pubdir_free + +struct gg_http *gg_userlist_get(uin_t uin, const char *password, int async) GG_DEPRECATED; +int gg_userlist_get_watch_fd(struct gg_http *f) GG_DEPRECATED; +void gg_userlist_get_free(struct gg_http *f) GG_DEPRECATED; + +struct gg_http *gg_userlist_put(uin_t uin, const char *password, const char *contacts, int async) GG_DEPRECATED; +int gg_userlist_put_watch_fd(struct gg_http *f) GG_DEPRECATED; +void gg_userlist_put_free(struct gg_http *f) GG_DEPRECATED; + +struct gg_http *gg_userlist_remove(uin_t uin, const char *password, int async) GG_DEPRECATED; +int gg_userlist_remove_watch_fd(struct gg_http *f) GG_DEPRECATED; +void gg_userlist_remove_free(struct gg_http *f) GG_DEPRECATED; + +int gg_pubdir50_handle_reply(struct gg_event *e, const char *packet, int length) GG_DEPRECATED; + +/** \endcond */ + +int gg_file_hash_sha1(int fd, uint8_t *result) GG_DEPRECATED; + +#ifdef __GNUC__ +char *gg_saprintf(const char *format, ...) __attribute__ ((format (printf, 1, 2))) GG_DEPRECATED; +#else +char *gg_saprintf(const char *format, ...) GG_DEPRECATED; +#endif + +char *gg_vsaprintf(const char *format, va_list ap) GG_DEPRECATED; + +#define gg_alloc_sprintf gg_saprintf + +char *gg_get_line(char **ptr) GG_DEPRECATED; + +SOCKET gg_connect(void *addr, int port, int async) GG_DEPRECATED; +#ifdef GG_CONFIG_MIRANDA +SOCKET gg_connect_internal(void *addr, int port, int async, SOCKET *gg_sock); +#endif +struct in_addr *gg_gethostbyname(const char *hostname) GG_DEPRECATED; +char *gg_read_line(SOCKET sock, char *buf, int length) GG_DEPRECATED; +void gg_chomp(char *line) GG_DEPRECATED; +char *gg_urlencode(const char *str) GG_DEPRECATED; +int gg_http_hash(const char *format, ...) GG_DEPRECATED; +void gg_http_free_fields(struct gg_http *h) GG_DEPRECATED; +int gg_read(struct gg_session *sess, char *buf, int length) GG_DEPRECATED; +int gg_write(struct gg_session *sess, const char *buf, int length) GG_DEPRECATED; +void *gg_recv_packet(struct gg_session *sess) GG_DEPRECATED; +int gg_send_packet(struct gg_session *sess, int type, ...) GG_DEPRECATED; +unsigned int gg_login_hash(const unsigned char *password, unsigned int seed) GG_DEPRECATED; +void gg_login_hash_sha1(const char *password, uint32_t seed, uint8_t *result) GG_DEPRECATED; +uint32_t gg_fix32(uint32_t x); +uint16_t gg_fix16(uint16_t x); +#define fix16 gg_fix16 +#define fix32 gg_fix32 +char *gg_proxy_auth(void) GG_DEPRECATED; +char *gg_base64_encode(const char *buf) GG_DEPRECATED; +char *gg_base64_decode(const char *buf) GG_DEPRECATED; +int gg_image_queue_remove(struct gg_session *s, struct gg_image_queue *q, int freeq) GG_DEPRECATED; + +/** + * Kolejka odbieranych obrazkĂłw. + */ +struct gg_image_queue { + uin_t sender; /**< Nadawca obrazka */ + uint32_t size; /**< Rozmiar obrazka */ + uint32_t crc32; /**< Suma kontrolna CRC32 */ + char *filename; /**< Nazwa pliku */ + char *image; /**< Bufor z odebranymi danymi */ + uint32_t done; /**< Rozmiar odebranych danych */ + + struct gg_image_queue *next; /**< Kolejny element listy */ +} GG_DEPRECATED; + +int gg_dcc7_handle_id(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED; +int gg_dcc7_handle_new(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED; +int gg_dcc7_handle_info(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED; +int gg_dcc7_handle_accept(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED; +int gg_dcc7_handle_reject(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED; +int gg_dcc7_handle_abort(struct gg_session *sess, struct gg_event *e, void *payload, int len) GG_DEPRECATED; + +#define GG_APPMSG_HOST "appmsg.gadu-gadu.pl" +#define GG_APPMSG_PORT 80 +#define GG_PUBDIR_HOST "pubdir.gadu-gadu.pl" +#define GG_PUBDIR_PORT 80 +#define GG_REGISTER_HOST "register.gadu-gadu.pl" +#define GG_REGISTER_PORT 80 +#define GG_REMIND_HOST "retr.gadu-gadu.pl" +#define GG_REMIND_PORT 80 +#define GG_RELAY_HOST "relay.gadu-gadu.pl" +#define GG_RELAY_PORT 80 + +#define GG_DEFAULT_PORT 8074 +#define GG_HTTPS_PORT 443 +#define GG_HTTP_USERAGENT "Mozilla/4.7 [en] (Win98; I)" + +#define GG_DEFAULT_CLIENT_VERSION "10.1.0.11070" +#define GG_DEFAULT_PROTOCOL_VERSION 0x2e +#define GG_DEFAULT_TIMEOUT 30 +#define GG_HAS_AUDIO_MASK 0x40000000 +#define GG_HAS_AUDIO7_MASK 0x20000000 +#define GG_ERA_OMNIX_MASK 0x04000000 +#define GG_LIBGADU_VERSION "1.10.0" + +#ifndef DOXYGEN + +#define GG_FEATURE_MSG77 0x0001 +#define GG_FEATURE_STATUS77 0x0002 +#define GG_FEATURE_UNKNOWN_4 0x0004 +#define GG_FEATURE_UNKNOWN_8 0x0008 +#define GG_FEATURE_DND_FFC 0x0010 +#define GG_FEATURE_IMAGE_DESCR 0x0020 +#define GG_FEATURE_UNKNOWN_40 0x0040 +#define GG_FEATURE_UNKNOWN_80 0x0080 +#define GG_FEATURE_UNKNOWN_100 0x0100 +#define GG_FEATURE_USER_DATA 0x0200 +#define GG_FEATURE_MSG_ACK 0x0400 +#define GG_FEATURE_UNKNOWN_800 0x0800 +#define GG_FEATURE_UNKNOWN_1000 0x1000 +#define GG_FEATURE_TYPING_NOTIFICATION 0x2000 +#define GG_FEATURE_MULTILOGON 0x4000 + +/* PoniĹĽsze makra zostaĹ‚y zachowane dla zgodnoĹ›ci API */ +#define GG_FEATURE_MSG80 0 +#define GG_FEATURE_STATUS80 0 +#define GG_FEATURE_STATUS80BETA 0 + +#define GG_FEATURE_ALL (GG_FEATURE_MSG80 | GG_FEATURE_STATUS80 | GG_FEATURE_DND_FFC | GG_FEATURE_IMAGE_DESCR | GG_FEATURE_UNKNOWN_100 | GG_FEATURE_USER_DATA | GG_FEATURE_MSG_ACK | GG_FEATURE_TYPING_NOTIFICATION) + +#else + +/** + * \ingroup login + * + * Flagi opcji protokoĹ‚u. + */ +enum { + GG_FEATURE_MSG77, /**< Klient ĹĽyczy sobie otrzymywać wiadomoĹ›ci zgodnie z protokoĹ‚em 7.7 */ + GG_FEATURE_STATUS77, /**< Klient ĹĽyczy sobie otrzymywać zmiany stanu zgodnie z protokoĹ‚em 7.7 */ + GG_FEATURE_DND_FFC, /**< Klient obsĹ‚uguje statusy "nie przeszkadzać" i "poGGadaj ze mnÄ…" */ + GG_FEATURE_IMAGE_DESCR, /**< Klient obsĹ‚uguje opisy graficzne oraz flagÄ™ \c GG_STATUS80_DESCR_MASK */ +}; + + +#endif + +#define GG_DEFAULT_DCC_PORT 1550 + +struct gg_header { + uint32_t type; /* typ pakietu */ + uint32_t length; /* dĹ‚ugość reszty pakietu */ +} GG_PACKED; + +#define GG_WELCOME 0x0001 +#define GG_NEED_EMAIL 0x0014 + +struct gg_welcome { + uint32_t key; /* klucz szyfrowania hasĹ‚a */ +} GG_PACKED; + +#define GG_LOGIN 0x000c + +struct gg_login { + uint32_t uin; /* mĂłj numerek */ + uint32_t hash; /* hash hasĹ‚a */ + uint32_t status; /* status na dzieĹ„ dobry */ + uint32_t version; /* moja wersja klienta */ + uint32_t local_ip; /* mĂłj adres ip */ + uint16_t local_port; /* port, na ktĂłrym sĹ‚ucham */ +} GG_PACKED; + +#define GG_LOGIN_EXT 0x0013 + +struct gg_login_ext { + uint32_t uin; /* mĂłj numerek */ + uint32_t hash; /* hash hasĹ‚a */ + uint32_t status; /* status na dzieĹ„ dobry */ + uint32_t version; /* moja wersja klienta */ + uint32_t local_ip; /* mĂłj adres ip */ + uint16_t local_port; /* port, na ktĂłrym sĹ‚ucham */ + uint32_t external_ip; /* zewnÄ™trzny adres ip */ + uint16_t external_port; /* zewnÄ™trzny port */ +} GG_PACKED; + +#define GG_LOGIN60 0x0015 + +struct gg_login60 { + uint32_t uin; /* mĂłj numerek */ + uint32_t hash; /* hash hasĹ‚a */ + uint32_t status; /* status na dzieĹ„ dobry */ + uint32_t version; /* moja wersja klienta */ + uint8_t dunno1; /* 0x00 */ + uint32_t local_ip; /* mĂłj adres ip */ + uint16_t local_port; /* port, na ktĂłrym sĹ‚ucham */ + uint32_t external_ip; /* zewnÄ™trzny adres ip */ + uint16_t external_port; /* zewnÄ™trzny port */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno2; /* 0xbe */ +} GG_PACKED; + +#define GG_LOGIN70 0x0019 + +struct gg_login70 { + uint32_t uin; /* mĂłj numerek */ + uint8_t hash_type; /* rodzaj hashowania hasĹ‚a */ + uint8_t hash[64]; /* hash hasĹ‚a dopeĹ‚niony zerami */ + uint32_t status; /* status na dzieĹ„ dobry */ + uint32_t version; /* moja wersja klienta */ + uint8_t dunno1; /* 0x00 */ + uint32_t local_ip; /* mĂłj adres ip */ + uint16_t local_port; /* port, na ktĂłrym sĹ‚ucham */ + uint32_t external_ip; /* zewnÄ™trzny adres ip (???) */ + uint16_t external_port; /* zewnÄ™trzny port (???) */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno2; /* 0xbe */ +} GG_PACKED; + +#define GG_LOGIN_OK 0x0003 + +#define GG_LOGIN_FAILED 0x0009 + +#define GG_PUBDIR50_REQUEST 0x0014 + +struct gg_pubdir50_request { + uint8_t type; /* GG_PUBDIR50_* */ + uint32_t seq; /* czas wysĹ‚ania zapytania */ +} GG_PACKED; + +#define GG_PUBDIR50_REPLY 0x000e + +struct gg_pubdir50_reply { + uint8_t type; /* GG_PUBDIR50_* */ + uint32_t seq; /* czas wysĹ‚ania zapytania */ +} GG_PACKED; + +#define GG_NEW_STATUS 0x0002 + +#ifndef DOXYGEN + +#define GG_STATUS_NOT_AVAIL 0x0001 +#define GG_STATUS_NOT_AVAIL_DESCR 0x0015 +#define GG_STATUS_FFC 0x0017 +#define GG_STATUS_FFC_DESCR 0x0018 +#define GG_STATUS_AVAIL 0x0002 +#define GG_STATUS_AVAIL_DESCR 0x0004 +#define GG_STATUS_BUSY 0x0003 +#define GG_STATUS_BUSY_DESCR 0x0005 +#define GG_STATUS_DND 0x0021 +#define GG_STATUS_DND_DESCR 0x0022 +#define GG_STATUS_INVISIBLE 0x0014 +#define GG_STATUS_INVISIBLE_DESCR 0x0016 +#define GG_STATUS_BLOCKED 0x0006 + +#define GG_STATUS_IMAGE_MASK 0x0100 +#define GG_STATUS_DESCR_MASK 0x4000 +#define GG_STATUS_FRIENDS_MASK 0x8000 + +#define GG_STATUS_FLAG_UNKNOWN 0x00000001 +#define GG_STATUS_FLAG_VIDEO 0x00000002 +#define GG_STATUS_FLAG_MOBILE 0x00100000 +#define GG_STATUS_FLAG_SPAM 0x00800000 + +#else + +/** + * Rodzaje statusĂłw uĹĽytkownika. + * + * \ingroup status + */ +enum { + GG_STATUS_NOT_AVAIL, /**< NiedostÄ™pny */ + GG_STATUS_NOT_AVAIL_DESCR, /**< NiedostÄ™pny z opisem */ + GG_STATUS_FFC, /**< PoGGadaj ze mnÄ… */ + GG_STATUS_FFC_DESCR, /**< PoGGadaj ze mnÄ… z opisem */ + GG_STATUS_AVAIL, /**< DostÄ™pny */ + GG_STATUS_AVAIL_DESCR, /**< DostÄ™pny z opisem */ + GG_STATUS_BUSY, /**< ZajÄ™ty */ + GG_STATUS_BUSY_DESCR, /**< ZajÄ™ty z opisem */ + GG_STATUS_DND, /**< Nie przeszkadzać */ + GG_STATUS_DND_DESCR, /**< Nie przeszakdzać z opisem */ + GG_STATUS_INVISIBLE, /**< Niewidoczny (tylko wĹ‚asny status) */ + GG_STATUS_INVISIBLE_DESCR, /**< Niewidoczny z opisem (tylko wĹ‚asny status) */ + GG_STATUS_BLOCKED, /**< Zablokowany (tylko status innych) */ + GG_STATUS_IMAGE_MASK, /**< Flaga bitowa oznaczajÄ…ca opis graficzny (tylko jeĹ›li wybrano \c GG_FEATURE_IMAGE_DESCR) */ + GG_STATUS_DESCR_MASK, /**< Flaga bitowa oznaczajÄ…ca status z opisem (tylko jeĹ›li wybrano \c GG_FEATURE_IMAGE_DESCR) */ + GG_STATUS_FRIENDS_MASK, /**< Flaga bitowa dostÄ™pnoĹ›ci tylko dla znajomych */ +}; + +/** + * Rodzaje statusĂłw uĹĽytkownika. Mapa bitowa. + * + * \ingroup status + */ +enum { + GG_STATUS_FLAG_UNKNOWN, /**< Przeznaczenie nieznane, ale wystÄ™puje zawsze */ + GG_STATUS_FLAG_VIDEO, /**< Klient obsĹ‚uguje wideorozmowy */ + GG_STATUS_FLAG_MOBILE, /**< Klient mobilny (ikona telefonu komĂłrkowego) */ + GG_STATUS_FLAG_SPAM, /**< Klient chce otrzymywać linki od nieznajomych */ +}; + +#endif /* DOXYGEN */ + +/** + * \ingroup status + * + * Flaga bitowa dostepnosci informujaca ze mozemy voipowac + */ + +#define GG_STATUS_VOICE_MASK 0x20000 /**< czy ma wlaczone audio (7.7) */ + +/** + * \ingroup status + * + * Maksymalna dĹ‚ugoĹ›c opisu. + */ +#define GG_STATUS_DESCR_MAXSIZE 255 +#define GG_STATUS_DESCR_MAXSIZE_PRE_8_0 70 + +#define GG_STATUS_MASK 0xff + +/* GG_S_F() tryb tylko dla znajomych */ +#define GG_S_F(x) (((x) & GG_STATUS_FRIENDS_MASK) != 0) + +/* GG_S() stan bez uwzglÄ™dnienia dodatkowych flag */ +#define GG_S(x) ((x) & GG_STATUS_MASK) + + +/* GG_S_FF() chÄ™tny do rozmowy */ +#define GG_S_FF(x) (GG_S(x) == GG_STATUS_FFC || GG_S(x) == GG_STATUS_FFC_DESCR) + +/* GG_S_AV() dostÄ™pny */ +#define GG_S_AV(x) (GG_S(x) == GG_STATUS_AVAIL || GG_S(x) == GG_STATUS_AVAIL_DESCR) + +/* GG_S_AW() zaraz wracam */ +#define GG_S_AW(x) (GG_S(x) == GG_STATUS_BUSY || GG_S(x) == GG_STATUS_BUSY_DESCR) + +/* GG_S_DD() nie przeszkadzać */ +#define GG_S_DD(x) (GG_S(x) == GG_STATUS_DND || GG_S(x) == GG_STATUS_DND_DESCR) + +/* GG_S_NA() niedostÄ™pny */ +#define GG_S_NA(x) (GG_S(x) == GG_STATUS_NOT_AVAIL || GG_S(x) == GG_STATUS_NOT_AVAIL_DESCR) + +/* GG_S_I() niewidoczny */ +#define GG_S_I(x) (GG_S(x) == GG_STATUS_INVISIBLE || GG_S(x) == GG_STATUS_INVISIBLE_DESCR) + + +/* GG_S_A() dostÄ™pny lub chÄ™tny do rozmowy */ +#define GG_S_A(x) (GG_S_FF(x) || GG_S_AV(x)) + +/* GG_S_B() zajÄ™ty lub nie przeszkadzać */ +#define GG_S_B(x) (GG_S_AW(x) || GG_S_DD(x)) + + +/* GG_S_D() stan opisowy */ +#define GG_S_D(x) (GG_S(x) == GG_STATUS_NOT_AVAIL_DESCR || \ + GG_S(x) == GG_STATUS_FFC_DESCR || \ + GG_S(x) == GG_STATUS_AVAIL_DESCR || \ + GG_S(x) == GG_STATUS_BUSY_DESCR || \ + GG_S(x) == GG_STATUS_DND_DESCR || \ + GG_S(x) == GG_STATUS_INVISIBLE_DESCR) + +/* GG_S_BL() blokowany lub blokujÄ…cy */ +#define GG_S_BL(x) (GG_S(x) == GG_STATUS_BLOCKED) + +/** + * Zmiana statusu (pakiet \c GG_NEW_STATUS i \c GG_NEW_STATUS80BETA) + */ +struct gg_new_status { + uint32_t status; /**< Nowy status */ +} GG_PACKED; + +#define GG_NOTIFY_FIRST 0x000f +#define GG_NOTIFY_LAST 0x0010 + +#define GG_NOTIFY 0x0010 + +struct gg_notify { + uint32_t uin; /* numerek danej osoby */ + uint8_t dunno1; /* rodzaj wpisu w liĹ›cie */ +} GG_PACKED; + +#ifndef DOXYGEN + +#define GG_USER_OFFLINE 0x01 +#define GG_USER_NORMAL 0x03 +#define GG_USER_BLOCKED 0x04 + +#else + +/** + * \ingroup contacts + * + * Rodzaj kontaktu. + */ +enum { + GG_USER_NORMAL, /**< ZwykĹ‚y kontakt */ + GG_USER_BLOCKED, /**< Zablokowany */ + GG_USER_OFFLINE, /**< Niewidoczny dla kontaktu */ +}; + +#endif /* DOXYGEN */ + +#define GG_LIST_EMPTY 0x0012 + +#define GG_NOTIFY_REPLY 0x000c /* tak, to samo co GG_LOGIN */ + +struct gg_notify_reply { + uint32_t uin; /* numerek */ + uint32_t status; /* status danej osoby */ + uint32_t remote_ip; /* adres ip delikwenta */ + uint16_t remote_port; /* port, na ktĂłrym sĹ‚ucha klient */ + uint32_t version; /* wersja klienta */ + uint16_t dunno2; /* znowu port? */ +} GG_PACKED; + +#define GG_NOTIFY_REPLY60 0x0011 + +struct gg_notify_reply60 { + uint32_t uin; /* numerek plus flagi w MSB */ + uint8_t status; /* status danej osoby */ + uint32_t remote_ip; /* adres ip delikwenta */ + uint16_t remote_port; /* port, na ktĂłrym sĹ‚ucha klient */ + uint8_t version; /* wersja klienta */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno1; /* 0x00 */ +} GG_PACKED; + +#define GG_STATUS60 0x000f + +struct gg_status60 { + uint32_t uin; /* numerek plus flagi w MSB */ + uint8_t status; /* status danej osoby */ + uint32_t remote_ip; /* adres ip delikwenta */ + uint16_t remote_port; /* port, na ktĂłrym sĹ‚ucha klient */ + uint8_t version; /* wersja klienta */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno1; /* 0x00 */ +} GG_PACKED; + +#define GG_NOTIFY_REPLY77 0x0018 + +struct gg_notify_reply77 { + uint32_t uin; /* numerek plus flagi w MSB */ + uint8_t status; /* status danej osoby */ + uint32_t remote_ip; /* adres ip delikwenta */ + uint16_t remote_port; /* port, na ktĂłrym sĹ‚ucha klient */ + uint8_t version; /* wersja klienta */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno1; /* 0x00 */ + uint32_t dunno2; /* ? */ +} GG_PACKED; + +#define GG_STATUS77 0x0017 + +struct gg_status77 { + uint32_t uin; /* numerek plus flagi w MSB */ + uint8_t status; /* status danej osoby */ + uint32_t remote_ip; /* adres ip delikwenta */ + uint16_t remote_port; /* port, na ktĂłrym sĹ‚ucha klient */ + uint8_t version; /* wersja klienta */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno1; /* 0x00 */ + uint32_t dunno2; /* ? */ +} GG_PACKED; + +#define GG_ADD_NOTIFY 0x000d +#define GG_REMOVE_NOTIFY 0x000e + +struct gg_add_remove { + uint32_t uin; /* numerek */ + uint8_t dunno1; /* bitmapa */ +} GG_PACKED; + +#define GG_STATUS 0x0002 + +struct gg_status { + uint32_t uin; /* numerek */ + uint32_t status; /* nowy stan */ +} GG_PACKED; + +#define GG_SEND_MSG 0x000b + +#ifndef DOXYGEN + +#define GG_CLASS_QUEUED 0x0001 +#define GG_CLASS_OFFLINE GG_CLASS_QUEUED +#define GG_CLASS_MSG 0x0004 +#define GG_CLASS_CHAT 0x0008 +#define GG_CLASS_CTCP 0x0010 +#define GG_CLASS_ACK 0x0020 +#define GG_CLASS_EXT GG_CLASS_ACK /**< Dla kompatybilnoĹ›ci wstecz */ + +#else + +/** + * Klasy wiadomoĹ›ci. WartoĹ›ci sÄ… maskami bitowymi, ktĂłre w wiÄ™kszoĹ›ci + * przypadkĂłw moĹĽna Ĺ‚Ä…czyć (poĹ‚Ä…czenie \c GG_CLASS_MSG i \c GG_CLASS_CHAT + * nie ma sensu). + * + * \ingroup messages + */ +enum { + GG_CLASS_MSG, /**< Wiadomość ma pojawić siÄ™ w osobnym oknie */ + GG_CLASS_CHAT, /**< Wiadomość ma pojawić siÄ™ w oknie rozmowy */ + GG_CLASS_CTCP, /**< Wiadomość przeznaczona dla klienta Gadu-Gadu */ + GG_CLASS_ACK, /**< Klient nie ĹĽyczy sobie potwierdzenia */ + GG_CLASS_QUEUED, /**< Wiadomość zakolejkowana na serwerze (tylko przy odbieraniu) */ +}; + +#endif /* DOXYGEN */ + +/** + * Maksymalna dĹ‚ugość wiadomoĹ›ci. + * + * \ingroup messages + */ +#define GG_MSG_MAXSIZE 1989 + +struct gg_send_msg { + uint32_t recipient; + uint32_t seq; + uint32_t msgclass; +} GG_PACKED; + +struct gg_msg_richtext { + uint8_t flag; + uint16_t length; +} GG_PACKED; + +/** + * Struktura opisujÄ…ca formatowanie tekstu. W zaleĹĽnoĹ›ci od wartoĹ›ci pola + * \c font, zaraz za tÄ… strukturÄ… moĹĽe wystÄ…pić \c gg_msg_richtext_color + * lub \c gg_msg_richtext_image. + * + * \ingroup messages + */ +struct gg_msg_richtext_format { + uint16_t position; /**< PoczÄ…tkowy znak formatowania (liczony od 0) */ + uint8_t font; /**< Atrybuty formatowania */ +} GG_PACKED; + +#ifndef DOXYGEN + +#define GG_FONT_BOLD 0x01 +#define GG_FONT_ITALIC 0x02 +#define GG_FONT_UNDERLINE 0x04 +#define GG_FONT_COLOR 0x08 +#define GG_FONT_IMAGE 0x80 + +#else + +/** + * Atrybuty formatowania wiadomoĹ›ci. + * + * \ingroup messages + */ +enum { + GG_FONT_BOLD, + GG_FONT_ITALIC, + GG_FONT_UNDERLINE, + GG_FONT_COLOR, + GG_FONT_IMAGE +}; + +#endif /* DOXYGEN */ + +/** + * Struktura opisujÄ…cÄ… kolor tekstu dla atrybutu \c GG_FONT_COLOR. + * + * \ingroup messages + */ +struct gg_msg_richtext_color { + uint8_t red; /**< SkĹ‚adowa czerwona koloru */ + uint8_t green; /**< SkĹ‚adowa zielona koloru */ + uint8_t blue; /**< SkĹ‚adowa niebieska koloru */ +} GG_PACKED; + +/** + * Strukturya opisujÄ…ca obrazek wstawiony do wiadomoĹ›ci dla atrubutu + * \c GG_FONT_IMAGE. + * + * \ingroup messages + */ +struct gg_msg_richtext_image { + uint16_t unknown1; /**< Nieznane pole o wartoĹ›ci 0x0109 */ + uint32_t size; /**< Rozmiar obrazka */ + uint32_t crc32; /**< Suma kontrolna CRC32 obrazka */ +} GG_PACKED; + +struct gg_msg_recipients { + uint8_t flag; + uint32_t count; +} GG_PACKED; + +struct gg_msg_image_request { + uint8_t flag; + uint32_t size; + uint32_t crc32; +} GG_PACKED; + +struct gg_msg_image_reply { + uint8_t flag; + uint32_t size; + uint32_t crc32; + /* char filename[]; */ + /* char image[]; */ +} GG_PACKED; + +#define GG_SEND_MSG_ACK 0x0005 + +#ifndef DOXYGEN + +#define GG_ACK_BLOCKED 0x0001 +#define GG_ACK_DELIVERED 0x0002 +#define GG_ACK_QUEUED 0x0003 +#define GG_ACK_MBOXFULL 0x0004 +#define GG_ACK_NOT_DELIVERED 0x0006 + +#else + +/** + * Status dorÄ™czenia wiadomoĹ›ci. + * + * \ingroup messages + */ +enum +{ + GG_ACK_DELIVERED, /**< Wiadomość dostarczono. */ + GG_ACK_QUEUED, /**< Wiadomość zakolejkowano z powodu niedostÄ™pnoĹ›ci odbiorcy. */ + GG_ACK_BLOCKED, /**< Wiadomość zablokowana przez serwer (spam, Ĺ›wiÄ…teczne ograniczenia itd.) */ + GG_ACK_MBOXFULL, /**< WiadomoĹ›ci nie dostarczono z powodu zapeĹ‚nionej kolejki wiadomoĹ›ci odbiorcy. */ + GG_ACK_NOT_DELIVERED /**< WiadomoĹ›ci nie dostarczono (tylko dla \c GG_CLASS_CTCP). */ +}; + +#endif /* DOXYGEN */ + +struct gg_send_msg_ack { + uint32_t status; + uint32_t recipient; + uint32_t seq; +} GG_PACKED; + +#define GG_RECV_MSG 0x000a + +struct gg_recv_msg { + uint32_t sender; + uint32_t seq; + uint32_t time; + uint32_t msgclass; +} GG_PACKED; + +#define GG_PING 0x0008 + +#define GG_PONG 0x0007 + +#define GG_DISCONNECTING 0x000b + +#define GG_USERLIST_REQUEST 0x0016 + +#define GG_XML_EVENT 0x0027 + +#ifndef DOXYGEN + +#define GG_USERLIST_PUT 0x00 +#define GG_USERLIST_PUT_MORE 0x01 +#define GG_USERLIST_GET 0x02 + +#else + +/** + * \ingroup importexport + * + * Rodzaj zapytania. + */ +enum { + GG_USERLIST_PUT, /**< Eksport listy kontaktĂłw. */ + GG_USERLIST_GET, /**< Import listy kontaktĂłw. */ +}; + +#endif /* DOXYGEN */ + +struct gg_userlist_request { + uint8_t type; +} GG_PACKED; + +#define GG_USERLIST_REPLY 0x0010 + +#ifndef DOXYGEN + +#define GG_USERLIST_PUT_REPLY 0x00 +#define GG_USERLIST_PUT_MORE_REPLY 0x02 +#define GG_USERLIST_GET_REPLY 0x06 +#define GG_USERLIST_GET_MORE_REPLY 0x04 + +#else + +/** + * \ingroup importexport + * + * Rodzaj odpowiedzi. + */ +enum { + GG_USERLIST_PUT_REPLY, /**< Wyeksportowano listy kontaktĂłw. */ + GG_USERLIST_GET_REPLY, /**< Zaimportowano listÄ™ kontaktĂłw. */ +}; + +#endif /* DOXYGEN */ + +struct gg_userlist_reply { + uint8_t type; +} GG_PACKED; + +struct gg_dcc_tiny_packet { + uint8_t type; /* rodzaj pakietu */ +} GG_PACKED; + +struct gg_dcc_small_packet { + uint32_t type; /* rodzaj pakietu */ +} GG_PACKED; + +struct gg_dcc_big_packet { + uint32_t type; /* rodzaj pakietu */ + uint32_t dunno1; /* niewiadoma */ + uint32_t dunno2; /* niewiadoma */ +} GG_PACKED; + +/* + * pĂłki co, nie znamy dokĹ‚adnie protokoĹ‚u. nie wiemy, co czemu odpowiada. + * nazwy sÄ… niepowaĹĽne i tymczasowe. + */ +#define GG_DCC_WANT_FILE 0x0003 /* peer chce plik */ +#define GG_DCC_HAVE_FILE 0x0001 /* wiÄ™c mu damy */ +#define GG_DCC_HAVE_FILEINFO 0x0003 /* niech ma informacje o pliku */ +#define GG_DCC_GIMME_FILE 0x0006 /* peer jest pewny */ +#define GG_DCC_CATCH_FILE 0x0002 /* wysyĹ‚amy plik */ + +#define GG_DCC_FILEATTR_READONLY 0x0020 + +#define GG_DCC_TIMEOUT_SEND 1800 /* 30 minut */ +#define GG_DCC_TIMEOUT_GET 1800 /* 30 minut */ +#define GG_DCC_TIMEOUT_FILE_ACK 300 /* 5 minut */ +#define GG_DCC_TIMEOUT_VOICE_ACK 300 /* 5 minut */ + +#define GG_DCC7_INFO 0x1f + +struct gg_dcc7_info { + uint32_t uin; /* numer nadawcy */ + uint32_t type; /* sposĂłb poĹ‚Ä…czenia */ + gg_dcc7_id_t id; /* identyfikator poĹ‚Ä…czenia */ + char info[GG_DCC7_INFO_LEN]; /* informacje o poĹ‚Ä…czeniu "ip port" */ + char hash[GG_DCC7_INFO_HASH_LEN];/* skrĂłt "ip" */ +} GG_PACKED; + +#define GG_DCC7_NEW 0x20 + +struct gg_dcc7_new { + gg_dcc7_id_t id; /* identyfikator poĹ‚Ä…czenia */ + uint32_t uin_from; /* numer nadawcy */ + uint32_t uin_to; /* numer odbiorcy */ + uint32_t type; /* rodzaj transmisji */ + unsigned char filename[GG_DCC7_FILENAME_LEN]; /* nazwa pliku */ + uint32_t size; /* rozmiar pliku */ + uint32_t size_hi; /* rozmiar pliku (starsze bajty) */ + unsigned char hash[GG_DCC7_HASH_LEN]; /* hash SHA1 */ +} GG_PACKED; + +#define GG_DCC7_ACCEPT 0x21 + +struct gg_dcc7_accept { + uint32_t uin; /* numer przyjmujÄ…cego poĹ‚Ä…czenie */ + gg_dcc7_id_t id; /* identyfikator poĹ‚Ä…czenia */ + uint32_t offset; /* offset przy wznawianiu transmisji */ + uint32_t dunno1; /* 0x00000000 */ +} GG_PACKED; + +// XXX API +#define GG_DCC7_TYPE_P2P 0x00000001 /**< PoĹ‚Ä…czenie bezpoĹ›rednie */ +#define GG_DCC7_TYPE_SERVER 0x00000002 /**< PoĹ‚Ä…czenie przez serwer */ + +#define GG_DCC7_REJECT 0x22 + +struct gg_dcc7_reject { + uint32_t uin; /**< Numer odrzucajÄ…cego poĹ‚Ä…czenie */ + gg_dcc7_id_t id; /**< Identyfikator poĹ‚Ä…czenia */ + uint32_t reason; /**< PowĂłd rozĹ‚Ä…czenia */ +} GG_PACKED; + +// XXX API +#define GG_DCC7_REJECT_BUSY 0x00000001 /**< PoĹ‚Ä…czenie bezpoĹ›rednie juĹĽ trwa, nie umiem obsĹ‚uĹĽyć wiÄ™cej */ +#define GG_DCC7_REJECT_USER 0x00000002 /**< UĹĽytkownik odrzuciĹ‚ poĹ‚Ä…czenie */ +#define GG_DCC7_REJECT_HIDDEN 0x00000003 /* uĹĽytkownik ojest ukryty i nie moĹĽesz mu wysĹ‚ać pliku */ +#define GG_DCC7_REJECT_VERSION 0x00000006 /**< Druga strona ma wersjÄ™ klienta nieobsĹ‚ugujÄ…cÄ… poĹ‚Ä…czeĹ„ bezpoĹ›rednich tego typu */ + +#define GG_DCC7_ID_REQUEST 0x23 + +struct gg_dcc7_id_request { + uint32_t type; /**< Rodzaj tranmisji */ +} GG_PACKED; + +// XXX API +#define GG_DCC7_TYPE_VOICE 0x00000001 /**< Transmisja gĹ‚osu */ +#define GG_DCC7_TYPE_FILE 0x00000004 /**< transmisja pliku */ + +#define GG_DCC7_ID_REPLY 0x23 + +struct gg_dcc7_id_reply { + uint32_t type; /** Rodzaj transmisji */ + gg_dcc7_id_t id; /** Przyznany identyfikator */ +} GG_PACKED; + +/* +#define GG_DCC7_DUNNO1 0x24 + +struct gg_dcc7_dunno1 { + // XXX +} GG_PACKED; +*/ + +#define GG_DCC7_ABORT 0x0025 + +struct gg_dcc7_abort { + gg_dcc7_id_t id; /* identyfikator poĹ‚Ä…czenia */ + uint32_t uin_from; /* numer nadawcy */ + uint32_t uin_to; /* numer odbiorcy */ +} GG_PACKED; + +struct gg_dcc7_aborted { + gg_dcc7_id_t id; /* identyfikator poĹ‚Ä…czenia */ +} GG_PACKED; + +#define GG_DCC7_TIMEOUT_CONNECT 10 /* 10 sekund */ +#define GG_DCC7_TIMEOUT_SEND 1800 /* 30 minut */ +#define GG_DCC7_TIMEOUT_GET 1800 /* 30 minut */ +#define GG_DCC7_TIMEOUT_FILE_ACK 300 /* 5 minut */ +#define GG_DCC7_TIMEOUT_VOICE_ACK 300 /* 5 minut */ + +#ifdef __cplusplus +} +#endif + +#if defined(__cplusplus) || defined(_WIN32) +#ifdef _WIN32 +#pragma pack(pop) +#endif +#endif + +#endif /* __GG_LIBGADU_H */ + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/protocols/Gadu-Gadu/src/libgadu/obsolete.c b/protocols/Gadu-Gadu/src/libgadu/obsolete.c new file mode 100644 index 0000000000..f8fe4dc5de --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/obsolete.c @@ -0,0 +1,238 @@ +/* coding: UTF-8 */ +/* $Id: obsolete.c 854 2009-10-12 21:06:28Z wojtekka $ */ + +/* + * (C) Copyright 2001-2003 Wojtek Kaniewski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +/** + * \file obsolete.c + * + * \brief Nieaktualne funkcje + * + * Plik zawiera definicje funkcji, ktĂłre sÄ… juĹĽ nieaktualne ze wzglÄ™du + * na zmiany w protokole. Programy konsolidowane ze starszych wersjami + * bibliotek powinny nadal mieć moĹĽliwość dziaĹ‚ania, mimo ograniczonej + * funkcjonalnoĹ›ci. + */ + +/** \cond obsolete */ + +#include + +#include "libgadu.h" +#include "internal.h" + +struct gg_http *gg_userlist_get(uin_t uin, const char *passwd, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_userlist_get() is obsolete. use gg_userlist_request() instead!\n"); + errno = EINVAL; + return NULL; +} + +int gg_userlist_get_watch_fd(struct gg_http *h) +{ + errno = EINVAL; + return -1; +} + +void gg_userlist_get_free(struct gg_http *h) +{ + +} + +struct gg_http *gg_userlist_put(uin_t uin, const char *password, const char *contacts, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_userlist_put() is obsolete. use gg_userlist_request() instead!\n"); + errno = EINVAL; + return NULL; +} + +int gg_userlist_put_watch_fd(struct gg_http *h) +{ + errno = EINVAL; + return -1; +} + +void gg_userlist_put_free(struct gg_http *h) +{ + +} + +struct gg_http *gg_userlist_remove(uin_t uin, const char *passwd, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_userlist_remove() is obsolete. use gg_userlist_request() instead!\n"); + errno = EINVAL; + return NULL; +} + +int gg_userlist_remove_watch_fd(struct gg_http *h) +{ + errno = EINVAL; + return -1; +} + +void gg_userlist_remove_free(struct gg_http *h) +{ + +} + +struct gg_http *gg_search(const struct gg_search_request *r, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_search() is obsolete. use gg_search50() instead!\n"); + errno = EINVAL; + return NULL; +} + +int gg_search_watch_fd(struct gg_http *h) +{ + errno = EINVAL; + return -1; +} + +void gg_search_free(struct gg_http *h) +{ + +} + +const struct gg_search_request *gg_search_request_mode_0(char *nickname, char *first_name, char *last_name, char *city, int gender, int min_birth, int max_birth, int active, int start) +{ + return NULL; +} + +const struct gg_search_request *gg_search_request_mode_1(char *email, int active, int start) +{ + return NULL; +} + +const struct gg_search_request *gg_search_request_mode_2(char *phone, int active, int start) +{ + return NULL; +} + +const struct gg_search_request *gg_search_request_mode_3(uin_t uin, int active, int start) +{ + return NULL; +} + +void gg_search_request_free(struct gg_search_request *r) +{ + +} + +struct gg_http *gg_register(const char *email, const char *password, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_register() is obsolete. use gg_register3() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_register2(const char *email, const char *password, const char *qa, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_register2() is obsolete. use gg_register3() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_unregister(uin_t uin, const char *password, const char *email, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_unregister() is obsolete. use gg_unregister3() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_unregister2(uin_t uin, const char *password, const char *qa, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_unregister2() is obsolete. use gg_unregister3() instead!\n"); + errno = EINVAL; + return NULL; +} + + +struct gg_http *gg_change_passwd(uin_t uin, const char *passwd, const char *newpasswd, const char *newemail, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_change_passwd() is obsolete. use gg_change_passwd4() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_change_passwd2(uin_t uin, const char *passwd, const char *newpasswd, const char *email, const char *newemail, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_change_passwd2() is obsolete. use gg_change_passwd4() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_change_passwd3(uin_t uin, const char *passwd, const char *newpasswd, const char *qa, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_change_passwd3() is obsolete. use gg_change_passwd4() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_remind_passwd(uin_t uin, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_remind_passwd() is obsolete. use gg_remind_passwd3() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_remind_passwd2(uin_t uin, const char *tokenid, const char *tokenval, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_remind_passwd2() is obsolete. use gg_remind_passwd3() instead!\n"); + errno = EINVAL; + return NULL; +} + +struct gg_http *gg_change_info(uin_t uin, const char *passwd, const struct gg_change_info_request *request, int async) +{ + gg_debug(GG_DEBUG_MISC, "// gg_change_info() is obsolete. use gg_pubdir50() instead\n"); + errno = EINVAL; + return NULL; +} + +struct gg_change_info_request *gg_change_info_request_new(const char *first_name, const char *last_name, const char *nickname, const char *email, int born, int gender, const char *city) +{ + return NULL; +} + +void gg_change_info_request_free(struct gg_change_info_request *r) +{ + +} + +int gg_resolve(int *fd, int *pid, const char *hostname) +{ + return -1; +} + +void gg_resolve_pthread_cleanup(void *arg, int kill) +{ + +} + +int gg_resolve_pthread(int *fd, void **resolver, const char *hostname) +{ + return -1; +} + +int gg_pubdir50_handle_reply(struct gg_event *e, const char *packet, int length) +{ + return -1; +} + +/** \endcond */ diff --git a/protocols/Gadu-Gadu/src/libgadu/protocol.h b/protocols/Gadu-Gadu/src/libgadu/protocol.h new file mode 100644 index 0000000000..5b4895c260 --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/protocol.h @@ -0,0 +1,277 @@ +/* coding: UTF-8 */ +/* $Id$ */ + +/* + * (C) Copyright 2009-2010 Jakub Zawadzki + * BartĹ‚omiej ZimoĹ„ + * Wojtek Kaniewski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +#ifndef LIBGADU_PROTOCOL_H +#define LIBGADU_PROTOCOL_H + +#include "libgadu.h" + +#ifdef _WIN32 +#pragma pack(push, 1) +#endif + +#define GG_LOGIN80BETA 0x0029 + +#define GG_LOGIN80 0x0031 + +#undef GG_FEATURE_STATUS80BETA +#undef GG_FEATURE_MSG80 +#undef GG_FEATURE_STATUS80 +#define GG_FEATURE_STATUS80BETA 0x01 +#define GG_FEATURE_MSG80 0x02 +#define GG_FEATURE_STATUS80 0x05 + +#define GG8_LANG "pl" +#define GG8_VERSION "Gadu-Gadu Client Build " + +struct gg_login80 { + uint32_t uin; /* mĂłj numerek */ + uint8_t language[2]; /* jÄ™zyk: GG8_LANG */ + uint8_t hash_type; /* rodzaj hashowania hasĹ‚a */ + uint8_t hash[64]; /* hash hasĹ‚a dopeĹ‚niony zerami */ + uint32_t status; /* status na dzieĹ„ dobry */ + uint32_t flags; /* flagi (przeznaczenie nieznane) */ + uint32_t features; /* opcje protokoĹ‚u (GG8_FEATURES) */ + uint32_t local_ip; /* mĂłj adres ip */ + uint16_t local_port; /* port, na ktĂłrym sĹ‚ucham */ + uint32_t external_ip; /* zewnÄ™trzny adres ip (???) */ + uint16_t external_port; /* zewnÄ™trzny port (???) */ + uint8_t image_size; /* maksymalny rozmiar grafiki w KiB */ + uint8_t dunno2; /* 0x64 */ +} GG_PACKED; + +#define GG_LOGIN_HASH_TYPE_INVALID 0x0016 + +#define GG_LOGIN80_OK 0x0035 + +/** + * Logowanie powiodĹ‚o siÄ™ (pakiet \c GG_LOGIN80_OK) + */ +struct gg_login80_ok { + uint32_t unknown1; /* 0x00000001 */ +} GG_PACKED; + +/** + * Logowanie nie powiodĹ‚o siÄ™ (pakiet \c GG_LOGIN80_FAILED) + */ +#define GG_LOGIN80_FAILED 0x0043 + +struct gg_login80_failed { + uint32_t unknown1; /* 0x00000001 */ +} GG_PACKED; + +#define GG_NEW_STATUS80BETA 0x0028 + +#define GG_NEW_STATUS80 0x0038 + +/** + * Zmiana stanu (pakiet \c GG_NEW_STATUS80) + */ +struct gg_new_status80 { + uint32_t status; /**< Nowy status */ + uint32_t flags; /**< flagi (nieznane przeznaczenie) */ + uint32_t description_size; /**< rozmiar opisu */ +} GG_PACKED; + +#define GG_STATUS80BETA 0x002a +#define GG_NOTIFY_REPLY80BETA 0x002b + +#define GG_STATUS80 0x0036 +#define GG_NOTIFY_REPLY80 0x0037 + +struct gg_notify_reply80 { + uint32_t uin; /* numerek plus flagi w najstarszym bajcie */ + uint32_t status; /* status danej osoby */ + uint32_t features; /* opcje protokoĹ‚u */ + uint32_t remote_ip; /* adres IP bezpoĹ›rednich poĹ‚Ä…czeĹ„ */ + uint16_t remote_port; /* port bezpoĹ›rednich poĹ‚Ä…czeĹ„ */ + uint8_t image_size; /* maksymalny rozmiar obrazkĂłw w KB */ + uint8_t unknown1; /* 0x00 */ + uint32_t flags; /* flagi poĹ‚Ä…czenia */ + uint32_t descr_len; /* rozmiar opisu */ +} GG_PACKED; + +#define GG_SEND_MSG80 0x002d + +struct gg_send_msg80 { + uint32_t recipient; + uint32_t seq; + uint32_t msgclass; + uint32_t offset_plain; + uint32_t offset_attr; +} GG_PACKED; + +#define GG_RECV_MSG80 0x002e + +struct gg_recv_msg80 { + uint32_t sender; + uint32_t seq; + uint32_t time; + uint32_t msgclass; + uint32_t offset_plain; + uint32_t offset_attr; +} GG_PACKED; + +#define GG_DISCONNECT_ACK 0x000d + +#define GG_RECV_MSG_ACK 0x0046 + +struct gg_recv_msg_ack { + uint32_t seq; +} GG_PACKED; + +#define GG_USER_DATA 0x0044 + +struct gg_user_data { + uint32_t type; + uint32_t user_count; +} GG_PACKED; + +struct gg_user_data_user { + uint32_t uin; + uint32_t attr_count; +} GG_PACKED; + +#define GG_TYPING_NOTIFICATION 0x0059 + +struct gg_typing_notification { + uint16_t length; + uint32_t uin; +} GG_PACKED; + +#define GG_XML_ACTION 0x002c + +#define GG_RECV_OWN_MSG 0x005a + +#define GG_MULTILOGON_INFO 0x005b + +struct gg_multilogon_info { + uint32_t count; +} GG_PACKED; + +struct gg_multilogon_info_item { + uint32_t addr; + uint32_t flags; + uint32_t features; + uint32_t logon_time; + gg_multilogon_id_t conn_id; + uint32_t unknown1; + uint32_t name_size; +} GG_PACKED; + +#define GG_MULTILOGON_DISCONNECT 0x0062 + +struct gg_multilogon_disconnect { + gg_multilogon_id_t conn_id; +} GG_PACKED; + +#define GG_DCC7_VOICE_RETRIES 0x11 /* 17 powtorzen */ + +#define GG_DCC7_RESERVED1 0xdeadc0de +#define GG_DCC7_RESERVED2 0xdeadbeaf + +struct gg_dcc7_voice_auth { + uint8_t type; /* 0x00 -> wysylanie ID + 0x01 -> potwierdzenie ID + */ + gg_dcc7_id_t id; /* identyfikator poĹ‚Ä…czenia */ + uint32_t reserved1; /* GG_DCC7_RESERVED1 */ + uint32_t reserved2; /* GG_DCC7_RESERVED2 */ +} GG_PACKED; + +struct gg_dcc7_voice_nodata { /* wyciszony mikrofon, ten pakiet jest wysylany co 1s (jesli chcemy podtrzymac polaczenie) */ + uint8_t type; /* 0x02 */ + gg_dcc7_id_t id; /* identyfikator poĹ‚Ä…czenia */ + uint32_t reserved1; /* GG_DCC7_RESERVED1 */ + uint32_t reserved2; /* GG_DCC7_RESERVED2 */ +} GG_PACKED; + +struct gg_dcc7_voice_data { + uint8_t type; /* 0x03 */ + uint32_t did; /* XXX: co ile zwieksza sie u nas id pakietu [uzywac 0x28] */ + uint32_t len; /* rozmiar strukturki - 1 (sizeof(type)) */ + uint32_t packet_id; /* numerek pakietu */ + uint32_t datalen; /* rozmiar danych */ + /* char data[]; */ /* ramki: albo gsm, albo speex, albo melp, albo inne. */ +} GG_PACKED; + +struct gg_dcc7_voice_init { + uint8_t type; /* 0x04 */ + uint32_t id; /* nr kroku [0x1 - 0x5] */ + uint32_t protocol; /* XXX: wersja protokolu (0x29, 0x2a, 0x2b) */ + uint32_t len; /* rozmiar sizeof(protocol)+sizeof(len)+sizeof(data) = 0x08 + sizeof(data) */ + /* char data[]; */ /* reszta danych */ +} GG_PACKED; + +struct gg_dcc7_voice_init_confirm { + uint8_t type; /* 0x05 */ + uint32_t id; /* id tego co potwierdzamy [0x1 - 0x5] */ +} GG_PACKED; + +#define GG_DCC7_RELAY_TYPE_SERVER 0x01 /* adres serwera, na ktĂłry spytać o proxy */ +#define GG_DCC7_RELAY_TYPE_PROXY 0x08 /* adresy proxy, na ktĂłre sie Ĺ‚Ä…czyć */ + +#define GG_DCC7_RELAY_DUNNO1 0x02 + +#define GG_DCC7_RELAY_REQUEST 0x0a + +struct gg_dcc7_relay_req { + uint32_t magic; /* 0x0a */ + uint32_t len; /* dĹ‚ugość caĹ‚ego pakietu */ + gg_dcc7_id_t id; /* identyfikator poĹ‚Ä…czenia */ + uint16_t type; /* typ zapytania */ + uint16_t dunno1; /* 0x02 */ +} GG_PACKED; + +#define GG_DCC7_RELAY_REPLY_RCOUNT 0x02 + +#define GG_DCC7_RELAY_REPLY 0x0b + +struct gg_dcc7_relay_reply { + uint32_t magic; /* 0x0b */ + uint32_t len; /* dĹ‚ugość caĹ‚ego pakietu */ + uint32_t rcount; /* ilość serwerĂłw */ +} GG_PACKED; + +struct gg_dcc7_relay_reply_server { + uint32_t addr; /* adres ip serwera */ + uint16_t port; /* port serwera */ + uint8_t family; /* rodzina adresĂłw (na koĹ„cu?!) AF_INET=2 */ +} GG_PACKED; + +#define GG_DCC7_WELCOME_SERVER 0xc0debabe + +struct gg_dcc7_welcome_server { + uint32_t magic; /* 0xc0debabe */ + gg_dcc7_id_t id; /* identyfikator poĹ‚Ä…czenia */ +} GG_PACKED; + +struct gg_dcc7_welcome_p2p { + gg_dcc7_id_t id; /* identyfikator poĹ‚Ä…czenia */ +} GG_PACKED; + +#ifdef _WIN32 +#pragma pack(pop) +#endif + +#endif /* LIBGADU_PROTOCOL_H */ diff --git a/protocols/Gadu-Gadu/src/libgadu/pthread.c b/protocols/Gadu-Gadu/src/libgadu/pthread.c new file mode 100644 index 0000000000..9a8988a358 --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/pthread.c @@ -0,0 +1,78 @@ +/* +AOL Instant Messenger Plugin for Miranda IM + +Copyright (c) 2003-2006 Robert Rainwater + +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 "pthread.h" +#include +#include +#include + +/****************************************/ +/* Portable pthread code for Miranda IM */ + +/* create thread */ +int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(__stdcall * thread_start) (void *), void *param) +{ + tid->hThread = (HANDLE)mir_forkthreadex((pThreadFuncEx)*(void**)&thread_start, param, (unsigned *)&tid->dwThreadId); + + return 0; +} + +/* detach a thread */ +int pthread_detach(pthread_t *tid) +{ + CloseHandle(tid->hThread); + return 0; +} + +/* wait for thread termination */ +int pthread_join(pthread_t *tid, void **value_ptr) +{ + if (tid->dwThreadId == GetCurrentThreadId()) + return 35 /*EDEADLK*/; + + WaitForSingleObject(tid->hThread, INFINITE); + return 0; +} + +/* get calling thread's ID */ +pthread_t *pthread_self(void) +{ + static int poolId = 0; + static pthread_t tidPool[32]; + /* mark & round pool to 32 items */ + pthread_t *tid = &tidPool[poolId ++]; + poolId %= 32; + + tid->hThread = GetCurrentThread(); + tid->dwThreadId = GetCurrentThreadId(); + return tid; +} + +/* cancel execution of a thread */ +int pthread_cancel(pthread_t *thread) +{ + return TerminateThread(thread->hThread, 0) ? 0 : 3 /*ESRCH*/; +} + +/* terminate thread */ +void pthread_exit(void *value_ptr) +{ +// _endthreadex((unsigned)value_ptr); +} diff --git a/protocols/Gadu-Gadu/src/libgadu/pthread.h b/protocols/Gadu-Gadu/src/libgadu/pthread.h new file mode 100644 index 0000000000..4de1053e0e --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/pthread.h @@ -0,0 +1,56 @@ +/* +pthread Portable implementation + +Copyright (c) 2003 Robert Rainwater +Copyright (c) 2003-2005 Adam Strzelecki + +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. +*/ + +#ifndef PTHREAD_H +#define PTHREAD_H + +#include + +// Minipthread code from Miranda IM source +typedef struct +{ + HANDLE hThread; + DWORD dwThreadId; +} +pthread_t; + +typedef int pthread_attr_t; +typedef CRITICAL_SECTION pthread_mutex_t; + +/* create thread */ +int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(__stdcall *thread_start) (void *), void *param); +/* wait for thread termination */ +int pthread_join(pthread_t *tid, void **value_ptr); +/* detach a thread */ +int pthread_detach(pthread_t *tid); +/* get calling thread's ID */ +pthread_t *pthread_self(void); +/* cancel execution of a thread */ +int pthread_cancel(pthread_t *thread); +/* terminate thread */ +void pthread_exit(void *value_ptr); + +#define pthread_mutex_init(pmutex, pattr) InitializeCriticalSection(pmutex) +#define pthread_mutex_destroy(pmutex) DeleteCriticalSection(pmutex) +#define pthread_mutex_lock(pmutex) EnterCriticalSection(pmutex) +#define pthread_mutex_unlock(pmutex) LeaveCriticalSection(pmutex) + +#endif diff --git a/protocols/Gadu-Gadu/src/libgadu/pubdir.c b/protocols/Gadu-Gadu/src/libgadu/pubdir.c new file mode 100644 index 0000000000..f9d656dfe3 --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/pubdir.c @@ -0,0 +1,862 @@ +/* coding: UTF-8 */ +/* $Id: pubdir.c 11370 2010-03-13 16:17:54Z dezred $ */ + +/* + * (C) Copyright 2001-2006 Wojtek Kaniewski + * Dawid Jarosz + * Adam Wysocki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +/** + * \file pubdir.c + * + * \brief ObsĹ‚uga katalogu publicznego + */ + +#include +#include +#include +#include +#include +#include +#ifdef _WIN32 +#include "win32.h" +#define random() rand() +#else +#include +#endif + +#include "libgadu.h" + +/** + * Rejestruje nowego uĹĽytkownika. + * + * Wymaga wczeĹ›niejszego pobrania tokenu za pomocÄ… \c gg_token(). + * + * \param email Adres e-mail + * \param password HasĹ‚o + * \param tokenid Identyfikator tokenu + * \param tokenval Zawartość tokenu + * \param async Flaga poĹ‚Ä…czenia asynchronicznego + * + * \return Struktura \c gg_http lub \c NULL w przypadku bĹ‚Ä™du + * + * \ingroup register + */ +struct gg_http *gg_register3(const char *email, const char *password, const char *tokenid, const char *tokenval, int async) +{ + struct gg_http *h; + char *__pwd, *__email, *__tokenid, *__tokenval, *form, *query; + + if (!email || !password || !tokenid || !tokenval) { + gg_debug(GG_DEBUG_MISC, "=> register, NULL parameter\n"); + errno = EFAULT; + return NULL; + } + + __pwd = gg_urlencode(password); + __email = gg_urlencode(email); + __tokenid = gg_urlencode(tokenid); + __tokenval = gg_urlencode(tokenval); + + if (!__pwd || !__email || !__tokenid || !__tokenval) { + gg_debug(GG_DEBUG_MISC, "=> register, not enough memory for form fields\n"); + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + return NULL; + } + + form = gg_saprintf("pwd=%s&email=%s&tokenid=%s&tokenval=%s&code=%u", + __pwd, __email, __tokenid, __tokenval, + gg_http_hash("ss", email, password)); + + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + + if (!form) { + gg_debug(GG_DEBUG_MISC, "=> register, not enough memory for form query\n"); + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "=> register, %s\n", form); + + query = gg_saprintf( + "Host: " GG_REGISTER_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: %d\r\n" + "Pragma: no-cache\r\n" + "\r\n" + "%s", + (int) strlen(form), form); + + free(form); + + if (!query) { + gg_debug(GG_DEBUG_MISC, "=> register, not enough memory for query\n"); + return NULL; + } + + if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/fmregister3.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> register, gg_http_connect() failed mysteriously\n"); + free(query); + return NULL; + } + + h->type = GG_SESSION_REGISTER; + + free(query); + + h->callback = gg_pubdir_watch_fd; + h->destroy = gg_pubdir_free; + + if (!async) + gg_pubdir_watch_fd(h); + + return h; +} + +#ifdef DOXYGEN + +/** + * Funkcja wywoĹ‚ywana po zaobserwowaniu zmian na deskryptorze poĹ‚Ä…czenia. + * + * Operacja bÄ™dzie zakoĹ„czona, gdy pole \c state bÄ™dzie rĂłwne \c GG_STATE_DONE. + * JeĹ›li wystÄ…pi bĹ‚Ä…d, \c state bÄ™dzie rĂłwne \c GG_STATE_ERROR, a kod bĹ‚Ä™du + * znajdzie siÄ™ w polu \c error. + * + * \note W rzeczywistoĹ›ci funkcja jest makrem rozwijanym do + * \c gg_pubdir_watch_fd(). + * + * \param h Struktura poĹ‚Ä…czenia + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup register + */ +int gg_register_watch_fd(struct gg_httpd *h) +{ + return gg_pubdir_watch_fd(h); +} + +/** + * Zwalnia zasoby po operacji. + * + * \note W rzeczywistoĹ›ci funkcja jest makrem rozwijanym do \c gg_pubdir_free(). + * + * \param h Struktura poĹ‚Ä…czenia + * + * \ingroup register + */ +void gg_register_free(struct gg_http *h) +{ + return gg_pubdir_free(h); +} + +#endif /* DOXYGEN */ + +/** + * Usuwa uĹĽytkownika. + * + * Wymaga wczeĹ›niejszego pobrania tokenu za pomocÄ… \c gg_token(). + * + * \param uin Numer Gadu-Gadu + * \param password HasĹ‚o + * \param tokenid Identyfikator tokenu + * \param tokenval Zawartość tokenu + * \param async Flaga poĹ‚Ä…czenia asynchronicznego + * + * \return Struktura \c gg_http lub \c NULL w przypadku bĹ‚Ä™du + * + * \ingroup unregister + */ +struct gg_http *gg_unregister3(uin_t uin, const char *password, const char *tokenid, const char *tokenval, int async) +{ + struct gg_http *h; + char *__fmpwd, *__pwd, *__tokenid, *__tokenval, *form, *query; + + if (!password || !tokenid || !tokenval) { + gg_debug(GG_DEBUG_MISC, "=> unregister, NULL parameter\n"); + errno = EFAULT; + return NULL; + } + + __pwd = gg_saprintf("%ld", random()); + __fmpwd = gg_urlencode(password); + __tokenid = gg_urlencode(tokenid); + __tokenval = gg_urlencode(tokenval); + + if (!__fmpwd || !__pwd || !__tokenid || !__tokenval) { + gg_debug(GG_DEBUG_MISC, "=> unregister, not enough memory for form fields\n"); + free(__pwd); + free(__fmpwd); + free(__tokenid); + free(__tokenval); + return NULL; + } + + form = gg_saprintf("fmnumber=%d&fmpwd=%s&delete=1&pwd=%s&email=deletedaccount@gadu-gadu.pl&tokenid=%s&tokenval=%s&code=%u", uin, __fmpwd, __pwd, __tokenid, __tokenval, gg_http_hash("ss", "deletedaccount@gadu-gadu.pl", __pwd)); + + free(__fmpwd); + free(__pwd); + free(__tokenid); + free(__tokenval); + + if (!form) { + gg_debug(GG_DEBUG_MISC, "=> unregister, not enough memory for form query\n"); + return NULL; + } + + gg_debug(GG_DEBUG_MISC, "=> unregister, %s\n", form); + + query = gg_saprintf( + "Host: " GG_REGISTER_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: %d\r\n" + "Pragma: no-cache\r\n" + "\r\n" + "%s", + (int) strlen(form), form); + + free(form); + + if (!query) { + gg_debug(GG_DEBUG_MISC, "=> unregister, not enough memory for query\n"); + return NULL; + } + + if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/fmregister3.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> unregister, gg_http_connect() failed mysteriously\n"); + free(query); + return NULL; + } + + h->type = GG_SESSION_UNREGISTER; + + free(query); + + h->callback = gg_pubdir_watch_fd; + h->destroy = gg_pubdir_free; + + if (!async) + gg_pubdir_watch_fd(h); + + return h; +} + +#ifdef DOXYGEN + +/** + * Funkcja wywoĹ‚ywana po zaobserwowaniu zmian na deskryptorze poĹ‚Ä…czenia. + * + * Operacja bÄ™dzie zakoĹ„czona, gdy pole \c state bÄ™dzie rĂłwne \c GG_STATE_DONE. + * JeĹ›li wystÄ…pi bĹ‚Ä…d, \c state bÄ™dzie rĂłwne \c GG_STATE_ERROR, a kod bĹ‚Ä™du + * znajdzie siÄ™ w polu \c error. + * + * \note W rzeczywistoĹ›ci funkcja jest makrem rozwijanym do + * \c gg_pubdir_watch_fd(). + * + * \param h Struktura poĹ‚Ä…czenia + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup unregister + */ +int gg_unregister_watch_fd(struct gg_httpd *h) +{ + return gg_pubdir_watch_fd(h); +} + +/** + * Zwalnia zasoby po operacji. + * + * \note W rzeczywistoĹ›ci funkcja jest makrem rozwijanym do \c gg_pubdir_free(). + * + * \param h Struktura poĹ‚Ä…czenia + * + * \ingroup unregister + */ +void gg_unregister_free(struct gg_http *h) +{ + return gg_pubdir_free(h); +} + +#endif /* DOXYGEN */ + +/** + * Zmienia hasĹ‚o uĹĽytkownika. + * + * Wymaga wczeĹ›niejszego pobrania tokenu za pomocÄ… \c gg_token(). + * + * \param uin Numer Gadu-Gadu + * \param email Adres e-mail + * \param passwd Obecne hasĹ‚o + * \param newpasswd Nowe hasĹ‚o + * \param tokenid Identyfikator tokenu + * \param tokenval Zawartość tokenu + * \param async Flaga poĹ‚Ä…czenia asynchronicznego + * + * \return Struktura \c gg_http lub \c NULL w przypadku bĹ‚Ä™du + * + * \ingroup passwd + */ +struct gg_http *gg_change_passwd4(uin_t uin, const char *email, const char *passwd, const char *newpasswd, const char *tokenid, const char *tokenval, int async) +{ + struct gg_http *h; + char *form, *query, *__email, *__fmpwd, *__pwd, *__tokenid, *__tokenval; + + if (!uin || !email || !passwd || !newpasswd || !tokenid || !tokenval) { + gg_debug(GG_DEBUG_MISC, "=> change, NULL parameter\n"); + errno = EFAULT; + return NULL; + } + + __fmpwd = gg_urlencode(passwd); + __pwd = gg_urlencode(newpasswd); + __email = gg_urlencode(email); + __tokenid = gg_urlencode(tokenid); + __tokenval = gg_urlencode(tokenval); + + if (!__fmpwd || !__pwd || !__email || !__tokenid || !__tokenval) { + gg_debug(GG_DEBUG_MISC, "=> change, not enough memory for form fields\n"); + free(__fmpwd); + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + return NULL; + } + + if (!(form = gg_saprintf("fmnumber=%d&fmpwd=%s&pwd=%s&email=%s&tokenid=%s&tokenval=%s&code=%u", uin, __fmpwd, __pwd, __email, __tokenid, __tokenval, gg_http_hash("ss", email, newpasswd)))) { + gg_debug(GG_DEBUG_MISC, "=> change, not enough memory for form fields\n"); + free(__fmpwd); + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + + return NULL; + } + + free(__fmpwd); + free(__pwd); + free(__email); + free(__tokenid); + free(__tokenval); + + gg_debug(GG_DEBUG_MISC, "=> change, %s\n", form); + + query = gg_saprintf( + "Host: " GG_REGISTER_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: %d\r\n" + "Pragma: no-cache\r\n" + "\r\n" + "%s", + (int) strlen(form), form); + + free(form); + + if (!query) { + gg_debug(GG_DEBUG_MISC, "=> change, not enough memory for query\n"); + return NULL; + } + + if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/fmregister3.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> change, gg_http_connect() failed mysteriously\n"); + free(query); + return NULL; + } + + h->type = GG_SESSION_PASSWD; + + free(query); + + h->callback = gg_pubdir_watch_fd; + h->destroy = gg_pubdir_free; + + if (!async) + gg_pubdir_watch_fd(h); + + return h; +} + +#ifdef DOXYGEN + +/** + * Funkcja wywoĹ‚ywana po zaobserwowaniu zmian na deskryptorze poĹ‚Ä…czenia. + * + * Operacja bÄ™dzie zakoĹ„czona, gdy pole \c state bÄ™dzie rĂłwne \c GG_STATE_DONE. + * JeĹ›li wystÄ…pi bĹ‚Ä…d, \c state bÄ™dzie rĂłwne \c GG_STATE_ERROR, a kod bĹ‚Ä™du + * znajdzie siÄ™ w polu \c error. + * + * \note W rzeczywistoĹ›ci funkcja jest makrem rozwijanym do + * \c gg_pubdir_watch_fd(). + * + * \param h Struktura poĹ‚Ä…czenia + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup passwd + */ +int gg_change_passwd_watch_fd(struct gg_httpd *h) +{ + return gg_pubdir_watch_fd(h); +} + +/** + * Zwalnia zasoby po operacji. + * + * \note W rzeczywistoĹ›ci funkcja jest makrem rozwijanym do \c gg_pubdir_free(). + * + * \param h Struktura poĹ‚Ä…czenia + * + * \ingroup passwd + */ +void gg_change_passwd_free(struct gg_http *h) +{ + return gg_pubdir_free(h); +} + +#endif /* DOXYGEN */ + +/** + * WysyĹ‚a hasĹ‚o uĹĽytkownika na e-mail. + * + * Wymaga wczeĹ›niejszego pobrania tokenu za pomocÄ… \c gg_token(). + * + * \param uin Numer Gadu-Gadu + * \param email Adres e-mail (podany przy rejestracji) + * \param tokenid Identyfikator tokenu + * \param tokenval Zawartość tokenu + * \param async Flaga poĹ‚Ä…czenia asynchronicznego + * + * \return Struktura \c gg_http lub \c NULL w przypadku bĹ‚Ä™du + * + * \ingroup remind + */ +struct gg_http *gg_remind_passwd3(uin_t uin, const char *email, const char *tokenid, const char *tokenval, int async) +{ + struct gg_http *h; + char *form, *query, *__tokenid, *__tokenval, *__email; + + if (!tokenid || !tokenval || !email) { + gg_debug(GG_DEBUG_MISC, "=> remind, NULL parameter\n"); + errno = EFAULT; + return NULL; + } + + __tokenid = gg_urlencode(tokenid); + __tokenval = gg_urlencode(tokenval); + __email = gg_urlencode(email); + + if (!__tokenid || !__tokenval || !__email) { + gg_debug(GG_DEBUG_MISC, "=> remind, not enough memory for form fields\n"); + free(__tokenid); + free(__tokenval); + free(__email); + return NULL; + } + + if (!(form = gg_saprintf("userid=%d&code=%u&tokenid=%s&tokenval=%s&email=%s", uin, gg_http_hash("u", uin), __tokenid, __tokenval, __email))) { + gg_debug(GG_DEBUG_MISC, "=> remind, not enough memory for form fields\n"); + free(__tokenid); + free(__tokenval); + free(__email); + return NULL; + } + + free(__tokenid); + free(__tokenval); + free(__email); + + gg_debug(GG_DEBUG_MISC, "=> remind, %s\n", form); + + query = gg_saprintf( + "Host: " GG_REMIND_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: %d\r\n" + "Pragma: no-cache\r\n" + "\r\n" + "%s", + (int) strlen(form), form); + + free(form); + + if (!query) { + gg_debug(GG_DEBUG_MISC, "=> remind, not enough memory for query\n"); + return NULL; + } + + if (!(h = gg_http_connect(GG_REMIND_HOST, GG_REMIND_PORT, async, "POST", "/appsvc/fmsendpwd3.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> remind, gg_http_connect() failed mysteriously\n"); + free(query); + return NULL; + } + + h->type = GG_SESSION_REMIND; + + free(query); + + h->callback = gg_pubdir_watch_fd; + h->destroy = gg_pubdir_free; + + if (!async) + gg_pubdir_watch_fd(h); + + return h; +} + +#ifdef DOXYGEN + +/** + * Funkcja wywoĹ‚ywana po zaobserwowaniu zmian na deskryptorze poĹ‚Ä…czenia. + * + * Operacja bÄ™dzie zakoĹ„czona, gdy pole \c state bÄ™dzie rĂłwne \c GG_STATE_DONE. + * JeĹ›li wystÄ…pi bĹ‚Ä…d, \c state bÄ™dzie rĂłwne \c GG_STATE_ERROR, a kod bĹ‚Ä™du + * znajdzie siÄ™ w polu \c error. + * + * \note W rzeczywistoĹ›ci funkcja jest makrem rozwijanym do + * \c gg_pubdir_watch_fd(). + * + * \param h Struktura poĹ‚Ä…czenia + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup remind + */ +int gg_remind_watch_fd(struct gg_httpd *h) +{ + return gg_pubdir_watch_fd(h); +} + +/** + * Zwalnia zasoby po operacji. + * + * \note W rzeczywistoĹ›ci funkcja jest makrem rozwijanym do \c gg_pubdir_free(). + * + * \param h Struktura poĹ‚Ä…czenia + * + * \ingroup remind + */ +void gg_remind_free(struct gg_http *h) +{ + return gg_pubdir_free(h); +} + +#endif /* DOXYGEN */ + +/** + * Funkcja wywoĹ‚ywana po zaobserwowaniu zmian na deskryptorze poĹ‚Ä…czenia. + * + * Operacja bÄ™dzie zakoĹ„czona, gdy pole \c state bÄ™dzie rĂłwne \c GG_STATE_DONE. + * JeĹ›li wystÄ…pi bĹ‚Ä…d, \c state bÄ™dzie rĂłwne \c GG_STATE_ERROR, a kod bĹ‚Ä™du + * znajdzie siÄ™ w polu \c error. + * + * \param h Struktura poĹ‚Ä…czenia + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +int gg_pubdir_watch_fd(struct gg_http *h) +{ + struct gg_pubdir *p; + char *tmp; + + if (!h) { + errno = EFAULT; + return -1; + } + + if (h->state == GG_STATE_ERROR) { + gg_debug(GG_DEBUG_MISC, "=> pubdir, watch_fd issued on failed session\n"); + errno = EINVAL; + return -1; + } + + if (h->state != GG_STATE_PARSING) { + if (gg_http_watch_fd(h) == -1) { + gg_debug(GG_DEBUG_MISC, "=> pubdir, http failure\n"); + errno = EINVAL; + return -1; + } + } + + if (h->state != GG_STATE_PARSING) + return 0; + + h->state = GG_STATE_DONE; + + if (!(h->data = p = malloc(sizeof(struct gg_pubdir)))) { + gg_debug(GG_DEBUG_MISC, "=> pubdir, not enough memory for results\n"); + return -1; + } + + p->success = 0; + p->uin = 0; + + gg_debug(GG_DEBUG_MISC, "=> pubdir, let's parse \"%s\"\n", h->body); + + if ((tmp = strstr(h->body, "Tokens okregisterreply_packet.reg.dwUserId="))) { + p->success = 1; + p->uin = strtol(tmp + sizeof("Tokens okregisterreply_packet.reg.dwUserId=") - 1, NULL, 0); + gg_debug(GG_DEBUG_MISC, "=> pubdir, success (okregisterreply, uin=%d)\n", p->uin); + } else if ((tmp = strstr(h->body, "success")) || (tmp = strstr(h->body, "results"))) { + p->success = 1; + if (tmp[7] == ':') + p->uin = strtol(tmp + 8, NULL, 0); + gg_debug(GG_DEBUG_MISC, "=> pubdir, success (uin=%d)\n", p->uin); + } else + gg_debug(GG_DEBUG_MISC, "=> pubdir, error.\n"); + + return 0; +} + +/** + * Zwalnia zasoby po operacji na katalogu publicznym. + * + * \param h Struktura poĹ‚Ä…czenia + */ +void gg_pubdir_free(struct gg_http *h) +{ + if (!h) + return; + + free(h->data); + gg_http_free(h); +} + +/** + * Pobiera token do autoryzacji operacji na katalogu publicznym. + * + * Token jest niezbÄ™dny do tworzenia nowego i usuwania uĹĽytkownika, + * zmiany hasĹ‚a itd. + * + * \param async Flaga poĹ‚Ä…czenia asynchronicznego + * + * \return Struktura \c gg_http lub \c NULL w przypadku bĹ‚Ä™du + * + * \ingroup token + */ +struct gg_http *gg_token(int async) +{ + struct gg_http *h; + const char *query; + + query = "Host: " GG_REGISTER_HOST "\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "User-Agent: " GG_HTTP_USERAGENT "\r\n" + "Content-Length: 0\r\n" + "Pragma: no-cache\r\n" + "\r\n"; + + if (!(h = gg_http_connect(GG_REGISTER_HOST, GG_REGISTER_PORT, async, "POST", "/appsvc/regtoken.asp", query))) { + gg_debug(GG_DEBUG_MISC, "=> token, gg_http_connect() failed mysteriously\n"); + return NULL; + } + + h->type = GG_SESSION_TOKEN; + + h->callback = gg_token_watch_fd; + h->destroy = gg_token_free; + + if (!async) + gg_token_watch_fd(h); + + return h; +} + +/** + * Funkcja wywoĹ‚ywana po zaobserwowaniu zmian na deskryptorze poĹ‚Ä…czenia. + * + * Operacja bÄ™dzie zakoĹ„czona, gdy pole \c state bÄ™dzie rĂłwne \c GG_STATE_DONE. + * JeĹ›li wystÄ…pi bĹ‚Ä…d, \c state bÄ™dzie rĂłwne \c GG_STATE_ERROR, a kod bĹ‚Ä™du + * znajdzie siÄ™ w polu \c error. + * + * \param h Struktura poĹ‚Ä…czenia + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup token + */ +int gg_token_watch_fd(struct gg_http *h) +{ + if (!h) { + errno = EFAULT; + return -1; + } + + if (h->state == GG_STATE_ERROR) { + gg_debug(GG_DEBUG_MISC, "=> token, watch_fd issued on failed session\n"); + errno = EINVAL; + return -1; + } + + if (h->state != GG_STATE_PARSING) { + if (gg_http_watch_fd(h) == -1) { + gg_debug(GG_DEBUG_MISC, "=> token, http failure\n"); + errno = EINVAL; + return -1; + } + } + + if (h->state != GG_STATE_PARSING) + return 0; + + /* jeĹ›li h->data jest puste, to Ĺ›ciÄ…galiĹ›my tokenid i url do niego, + * ale jeĹ›li coĹ› tam jest, to znaczy, ĹĽe mamy drugi etap polegajÄ…cy + * na pobieraniu tokenu. */ + if (!h->data) { + int width, height, length; + char *url = NULL, *tokenid = NULL, *path, *headers; + const char *host; + struct gg_http *h2; + struct gg_token *t; + + gg_debug(GG_DEBUG_MISC, "=> token body \"%s\"\n", h->body); + + if (h->body && (!(url = malloc(strlen(h->body))) || !(tokenid = malloc(strlen(h->body))))) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for results\n"); + free(url); + return -1; + } + + if (!h->body || sscanf(h->body, "%d %d %d\r\n%s\r\n%s", &width, &height, &length, tokenid, url) != 5) { + gg_debug(GG_DEBUG_MISC, "=> token, parsing failed\n"); + free(url); + free(tokenid); + errno = EINVAL; + return -1; + } + + /* dostaliĹ›my tokenid i wszystkie niezbÄ™dne informacje, + * wiÄ™c pobierzmy obrazek z tokenem */ + + if (strncmp(url, "http://", 7)) { + path = gg_saprintf("%s?tokenid=%s", url, tokenid); + host = GG_REGISTER_HOST; + } else { + char *slash = strchr(url + 7, '/'); + + if (slash) { + path = gg_saprintf("%s?tokenid=%s", slash, tokenid); + *slash = 0; + host = url + 7; + } else { + gg_debug(GG_DEBUG_MISC, "=> token, url parsing failed\n"); + free(url); + free(tokenid); + errno = EINVAL; + return -1; + } + } + + if (!path) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for token url\n"); + free(url); + free(tokenid); + return -1; + } + + if (!(headers = gg_saprintf("Host: %s\r\nUser-Agent: " GG_HTTP_USERAGENT "\r\n\r\n", host))) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for token url\n"); + free(path); + free(url); + free(tokenid); + return -1; + } + + if (!(h2 = gg_http_connect(host, GG_REGISTER_PORT, h->async, "GET", path, headers))) { + gg_debug(GG_DEBUG_MISC, "=> token, gg_http_connect() failed mysteriously\n"); + free(headers); + free(url); + free(path); + free(tokenid); + return -1; + } + + free(headers); + free(path); + free(url); + + gg_http_free_fields(h); + + memcpy(h, h2, sizeof(struct gg_http)); + free(h2); + + h->type = GG_SESSION_TOKEN; + + h->callback = gg_token_watch_fd; + h->destroy = gg_token_free; + + if (!h->async) + gg_token_watch_fd(h); + + if (!(h->data = t = malloc(sizeof(struct gg_token)))) { + gg_debug(GG_DEBUG_MISC, "=> token, not enough memory for token data\n"); + free(tokenid); + return -1; + } + + t->width = width; + t->height = height; + t->length = length; + t->tokenid = tokenid; + } else { + /* obrazek mamy w h->body */ + h->state = GG_STATE_DONE; + } + + return 0; +} + +/** + * Zwalnia zasoby po operacji pobierania tokenu. + * + * \param h Struktura poĹ‚Ä…czenia + * + * \ingroup token + */ +void gg_token_free(struct gg_http *h) +{ + struct gg_token *t; + + if (!h) + return; + + if ((t = h->data)) + free(t->tokenid); + + free(h->data); + gg_http_free(h); +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/protocols/Gadu-Gadu/src/libgadu/pubdir50.c b/protocols/Gadu-Gadu/src/libgadu/pubdir50.c new file mode 100644 index 0000000000..6914cbd01a --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/pubdir50.c @@ -0,0 +1,557 @@ +/* coding: UTF-8 */ +/* $Id: pubdir50.c 11370 2010-03-13 16:17:54Z dezred $ */ + +/* + * (C) Copyright 2003 Wojtek Kaniewski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +/** + * \file pubdir50.c + * + * \brief ObsĹ‚uga katalogu publicznego od wersji Gadu-Gadu 5.x + */ + +#ifndef _WIN64 +#define _USE_32BIT_TIME_T +#endif + +#include +#include +#include +#include + +#ifdef _WIN32 +#include "win32.h" +#undef small +#endif + +#include "libgadu.h" +#include "internal.h" + +/** + * Tworzy nowe zapytanie katalogu publicznego. + * + * \param type Rodzaj zapytania + * + * \return Zmienna \c gg_pubdir50_t lub \c NULL w przypadku bĹ‚Ä™du. + * + * \ingroup pubdir50 + */ +gg_pubdir50_t gg_pubdir50_new(int type) +{ + gg_pubdir50_t res = malloc(sizeof(struct gg_pubdir50_s)); + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_new(%d);\n", type); + + if (!res) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_new() out of memory\n"); + return NULL; + } + + memset(res, 0, sizeof(struct gg_pubdir50_s)); + + res->type = type; + + return res; +} + +/** + * \internal Dodaje lub zastÄ™puje pole zapytania lub odpowiedzi katalogu + * publicznego. + * + * \param req Zapytanie lub odpowiedĹş + * \param num Numer wyniku odpowiedzi (0 dla zapytania) + * \param field Nazwa pola + * \param value Wartość pola + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +static int gg_pubdir50_add_n(gg_pubdir50_t req, int num, const char *field, const char *value) +{ + struct gg_pubdir50_entry *tmp = NULL, *entry; + char *dupfield, *dupvalue; + int i; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_add_n(%p, %d, \"%s\", \"%s\");\n", req, num, field, value); + + if (!(dupvalue = strdup(value))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n"); + return -1; + } + + for (i = 0; i < req->entries_count; i++) { + if (req->entries[i].num != num || strcmp(req->entries[i].field, field)) + continue; + + free(req->entries[i].value); + req->entries[i].value = dupvalue; + + return 0; + } + + if (!(dupfield = strdup(field))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n"); + free(dupvalue); + return -1; + } + + if (!(tmp = realloc(req->entries, sizeof(struct gg_pubdir50_entry) * (req->entries_count + 1)))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_add_n() out of memory\n"); + free(dupfield); + free(dupvalue); + return -1; + } + + req->entries = tmp; + + entry = &req->entries[req->entries_count]; + entry->num = num; + entry->field = dupfield; + entry->value = dupvalue; + + req->entries_count++; + + return 0; +} + +/** + * Dodaje pole zapytania. + * + * \param req Zapytanie + * \param field Nazwa pola + * \param value Wartość pola + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup pubdir50 + */ +int gg_pubdir50_add(gg_pubdir50_t req, const char *field, const char *value) +{ + return gg_pubdir50_add_n(req, 0, field, value); +} + +/** + * Ustawia numer sekwencyjny zapytania. + * + * \param req Zapytanie + * \param seq Numer sekwencyjny + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + * + * \ingroup pubdir50 + */ +int gg_pubdir50_seq_set(gg_pubdir50_t req, uint32_t seq) +{ + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_seq_set(%p, %d);\n", req, seq); + + if (!req) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_seq_set() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + req->seq = seq; + + return 0; +} + +/** + * Zwalnia zasoby po zapytaniu lub odpowiedzi katalogu publicznego. + * + * \param s Zapytanie lub odpowiedĹş + * + * \ingroup pubdir50 + */ +void gg_pubdir50_free(gg_pubdir50_t s) +{ + int i; + + if (!s) + return; + + for (i = 0; i < s->entries_count; i++) { + free(s->entries[i].field); + free(s->entries[i].value); + } + + free(s->entries); + free(s); +} + +/** + * WysyĹ‚a zapytanie katalogu publicznego do serwera. + * + * \param sess Struktura sesji + * \param req Zapytanie + * + * \return Numer sekwencyjny zapytania lub 0 w przypadku bĹ‚Ä™du + * + * \ingroup pubdir50 + */ +uint32_t gg_pubdir50(struct gg_session *sess, gg_pubdir50_t req) +{ + size_t size = 5; + int i; + uint32_t res; + char *buf, *p; + struct gg_pubdir50_request *r; + + gg_debug_session(sess, GG_DEBUG_FUNCTION, "** gg_pubdir50(%p, %p);\n", sess, req); + + if (!sess || !req) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_pubdir50() invalid arguments\n"); + errno = EFAULT; + return 0; + } + + if (sess->state != GG_STATE_CONNECTED) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_pubdir50() not connected\n"); + errno = ENOTCONN; + return 0; + } + + for (i = 0; i < req->entries_count; i++) { + /* wyszukiwanie bierze tylko pierwszy wpis */ + if (req->entries[i].num) + continue; + + if (sess->encoding == GG_ENCODING_CP1250) { + size += strlen(req->entries[i].field) + 1; + size += strlen(req->entries[i].value) + 1; + } else { + char *tmp; + + tmp = gg_utf8_to_cp(req->entries[i].field); + + if (tmp == NULL) + return -1; + + size += strlen(tmp) + 1; + + free(tmp); + + tmp = gg_utf8_to_cp(req->entries[i].value); + + if (tmp == NULL) + return -1; + + size += strlen(tmp) + 1; + + free(tmp); + } + } + + if (!(buf = malloc(size))) { + gg_debug_session(sess, GG_DEBUG_MISC, "// gg_pubdir50() out of memory (%d bytes)\n", size); + return 0; + } + + if (!req->seq) + req->seq = (uint32_t)time(NULL); + + res = req->seq; + + r = (struct gg_pubdir50_request*) buf; + r->type = req->type; + r->seq = gg_fix32(req->seq); + + for (i = 0, p = buf + 5; i < req->entries_count; i++) { + if (req->entries[i].num) + continue; + + if (sess->encoding == GG_ENCODING_CP1250) { + strcpy(p, req->entries[i].field); + p += strlen(p) + 1; + + strcpy(p, req->entries[i].value); + p += strlen(p) + 1; + } else { + char *tmp; + + tmp = gg_utf8_to_cp(req->entries[i].field); + + if (tmp == NULL) { + free(buf); + return -1; + } + + strcpy(p, tmp); + p += strlen(tmp) + 1; + free(tmp); + + tmp = gg_utf8_to_cp(req->entries[i].value); + + if (tmp == NULL) { + free(buf); + return -1; + } + + strcpy(p, tmp); + p += strlen(tmp) + 1; + free(tmp); + } + } + + if (gg_send_packet(sess, GG_PUBDIR50_REQUEST, buf, size, NULL, 0) == -1) + res = 0; + + free(buf); + + return res; +} + +/* + * \internal Analizuje przychodzÄ…cy pakiet odpowiedzi i zapisuje wynik + * w strukturze \c gg_event. + * + * \param sess Struktura sesji + * \param e Struktura zdarzenia + * \param packet Pakiet odpowiedzi + * \param length DĹ‚ugość pakietu odpowiedzi + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +int gg_pubdir50_handle_reply_sess(struct gg_session *sess, struct gg_event *e, const char *packet, int length) +{ + const char *end = packet + length, *p; + struct gg_pubdir50_reply *r = (struct gg_pubdir50_reply*) packet; + gg_pubdir50_t res; + int num = 0; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_handle_reply_sess(%p, %p, %p, %d);\n", sess, e, packet, length); + + if (!sess || !e || !packet) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + if (length < 5) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() packet too short\n"); + errno = EINVAL; + return -1; + } + + if (!(res = gg_pubdir50_new(r->type))) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() unable to allocate reply\n"); + return -1; + } + + e->event.pubdir50 = res; + + res->seq = gg_fix32(r->seq); + + switch (res->type) { + case GG_PUBDIR50_READ: + e->type = GG_EVENT_PUBDIR50_READ; + break; + + case GG_PUBDIR50_WRITE: + e->type = GG_EVENT_PUBDIR50_WRITE; + break; + + default: + e->type = GG_EVENT_PUBDIR50_SEARCH_REPLY; + break; + } + + /* brak wynikĂłw? */ + if (length == 5) + return 0; + + /* pomiĹ„ poczÄ…tek odpowiedzi */ + p = packet + 5; + + while (p < end) { + const char *field, *value; + + field = p; + + /* sprawdĹş, czy nie mamy podziaĹ‚u na kolejne pole */ + if (!*field) { + num++; + field++; + } + + value = NULL; + + for (p = field; p < end; p++) { + /* jeĹ›li mamy koniec tekstu... */ + if (!*p) { + /* ...i jeszcze nie mieliĹ›my wartoĹ›ci pola to + * wiemy, ĹĽe po tym zerze jest wartość... */ + if (!value) + value = p + 1; + else + /* ...w przeciwym wypadku koniec + * wartoĹ›ci i moĹĽemy wychodzić + * grzecznie z pÄ™tli */ + break; + } + } + + /* sprawdĹşmy, czy pole nie wychodzi poza pakiet, ĹĽeby nie + * mieć segfaultĂłw, jeĹ›li serwer przestanie zakaĹ„czać pakietĂłw + * przez \0 */ + + if (p == end) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_handle_reply() premature end of packet\n"); + goto failure; + } + + p++; + + /* jeĹ›li dostaliĹ›my namier na nastÄ™pne wyniki, to znaczy ĹĽe + * mamy koniec wynikĂłw i nie jest to kolejna osoba. */ + if (!strcasecmp(field, "nextstart")) { + res->next = atoi(value); + num--; + } else { + if (sess->encoding == GG_ENCODING_CP1250) { + if (gg_pubdir50_add_n(res, num, field, value) == -1) + goto failure; + } else { + char *tmp; + + tmp = gg_cp_to_utf8(value); + + if (tmp == NULL) + goto failure; + + if (gg_pubdir50_add_n(res, num, field, tmp) == -1) { + free(tmp); + goto failure; + } + + free(tmp); + } + } + } + + res->count = num + 1; + + return 0; + +failure: + gg_pubdir50_free(res); + return -1; +} + +/** + * Pobiera pole z odpowiedzi katalogu publicznego. + * + * \param res OdpowiedĹş + * \param num Numer wyniku odpowiedzi + * \param field Nazwa pola (wielkość liter nie ma znaczenia) + * + * \return Wartość pola lub \c NULL jeĹ›li nie znaleziono + * + * \ingroup pubdir50 + */ +const char *gg_pubdir50_get(gg_pubdir50_t res, int num, const char *field) +{ + char *value = NULL; + int i; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_pubdir50_get(%p, %d, \"%s\");\n", res, num, field); + + if (!res || num < 0 || !field) { + gg_debug(GG_DEBUG_MISC, "// gg_pubdir50_get() invalid arguments\n"); + errno = EINVAL; + return NULL; + } + + for (i = 0; i < res->entries_count; i++) { + if (res->entries[i].num == num && !strcasecmp(res->entries[i].field, field)) { + value = res->entries[i].value; + break; + } + } + + return value; +} + +/** + * Zwraca liczbÄ™ wynikĂłw odpowiedzi. + * + * \param res OdpowiedĹş + * + * \return Liczba wynikĂłw lub -1 w przypadku bĹ‚Ä™du + * + * \ingroup pubdir50 + */ +int gg_pubdir50_count(gg_pubdir50_t res) +{ + return (!res) ? -1 : res->count; +} + +/** + * Zwraca rodzaj zapytania lub odpowiedzi. + * + * \param res Zapytanie lub odpowiedĹş + * + * \return Rodzaj lub -1 w przypadku bĹ‚Ä™du + * + * \ingroup pubdir50 + */ +int gg_pubdir50_type(gg_pubdir50_t res) +{ + return (!res) ? -1 : res->type; +} + +/** + * Zwraca numer, od ktĂłrego naleĹĽy rozpoczÄ…c kolejne wyszukiwanie. + * + * DĹ‚uĹĽsze odpowiedzi katalogu publicznego sÄ… wysyĹ‚ane przez serwer + * w mniejszych paczkach. Po otrzymaniu odpowiedzi, jeĹ›li numer kolejnego + * wyszukiwania jest wiÄ™kszy od zera, dalsze wyniki moĹĽna otrzymać przez + * wywoĹ‚anie kolejnego zapytania z okreĹ›lonym numerem poczÄ…tkowym. + * + * \param res OdpowiedĹş + * + * \return Numer lub -1 w przypadku bĹ‚Ä™du + * + * \ingroup pubdir50 + */ +uin_t gg_pubdir50_next(gg_pubdir50_t res) +{ + return (!res) ? (unsigned) -1 : res->next; +} + +/** + * Zwraca numer sekwencyjny zapytania lub odpowiedzi. + * + * \param res Zapytanie lub odpowiedĹş + * + * \return Numer sekwencyjny lub -1 w przypadku bĹ‚Ä™du + * + * \ingroup pubdir50 + */ +uint32_t gg_pubdir50_seq(gg_pubdir50_t res) +{ + return (!res) ? (unsigned) -1 : res->seq; +} + +/* + * Local variables: + * c-indentation-style: k&r + * c-basic-offset: 8 + * indent-tabs-mode: notnil + * End: + * + * vim: shiftwidth=8: + */ diff --git a/protocols/Gadu-Gadu/src/libgadu/resolver.c b/protocols/Gadu-Gadu/src/libgadu/resolver.c new file mode 100644 index 0000000000..1adef3ef9d --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/resolver.c @@ -0,0 +1,766 @@ +/* coding: UTF-8 */ +/* $Id$ */ + +/* + * (C) Copyright 2001-2009 Wojtek Kaniewski + * Robert J. WoĹşny + * Arkadiusz MiĹ›kiewicz + * Tomasz ChiliĹ„ski + * Adam Wysocki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +/** + * \file resolver.c + * + * \brief Funkcje rozwiÄ…zywania nazw + */ + +#ifdef _WIN32 +#include "win32.h" +#else +#include +#include +#include +#endif /* _WIN32 */ + +#ifndef _WIN32 +#include +#endif +#include +#include +#include +#ifndef _WIN32 +#include +#endif +#include + +#include "libgadu.h" +#include "resolver.h" +#include "compat.h" + +/** SposĂłb rozwiÄ…zywania nazw serwerĂłw */ +static gg_resolver_t gg_global_resolver_type = GG_RESOLVER_DEFAULT; + +/** Funkcja rozpoczynajÄ…ca rozwiÄ…zywanie nazwy */ +static int (*gg_global_resolver_start)(SOCKET *fd, void **private_data, const char *hostname); + +/** Funkcja zwalniajÄ…ca zasoby po rozwiÄ…zaniu nazwy */ +static void (*gg_global_resolver_cleanup)(void **private_data, int force); + +#ifdef GG_CONFIG_HAVE_PTHREAD + +#include + +#ifdef GG_CONFIG_HAVE_GETHOSTBYNAME_R +/** + * \internal Funkcja pomocnicza zwalniajÄ…ca zasoby po rozwiÄ…zywaniu nazwy + * w wÄ…tku. + * + * \param data WskaĹşnik na wskaĹşnik bufora zaalokowanego w wÄ…tku + */ +static void gg_gethostbyname_cleaner(void *data) +{ + char **buf_ptr = (char**) data; + + if (buf_ptr != NULL) { + free(*buf_ptr); + *buf_ptr = NULL; + } +} +#endif +#endif /* GG_CONFIG_HAVE_PTHREAD */ + +/** + * \internal Odpowiednik \c gethostbyname zapewniajÄ…cy współbieĹĽność. + * + * JeĹ›li dany system dostarcza \c gethostbyname_r, uĹĽywa siÄ™ tej wersji, jeĹ›li + * nie, to zwykĹ‚ej \c gethostbyname. + * + * \param hostname Nazwa serwera + * \param addr WskaĹşnik na rezultat rozwiÄ…zywania nazwy + * \param pthread Flaga blokowania unicestwiania wÄ…tku podczas alokacji pamiÄ™ci + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +int gg_gethostbyname_real(const char *hostname, struct in_addr *addr, int pthread) +{ +#ifdef GG_CONFIG_HAVE_GETHOSTBYNAME_R + char *buf = NULL; + char *new_buf = NULL; + struct hostent he; + struct hostent *he_ptr = NULL; + size_t buf_len = 1024; + int result = -1; + int h_errnop; + int ret = 0; +#ifdef GG_CONFIG_HAVE_PTHREAD + int old_state; +#endif + +#ifdef GG_CONFIG_HAVE_PTHREAD + pthread_cleanup_push(gg_gethostbyname_cleaner, &buf); + + if (pthread) + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); +#endif + + buf = malloc(buf_len); + +#ifdef GG_CONFIG_HAVE_PTHREAD + if (pthread) + pthread_setcancelstate(old_state, NULL); +#endif + + if (buf != NULL) { +#ifndef sun + while ((ret = gethostbyname_r(hostname, &he, buf, buf_len, &he_ptr, &h_errnop)) == ERANGE) { +#else + while (((he_ptr = gethostbyname_r(hostname, &he, buf, buf_len, &h_errnop)) == NULL) && (errno == ERANGE)) { +#endif + buf_len *= 2; + +#ifdef GG_CONFIG_HAVE_PTHREAD + if (pthread) + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); +#endif + + new_buf = realloc(buf, buf_len); + + if (new_buf != NULL) + buf = new_buf; + +#ifdef GG_CONFIG_HAVE_PTHREAD + if (pthread) + pthread_setcancelstate(old_state, NULL); +#endif + + if (new_buf == NULL) { + ret = ENOMEM; + break; + } + } + + if (ret == 0 && he_ptr != NULL) { + memcpy(addr, he_ptr->h_addr, sizeof(struct in_addr)); + result = 0; + } + +#ifdef GG_CONFIG_HAVE_PTHREAD + if (pthread) + pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_state); +#endif + + free(buf); + buf = NULL; + +#ifdef GG_CONFIG_HAVE_PTHREAD + if (pthread) + pthread_setcancelstate(old_state, NULL); +#endif + } + +#ifdef GG_CONFIG_HAVE_PTHREAD + pthread_cleanup_pop(1); +#endif + + return result; +#else + struct hostent *he; + + he = gethostbyname(hostname); + + if (he == NULL) + return -1; + + memcpy(addr, he->h_addr, sizeof(struct in_addr)); + + return 0; +#endif /* GG_CONFIG_HAVE_GETHOSTBYNAME_R */ +} + +/** + * \internal Odpowiednik \c gethostbyname zapewniajÄ…cy współbieĹĽność. + * + * JeĹ›li dany system dostarcza \c gethostbyname_r, uĹĽywa siÄ™ tej wersji, jeĹ›li + * nie, to zwykĹ‚ej \c gethostbyname. + * + * \param hostname Nazwa serwera + * + * \return Zaalokowana struktura \c in_addr lub NULL w przypadku bĹ‚Ä™du. + */ +struct in_addr *gg_gethostbyname(const char *hostname) +{ + struct in_addr *addr; + + if (!(addr = malloc(sizeof(struct in_addr)))) + return NULL; + + if (gg_gethostbyname_real(hostname, addr, 0)) { + free(addr); + return NULL; + } + return addr; +} + +/** + * \internal Struktura przekazywana do wÄ…tku rozwiÄ…zujÄ…cego nazwÄ™. + */ +struct gg_resolver_fork_data { + int pid; /*< Identyfikator procesu */ +}; + +/** + * \internal RozwiÄ…zuje nazwÄ™ serwera w osobnym procesie. + * + * PoĹ‚Ä…czenia asynchroniczne nie mogÄ… blokować procesu w trakcie rozwiÄ…zywania + * nazwy serwera. W tym celu tworzony jest potok, nowy proces i dopiero w nim + * przeprowadzane jest rozwiÄ…zywanie nazwy. Deskryptor strony do odczytu + * zapisuje siÄ™ w strukturze sieci i czeka na dane w postaci struktury + * \c in_addr. JeĹ›li nie znaleziono nazwy, zwracana jest \c INADDR_NONE. + * + * \param fd WskaĹşnik na zmiennÄ…, gdzie zostanie umieszczony deskryptor + * potoku + * \param priv_data WskaĹşnik na zmiennÄ…, gdzie zostanie umieszczony wskaĹşnik + * do numeru procesu potomnego rozwiÄ…zujÄ…cego nazwÄ™ + * \param hostname Nazwa serwera do rozwiÄ…zania + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +static int gg_resolver_fork_start(SOCKET *fd, void **priv_data, const char *hostname) +{ + struct gg_resolver_fork_data *data = NULL; + struct in_addr addr; + int new_errno; + SOCKET pipes[2]; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_resolver_fork_start(%p, %p, \"%s\");\n", fd, priv_data, hostname); + + if (fd == NULL || priv_data == NULL || hostname == NULL) { + gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + data = malloc(sizeof(struct gg_resolver_fork_data)); + + if (data == NULL) { + gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() out of memory for resolver data\n"); + return -1; + } + + if (pipe(pipes) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno)); + free(data); + return -1; + } + + data->pid = fork(); + + if (data->pid == -1) { + new_errno = errno; + goto cleanup; + } + + if (data->pid == 0) { + gg_sock_close(pipes[0]); + + if ((addr.s_addr = inet_addr(hostname)) == INADDR_NONE) { + /* W przypadku bĹ‚Ä™du gg_gethostbyname_real() zwrĂłci -1 + * i nie zmieni &addr. Tam jest juĹĽ INADDR_NONE, + * wiÄ™c nie musimy robić nic wiÄ™cej. */ + gg_gethostbyname_real(hostname, &addr, 0); + } + + if (gg_sock_write(pipes[1], &addr, sizeof(addr)) != sizeof(addr)) + exit(1); + + exit(0); + } + + gg_sock_close(pipes[1]); + + gg_debug(GG_DEBUG_MISC, "// gg_resolver_fork_start() %p\n", data); + + *fd = pipes[0]; + *priv_data = data; + + return 0; + +cleanup: + free(data); + gg_sock_close(pipes[0]); + gg_sock_close(pipes[1]); + + errno = new_errno; + + return -1; +} + +/** + * \internal Usuwanie zasobĂłw po procesie rozwiÄ…zywaniu nazwy. + * + * Funkcja wywoĹ‚ywana po zakoĹ„czeniu rozwiÄ…zanywania nazwy lub przy zwalnianiu + * zasobĂłw sesji podczas rozwiÄ…zywania nazwy. + * + * \param priv_data WskaĹşnik na zmiennÄ… przechowujÄ…cÄ… wskaĹşnik do prywatnych + * danych + * \param force Flaga usuwania zasobĂłw przed zakoĹ„czeniem dziaĹ‚ania + */ +static void gg_resolver_fork_cleanup(void **priv_data, int force) +{ + struct gg_resolver_fork_data *data; + + if (priv_data == NULL || *priv_data == NULL) + return; + + data = (struct gg_resolver_fork_data*) *priv_data; + *priv_data = NULL; + + if (force) + kill(data->pid, SIGKILL); + + waitpid(data->pid, NULL, WNOHANG); + + free(data); +} + +#ifdef GG_CONFIG_HAVE_PTHREAD + +/** + * \internal Struktura przekazywana do wÄ…tku rozwiÄ…zujÄ…cego nazwÄ™. + */ +struct gg_resolver_pthread_data { + pthread_t thread; /*< Identyfikator wÄ…tku */ + char *hostname; /*< Nazwa serwera */ + SOCKET rfd; /*< Deskryptor do odczytu */ + SOCKET wfd; /*< Deskryptor do zapisu */ +}; + +/** + * \internal Usuwanie zasobĂłw po wÄ…tku rozwiÄ…zywaniu nazwy. + * + * Funkcja wywoĹ‚ywana po zakoĹ„czeniu rozwiÄ…zanywania nazwy lub przy zwalnianiu + * zasobĂłw sesji podczas rozwiÄ…zywania nazwy. + * + * \param priv_data WskaĹşnik na zmiennÄ… przechowujÄ…cÄ… wskaĹşnik do prywatnych + * danych + * \param force Flaga usuwania zasobĂłw przed zakoĹ„czeniem dziaĹ‚ania + */ +static void gg_resolver_pthread_cleanup(void **priv_data, int force) +{ + struct gg_resolver_pthread_data *data; + + if (priv_data == NULL || *priv_data == NULL) + return; + + data = (struct gg_resolver_pthread_data *) *priv_data; + *priv_data = NULL; + + if (force) { + pthread_cancel(&data->thread); + pthread_join(&data->thread, NULL); + } + + free(data->hostname); + data->hostname = NULL; + + if (data->wfd != -1) { + gg_sock_close(data->wfd); + data->wfd = -1; + } + + free(data); +} + +/** + * \internal WÄ…tek rozwiÄ…zujÄ…cy nazwÄ™. + * + * \param arg WskaĹşnik na strukturÄ™ \c gg_resolver_pthread_data + */ +static void *__stdcall gg_resolver_pthread_thread(void *arg) +{ + struct gg_resolver_pthread_data *data = arg; + struct in_addr addr; + + pthread_detach(pthread_self()); + + if ((addr.s_addr = inet_addr(data->hostname)) == INADDR_NONE) { + /* W przypadku bĹ‚Ä™du gg_gethostbyname_real() zwrĂłci -1 + * i nie zmieni &addr. Tam jest juĹĽ INADDR_NONE, + * wiÄ™c nie musimy robić nic wiÄ™cej. */ + gg_gethostbyname_real(data->hostname, &addr, 1); + } + + if (gg_sock_write(data->wfd, &addr, sizeof(addr)) == sizeof(addr)) + pthread_exit(NULL); + else + pthread_exit((void*) -1); + + return NULL; /* ĹĽeby kompilator nie marudziĹ‚ */ +} + +/** + * \internal RozwiÄ…zuje nazwÄ™ serwera w osobnym wÄ…tku. + * + * Funkcja dziaĹ‚a analogicznie do \c gg_resolver_fork_start(), z tÄ… różnicÄ…, + * ĹĽe dziaĹ‚a na wÄ…tkach, nie procesach. Jest dostÄ™pna wyĹ‚Ä…cznie gdy podczas + * kompilacji wĹ‚Ä…czono odpowiedniÄ… opcjÄ™. + * + * \param fd WskaĹşnik na zmiennÄ…, gdzie zostanie umieszczony deskryptor + * potoku + * \param priv_data WskaĹşnik na zmiennÄ…, gdzie zostanie umieszczony wskaĹşnik + * do prywatnych danych wÄ…tku rozwiÄ…zujÄ…cego nazwÄ™ + * \param hostname Nazwa serwera do rozwiÄ…zania + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +static int gg_resolver_pthread_start(SOCKET *fd, void **priv_data, const char *hostname) +{ + struct gg_resolver_pthread_data *data = NULL; + int new_errno; + SOCKET pipes[2]; + + gg_debug(GG_DEBUG_FUNCTION, "** gg_resolver_pthread_start(%p, %p, \"%s\");\n", fd, priv_data, hostname); + + if (fd == NULL || priv_data == NULL || hostname == NULL) { + gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() invalid arguments\n"); + errno = EFAULT; + return -1; + } + + data = malloc(sizeof(struct gg_resolver_pthread_data)); + + if (data == NULL) { + gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() out of memory for resolver data\n"); + return -1; + } + + if (pipe(pipes) == -1) { + gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() unable to create pipes (errno=%d, %s)\n", errno, strerror(errno)); + free(data); + return -1; + } + + data->hostname = strdup(hostname); + + if (data->hostname == NULL) { + gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() out of memory\n"); + new_errno = errno; + goto cleanup; + } + + data->rfd = pipes[0]; + data->wfd = pipes[1]; + + if (pthread_create(&data->thread, NULL, gg_resolver_pthread_thread, data)) { + gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() unable to create thread\n"); + new_errno = errno; + goto cleanup; + } + + gg_debug(GG_DEBUG_MISC, "// gg_resolver_pthread_start() %p\n", data); + + *fd = pipes[0]; + *priv_data = data; + + return 0; + +cleanup: + if (data) { + free(data->hostname); + free(data); + } + + gg_sock_close(pipes[0]); + gg_sock_close(pipes[1]); + + errno = new_errno; + + return -1; +} + +#endif /* GG_CONFIG_HAVE_PTHREAD */ + +/** + * Ustawia sposĂłb rozwiÄ…zywania nazw w sesji. + * + * \param gs Struktura sesji + * \param type SposĂłb rozwiÄ…zywania nazw (patrz \ref build-resolver) + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +int gg_session_set_resolver(struct gg_session *gs, gg_resolver_t type) +{ + if (gs == NULL) { + errno = EINVAL; + return -1; + } + + if (type == GG_RESOLVER_DEFAULT) { + if (gg_global_resolver_type != GG_RESOLVER_DEFAULT) { + gs->resolver_type = gg_global_resolver_type; + gs->resolver_start = gg_global_resolver_start; + gs->resolver_cleanup = gg_global_resolver_cleanup; + return 0; + } + +#if !defined(GG_CONFIG_HAVE_PTHREAD) || !defined(GG_CONFIG_PTHREAD_DEFAULT) + type = GG_RESOLVER_FORK; +#else + type = GG_RESOLVER_PTHREAD; +#endif + } + + switch (type) { + case GG_RESOLVER_FORK: + gs->resolver_type = type; + gs->resolver_start = gg_resolver_fork_start; + gs->resolver_cleanup = gg_resolver_fork_cleanup; + return 0; + +#ifdef GG_CONFIG_HAVE_PTHREAD + case GG_RESOLVER_PTHREAD: + gs->resolver_type = type; + gs->resolver_start = gg_resolver_pthread_start; + gs->resolver_cleanup = gg_resolver_pthread_cleanup; + return 0; +#endif + + default: + errno = EINVAL; + return -1; + } +} + +/** + * Zwraca sposĂłb rozwiÄ…zywania nazw w sesji. + * + * \param gs Struktura sesji + * + * \return SposĂłb rozwiÄ…zywania nazw + */ +gg_resolver_t gg_session_get_resolver(struct gg_session *gs) +{ + if (gs == NULL) { + errno = EINVAL; + return GG_RESOLVER_INVALID; + } + + return gs->resolver_type; +} + +/** + * Ustawia wĹ‚asny sposĂłb rozwiÄ…zywania nazw w sesji. + * + * \param gs Struktura sesji + * \param resolver_start Funkcja rozpoczynajÄ…ca rozwiÄ…zywanie nazwy + * \param resolver_cleanup Funkcja zwalniajÄ…ca zasoby + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +int gg_session_set_custom_resolver(struct gg_session *gs, int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int)) +{ + if (gs == NULL || resolver_start == NULL || resolver_cleanup == NULL) { + errno = EINVAL; + return -1; + } + + gs->resolver_type = GG_RESOLVER_CUSTOM; + gs->resolver_start = resolver_start; + gs->resolver_cleanup = resolver_cleanup; + + return 0; +} + +/** + * Ustawia sposĂłb rozwiÄ…zywania nazw poĹ‚Ä…czenia HTTP. + * + * \param gh Struktura poĹ‚Ä…czenia + * \param type SposĂłb rozwiÄ…zywania nazw (patrz \ref build-resolver) + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +int gg_http_set_resolver(struct gg_http *gh, gg_resolver_t type) +{ + if (gh == NULL) { + errno = EINVAL; + return -1; + } + + if (type == GG_RESOLVER_DEFAULT) { + if (gg_global_resolver_type != GG_RESOLVER_DEFAULT) { + gh->resolver_type = gg_global_resolver_type; + gh->resolver_start = gg_global_resolver_start; + gh->resolver_cleanup = gg_global_resolver_cleanup; + return 0; + } + +#if !defined(GG_CONFIG_HAVE_PTHREAD) || !defined(GG_CONFIG_PTHREAD_DEFAULT) + type = GG_RESOLVER_FORK; +#else + type = GG_RESOLVER_PTHREAD; +#endif + } + + switch (type) { + case GG_RESOLVER_FORK: + gh->resolver_type = type; + gh->resolver_start = gg_resolver_fork_start; + gh->resolver_cleanup = gg_resolver_fork_cleanup; + return 0; + +#ifdef GG_CONFIG_HAVE_PTHREAD + case GG_RESOLVER_PTHREAD: + gh->resolver_type = type; + gh->resolver_start = gg_resolver_pthread_start; + gh->resolver_cleanup = gg_resolver_pthread_cleanup; + return 0; +#endif + + default: + errno = EINVAL; + return -1; + } +} + +/** + * Zwraca sposĂłb rozwiÄ…zywania nazw poĹ‚Ä…czenia HTTP. + * + * \param gh Struktura poĹ‚Ä…czenia + * + * \return SposĂłb rozwiÄ…zywania nazw + */ +gg_resolver_t gg_http_get_resolver(struct gg_http *gh) +{ + if (gh == NULL) { + errno = EINVAL; + return GG_RESOLVER_INVALID; + } + + return gh->resolver_type; +} + +/** + * Ustawia wĹ‚asny sposĂłb rozwiÄ…zywania nazw poĹ‚Ä…czenia HTTP. + * + * \param gh Struktura sesji + * \param resolver_start Funkcja rozpoczynajÄ…ca rozwiÄ…zywanie nazwy + * \param resolver_cleanup Funkcja zwalniajÄ…ca zasoby + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +int gg_http_set_custom_resolver(struct gg_http *gh, int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int)) +{ + if (gh == NULL || resolver_start == NULL || resolver_cleanup == NULL) { + errno = EINVAL; + return -1; + } + + gh->resolver_type = GG_RESOLVER_CUSTOM; + gh->resolver_start = resolver_start; + gh->resolver_cleanup = resolver_cleanup; + + return 0; +} + +/** + * Ustawia sposĂłb rozwiÄ…zywania nazw globalnie dla biblioteki. + * + * \param type SposĂłb rozwiÄ…zywania nazw (patrz \ref build-resolver) + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +int gg_global_set_resolver(gg_resolver_t type) +{ + switch (type) { + case GG_RESOLVER_DEFAULT: + gg_global_resolver_type = type; + gg_global_resolver_start = NULL; + gg_global_resolver_cleanup = NULL; + return 0; + + case GG_RESOLVER_FORK: + gg_global_resolver_type = type; + gg_global_resolver_start = gg_resolver_fork_start; + gg_global_resolver_cleanup = gg_resolver_fork_cleanup; + return 0; + +#ifdef GG_CONFIG_HAVE_PTHREAD + case GG_RESOLVER_PTHREAD: + gg_global_resolver_type = type; + gg_global_resolver_start = gg_resolver_pthread_start; + gg_global_resolver_cleanup = gg_resolver_pthread_cleanup; + return 0; +#endif + + default: + errno = EINVAL; + return -1; + } +} + +/** + * Zwraca sposĂłb rozwiÄ…zywania nazw globalnie dla biblioteki. + * + * \return SposĂłb rozwiÄ…zywania nazw + */ +gg_resolver_t gg_global_get_resolver(void) +{ + return gg_global_resolver_type; +} + +/** + * Ustawia wĹ‚asny sposĂłb rozwiÄ…zywania nazw globalnie dla biblioteki. + * + * \param resolver_start Funkcja rozpoczynajÄ…ca rozwiÄ…zywanie nazwy + * \param resolver_cleanup Funkcja zwalniajÄ…ca zasoby + * + * Parametry funkcji rozpoczynajÄ…cej rozwiÄ…zywanie nazwy wyglÄ…dajÄ… nastÄ™pujÄ…co: + * - \c "SOCKET *fd" — wskaĹşnik na zmiennÄ…, gdzie zostanie umieszczony deskryptor potoku + * - \c "void **priv_data" — wskaĹşnik na zmiennÄ…, gdzie moĹĽna umieĹ›cić wskaĹşnik do prywatnych danych na potrzeby rozwiÄ…zywania nazwy + * - \c "const char *name" — nazwa serwera do rozwiÄ…zania + * + * Parametry funkcji zwalniajÄ…cej zasoby wyglÄ…dajÄ… nastÄ™pujÄ…co: + * - \c "void **priv_data" — wskaĹşnik na zmiennÄ… przechowujÄ…cÄ… wskaĹşnik do prywatnych danych, naleĹĽy go ustawić na \c NULL po zakoĹ„czeniu + * - \c "int force" — flaga mĂłwiÄ…ca o tym, ĹĽe zasoby sÄ… zwalniane przed zakoĹ„czeniem rozwiÄ…zywania nazwy, np. z powodu zamkniÄ™cia sesji. + * + * WĹ‚asny kod rozwiÄ…zywania nazwy powinien stworzyć potok, parÄ™ gniazd lub + * inny deskryptor pozwalajÄ…cy na co najmniej jednostronnÄ… komunikacjÄ™ i + * przekazać go w parametrze \c fd. Po zakoĹ„czeniu rozwiÄ…zywania nazwy, + * powinien wysĹ‚ać otrzymany adres IP w postaci sieciowej (big-endian) do + * deskryptora. JeĹ›li rozwiÄ…zywanie nazwy siÄ™ nie powiedzie, naleĹĽy wysĹ‚ać + * \c INADDR_NONE. NastÄ™pnie zostanie wywoĹ‚ana funkcja zwalniajÄ…ca zasoby + * z parametrem \c force rĂłwnym \c 0. Gdyby sesja zostaĹ‚a zakoĹ„czona przed + * rozwiÄ…zaniem nazwy, np. za pomocÄ… funkcji \c gg_logoff(), funkcja + * zwalniajÄ…ca zasoby zostanie wywoĹ‚ana z parametrem \c force rĂłwnym \c 1. + * + * \return 0 jeĹ›li siÄ™ powiodĹ‚o, -1 w przypadku bĹ‚Ä™du + */ +int gg_global_set_custom_resolver(int (*resolver_start)(SOCKET*, void**, const char*), void (*resolver_cleanup)(void**, int)) +{ + if (resolver_start == NULL || resolver_cleanup == NULL) { + errno = EINVAL; + return -1; + } + + gg_global_resolver_type = GG_RESOLVER_CUSTOM; + gg_global_resolver_start = resolver_start; + gg_global_resolver_cleanup = resolver_cleanup; + + return 0; +} + diff --git a/protocols/Gadu-Gadu/src/libgadu/resolver.h b/protocols/Gadu-Gadu/src/libgadu/resolver.h new file mode 100644 index 0000000000..145c5178a4 --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/resolver.h @@ -0,0 +1,33 @@ +/* coding: UTF-8 */ +/* $Id$ */ + +/* + * (C) Copyright 2008 Wojtek Kaniewski + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +#ifndef LIBGADU_RESOLVER_H +#define LIBGADU_RESOLVER_H + +#ifdef _WIN32 +#include "win32.h" +#else +#include +#endif /* _WIN32 */ + +int gg_gethostbyname_real(const char *hostname, struct in_addr *result, int pthread); + +#endif /* LIBGADU_RESOLVER_H */ diff --git a/protocols/Gadu-Gadu/src/libgadu/sha1.c b/protocols/Gadu-Gadu/src/libgadu/sha1.c new file mode 100644 index 0000000000..124a1cffaa --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/sha1.c @@ -0,0 +1,308 @@ +/* coding: UTF-8 */ +/* $Id: sha1.c,v 1.4 2007-07-20 23:00:50 wojtekka Exp $ */ + +/* + * (C) Copyright 2007 Wojtek Kaniewski + * + * Public domain SHA-1 implementation by Steve Reid + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License Version + * 2.1 as published by the Free Software Foundation. + * + * 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +/** + * \file sha1.c + * + * \brief Funkcje wyznaczania skrÄ‚Ĺ‚tu SHA1 + */ + +#include +#include +#ifdef _WIN32 +#include "win32.h" +#else +#include +#endif + +#include "libgadu.h" + +/** \cond ignore */ + +#if defined(GG_CONFIG_HAVE_OPENSSL) && !defined(GG_CONFIG_MIRANDA) + +#include + +#else + +/* +SHA-1 in C +By Steve Reid +100% Public Domain + +Modified by Wojtek Kaniewski for compatibility +with libgadu and OpenSSL API. + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#include + +typedef struct { + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +} SHA_CTX; + +static void SHA1_Transform(uint32_t state[5], const unsigned char buffer[64]); +static void SHA1_Init(SHA_CTX* context); +static void SHA1_Update(SHA_CTX* context, const unsigned char* data, unsigned int len); +static void SHA1_Final(unsigned char digest[20], SHA_CTX* context); + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#ifndef GG_CONFIG_BIGENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#else +#define blk0(i) block->l[i] +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +static void SHA1_Transform(uint32_t state[5], const unsigned char buffer[64]) +{ +uint32_t a, b, c, d, e; +typedef union { + unsigned char c[64]; + uint32_t l[16]; +} CHAR64LONG16; +CHAR64LONG16* block; +static unsigned char workspace[64]; + block = (CHAR64LONG16*)workspace; + memcpy(block, buffer, 64); + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +} + + +/* SHA1_Init - Initialize new context */ + +static void SHA1_Init(SHA_CTX* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +static void SHA1_Update(SHA_CTX* context, const unsigned char* data, unsigned int len) +{ +unsigned int i, j; + + j = (context->count[0] >> 3) & 63; + if ((context->count[0] += len << 3) < (len << 3)) context->count[1]++; + context->count[1] += (len >> 29); + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1_Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1_Transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +static void SHA1_Final(unsigned char digest[20], SHA_CTX* context) +{ +uint32_t i, j; +unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8)) & 255); /* Endian independent */ + } + SHA1_Update(context, (unsigned char *)"\200", 1); + while ((context->count[0] & 504) != 448) { + SHA1_Update(context, (unsigned char *)"\0", 1); + } + SHA1_Update(context, finalcount, 8); /* Should cause a SHA1_Transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8)) & 255); + } + /* Wipe variables */ + i = j = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); +#ifdef SHA1HANDSOFF /* make SHA1_Transform overwrite it's own static vars */ + SHA1_Transform(context->state, context->buffer); +#endif +} + +#endif /* GG_CONFIG_HAVE_OPENSSL */ + +/** \endcond */ + +/** \cond internal */ + +/** + * \internal Liczy skrÄ‚Ĺ‚t SHA1 z ziarna i hasła. + * + * \param password Hasło + * \param seed Ziarno + * \param result Bufor na wynik funkcji skrÄ‚Ĺ‚tu (20 bajtÄ‚Ĺ‚w) + */ +void gg_login_hash_sha1(const char *password, uint32_t seed, uint8_t *result) +{ + SHA_CTX ctx; + + SHA1_Init(&ctx); + SHA1_Update(&ctx, (const unsigned char*) password, (unsigned int)strlen(password)); + seed = gg_fix32(seed); + SHA1_Update(&ctx, (uint8_t*) &seed, 4); + + SHA1_Final(result, &ctx); +} + +/** + * \internal Liczy skrÄ‚Ĺ‚t SHA1 z pliku. + * + * \param fd Deskryptor pliku + * \param result WskaĹşnik na skrÄ‚Ĺ‚t + * + * \return 0 lub -1 + */ +int gg_file_hash_sha1(int fd, uint8_t *result) +{ + unsigned char buf[4096]; + SHA_CTX ctx; + off_t pos, len; + int res; + + if ((pos = lseek(fd, 0, SEEK_CUR)) == (off_t) -1) + return -1; + + if ((len = lseek(fd, 0, SEEK_END)) == (off_t) -1) + return -1; + + if (lseek(fd, 0, SEEK_SET) == (off_t) -1) + return -1; + + SHA1_Init(&ctx); + + if (len <= 10485760) { + while ((res = read(fd, buf, sizeof(buf))) > 0) + SHA1_Update(&ctx, buf, res); + } else { + int i; + + for (i = 0; i < 9; i++) { + int j; + + if (lseek(fd, (len - 1048576) / 9 * i, SEEK_SET) == (off_t) - 1) + return -1; + + for (j = 0; j < 1048576 / sizeof(buf); j++) { + if ((res = read(fd, buf, sizeof(buf))) != sizeof(buf)) { + res = -1; + break; + } + + SHA1_Update(&ctx, buf, res); + } + + if (res == -1) + break; + } + } + + if (res == -1) + return -1; + + SHA1_Final(result, &ctx); + + if (lseek(fd, pos, SEEK_SET) == (off_t) -1) + return -1; + + return 0; +} + +/** \endcond */ diff --git a/protocols/Gadu-Gadu/src/libgadu/win32.c b/protocols/Gadu-Gadu/src/libgadu/win32.c new file mode 100644 index 0000000000..4f5ad3ff5d --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/win32.c @@ -0,0 +1,65 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2009 Adam Strzelecki +// Copyright (c) 2009-2010 Bartosz Białek +// +// 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. +//////////////////////////////////////////////////////////////////////////////// + +#ifdef _WIN32 +#include "win32.h" + +int sockpipe(SOCKET filedes[2]) +{ + SOCKET sock; + struct sockaddr_in sin; + unsigned int len = sizeof(sin); + + filedes[0] = filedes[1] = INVALID_SOCKET; + + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) + return -1; + + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_port = htons(0); + sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (bind(sock, (SOCKADDR *)&sin, len) == SOCKET_ERROR || + listen(sock, 1) == SOCKET_ERROR || + getsockname(sock, (SOCKADDR *)&sin, &len) == SOCKET_ERROR) { + closesocket(sock); + return -1; + } + + if ((filedes[1] = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET || + connect(filedes[1], (SOCKADDR *)&sin, len) == SOCKET_ERROR) { + closesocket(sock); + return -1; + } + + if ((filedes[0] = accept(sock, (SOCKADDR *)&sin, &len)) == INVALID_SOCKET) { + closesocket(filedes[1]); + filedes[1] = INVALID_SOCKET; + closesocket(sock); + return -1; + } + + closesocket(sock); + return 0; +} + +#endif /* _WIN32 */ diff --git a/protocols/Gadu-Gadu/src/libgadu/win32.h b/protocols/Gadu-Gadu/src/libgadu/win32.h new file mode 100644 index 0000000000..d432a359ac --- /dev/null +++ b/protocols/Gadu-Gadu/src/libgadu/win32.h @@ -0,0 +1,75 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2009 Adam Strzelecki +// Copyright (c) 2009-2010 Bartosz Białek +// +// 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. +//////////////////////////////////////////////////////////////////////////////// + +/* Windows wrappers for missing POSIX functions */ + +#ifndef __GG_WIN32_H +#define __GG_WIN32_H + +#include +#include + +/* Some Visual C++ overrides having no problems with MinGW */ +#ifdef _MSC_VER +#define S_IWUSR 0x0080 +/* Make sure we included errno before that */ +#include +#endif + +#ifdef EINPROGRESS +# undef EINPROGRESS +#endif +#ifdef ENOTCONN +# undef ENOTCONN +#endif +#ifdef EINTR +# undef EINTR +#endif +#ifdef ECONNRESET +# undef ECONNRESET +#endif +#ifdef ETIMEDOUT +# undef ETIMEDOUT +#endif + +#define EINPROGRESS WSAEINPROGRESS +#define ENOTCONN WSAENOTCONN +#define EINTR WSAEINTR +#define ECONNRESET WSAECONNRESET +#define ETIMEDOUT WSAETIMEDOUT + +#define WNOHANG WHOHANG +#define SHUT_RDWR 2 + +/* Defined in gg.c custom error reporting function */ +#ifdef GG_CONFIG_MIRANDA +char *ws_strerror(int code); +#define strerror(x) ws_strerror(x) +#endif + +#define fork() (-1) +int sockpipe(SOCKET filedes[2]); +#define pipe(filedes) sockpipe(filedes) +#define wait(x) (-1) +#define waitpid(x,y,z) (-1) +#define ioctl(fd,request,val) ioctlsocket(fd,request,(unsigned long *)val) + +#endif /* __GG_WIN32_H */ diff --git a/protocols/Gadu-Gadu/src/links.cpp b/protocols/Gadu-Gadu/src/links.cpp new file mode 100644 index 0000000000..53770f0bcd --- /dev/null +++ b/protocols/Gadu-Gadu/src/links.cpp @@ -0,0 +1,173 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2009-2012 Bartosz Białek +// +// 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 "gg.h" +#include "m_assocmgr.h" + +////////////////////////////////////////////////////////// +// File Association Manager support + +#define GGS_PARSELINK "%s/ParseLink" +#define GGS_MENUCHOOSE "%s/MenuChoose" + +static HANDLE hInstanceMenu; +static HANDLE hServiceMenuChoose; +static HANDLE hServiceParseLink; + +static INT_PTR gg_menuchoose(WPARAM wParam, LPARAM lParam) +{ + if (lParam) + *(void**)lParam = (void*)wParam; + return 0; +} + +static INT_PTR gg_parselink(WPARAM wParam, LPARAM lParam) +{ + char *arg = (char*)lParam; + list_t l = g_Instances; + GGPROTO *gg = NULL; + uin_t uin; + CLISTMENUITEM mi = {0}; + int items = 0; + + if (list_count(l) == 0) + return 0; + + if (arg == NULL) + return 1; + + arg = strchr(arg, ':'); + + if (arg == NULL) + return 1; + + for (++arg; *arg == '/'; ++arg); + uin = atoi(arg); + + if (!uin) + return 1; + + for (mi.cbSize = sizeof(mi); l; l = l->next) + { + GGPROTO *gginst = (GGPROTO*)l->data; + + mi.flags = CMIM_FLAGS; + if (gginst->m_iStatus > ID_STATUS_OFFLINE) + { + ++items; + gg = (GGPROTO*)l->data; + mi.flags |= CMIM_ICON; + mi.hIcon = LoadSkinnedProtoIcon(gg->m_szModuleName, gg->m_iStatus); + } + else + { + mi.flags |= CMIF_HIDDEN; + mi.hIcon = NULL; + } + + CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)gginst->hInstanceMenuItem, (LPARAM)&mi); + if (mi.hIcon) + CallService(MS_SKIN2_RELEASEICON, (WPARAM)mi.hIcon, 0); + } + + if (items > 1) + { + ListParam param = {0}; + HMENU hMenu = CreatePopupMenu(); + POINT pt; + int cmd = 0; + + param.MenuObjectHandle = hInstanceMenu; + CallService(MO_BUILDMENU, (WPARAM)hMenu, (LPARAM)¶m); + + GetCursorPos(&pt); + cmd = TrackPopupMenu(hMenu, TPM_RETURNCMD, pt.x, pt.y, 0, pcli->hwndContactList, NULL); + DestroyMenu(hMenu); + + if (cmd) + CallService(MO_PROCESSCOMMANDBYMENUIDENT, cmd, (LPARAM)&gg); + } + + if (gg == NULL) + return 0; + + if (ServiceExists(MS_MSG_SENDMESSAGE)) + { + HANDLE hContact = gg->getcontact(uin, 1, 0, NULL); + if (hContact != NULL) + CallService(MS_MSG_SENDMESSAGE, (WPARAM)hContact, 0); + } + + return 0; +} + +void gg_links_instancemenu_init() +{ + char service[MAXMODULELABELLENGTH]; + TMenuParam mnu = {0}; + TMO_MenuItem tmi = {0}; + + mir_snprintf(service, sizeof(service), GGS_MENUCHOOSE, GGDEF_PROTO); + hServiceMenuChoose = CreateServiceFunction(service, gg_menuchoose); + mnu.cbSize = sizeof(mnu); + mnu.name = "GGAccountChooser"; + mnu.ExecService = service; + hInstanceMenu = (HANDLE)CallService(MO_CREATENEWMENUOBJECT, 0, (LPARAM)&mnu); + + tmi.cbSize = sizeof(tmi); + tmi.flags = CMIF_ICONFROMICOLIB; + tmi.pszName = "Cancel"; + tmi.position = 9999999; + tmi.hIcolibItem = LoadSkinnedIconHandle(SKINICON_OTHER_DELETE); + CallService(MO_ADDNEWMENUITEM, (WPARAM)hInstanceMenu, (LPARAM)&tmi); +} + +void gg_links_init() +{ + if (ServiceExists(MS_ASSOCMGR_ADDNEWURLTYPE)) + { + char service[MAXMODULELABELLENGTH]; + + mir_snprintf(service, sizeof(service), GGS_PARSELINK, GGDEF_PROTO); + hServiceParseLink = CreateServiceFunction(service, gg_parselink); + AssocMgr_AddNewUrlType("gg:", Translate("Gadu-Gadu Link Protocol"), hInstance, IDI_GG, service, 0); + } +} + +void gg_links_destroy() +{ + DestroyServiceFunction(hServiceMenuChoose); + if (ServiceExists(MS_ASSOCMGR_ADDNEWURLTYPE)) + DestroyServiceFunction(hServiceParseLink); +} + +void GGPROTO::links_instance_init() +{ + if (ServiceExists(MS_ASSOCMGR_ADDNEWURLTYPE)) + { + TMO_MenuItem tmi = {0}; + tmi.cbSize = sizeof(tmi); + tmi.flags = CMIF_TCHAR; + tmi.ownerdata = this; + tmi.position = list_count(g_Instances); + tmi.ptszName = m_tszUserName; + hInstanceMenuItem = (HANDLE)CallService(MO_ADDNEWMENUITEM, (WPARAM)hInstanceMenu, (LPARAM)&tmi); + } +} diff --git a/protocols/Gadu-Gadu/src/oauth.cpp b/protocols/Gadu-Gadu/src/oauth.cpp new file mode 100644 index 0000000000..43983edb6d --- /dev/null +++ b/protocols/Gadu-Gadu/src/oauth.cpp @@ -0,0 +1,584 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2010 Bartosz Białek +// +// 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 "gg.h" +#include +#include +#include "protocol.h" + +////////////////////////////////////////////////////////// +// OAuth 1.0 implementation + +// Service Provider must accept the HTTP Authorization header + +// RSA-SHA1 signature method (see RFC 3447 section 8.2 +// and RSASSA-PKCS1-v1_5 algorithm) is unimplemented + +typedef struct +{ + char *name; + char *value; +} OAUTHPARAMETER; + +typedef enum +{ + HMACSHA1, + RSASHA1, + PLAINTEXT +} OAUTHSIGNMETHOD; + +static int paramsortFunc(const OAUTHPARAMETER *p1, const OAUTHPARAMETER *p2) +{ + int res = strcmp(p1->name, p2->name); + return res != 0 ? res : strcmp(p1->value, p2->value); +} + +// HMAC-SHA1 (see RFC 2104 for details) +void hmacsha1_hash(mir_sha1_byte_t *text, int text_len, mir_sha1_byte_t *key, int key_len, + mir_sha1_byte_t digest[MIR_SHA1_HASH_SIZE]) +{ + mir_sha1_ctx context; + mir_sha1_byte_t k_ipad[64]; + mir_sha1_byte_t k_opad[64]; + int i; + + if (key_len > 64) { + mir_sha1_ctx tctx; + mir_sha1_byte_t tk[MIR_SHA1_HASH_SIZE]; + + mir_sha1_init(&tctx); + mir_sha1_append(&tctx, key, key_len); + mir_sha1_finish(&tctx, tk); + + key = tk; + key_len = MIR_SHA1_HASH_SIZE; + } + + memset(k_ipad, 0x36, 64); + memset(k_opad, 0x5c, 64); + + for (i = 0; i < key_len; i++) { + k_ipad[i] ^= key[i]; + k_opad[i] ^= key[i]; + } + + mir_sha1_init(&context); + mir_sha1_append(&context, k_ipad, 64); + mir_sha1_append(&context, text, text_len); + mir_sha1_finish(&context, digest); + + mir_sha1_init(&context); + mir_sha1_append(&context, k_opad, 64); + mir_sha1_append(&context, digest, MIR_SHA1_HASH_SIZE); + mir_sha1_finish(&context, digest); +} + +// see RFC 3986 for details +#define isunreserved(c) ( isalnum((unsigned char)c) || c == '-' || c == '.' || c == '_' || c == '~') +char *oauth_uri_escape(const char *str) +{ + char *res; + int size, ix = 0; + + if (str == NULL) return mir_strdup(""); + + size = (int)strlen(str) + 1; + res = (char *)mir_alloc(size); + + while (*str) { + if (!isunreserved(*str)) { + size += 2; + res = (char *)mir_realloc(res, size); + mir_snprintf(&res[ix], 4, "%%%X%X", (*str >> 4) & 15, *str & 15); + ix += 3; + } + else + res[ix++] = *str; + str++; + } + res[ix] = 0; + + return res; +} + +// generates Signature Base String + +char *oauth_generate_signature(LIST ¶ms, const char *httpmethod, const char *url) +{ + char *res, *urlenc, *urlnorm; + OAUTHPARAMETER *p; + int i, ix = 0, size; + + if (httpmethod == NULL || url == NULL || !params.getCount()) return mir_strdup(""); + + urlnorm = (char *)mir_alloc(strlen(url) + 1); + while (*url) { + if (*url == '?' || *url == '#') break; // see RFC 3986 section 3 + urlnorm[ix++] = tolower(*url); + url++; + } + urlnorm[ix] = 0; + if ((res = strstr(urlnorm, ":80")) != NULL) + memmove(res, res + 3, strlen(res) - 2); + else if ((res = strstr(urlnorm, ":443")) != NULL) + memmove(res, res + 4, strlen(res) - 3); + + urlenc = oauth_uri_escape(urlnorm); + mir_free(urlnorm); + size = (int)strlen(httpmethod) + (int)strlen(urlenc) + 1 + 2; + + for (i = 0; i < params.getCount(); i++) { + p = params[i]; + if (!strcmp(p->name, "oauth_signature")) continue; + if (i > 0) size += 3; + size += (int)strlen(p->name) + (int)strlen(p->value) + 3; + } + + res = (char *)mir_alloc(size); + strcpy(res, httpmethod); + strcat(res, "&"); + strcat(res, urlenc); + mir_free(urlenc); + strcat(res, "&"); + + for (i = 0; i < params.getCount(); i++) { + p = params[i]; + if (!strcmp(p->name, "oauth_signature")) continue; + if (i > 0) strcat(res, "%26"); + strcat(res, p->name); + strcat(res, "%3D"); + strcat(res, p->value); + } + + return res; +} + +char *oauth_getparam(LIST ¶ms, const char *name) +{ + OAUTHPARAMETER *p; + int i; + + if (name == NULL) return NULL; + + for (i = 0; i < params.getCount(); i++) { + p = params[i]; + if (!strcmp(p->name, name)) + return p->value; + } + + return NULL; +} + +void oauth_setparam(LIST ¶ms, const char *name, const char *value) +{ + OAUTHPARAMETER *p; + int i; + + if (name == NULL) return; + + for (i = 0; i < params.getCount(); i++) { + p = params[i]; + if (!strcmp(p->name, name)) { + mir_free(p->value); + p->value = oauth_uri_escape(value); + return; + } + } + + p = (OAUTHPARAMETER*)mir_alloc(sizeof(OAUTHPARAMETER)); + p->name = oauth_uri_escape(name); + p->value = oauth_uri_escape(value); + params.insert(p); +} + +void oauth_freeparams(LIST ¶ms) +{ + OAUTHPARAMETER *p; + int i; + + for (i = 0; i < params.getCount(); i++) { + p = params[i]; + mir_free(p->name); + mir_free(p->value); + } +} + +int oauth_sign_request(LIST ¶ms, const char *httpmethod, const char *url, + const char *consumer_secret, const char *token_secret) +{ + char *sign = NULL, *signmethod; + + if (!params.getCount()) return -1; + + signmethod = oauth_getparam(params, "oauth_signature_method"); + if (signmethod == NULL) return -1; + + if (!strcmp(signmethod, "HMAC-SHA1")) { + char *text = oauth_generate_signature(params, httpmethod, url); + char *key; + char *csenc = oauth_uri_escape(consumer_secret); + char *tsenc = oauth_uri_escape(token_secret); + mir_sha1_byte_t digest[MIR_SHA1_HASH_SIZE]; + NETLIBBASE64 nlb64 = {0}; + int signlen; + + key = (char *)mir_alloc(strlen(csenc) + strlen(tsenc) + 2); + strcpy(key, csenc); + strcat(key, "&"); + strcat(key, tsenc); + mir_free(csenc); + mir_free(tsenc); + + hmacsha1_hash((BYTE*)text, (int)strlen(text), (BYTE*)key, (int)strlen(key), digest); + + signlen = Netlib_GetBase64EncodedBufferSize(MIR_SHA1_HASH_SIZE); + sign = (char *)mir_alloc(signlen); + nlb64.pszEncoded = sign; + nlb64.cchEncoded = signlen; + nlb64.pbDecoded = digest; + nlb64.cbDecoded = MIR_SHA1_HASH_SIZE; + CallService(MS_NETLIB_BASE64ENCODE, 0, (LPARAM)&nlb64); + + mir_free(text); + mir_free(key); + } +// else if (!strcmp(signmethod, "RSA-SHA1")) { // unimplemented +// } + else { // PLAINTEXT + char *csenc = oauth_uri_escape(consumer_secret); + char *tsenc = oauth_uri_escape(token_secret); + + sign = (char *)mir_alloc(strlen(csenc) + strlen(tsenc) + 2); + strcpy(sign, csenc); + strcat(sign, "&"); + strcat(sign, tsenc); + mir_free(csenc); + mir_free(tsenc); + } + + oauth_setparam(params, "oauth_signature", sign); + mir_free(sign); + + return 0; +} + +char *oauth_generate_nonce() +{ + mir_md5_byte_t digest[16]; + char *result, *str, timestamp[22], randnum[16]; + int i; + + mir_snprintf(timestamp, sizeof(timestamp), "%ld", time(NULL)); + CallService(MS_UTILS_GETRANDOM, (WPARAM)sizeof(randnum), (LPARAM)randnum); + + str = (char *)mir_alloc(strlen(timestamp) + strlen(randnum) + 1); + strcpy(str, timestamp); + strcat(str, randnum); + mir_md5_hash((BYTE*)str, (int)strlen(str), digest); + mir_free(str); + + result = (char *)mir_alloc(32 + 1); + for (i = 0; i < 16; i++) + sprintf(result + (i<<1), "%02x", digest[i]); + + return result; +} + +char *oauth_auth_header(const char *httpmethod, const char *url, OAUTHSIGNMETHOD signmethod, + const char *consumer_key, const char *consumer_secret, + const char *token, const char *token_secret) +{ + int i, size; + char *res, timestamp[22], *nonce; + + if (httpmethod == NULL || url == NULL) return NULL; + + LIST oauth_parameters(1, paramsortFunc); + oauth_setparam(oauth_parameters, "oauth_consumer_key", consumer_key); + oauth_setparam(oauth_parameters, "oauth_version", "1.0"); + switch (signmethod) { + case HMACSHA1: oauth_setparam(oauth_parameters, "oauth_signature_method", "HMAC-SHA1"); break; + case RSASHA1: oauth_setparam(oauth_parameters, "oauth_signature_method", "RSA-SHA1"); break; + default: oauth_setparam(oauth_parameters, "oauth_signature_method", "PLAINTEXT"); break; + }; + mir_snprintf(timestamp, sizeof(timestamp), "%ld", time(NULL)); + oauth_setparam(oauth_parameters, "oauth_timestamp", timestamp); + nonce = oauth_generate_nonce(); + oauth_setparam(oauth_parameters, "oauth_nonce", nonce); + mir_free(nonce); + if (token != NULL && *token) + oauth_setparam(oauth_parameters, "oauth_token", token); + + if (oauth_sign_request(oauth_parameters, httpmethod, url, consumer_secret, token_secret)) { + oauth_freeparams(oauth_parameters); + oauth_parameters.destroy(); + return NULL; + } + + size = 7; + for (i = 0; i < oauth_parameters.getCount(); i++) { + OAUTHPARAMETER *p = oauth_parameters[i]; + if (i > 0) size++; + size += (int)strlen(p->name) + (int)strlen(p->value) + 3; + } + + res = (char *)mir_alloc(size); + strcpy(res, "OAuth "); + + for (i = 0; i < oauth_parameters.getCount(); i++) { + OAUTHPARAMETER *p = oauth_parameters[i]; + if (i > 0) strcat(res, ","); + strcat(res, p->name); + strcat(res, "=\""); + strcat(res, p->value); + strcat(res, "\""); + } + + oauth_freeparams(oauth_parameters); + oauth_parameters.destroy(); + return res; +} + +char* GGPROTO::oauth_header(const char *httpmethod, const char *url) +{ + char *res, uin[32], *password = NULL, *token = NULL, *token_secret = NULL; + DBVARIANT dbv; + + UIN2ID( db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0), uin); + if (!db_get_s(NULL, m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) { + CallService(MS_DB_CRYPT_DECODESTRING, (WPARAM)(int)strlen(dbv.pszVal) + 1, (LPARAM)dbv.pszVal); + password = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + if (!db_get_s(NULL, m_szModuleName, GG_KEY_TOKEN, &dbv, DBVT_ASCIIZ)) { + token = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + if (!db_get_s(NULL, m_szModuleName, GG_KEY_TOKENSECRET, &dbv, DBVT_ASCIIZ)) { + CallService(MS_DB_CRYPT_DECODESTRING, (WPARAM)(int)strlen(dbv.pszVal) + 1, (LPARAM)dbv.pszVal); + token_secret = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + + res = oauth_auth_header(httpmethod, url, HMACSHA1, uin, password, token, token_secret); + mir_free(password); + mir_free(token); + mir_free(token_secret); + + return res; +} + +int GGPROTO::oauth_receivetoken() +{ + NETLIBHTTPHEADER httpHeaders[3]; + NETLIBHTTPREQUEST req = {0}; + NETLIBHTTPREQUEST *resp; + char szUrl[256], uin[32], *password = NULL, *str, *token = NULL, *token_secret = NULL; + DBVARIANT dbv; + int res = 0; + HANDLE nlc = NULL; + + UIN2ID( db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0), uin); + if (!db_get_s(NULL, m_szModuleName, GG_KEY_PASSWORD, &dbv, DBVT_ASCIIZ)) { + CallService(MS_DB_CRYPT_DECODESTRING, strlen(dbv.pszVal) + 1, (LPARAM)dbv.pszVal); + password = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + + // 1. Obtaining an Unauthorized Request Token + netlog("gg_oauth_receivetoken(): Obtaining an Unauthorized Request Token..."); + strcpy(szUrl, "http://api.gadu-gadu.pl/request_token"); + str = oauth_auth_header("POST", szUrl, HMACSHA1, uin, password, NULL, NULL); + + req.cbSize = sizeof(req); + req.requestType = REQUEST_POST; + req.szUrl = szUrl; + req.flags = NLHRF_NODUMP | NLHRF_HTTP11 | NLHRF_PERSISTENT; + req.headersCount = 3; + req.headers = httpHeaders; + httpHeaders[0].szName = "User-Agent"; + httpHeaders[0].szValue = GG8_VERSION; + httpHeaders[1].szName = "Authorization"; + httpHeaders[1].szValue = str; + httpHeaders[2].szName = "Accept"; + httpHeaders[2].szValue = "*/*"; + + resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)netlib, (LPARAM)&req); + if (resp) { + nlc = resp->nlc; + if (resp->resultCode == 200 && resp->dataLength > 0 && resp->pData) { + HXML hXml; + TCHAR *xmlAction; + TCHAR *tag; + + xmlAction = mir_a2t(resp->pData); + tag = mir_a2t("result"); + hXml = xi.parseString(xmlAction, 0, tag); + if (hXml != NULL) { + HXML node; + + mir_free(tag); tag = mir_a2t("oauth_token"); + node = xi.getChildByPath(hXml, tag, 0); + token = node != NULL ? mir_t2a(xi.getText(node)) : NULL; + + mir_free(tag); tag = mir_a2t("oauth_token_secret"); + node = xi.getChildByPath(hXml, tag, 0); + token_secret = node != NULL ? mir_t2a(xi.getText(node)) : NULL; + + xi.destroyNode(hXml); + } + mir_free(tag); + mir_free(xmlAction); + } + else netlog("gg_oauth_receivetoken(): Invalid response code from HTTP request"); + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp); + } + else netlog("gg_oauth_receivetoken(): No response from HTTP request"); + + // 2. Obtaining User Authorization + netlog("gg_oauth_receivetoken(): Obtaining User Authorization..."); + mir_free(str); + str = oauth_uri_escape("http://www.mojageneracja.pl"); + + mir_snprintf(szUrl, 256, "callback_url=%s&request_token=%s&uin=%s&password=%s", + str, token, uin, password); + mir_free(str); + str = mir_strdup(szUrl); + + ZeroMemory(&req, sizeof(req)); + req.cbSize = sizeof(req); + req.requestType = REQUEST_POST; + req.szUrl = szUrl; + req.flags = NLHRF_NODUMP | NLHRF_HTTP11; + req.headersCount = 3; + req.headers = httpHeaders; + strcpy(szUrl, "https://login.gadu-gadu.pl/authorize"); + httpHeaders[1].szName = "Content-Type"; + httpHeaders[1].szValue = "application/x-www-form-urlencoded"; + req.pData = str; + req.dataLength = (int)strlen(str); + + resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)netlib, (LPARAM)&req); + if (resp) CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp); + else netlog("gg_oauth_receivetoken(): No response from HTTP request"); + + // 3. Obtaining an Access Token + netlog("gg_oauth_receivetoken(): Obtaining an Access Token..."); + strcpy(szUrl, "http://api.gadu-gadu.pl/access_token"); + mir_free(str); + str = oauth_auth_header("POST", szUrl, HMACSHA1, uin, password, token, token_secret); + mir_free(token); + mir_free(token_secret); + token = NULL; + token_secret = NULL; + + ZeroMemory(&req, sizeof(req)); + req.cbSize = sizeof(req); + req.requestType = REQUEST_POST; + req.szUrl = szUrl; + req.flags = NLHRF_NODUMP | NLHRF_HTTP11 | NLHRF_PERSISTENT; + req.nlc = nlc; + req.headersCount = 3; + req.headers = httpHeaders; + httpHeaders[1].szName = "Authorization"; + httpHeaders[1].szValue = str; + + resp = (NETLIBHTTPREQUEST *)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)netlib, (LPARAM)&req); + if (resp) { + if (resp->resultCode == 200 && resp->dataLength > 0 && resp->pData) { + HXML hXml; + TCHAR *xmlAction; + TCHAR *tag; + + xmlAction = mir_a2t(resp->pData); + tag = mir_a2t("result"); + hXml = xi.parseString(xmlAction, 0, tag); + if (hXml != NULL) { + HXML node; + + mir_free(tag); tag = mir_a2t("oauth_token"); + node = xi.getChildByPath(hXml, tag, 0); + token = node != NULL ? mir_t2a(xi.getText(node)) : NULL; + + mir_free(tag); tag = mir_a2t("oauth_token_secret"); + node = xi.getChildByPath(hXml, tag, 0); + token_secret = node != NULL ? mir_t2a(xi.getText(node)) : NULL; + + xi.destroyNode(hXml); + } + mir_free(tag); + mir_free(xmlAction); + } + else netlog("gg_oauth_receivetoken(): Invalid response code from HTTP request"); + Netlib_CloseHandle(resp->nlc); + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)resp); + } + else netlog("gg_oauth_receivetoken(): No response from HTTP request"); + + mir_free(password); + mir_free(str); + + if (token != NULL && token_secret != NULL) { + db_set_s(NULL, m_szModuleName, GG_KEY_TOKEN, token); + CallService(MS_DB_CRYPT_ENCODESTRING, (WPARAM)(int)strlen(token_secret) + 1, (LPARAM) token_secret); + db_set_s(NULL, m_szModuleName, GG_KEY_TOKENSECRET, token_secret); + netlog("gg_oauth_receivetoken(): Access Token obtained successfully."); + res = 1; + } + else { + db_unset(NULL, m_szModuleName, GG_KEY_TOKEN); + db_unset(NULL, m_szModuleName, GG_KEY_TOKENSECRET); + netlog("gg_oauth_receivetoken(): Failed to obtain Access Token."); + } + mir_free(token); + mir_free(token_secret); + + return res; +} + +int GGPROTO::oauth_checktoken(int force) +{ + if (!force) { + char *token = NULL, *token_secret = NULL; + DBVARIANT dbv; + int res = 1; + + if (!db_get_s(NULL, m_szModuleName, GG_KEY_TOKEN, &dbv, DBVT_ASCIIZ)) { + token = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + if (!db_get_s(NULL, m_szModuleName, GG_KEY_TOKENSECRET, &dbv, DBVT_ASCIIZ)) { + CallService(MS_DB_CRYPT_DECODESTRING, (WPARAM)(int)strlen(dbv.pszVal) + 1, (LPARAM)dbv.pszVal); + token_secret = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + + if (token == NULL || token_secret == NULL) { + res = oauth_receivetoken(); + } + + mir_free(token); + mir_free(token_secret); + + return res; + } + + return oauth_receivetoken(); +} diff --git a/protocols/Gadu-Gadu/src/ownerinfo.cpp b/protocols/Gadu-Gadu/src/ownerinfo.cpp new file mode 100644 index 0000000000..6c37cdb180 --- /dev/null +++ b/protocols/Gadu-Gadu/src/ownerinfo.cpp @@ -0,0 +1,78 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2006 Adam Strzelecki +// +// 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 "gg.h" + +////////////////////////////////////////////////////////// +// remind password + +typedef struct +{ + uin_t uin; + const char *email; +} GG_REMIND_PASS; + +void __cdecl GGPROTO::remindpasswordthread(void *param) +{ + // Connection handle + struct gg_http *h; + GG_REMIND_PASS *rp = (GG_REMIND_PASS *)param; + GGTOKEN token; + +#ifdef DEBUGMODE + netlog("gg_remindpasswordthread(): Starting."); +#endif + if (!rp || !rp->email || !rp->uin || !strlen(rp->email)) + { + if (rp) free(rp); + return; + } + + // Get token + if (!gettoken(&token)) return; + + if (!(h = gg_remind_passwd3(rp->uin, rp->email, token.id, token.val, 0))) + { + TCHAR error[128]; + mir_sntprintf(error, SIZEOF(error), TranslateT("Password could not be reminded because of error:\n\t%s"), _tcserror(errno)); + MessageBox(NULL, error, m_tszUserName, MB_OK | MB_ICONSTOP); + netlog("gg_remindpasswordthread(): Password could not be reminded because of \"%s\".", strerror(errno)); + } + else + { + gg_pubdir_free(h); + netlog("gg_remindpasswordthread(): Password remind successful."); + MessageBox(NULL, TranslateT("Password was sent to your e-mail."), m_tszUserName, MB_OK | MB_ICONINFORMATION); + } + +#ifdef DEBUGMODE + netlog("gg_remindpasswordthread(): End."); +#endif + if (rp) free(rp); +} + +void GGPROTO::remindpassword(uin_t uin, const char *email) +{ + GG_REMIND_PASS *rp = (GG_REMIND_PASS*)malloc(sizeof(GG_REMIND_PASS)); + + rp->uin = uin; + rp->email = email; + forkthread(&GGPROTO::remindpasswordthread, rp); +} diff --git a/protocols/Gadu-Gadu/src/popups.cpp b/protocols/Gadu-Gadu/src/popups.cpp new file mode 100644 index 0000000000..69ac1c350d --- /dev/null +++ b/protocols/Gadu-Gadu/src/popups.cpp @@ -0,0 +1,174 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2011-2012 Bartosz Białek +// +// 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 "gg.h" + +struct PopupData +{ + unsigned flags; + TCHAR* title; + TCHAR* text; + GGPROTO* gg; +}; + +///////////////////////////////////////////////////////////////////////////////////////// +// Popup plugin window proc + +LRESULT CALLBACK PopupWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) + { + case WM_COMMAND: + { + PopupData* puData = (PopupData*)PUGetPluginData(hWnd); + if (puData != NULL) + { + if (puData->flags & GG_POPUP_MULTILOGON) + puData->gg->sessions_view(0, 0); + } + PUDeletePopUp(hWnd); + break; + } + + case WM_CONTEXTMENU: + PUDeletePopUp(hWnd); + break; + + case UM_FREEPLUGINDATA: + { + PopupData* puData = (PopupData*)PUGetPluginData(hWnd); + if (puData != NULL && puData != (PopupData*)CALLSERVICE_NOTFOUND) + { + mir_free(puData->title); + mir_free(puData->text); + mir_free(puData); + } + break; + } + } + + return DefWindowProc(hWnd, msg, wParam, lParam); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Popup plugin class registration + +void GGPROTO::initpopups() +{ + TCHAR szDescr[256]; + char szName[256]; + + POPUPCLASS puc = {0}; + puc.cbSize = sizeof(puc); + puc.PluginWindowProc = PopupWindowProc; + puc.flags = PCF_TCHAR; + + puc.ptszDescription = szDescr; + puc.pszName = szName; + puc.colorBack = RGB(173, 206, 247); + puc.colorText = GetSysColor(COLOR_WINDOWTEXT); + puc.hIcon = CopyIcon(LoadIconEx("main", FALSE)); + ReleaseIconEx("main", FALSE); + puc.iSeconds = 4; + mir_sntprintf(szDescr, SIZEOF(szDescr), _T("%s/%s"), m_tszUserName, TranslateT("Notify")); + mir_snprintf(szName, SIZEOF(szName), "%s_%s", m_szModuleName, "Notify"); + CallService(MS_POPUP_REGISTERCLASS, 0, (WPARAM)&puc); + + puc.ptszDescription = szDescr; + puc.pszName = szName; + puc.colorBack = RGB(191, 0, 0); // Red + puc.colorText = RGB(255, 245, 225); // Yellow + puc.iSeconds = 60; + puc.hIcon = (HICON)LoadImage(NULL, IDI_WARNING, IMAGE_ICON, 0, 0, LR_SHARED); + mir_sntprintf(szDescr, SIZEOF(szDescr), _T("%s/%s"), m_tszUserName, TranslateT("Error")); + mir_snprintf(szName, SIZEOF(szName), "%s_%s", m_szModuleName, "Error"); + CallService(MS_POPUP_REGISTERCLASS, 0, (WPARAM)&puc); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Show popup - popup plugin support + +void CALLBACK sttMainThreadCallback(PVOID dwParam) +{ + PopupData* puData = (PopupData*)dwParam; + GGPROTO* gg = puData->gg; + + if (ServiceExists(MS_POPUP_ADDPOPUPCLASS)) + { + char szName[256]; + POPUPDATACLASS ppd = {sizeof(ppd)}; + ppd.ptszTitle = puData->title; + ppd.ptszText = puData->text; + ppd.PluginData = puData; + ppd.pszClassName = szName; + + if (puData->flags & GG_POPUP_ERROR || puData->flags & GG_POPUP_WARNING) + mir_snprintf(szName, SIZEOF(szName), "%s_%s", gg->m_szModuleName, "Error"); + else + mir_snprintf(szName, SIZEOF(szName), "%s_%s", gg->m_szModuleName, "Notify"); + + CallService(MS_POPUP_ADDPOPUPCLASS, 0, (LPARAM)&ppd); + } + else + { + if (puData->flags & GG_POPUP_ALLOW_MSGBOX) + { + BOOL bShow = TRUE; + + if (puData->flags & GG_POPUP_ONCE) + { + HWND hWnd = FindWindow(NULL, gg->m_tszUserName); + while (hWnd != NULL) + { + if (FindWindowEx(hWnd, NULL, NULL, puData->text) != NULL) + { + bShow = FALSE; + break; + } + hWnd = FindWindowEx(NULL, hWnd, NULL, gg->m_tszUserName); + } + } + + if (bShow) + { + UINT uIcon = puData->flags & GG_POPUP_ERROR ? MB_ICONERROR : puData->flags & GG_POPUP_WARNING ? MB_ICONEXCLAMATION : MB_ICONINFORMATION; + MessageBox(NULL, puData->text, gg->m_tszUserName, MB_OK | uIcon); + } + } + mir_free(puData->title); + mir_free(puData->text); + mir_free(puData); + } +} + +void GGPROTO::showpopup(const TCHAR* nickname, const TCHAR* msg, int flags) +{ + PopupData* puData; + + if (Miranda_Terminated()) return; + + puData = (PopupData*)mir_alloc(sizeof(PopupData)); + puData->flags = flags; + puData->title = mir_tstrdup(nickname); + puData->text = mir_tstrdup(msg); + puData->gg = this; + + CallFunctionAsync(sttMainThreadCallback, puData); +} diff --git a/protocols/Gadu-Gadu/src/resource.h b/protocols/Gadu-Gadu/src/resource.h new file mode 100644 index 0000000000..86ec31eadb --- /dev/null +++ b/protocols/Gadu-Gadu/src/resource.h @@ -0,0 +1,147 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2006 Adam Strzelecki +// +// 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. +//////////////////////////////////////////////////////////////////////////////// + +#ifndef IDC_STATIC +#define IDC_STATIC -1 +#endif +#ifndef IDOK +#define IDOK 1 +#endif +#ifndef IDCANCEL +#define IDCANCEL 2 +#endif + +#define IDI_GG 251 +#define IDI_IMPORT_TEXT 252 +#define IDI_IMPORT_SERVER 253 +#define IDI_EXPORT_TEXT 254 +#define IDI_EXPORT_SERVER 255 +#define IDI_REMOVE_SERVER 256 +#define IDI_SETTINGS 257 +#define IDI_LIST 258 +#define IDI_NEXT 259 +#define IDI_PREV 260 +#define IDI_SCALE 261 +#define IDI_IMAGE 262 +#define IDI_DELETE 263 +#define IDI_SAVE 264 +#define IDI_CONFERENCE 265 +#define IDI_CLEAR_CONFERENCE 266 +#define IDI_SESSIONS 267 +#define IDI_BLOCK 268 + +#define IDD_INFO_GG 301 +#define IDD_CHPASS 302 +#define IDD_CHINFO_GG 303 +#define IDD_GGADVANCEDSEARCH 304 +#define IDD_CREATEACCOUNT 305 +#define IDD_REMOVEACCOUNT 306 +#define IDD_CHEMAIL 307 +#define IDD_OPT_GG_ADVANCED 308 +#define IDD_TOKEN 309 +#define IDD_CONFERENCE 310 +#define IDD_OPT_GG_GENERAL 311 +#define IDD_OPT_GG_CONFERENCE 312 +#define IDD_IMAGE_RECV 313 +#define IDD_IMAGE_SEND 314 +#define IDD_ACCMGRUI 315 +#define IDD_SESSIONS 316 + +#define IDC_UIN 401 +#define IDC_PASSWORD 402 +#define IDC_LOSTPASS 403 +#define IDC_FRIENDSONLY 404 +#define IDC_SHOWINVISIBLE 405 +#define IDC_KEEPALIVE 406 +#define IDC_SAFESTATUS 407 +#define IDC_MANUALHOST 408 +#define IDC_HOST 409 +#define IDC_PORT 410 +#define IDC_RELOADREQD 411 +#define IDC_IP 412 +#define IDC_REALIP 413 +#define IDC_FIRSTNAME 420 +#define IDC_LASTNAME 421 +#define IDC_NICKNAME 422 +#define IDC_GENDER 423 +#define IDC_BIRTHYEAR 424 +#define IDC_CITY 425 +#define IDC_FAMILYNAME 426 +#define IDC_CITYORIGIN 427 +#define IDC_STATUSDESCR 428 +#define IDC_EMAIL 429 +#define IDC_CPASSWORD 430 +#define IDC_SHOWCERRORS 431 +#define IDC_ARECONNECT 432 +#define IDC_LEAVESTATUSMSG 433 +#define IDC_LEAVESTATUS 434 +#define IDC_AGEFROM 435 +#define IDC_AGETO 436 +#define IDC_ONLYCONNECTED 437 +#define IDC_WHITERECT 438 +#define IDC_CREATEACCOUNT 439 +#define IDC_REMOVEACCOUNT 440 +#define IDC_CONFIRM 441 +#define IDC_SAVE 442 +#define IDC_CHPASS 443 +#define IDC_CHEMAIL 444 +#define IDC_DIRECTCONNS 445 +#define IDC_DIRECTPORT 446 +#define IDC_FORWARDHOST 447 +#define IDC_FORWARDPORT 448 +#define IDC_FORWARDING 449 +#define IDC_MSGACK 450 +#define IDC_SSLCONN 451 +#define IDC_VERSION 452 +#define IDC_TOKEN 453 +#define IDC_IGNORECONF 454 +#define IDC_SHOWLINKS 455 +#define IDC_IMGRECEIVE 456 +#define IDC_IMGMETHOD 457 + +#define IDC_IMAGECLOSE 458 +#define IDC_TABCONTROL 459 +#define IDC_IMAGE_SEND 460 +#define IDC_IMAGE_SAVE 461 + +#define IDC_GC_POLICY_TOTAL 462 +#define IDC_GC_POLICY_UNKNOWN 463 +#define IDC_GC_POLICY_DEFAULT 464 +#define IDC_GC_COUNT_TOTAL 465 +#define IDC_GC_COUNT_UNKNOWN 466 + +#define IDC_OPTIONSTAB 467 +#define IDC_ENABLEAVATARS 468 + +#define IDC_HEADERBAR 1001 +#define IDC_SESSIONS 1002 +#define IDC_SIGNOUTALL 1003 + +#define IDC_IMG_DELETE 1010 +#define IDC_IMG_SEND 1011 +#define IDC_IMG_PREV 1012 +#define IDC_IMG_NEXT 1013 +#define IDC_IMG_SCALE 1014 +#define IDC_IMG_SAVE 1015 +#define IDC_IMG_CANCEL 1016 +#define IDC_IMG_IMAGE 1017 +#define IDC_IMG_NAME 1018 + +#define IDC_CLIST 1200 diff --git a/protocols/Gadu-Gadu/src/services.cpp b/protocols/Gadu-Gadu/src/services.cpp new file mode 100644 index 0000000000..a172d10609 --- /dev/null +++ b/protocols/Gadu-Gadu/src/services.cpp @@ -0,0 +1,330 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2009 Adam Strzelecki +// Copyright (c) 2009-2012 Bartosz Białek +// +// 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 "gg.h" +#include + +////////////////////////////////////////////////////////// +// Status mode -> DB +char *gg_status2db(int status, const char *suffix) +{ + char *prefix; + static char str[64]; + + switch(status) { + case ID_STATUS_AWAY: prefix = "Away"; break; + case ID_STATUS_NA: prefix = "Na"; break; + case ID_STATUS_DND: prefix = "Dnd"; break; + case ID_STATUS_OCCUPIED: prefix = "Occupied"; break; + case ID_STATUS_FREECHAT: prefix = "FreeChat"; break; + case ID_STATUS_ONLINE: prefix = "On"; break; + case ID_STATUS_OFFLINE: prefix = "Off"; break; + case ID_STATUS_INVISIBLE: prefix = "Inv"; break; + case ID_STATUS_ONTHEPHONE: prefix = "Otp"; break; + case ID_STATUS_OUTTOLUNCH: prefix = "Otl"; break; + default: return NULL; + } + strncpy(str, prefix, sizeof(str)); + strncat(str, suffix, sizeof(str) - strlen(str)); + return str; +} + +////////////////////////////////////////////////////////// +// gets protocol status + +char* GGPROTO::getstatusmsg(int status) +{ + switch(status) { + case ID_STATUS_ONLINE: + return modemsg.online; + break; + case ID_STATUS_DND: + return modemsg.dnd; + break; + case ID_STATUS_FREECHAT: + return modemsg.freechat; + break; + case ID_STATUS_INVISIBLE: + return modemsg.invisible; + break; + case ID_STATUS_AWAY: + default: + return modemsg.away; + } +} + +////////////////////////////////////////////////////////// +// sets specified protocol status + +int GGPROTO::refreshstatus(int status) +{ + if (status == ID_STATUS_OFFLINE) + { + disconnect(); + return TRUE; + } + + if (!isonline()) + { + DWORD exitCode = 0; + GetExitCodeThread(pth_sess.hThread, &exitCode); + if (exitCode == STILL_ACTIVE) + return TRUE; +#ifdef DEBUGMODE + netlog("gg_refreshstatus(): Going to connect..."); +#endif + threadwait(&pth_sess); + pth_sess.hThread = forkthreadex(&GGPROTO::mainthread, NULL, &pth_sess.dwThreadId); + } + else + { + char *szMsg = NULL; + // Select proper msg + EnterCriticalSection(&modemsg_mutex); + szMsg = mir_strdup(getstatusmsg(status)); + LeaveCriticalSection(&modemsg_mutex); + if (szMsg) + { + netlog("gg_refreshstatus(): Setting status and away message."); + EnterCriticalSection(&sess_mutex); + gg_change_status_descr(sess, status_m2gg(status, szMsg != NULL), szMsg); + LeaveCriticalSection(&sess_mutex); + } + else + { + netlog("gg_refreshstatus(): Setting just status."); + EnterCriticalSection(&sess_mutex); + gg_change_status(sess, status_m2gg(status, 0)); + LeaveCriticalSection(&sess_mutex); + } + // Change status of the contact with our own UIN (if got yourself added to the contact list) + changecontactstatus( db_get_dw(NULL, m_szModuleName, GG_KEY_UIN, 0), status_m2gg(status, szMsg != NULL), szMsg, 0, 0, 0, 0); + broadcastnewstatus(status); + mir_free(szMsg); + } + + return TRUE; +} + +////////////////////////////////////////////////////////// +// normalize gg status + +int gg_normalizestatus(int status) +{ + switch(status) { + case ID_STATUS_ONLINE: return ID_STATUS_ONLINE; + case ID_STATUS_DND: return ID_STATUS_DND; + case ID_STATUS_FREECHAT: return ID_STATUS_FREECHAT; + case ID_STATUS_OFFLINE: return ID_STATUS_OFFLINE; + case ID_STATUS_INVISIBLE: return ID_STATUS_INVISIBLE; + } + return ID_STATUS_AWAY; +} + +////////////////////////////////////////////////////////// +// gets avatar capabilities + +INT_PTR GGPROTO::getavatarcaps(WPARAM wParam, LPARAM lParam) +{ + switch (wParam) { + case AF_MAXSIZE: + ((POINT *)lParam)->x = ((POINT *)lParam)->y = 200; + return 0; + case AF_FORMATSUPPORTED: + return (lParam == PA_FORMAT_JPEG || lParam == PA_FORMAT_GIF || lParam == PA_FORMAT_PNG); + case AF_ENABLED: + return db_get_b(NULL, m_szModuleName, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS); + case AF_DONTNEEDDELAYS: + return 1; + case AF_MAXFILESIZE: + return 307200; + case AF_FETCHALWAYS: + return 1; + } + return 0; +} + +////////////////////////////////////////////////////////// +// gets avatar information + +INT_PTR GGPROTO::getavatarinfo(WPARAM wParam, LPARAM lParam) +{ + PROTO_AVATAR_INFORMATIONT *pai = (PROTO_AVATAR_INFORMATIONT *)lParam; + char *AvatarHash = NULL, *AvatarSavedHash = NULL; + char *AvatarURL = NULL; + INT_PTR result = GAIR_NOAVATAR; + DBVARIANT dbv; + uin_t uin = (uin_t)db_get_dw(pai->hContact, m_szModuleName, GG_KEY_UIN, 0); + + netlog("gg_getavatarinfo(): Requesting avatar information for %d.", uin); + + pai->filename[0] = 0; + pai->format = PA_FORMAT_UNKNOWN; + + if (!uin || !db_get_b(NULL, m_szModuleName, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS)) + return GAIR_NOAVATAR; + + if (!db_get_b(pai->hContact, m_szModuleName, GG_KEY_AVATARREQUESTED, GG_KEYDEF_AVATARREQUESTED)) { + requestAvatar(pai->hContact, 1); + return (wParam & GAIF_FORCE) != 0 ? GAIR_WAITFOR : GAIR_NOAVATAR; + } + db_unset(pai->hContact, m_szModuleName, GG_KEY_AVATARREQUESTED); + + pai->format = db_get_b(pai->hContact, m_szModuleName, GG_KEY_AVATARTYPE, GG_KEYDEF_AVATARTYPE); + + if (!db_get_s(pai->hContact, m_szModuleName, GG_KEY_AVATARURL, &dbv, DBVT_ASCIIZ)) { + AvatarURL = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + + if (AvatarURL != NULL && strlen(AvatarURL) > 0) { + char *AvatarName = strrchr(AvatarURL, '/'); + AvatarName++; + AvatarHash = gg_avatarhash(AvatarName); + } + + if (!db_get_s(pai->hContact, m_szModuleName, GG_KEY_AVATARHASH, &dbv, DBVT_ASCIIZ)) { + AvatarSavedHash = mir_strdup(dbv.pszVal); + DBFreeVariant(&dbv); + } + + if (AvatarHash != NULL && AvatarSavedHash != NULL) { + getAvatarFilename(pai->hContact, pai->filename, SIZEOF(pai->filename)); + if (!strcmp(AvatarHash, AvatarSavedHash) && !_taccess(pai->filename, 0)) { + result = GAIR_SUCCESS; + } + else if ((wParam & GAIF_FORCE) != 0) { + netlog("gg_getavatarinfo(): Contact %d changed avatar.", uin); + _tremove(pai->filename); + db_set_s(pai->hContact, m_szModuleName, GG_KEY_AVATARHASH, AvatarHash); + getAvatar(pai->hContact, AvatarURL); + result = GAIR_WAITFOR; + } + } + else if ((wParam & GAIF_FORCE) != 0) { + if (AvatarHash == NULL && AvatarSavedHash != NULL) { + netlog("gg_getavatarinfo(): Contact %d deleted avatar.", uin); + getAvatarFilename(pai->hContact, pai->filename, sizeof(pai->filename)); + _tremove(pai->filename); + db_unset(pai->hContact, m_szModuleName, GG_KEY_AVATARHASH); + db_unset(pai->hContact, m_szModuleName, GG_KEY_AVATARURL); + db_unset(pai->hContact, m_szModuleName, GG_KEY_AVATARTYPE); + } + else if (AvatarHash != NULL && AvatarSavedHash == NULL) { + netlog("gg_getavatarinfo(): Contact %d set avatar.", uin); + db_set_s(pai->hContact, m_szModuleName, GG_KEY_AVATARHASH, AvatarHash); + getAvatar(pai->hContact, AvatarURL); + result = GAIR_WAITFOR; + } + } + + mir_free(AvatarHash); + mir_free(AvatarSavedHash); + mir_free(AvatarURL); + + return result; +} + +////////////////////////////////////////////////////////// +// gets avatar + +INT_PTR GGPROTO::getmyavatar(WPARAM wParam, LPARAM lParam) +{ + TCHAR *szFilename = (TCHAR*)wParam; + int len = (int)lParam; + + netlog("gg_getmyavatar(): Requesting user avatar."); + + if (szFilename == NULL || len <= 0) + return -1; + + if (!db_get_b(NULL, m_szModuleName, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS)) + return -2; + + getAvatarFilename(NULL, szFilename, len); + return _taccess(szFilename, 0); +} + +////////////////////////////////////////////////////////// +// sets avatar + +INT_PTR GGPROTO::setmyavatar(WPARAM wParam, LPARAM lParam) +{ + TCHAR *szFilename = (TCHAR*)lParam; + + if (!db_get_b(NULL, m_szModuleName, GG_KEY_ENABLEAVATARS, GG_KEYDEF_ENABLEAVATARS)) + return -2; + + if (szFilename == NULL) { + MessageBox(NULL, + TranslateT("To remove your Gadu-Gadu avatar, you must use the MojaGeneracja.pl website."), + m_tszUserName, MB_OK | MB_ICONINFORMATION); + return -1; + } + + TCHAR szMyFilename[MAX_PATH]; + getAvatarFilename(NULL, szMyFilename, SIZEOF(szMyFilename)); + if ( _tcscmp(szFilename, szMyFilename) && !CopyFile(szFilename, szMyFilename, FALSE)) { + netlog("gg_setmyavatar(): Failed to set user avatar. File %s could not be created/overwritten.", szMyFilename); + return -1; + } + + setAvatar(szMyFilename); + return 0; +} + +////////////////////////////////////////////////////////// +// gets protocol status message + +INT_PTR GGPROTO::getmyawaymsg(WPARAM wParam, LPARAM lParam) +{ + INT_PTR res = 0; + char *szMsg; + + EnterCriticalSection(&modemsg_mutex); + szMsg = getstatusmsg(wParam ? gg_normalizestatus(wParam) : m_iStatus); + if (isonline() && szMsg) + res = (lParam & SGMA_UNICODE) ? (INT_PTR)mir_a2u(szMsg) : (INT_PTR)mir_strdup(szMsg); + LeaveCriticalSection(&modemsg_mutex); + return res; +} + +////////////////////////////////////////////////////////// +// gets account manager GUI + +extern INT_PTR CALLBACK gg_acc_mgr_guidlgproc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + +INT_PTR GGPROTO::get_acc_mgr_gui(WPARAM wParam, LPARAM lParam) +{ + return (INT_PTR) CreateDialogParam(hInstance, MAKEINTRESOURCE(IDD_ACCMGRUI), (HWND)lParam, gg_acc_mgr_guidlgproc, (LPARAM)this); +} + +////////////////////////////////////////////////////////// +// leaves (terminates) conference + +INT_PTR GGPROTO::leavechat(WPARAM wParam, LPARAM lParam) +{ + HANDLE hContact = (HANDLE)wParam; + if (hContact) + CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0); + + return 0; +} diff --git a/protocols/Gadu-Gadu/src/sessions.cpp b/protocols/Gadu-Gadu/src/sessions.cpp new file mode 100644 index 0000000000..6f047cd783 --- /dev/null +++ b/protocols/Gadu-Gadu/src/sessions.cpp @@ -0,0 +1,445 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2009-2012 Bartosz Białek +// +// 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 "gg.h" + +#define GGS_CONCUR_SESS "%s/ConcurSess" + +static void gg_clearsessionslist(HWND hwndDlg) +{ + HWND hList = GetDlgItem(hwndDlg, IDC_SESSIONS); + LV_COLUMN column = {0}; + RECT rc; + int iWidth; + + if (!hList) return; + + ListView_DeleteAllItems(hList); + while (ListView_DeleteColumn(hList, 0)); + + column.mask = LVCF_TEXT; + column.cx = 500; + column.pszText = TranslateT("Client Name"); + ListView_InsertColumn(hList, 1, &column); + + column.pszText=TranslateT("IP Address"); + ListView_InsertColumn(hList, 2, &column); + + column.pszText = TranslateT("Login Time"); + ListView_InsertColumn(hList, 3, &column); + + column.pszText = TranslateT("Action"); + ListView_InsertColumn(hList, 4, &column); + + GetClientRect(hList, &rc); + iWidth = rc.right - rc.left; + ListView_SetColumnWidth(hList, 0, iWidth * 45 / 100); + ListView_SetColumnWidth(hList, 1, iWidth * 20 / 100); + ListView_SetColumnWidth(hList, 2, iWidth * 20 / 100); + ListView_SetColumnWidth(hList, 3, LVSCW_AUTOSIZE_USEHEADER); +} + +static void ListView_SetItemTextA(HWND hwndLV, int i, int iSubItem, char* pszText) +{ + LV_ITEMA _ms_lvi; + _ms_lvi.iSubItem = iSubItem; + _ms_lvi.pszText = pszText; + SendMessageA(hwndLV, LVM_SETITEMTEXTA, i, (LPARAM)&_ms_lvi); +} + +static int gg_insertlistitem(HWND hList, gg_multilogon_id_t* id, const char* clientName, const char* ip, const char* loginTime) +{ + LVITEM item = {0}; + int index; + + item.iItem = ListView_GetItemCount(hList); + item.mask = LVIF_PARAM; + item.lParam = (LPARAM)id; + + index = ListView_InsertItem(hList, &item); + if (index < 0) return index; + + ListView_SetItemTextA(hList, index, 0, (char*)clientName); + ListView_SetItemTextA(hList, index, 1, (char*)ip); + ListView_SetItemTextA(hList, index, 2, (char*)loginTime); + ListView_SetItemText(hList, index, 3, TranslateT("sign out")); + + return index; +} + +static void gg_listsessions(GGPROTO* gg, HWND hwndDlg) +{ + HWND hList = GetDlgItem(hwndDlg, IDC_SESSIONS); + list_t l; + + if (!hList) return; + + EnterCriticalSection(&gg->sessions_mutex); + for (l = gg->sessions; l; l = l->next) + { + struct gg_multilogon_session* sess = (struct gg_multilogon_session*)l->data; + struct in_addr ia; + char* ip; + char loginTime[20]; + ia.S_un.S_addr = sess->remote_addr; + ip = inet_ntoa(ia); + strftime(loginTime, sizeof(loginTime), "%d-%m-%Y %H:%M:%S", localtime(&sess->logon_time)); + gg_insertlistitem(hList, &sess->id, sess->name, ip, loginTime); + } + LeaveCriticalSection(&gg->sessions_mutex); + EnableWindow(GetDlgItem(hwndDlg, IDC_SIGNOUTALL), ListView_GetItemCount(hList) > 0); +} + +static int sttSessionsDlgResizer(HWND hwndDlg, LPARAM lParam, UTILRESIZECONTROL* urc) +{ + switch (urc->wId) + { + case IDC_HEADERBAR: + return RD_ANCHORX_LEFT | RD_ANCHORY_TOP | RD_ANCHORX_WIDTH; + case IDC_SESSIONS: + return RD_ANCHORX_LEFT | RD_ANCHORY_TOP | RD_ANCHORY_HEIGHT | RD_ANCHORX_WIDTH; + case IDC_SIGNOUTALL: + return RD_ANCHORX_RIGHT | RD_ANCHORY_BOTTOM; + } + return RD_ANCHORX_LEFT | RD_ANCHORY_TOP; +} + +static BOOL IsOverAction(HWND hwndDlg) +{ + HWND hList = GetDlgItem(hwndDlg, IDC_SESSIONS); + LVHITTESTINFO hti; + RECT rc; + HDC hdc; + TCHAR szText[256]; + SIZE textSize; + int textPosX; + + GetCursorPos(&hti.pt); + ScreenToClient(hList, &hti.pt); + GetClientRect(hList, &rc); + if (!PtInRect(&rc, hti.pt) || ListView_SubItemHitTest(hList, &hti) == -1 + || hti.iSubItem != 3 || !(hti.flags & LVHT_ONITEMLABEL)) + return FALSE; + + ListView_GetSubItemRect(hList, hti.iItem, hti.iSubItem, LVIR_LABEL, &rc); + szText[0] = 0; + ListView_GetItemText(hList, hti.iItem, hti.iSubItem, szText, SIZEOF(szText)); + hdc = GetDC(hList); + GetTextExtentPoint32(hdc, szText, lstrlen(szText), &textSize); + ReleaseDC(hList, hdc); + textPosX = rc.left + (((rc.right - rc.left) - textSize.cx) / 2); + return (hti.pt.x > textPosX && hti.pt.x < textPosX + textSize.cx); +} + +static HCURSOR hHandCursor = NULL; +#define WM_MULTILOGONINFO (WM_USER + 10) +#define HM_PROTOACK (WM_USER + 11) + +static INT_PTR CALLBACK gg_sessions_viewdlg(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + GGPROTO* gg = (GGPROTO*)GetWindowLongPtr(hwndDlg, DWLP_USER); + switch (message) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + + gg = (GGPROTO*)lParam; + gg->hwndSessionsDlg = hwndDlg; + + SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)lParam); + { + TCHAR oldTitle[256], newTitle[256]; + HANDLE hProtoAckEvent; + + GetDlgItemText(hwndDlg, IDC_HEADERBAR, oldTitle, SIZEOF(oldTitle)); + mir_sntprintf(newTitle, SIZEOF(newTitle), oldTitle, gg->m_tszUserName); + SetDlgItemText(hwndDlg, IDC_HEADERBAR, newTitle); + WindowSetIcon(hwndDlg, "sessions"); + + if (hHandCursor == NULL) + hHandCursor = LoadCursor(NULL, IDC_HAND); + hProtoAckEvent = HookEventMessage(ME_PROTO_ACK, hwndDlg, HM_PROTOACK); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)hProtoAckEvent); + + ListView_SetExtendedListViewStyle(GetDlgItem(hwndDlg, IDC_SESSIONS), LVS_EX_FULLROWSELECT); + SendMessage(hwndDlg, WM_MULTILOGONINFO, 0, 0); + return TRUE; + } + + case HM_PROTOACK: + { + ACKDATA* ack = (ACKDATA*)lParam; + if (!strcmp(ack->szModule, gg->m_szModuleName) && !ack->hContact && ack->type == ACKTYPE_STATUS + && ack->result == ACKRESULT_SUCCESS && (ack->lParam == ID_STATUS_OFFLINE + || (ack->hProcess == (HANDLE)ID_STATUS_CONNECTING && ack->lParam != ID_STATUS_OFFLINE + && !ListView_GetItemCount(GetDlgItem(hwndDlg, IDC_SESSIONS))))) + { + gg_clearsessionslist(hwndDlg); + EnableWindow(GetDlgItem(hwndDlg, IDC_SIGNOUTALL), FALSE); + } + break; + } + + case WM_MULTILOGONINFO: + gg_clearsessionslist(hwndDlg); + gg_listsessions(gg, hwndDlg); + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_SIGNOUTALL: + { + HWND hList = GetDlgItem(hwndDlg, IDC_SESSIONS); + LVITEM lvi = {0}; + int iCount = ListView_GetItemCount(hList), i; + lvi.mask = LVIF_PARAM; + for (i = 0; i < iCount; i++) + { + lvi.iItem = i; + ListView_GetItem(hList, &lvi); + EnterCriticalSection(&gg->sess_mutex); + gg_multilogon_disconnect(gg->sess, *((gg_multilogon_id_t*)lvi.lParam)); + LeaveCriticalSection(&gg->sess_mutex); + } + break; + } + } + break; + + case WM_NOTIFY: + if (((LPNMHDR)lParam)->idFrom == IDC_SESSIONS) + { + switch (((LPNMHDR)lParam)->code) + { + case NM_CUSTOMDRAW: + { + LPNMLVCUSTOMDRAW nm = (LPNMLVCUSTOMDRAW)lParam; + switch (nm->nmcd.dwDrawStage) + { + case CDDS_PREPAINT: + if (ListView_GetItemCount(nm->nmcd.hdr.hwndFrom) == 0) + { + const LPCTSTR szText = gg->isonline() + ? TranslateT("There are no active concurrent sessions for this account.") + : TranslateT("You have to be logged in to view concurrent sessions."); + RECT rc; + HWND hwndHeader = ListView_GetHeader(nm->nmcd.hdr.hwndFrom); + SIZE textSize; + int textPosX; + GetClientRect(nm->nmcd.hdr.hwndFrom, &rc); + if (hwndHeader != NULL) + { + RECT rcHeader; + GetClientRect(hwndHeader, &rcHeader); + rc.top += rcHeader.bottom; + } + GetTextExtentPoint32(nm->nmcd.hdc, szText, lstrlen(szText), &textSize); + textPosX = rc.left + (((rc.right - rc.left) - textSize.cx) / 2); + ExtTextOut(nm->nmcd.hdc, textPosX, rc.top + textSize.cy, ETO_OPAQUE, &rc, szText, lstrlen(szText), NULL); + } + // FALL THROUGH + + case CDDS_ITEMPREPAINT: + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT , CDRF_NOTIFYSUBITEMDRAW); + return TRUE; + + case CDDS_SUBITEM | CDDS_ITEMPREPAINT: + { + RECT rc; + ListView_GetSubItemRect(nm->nmcd.hdr.hwndFrom, nm->nmcd.dwItemSpec, nm->iSubItem, LVIR_LABEL, &rc); + if (nm->nmcd.hdr.idFrom == IDC_SESSIONS && nm->iSubItem == 3) + { + TCHAR szText[256]; + szText[0] = 0; + ListView_GetItemText(nm->nmcd.hdr.hwndFrom, nm->nmcd.dwItemSpec, nm->iSubItem, szText, SIZEOF(szText)); + FillRect(nm->nmcd.hdc, &rc, GetSysColorBrush(COLOR_WINDOW)); + SetTextColor(nm->nmcd.hdc, RGB(0, 0, 255)); + DrawText(nm->nmcd.hdc, szText, -1, &rc, DT_END_ELLIPSIS | DT_CENTER | DT_NOPREFIX | DT_SINGLELINE | DT_TOP); + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, CDRF_SKIPDEFAULT); + return TRUE; + } + break; + } + } + break; + } + + case NM_CLICK: + if (IsOverAction(hwndDlg)) + { + LPNMITEMACTIVATE nm = (LPNMITEMACTIVATE)lParam; + LVITEM lvi = {0}; + lvi.mask = LVIF_PARAM; + lvi.iItem = nm->iItem; + ListView_GetItem(nm->hdr.hwndFrom, &lvi); + EnterCriticalSection(&gg->sess_mutex); + gg_multilogon_disconnect(gg->sess, *((gg_multilogon_id_t*)lvi.lParam)); + LeaveCriticalSection(&gg->sess_mutex); + } + break; + } + } + break; + + case WM_CONTEXTMENU: + { + HWND hList = GetDlgItem(hwndDlg, IDC_SESSIONS); + POINT pt = {(short)LOWORD(lParam), (short)HIWORD(lParam)}, ptDlg = pt; + LVHITTESTINFO lvhti = {0}; + + ScreenToClient(hwndDlg, &ptDlg); + if (ChildWindowFromPoint(hwndDlg, ptDlg) == hList) + { + HMENU hMenu; + int iSelection; + + lvhti.pt = pt; + ScreenToClient(hList, &lvhti.pt); + if (ListView_HitTest(hList, &lvhti) == -1) break; + + hMenu = CreatePopupMenu(); + AppendMenu(hMenu, MFT_STRING, 10001, TranslateT("Copy Text")); + AppendMenu(hMenu, MFT_STRING, 10002, TranslateT("Whois")); + iSelection = TrackPopupMenu(hMenu, TPM_RIGHTBUTTON | TPM_RETURNCMD, pt.x, pt.y, 0, hwndDlg, NULL); + switch (iSelection) { + case 10001: + { + TCHAR szText[512], szClientName[256], szIP[64], szLoginTime[64]; + HGLOBAL hData; + if (!OpenClipboard(hwndDlg)) + break; + + EmptyClipboard(); + szClientName[0] = szIP[0] = szLoginTime[0] = 0; + ListView_GetItemText(hList, lvhti.iItem, 0, szClientName, SIZEOF(szClientName)); + ListView_GetItemText(hList, lvhti.iItem, 1, szIP, SIZEOF(szIP)); + ListView_GetItemText(hList, lvhti.iItem, 2, szLoginTime, SIZEOF(szLoginTime)); + mir_sntprintf(szText, SIZEOF(szText), _T("%s\t%s\t%s"), szClientName, szIP, szLoginTime); + if ((hData = GlobalAlloc(GMEM_MOVEABLE, lstrlen(szText) + 1)) != NULL) + { + lstrcpy((TCHAR*)GlobalLock(hData), szText); + GlobalUnlock(hData); + SetClipboardData(CF_TEXT, hData); + } + CloseClipboard(); + break; + } + + case 10002: + { + TCHAR szUrl[256], szIP[64]; + szIP[0] = 0; + ListView_GetItemText(hList, lvhti.iItem, 1, szIP, SIZEOF(szIP)); + mir_sntprintf(szUrl, SIZEOF(szUrl), _T("http://whois.domaintools.com/%s"), szIP); + CallService(MS_UTILS_OPENURL, OUF_TCHAR, (LPARAM)szUrl); + break; + } + } + DestroyMenu(hMenu); + } + break; + } + + case WM_GETMINMAXINFO: + ((LPMINMAXINFO)lParam)->ptMinTrackSize.x = 620; + ((LPMINMAXINFO)lParam)->ptMinTrackSize.y = 220; + return 0; + + case WM_SIZE: + { + UTILRESIZEDIALOG urd = {0}; + urd.cbSize = sizeof(urd); + urd.hInstance = hInstance; + urd.hwndDlg = hwndDlg; + urd.lpTemplate = MAKEINTRESOURCEA(IDD_SESSIONS); + urd.pfnResizer = sttSessionsDlgResizer; + CallService(MS_UTILS_RESIZEDIALOG, 0, (LPARAM)&urd); + return 0; + } + + case WM_SETCURSOR: + if (LOWORD(lParam) == HTCLIENT && IsOverAction(hwndDlg)) + { + SetCursor(hHandCursor); + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, TRUE); + return TRUE; + } + break; + + case WM_CLOSE: + DestroyWindow(hwndDlg); + break; + + case WM_DESTROY: + { + HANDLE hProtoAckEvent = (HANDLE)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if (hProtoAckEvent) UnhookEvent(hProtoAckEvent); + gg->hwndSessionsDlg = NULL; + WindowFreeIcon(hwndDlg); + break; + } + } + return FALSE; +} + +INT_PTR GGPROTO::sessions_view(WPARAM wParam, LPARAM lParam) +{ + if (hwndSessionsDlg && IsWindow(hwndSessionsDlg)) { + ShowWindow(hwndSessionsDlg, SW_SHOWNORMAL); + SetForegroundWindow(hwndSessionsDlg); + SetFocus(hwndSessionsDlg); + } + else CreateDialogParam(hInstance, MAKEINTRESOURCE(IDD_SESSIONS), NULL, gg_sessions_viewdlg, (LPARAM)this); + return 0; +} + +void GGPROTO::sessions_updatedlg() +{ + if (hwndSessionsDlg && IsWindow(hwndSessionsDlg)) + SendMessage(hwndSessionsDlg, WM_MULTILOGONINFO, 0, 0); +} + +BOOL GGPROTO::sessions_closedlg() +{ + if (hwndSessionsDlg && IsWindow(hwndSessionsDlg)) + return PostMessage(hwndSessionsDlg, WM_CLOSE, 0, 0); + return FALSE; +} + +void GGPROTO::sessions_menus_init(HGENMENU hRoot) +{ + CLISTMENUITEM mi = {0}; + char service[64]; + + mi.cbSize = sizeof(mi); + mi.flags = CMIF_ICONFROMICOLIB | CMIF_ROOTHANDLE | CMIF_TCHAR; + mi.hParentMenu = hRoot; + + mir_snprintf(service, sizeof(service), GGS_CONCUR_SESS, m_szModuleName); + createObjService(service, &GGPROTO::sessions_view); + if (hMenuRoot) + mi.position = 2050000001; + else + mi.position = 200003; + mi.icolibItem = GetIconHandle(IDI_SESSIONS); + mi.ptszName = LPGENT("Concurrent &sessions"); + mi.pszService = service; + Menu_AddProtoMenuItem(&mi); +} diff --git a/protocols/Gadu-Gadu/src/token.cpp b/protocols/Gadu-Gadu/src/token.cpp new file mode 100644 index 0000000000..d2946c12ef --- /dev/null +++ b/protocols/Gadu-Gadu/src/token.cpp @@ -0,0 +1,161 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2006 Adam Strzelecki +// +// 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 "gg.h" + +#define MAX_LOADSTRING 100 +#define HIMETRIC_INCH 2540 +#define MAP_LOGHIM_TO_PIX(x,ppli) ( ((ppli)*(x) + HIMETRIC_INCH/2) / HIMETRIC_INCH ) + +//////////////////////////////////////////////////////////////////////////////// +// User Util Dlg Page : Data + +typedef struct _GGTOKENDLGDATA +{ + int width; + int height; + char id[256]; + char val[256]; + HBITMAP hBitmap; +} GGTOKENDLGDATA; + +INT_PTR CALLBACK gg_tokendlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + GGTOKENDLGDATA *dat = (GGTOKENDLGDATA *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch(msg) + { + case WM_INITDIALOG: + { + RECT rc; + TranslateDialogDefault(hwndDlg); + GetClientRect(GetDlgItem(hwndDlg, IDC_WHITERECT), &rc); + InvalidateRect(hwndDlg, &rc, TRUE); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); + return TRUE; + } + + case WM_COMMAND: + switch(LOWORD(wParam)) + { + case IDOK: + { + GetDlgItemTextA(hwndDlg, IDC_TOKEN, dat->val, sizeof(dat->val)); + EndDialog(hwndDlg, IDOK); + break; + } + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + } + break; + + case WM_PAINT: + { + PAINTSTRUCT paintStruct; + HDC hdc = BeginPaint(hwndDlg, &paintStruct); + RECT rc; GetClientRect(GetDlgItem(hwndDlg, IDC_WHITERECT), &rc); + FillRect(hdc, &rc, (HBRUSH)GetStockObject(WHITE_BRUSH)); + + if (dat && dat->hBitmap) + { + HDC hdcBmp = NULL; + int nWidth, nHeight; + BITMAP bmp; + + GetObject(dat->hBitmap, sizeof(bmp), &bmp); + nWidth = bmp.bmWidth; nHeight = bmp.bmHeight; + + if (hdcBmp = CreateCompatibleDC(hdc)) + { + SelectObject(hdcBmp, dat->hBitmap); + SetStretchBltMode(hdc, HALFTONE); + BitBlt(hdc, + (rc.left + rc.right - nWidth) / 2, + (rc.top + rc.bottom - nHeight) / 2, + nWidth, nHeight, + hdcBmp, 0, 0, SRCCOPY); + DeleteDC(hdcBmp); + } + } + EndPaint(hwndDlg, &paintStruct); + return 0; + } + } + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////////// +// Gets GG token +int GGPROTO::gettoken(GGTOKEN *token) +{ + struct gg_http *h = NULL; + struct gg_token *t = NULL; + IMGSRVC_MEMIO memio = {0}; + GGTOKENDLGDATA dat = {0}; + + // Zero tokens + strcpy(token->id, ""); + strcpy(token->val, ""); + + if (!(h = gg_token(0)) || gg_token_watch_fd(h) || h->state == GG_STATE_ERROR || h->state != GG_STATE_DONE) { + TCHAR error[128]; + mir_sntprintf(error, SIZEOF(error), TranslateT("Token retrieval failed because of error:\n\t%S"), http_error_string(h ? h->error : 0)); + MessageBox(NULL, error, m_tszUserName, MB_OK | MB_ICONSTOP); + gg_free_pubdir(h); + return FALSE; + } + + if (!(t = (struct gg_token *)h->data) || (!h->body)) { + TCHAR error[128]; + mir_sntprintf(error, SIZEOF(error), TranslateT("Token retrieval failed because of error:\n\t%S"), http_error_string(h ? h->error : 0)); + MessageBox(NULL, error, m_tszUserName, MB_OK | MB_ICONSTOP); + gg_free_pubdir(h); + return FALSE; + } + + // Return token id + strncpy(dat.id, t->tokenid, sizeof(dat.id)); + dat.width = t->width; + dat.height = t->height; + + // Load bitmap + memio.iLen = h->body_size; + memio.pBuf = (void *)h->body; + memio.fif = FIF_UNKNOWN; /* detect */ + memio.flags = 0; + dat.hBitmap = (HBITMAP) CallService(MS_IMG_LOADFROMMEM, (WPARAM) &memio, 0); + if (dat.hBitmap == NULL) + { + MessageBox(NULL, TranslateT("Could not load token image."), m_tszUserName, MB_OK | MB_ICONSTOP); + gg_free_pubdir(h); + return FALSE; + } + + // Load token dialog + if (DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_TOKEN), NULL, gg_tokendlgproc, (LPARAM)&dat) == IDCANCEL) + return FALSE; + + // Fillup patterns + strncpy(token->id, dat.id, sizeof(token->id)); + strncpy(token->val, dat.val, sizeof(token->val)); + + return TRUE; +} diff --git a/protocols/Gadu-Gadu/src/userutils.cpp b/protocols/Gadu-Gadu/src/userutils.cpp new file mode 100644 index 0000000000..c9681c56ef --- /dev/null +++ b/protocols/Gadu-Gadu/src/userutils.cpp @@ -0,0 +1,329 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2003-2006 Adam Strzelecki +// +// 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 "gg.h" + +//////////////////////////////////////////////////////////////////////////////// +// Create New Account : Proc + +void *gg_doregister(GGPROTO *gg, char *newPass, char *newEmail) +{ + // Connection handles + struct gg_http *h = NULL; + struct gg_pubdir *s = NULL; + GGTOKEN token; + +#ifdef DEBUGMODE + gg->netlog("gg_doregister(): Starting."); +#endif + if (!newPass || !newEmail) return NULL; + + // Load token + if (!gg->gettoken(&token)) return NULL; + + if (!(h = gg_register3(newEmail, newPass, token.id, token.val, 0)) || !(s = (gg_pubdir*)h->data) || !s->success || !s->uin) + { + TCHAR error[128]; + mir_sntprintf(error, SIZEOF(error), TranslateT("Cannot register new account because of error:\n\t%S"), + (h && !s) ? http_error_string(h ? h->error : 0) : + (s ? Translate("Registration rejected") : strerror(errno))); + MessageBox( + NULL, + error, + gg->m_tszUserName, + MB_OK | MB_ICONSTOP + ); + gg->netlog("gg_doregister(): Cannot register because of \"%s\".", strerror(errno)); + } + else + { + db_set_dw(NULL, gg->m_szModuleName, GG_KEY_UIN, s->uin); + CallService(MS_DB_CRYPT_ENCODESTRING, strlen(newPass) + 1, (LPARAM) newPass); + gg->checknewuser(s->uin, newPass); + db_set_s(NULL, gg->m_szModuleName, GG_KEY_PASSWORD, newPass); + db_set_s(NULL, gg->m_szModuleName, GG_KEY_EMAIL, newEmail); + gg_pubdir_free(h); + gg->netlog("gg_doregister(): Account registration succesful."); + MessageBox( NULL, + TranslateT("You have registered new account.\nPlease fill up your personal details in \"M->View/Change My Details...\""), + gg->m_tszUserName, MB_OK | MB_ICONINFORMATION); + } + +#ifdef DEBUGMODE + gg->netlog("gg_doregister(): End."); +#endif + + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +// Remove Account : Proc +void *gg_dounregister(GGPROTO *gg, uin_t uin, char *password) +{ + // Connection handles + struct gg_http *h; + struct gg_pubdir *s; + GGTOKEN token; + +#ifdef DEBUGMODE + gg->netlog("gg_dounregister(): Starting."); +#endif + if (!uin || !password) return NULL; + + // Load token + if (!gg->gettoken(&token)) return NULL; + + if (!(h = gg_unregister3(uin, password, token.id, token.val, 0)) || !(s = (gg_pubdir*)h->data) || !s->success || s->uin != uin) + { + TCHAR error[128]; + mir_sntprintf(error, SIZEOF(error), TranslateT("Your account cannot be removed because of error:\n\t%S"), + (h && !s) ? http_error_string(h ? h->error : 0) : + (s ? Translate("Bad number or password") : strerror(errno))); + MessageBox(NULL, error, gg->m_tszUserName, MB_OK | MB_ICONSTOP); + gg->netlog("gg_dounregister(): Cannot remove account because of \"%s\".", strerror(errno)); + } + else + { + gg_pubdir_free(h); + db_unset(NULL, gg->m_szModuleName, GG_KEY_PASSWORD); + db_unset(NULL, gg->m_szModuleName, GG_KEY_UIN); + gg->netlog("gg_dounregister(): Account %d has been removed.", uin); + MessageBox(NULL, TranslateT("Your account has been removed."), gg->m_tszUserName, MB_OK | MB_ICONINFORMATION); + } + +#ifdef DEBUGMODE + gg->netlog("gg_dounregister(): End."); +#endif + + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +// Change Password Page : Proc + +void *gg_dochpass(GGPROTO *gg, uin_t uin, char *password, char *newPass) +{ + // Readup email + char email[255] = "\0"; DBVARIANT dbv_email; + // Connection handles + struct gg_http *h; + struct gg_pubdir *s; + GGTOKEN token; + +#ifdef DEBUGMODE + gg->netlog("gg_dochpass(): Starting."); +#endif + if (!uin || !password || !newPass) return NULL; + + if (!db_get_s(NULL, gg->m_szModuleName, GG_KEY_EMAIL, &dbv_email, DBVT_ASCIIZ)) + { + strncpy(email, dbv_email.pszVal, sizeof(email)); + DBFreeVariant(&dbv_email); + } + + // Load token + if (!gg->gettoken(&token)) + return NULL; + + if (!(h = gg_change_passwd4(uin, email, password, newPass, token.id, token.val, 0)) || !(s = (gg_pubdir*)h->data) || !s->success) + { + TCHAR error[128]; + mir_sntprintf(error, SIZEOF(error), TranslateT("Your password cannot be changed because of error:\n\t%S"), + (h && !s) ? http_error_string(h ? h->error : 0) : + (s ? Translate("Invalid data entered") : strerror(errno))); + MessageBox(NULL, error, gg->m_tszUserName, MB_OK | MB_ICONSTOP); + gg->netlog("gg_dochpass(): Cannot change password because of \"%s\".", strerror(errno)); + } + else + { + gg_pubdir_free(h); + CallService(MS_DB_CRYPT_ENCODESTRING, strlen(newPass) + 1, (LPARAM) newPass); + db_set_s(NULL, gg->m_szModuleName, GG_KEY_PASSWORD, newPass); + gg->netlog("gg_dochpass(): Password change succesful."); + MessageBox(NULL, TranslateT("Your password has been changed."), gg->m_tszUserName, MB_OK | MB_ICONINFORMATION); + } + +#ifdef DEBUGMODE + gg->netlog("gg_dochpass(): End."); +#endif + + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +// Change E-mail Page : Proc + +void *gg_dochemail(GGPROTO *gg, uin_t uin, char *password, char *email, char *newEmail) +{ + // Connection handles + struct gg_http *h; + struct gg_pubdir *s; + GGTOKEN token; + +#ifdef DEBUGMODE + gg->netlog("gg_doemail(): Starting."); +#endif + if (!uin || !email || !newEmail) return NULL; + + // Load token + if (!gg->gettoken(&token)) return NULL; + + if (!(h = gg_change_passwd4(uin, newEmail, password, password, token.id, token.val, 0)) || !(s = (gg_pubdir*)h->data) || !s->success) + { + TCHAR error[128]; + mir_sntprintf(error, SIZEOF(error), TranslateT("Your e-mail cannot be changed because of error:\n\t%s"), + (h && !s) ? http_error_string(h ? h->error : 0) : (s ? Translate("Bad old e-mail or password") : strerror(errno))); + MessageBox(NULL, error, gg->m_tszUserName, MB_OK | MB_ICONSTOP); + gg->netlog("gg_dochpass(): Cannot change e-mail because of \"%s\".", strerror(errno)); + } + else + { + gg_pubdir_free(h); + db_set_s(NULL, gg->m_szModuleName, GG_KEY_EMAIL, newEmail); + gg->netlog("gg_doemail(): E-mail change succesful."); + MessageBox(NULL, TranslateT("Your e-mail has been changed."), gg->m_tszUserName, MB_OK | MB_ICONINFORMATION); + } + +#ifdef DEBUGMODE + gg->netlog("gg_doemail(): End."); +#endif + + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +// User Util Dlg Page : Data +INT_PTR CALLBACK gg_userutildlgproc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + GGUSERUTILDLGDATA *dat = (GGUSERUTILDLGDATA *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch (msg) + { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + WindowSetIcon(hwndDlg, "settings"); + dat = (GGUSERUTILDLGDATA *)lParam; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam); + if (dat) SetDlgItemTextA(hwndDlg, IDC_EMAIL, dat->email); // Readup email + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_PASSWORD: + case IDC_CPASSWORD: + case IDC_CONFIRM: + { + char pass[128], cpass[128]; + BOOL enable; + GetDlgItemTextA(hwndDlg, IDC_PASSWORD, pass, sizeof(pass)); + GetDlgItemTextA(hwndDlg, IDC_CPASSWORD, cpass, sizeof(cpass)); + enable = strlen(pass) && strlen(cpass) && !strcmp(cpass, pass); + if (dat && dat->mode == GG_USERUTIL_REMOVE) + EnableWindow(GetDlgItem(hwndDlg, IDOK), IsDlgButtonChecked(hwndDlg, IDC_CONFIRM) ? enable : FALSE); + else + EnableWindow(GetDlgItem(hwndDlg, IDOK), enable); + break; + } + + case IDOK: + { + char pass[128], cpass[128], email[128]; + GetDlgItemTextA(hwndDlg, IDC_PASSWORD, pass, sizeof(pass)); + GetDlgItemTextA(hwndDlg, IDC_CPASSWORD, cpass, sizeof(cpass)); + GetDlgItemTextA(hwndDlg, IDC_EMAIL, email, sizeof(email)); + EndDialog(hwndDlg, IDOK); + + // Check dialog box mode + if (!dat) break; + switch (dat->mode) + { + case GG_USERUTIL_CREATE: gg_doregister(dat->gg, pass, email); break; + case GG_USERUTIL_REMOVE: gg_dounregister(dat->gg, dat->uin, pass); break; + case GG_USERUTIL_PASS: gg_dochpass(dat->gg, dat->uin, dat->pass, pass); break; + case GG_USERUTIL_EMAIL: gg_dochemail(dat->gg, dat->uin, dat->pass, dat->email, email); break; + } + break; + } + + case IDCANCEL: + EndDialog(hwndDlg, IDCANCEL); + break; + } + break; + + case WM_DESTROY: + WindowFreeIcon(hwndDlg); + break; + } + return FALSE; +} + +////////////////////////////////////////////////////////// +// Hooks protocol event + +HANDLE GGPROTO::hookProtoEvent(const char* szEvent, GGEventFunc handler) +{ + return ::HookEventObj(szEvent, ( MIRANDAHOOKOBJ )*( void** )&handler, this); +} + +////////////////////////////////////////////////////////// +// Adds a new protocol specific service function + +void GGPROTO::createObjService(const char* szService, GGServiceFunc serviceProc) +{ + CreateServiceFunctionObj(szService, (MIRANDASERVICEOBJ)*( void** )&serviceProc, this); +} + +void GGPROTO::createProtoService(const char* szService, GGServiceFunc serviceProc) +{ + char str[MAXMODULELABELLENGTH]; + mir_snprintf(str, sizeof(str), "%s%s", m_szModuleName, szService); + CreateServiceFunctionObj(str, (MIRANDASERVICEOBJ)*( void** )&serviceProc, this); +} + +////////////////////////////////////////////////////////// +// Forks a thread + +void GGPROTO::forkthread(GGThreadFunc pFunc, void *param) +{ + UINT threadId; + CloseHandle( mir_forkthreadowner((pThreadFuncOwner)*(void**)&pFunc, this, param, &threadId)); +} + +////////////////////////////////////////////////////////// +// Forks a thread and returns a pseudo handle for it + +HANDLE GGPROTO::forkthreadex(GGThreadFunc pFunc, void *param, UINT *threadId) +{ + return mir_forkthreadowner((pThreadFuncOwner)*(void**)&pFunc, this, param, threadId); +} + +////////////////////////////////////////////////////////// +// Wait for thread to stop + +void GGPROTO::threadwait(GGTHREAD *thread) +{ + if (!thread->hThread) return; + while (WaitForSingleObjectEx(thread->hThread, INFINITE, TRUE) != WAIT_OBJECT_0); + CloseHandle(thread->hThread); + ZeroMemory(thread, sizeof(GGTHREAD)); +} + diff --git a/protocols/Gadu-Gadu/src/version.h b/protocols/Gadu-Gadu/src/version.h new file mode 100644 index 0000000000..ce9e719bd6 --- /dev/null +++ b/protocols/Gadu-Gadu/src/version.h @@ -0,0 +1,23 @@ +//////////////////////////////////////////////////////////////////////////////// +// Gadu-Gadu Plugin for Miranda IM +// +// Copyright (c) 2010 Bartosz Białek +// +// 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. +//////////////////////////////////////////////////////////////////////////////// + +#define __FILEVERSION_STRING 0,11,0,1 +#define __VERSION_STRING "0.11.0.1" +#define __VERSION_DWORD PLUGIN_MAKE_VERSION(0, 11, 0, 1) -- cgit v1.2.3