From 3b55a62fdcb1f8222de3c2c8fbed530792c419a0 Mon Sep 17 00:00:00 2001 From: Vadim Dashevskiy Date: Fri, 12 Oct 2012 14:53:57 +0000 Subject: GTalkExt, ICQ, IRC, Jabber: folders restructurization git-svn-id: http://svn.miranda-ng.org/main/trunk@1890 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/IcqOscarJ/src/UI/askauthentication.cpp | 96 + protocols/IcqOscarJ/src/UI/icqoscar.h | 2 + protocols/IcqOscarJ/src/UI/loginpassword.cpp | 97 + protocols/IcqOscarJ/src/UI/userinfotab.cpp | 315 +++ protocols/IcqOscarJ/src/capabilities.cpp | 271 ++ protocols/IcqOscarJ/src/capabilities.h | 45 + protocols/IcqOscarJ/src/chan_01login.cpp | 105 + protocols/IcqOscarJ/src/chan_02data.cpp | 222 ++ protocols/IcqOscarJ/src/chan_03error.cpp | 35 + protocols/IcqOscarJ/src/chan_04close.cpp | 314 +++ protocols/IcqOscarJ/src/chan_05ping.cpp | 89 + protocols/IcqOscarJ/src/changeinfo/changeinfo.h | 122 + protocols/IcqOscarJ/src/changeinfo/constants.cpp | 198 ++ protocols/IcqOscarJ/src/changeinfo/db.cpp | 224 ++ protocols/IcqOscarJ/src/changeinfo/dlgproc.cpp | 540 ++++ protocols/IcqOscarJ/src/changeinfo/editlist.cpp | 201 ++ protocols/IcqOscarJ/src/changeinfo/editstring.cpp | 367 +++ protocols/IcqOscarJ/src/changeinfo/icqoscar.h | 2 + protocols/IcqOscarJ/src/changeinfo/main.cpp | 28 + protocols/IcqOscarJ/src/changeinfo/upload.cpp | 91 + protocols/IcqOscarJ/src/channels.h | 47 + protocols/IcqOscarJ/src/cookies.cpp | 301 ++ protocols/IcqOscarJ/src/cookies.h | 148 + protocols/IcqOscarJ/src/directpackets.cpp | 293 ++ protocols/IcqOscarJ/src/fam_01service.cpp | 983 +++++++ protocols/IcqOscarJ/src/fam_02location.cpp | 312 +++ protocols/IcqOscarJ/src/fam_03buddy.cpp | 787 ++++++ protocols/IcqOscarJ/src/fam_04message.cpp | 3043 +++++++++++++++++++++ protocols/IcqOscarJ/src/fam_09bos.cpp | 106 + protocols/IcqOscarJ/src/fam_0alookup.cpp | 130 + protocols/IcqOscarJ/src/fam_0bstatus.cpp | 62 + protocols/IcqOscarJ/src/fam_13servclist.cpp | 2068 ++++++++++++++ protocols/IcqOscarJ/src/fam_15icqserver.cpp | 1201 ++++++++ protocols/IcqOscarJ/src/fam_17signon.cpp | 175 ++ protocols/IcqOscarJ/src/families.h | 74 + protocols/IcqOscarJ/src/globals.h | 57 + protocols/IcqOscarJ/src/guids.h | 81 + protocols/IcqOscarJ/src/i18n.cpp | 541 ++++ protocols/IcqOscarJ/src/i18n.h | 70 + protocols/IcqOscarJ/src/iconlib.cpp | 92 + protocols/IcqOscarJ/src/iconlib.h | 52 + protocols/IcqOscarJ/src/icq_advsearch.cpp | 185 ++ protocols/IcqOscarJ/src/icq_advsearch.h | 32 + protocols/IcqOscarJ/src/icq_avatar.cpp | 1814 ++++++++++++ protocols/IcqOscarJ/src/icq_avatar.h | 127 + protocols/IcqOscarJ/src/icq_clients.cpp | 1155 ++++++++ protocols/IcqOscarJ/src/icq_constants.h | 652 +++++ protocols/IcqOscarJ/src/icq_db.cpp | 399 +++ protocols/IcqOscarJ/src/icq_db.h | 44 + protocols/IcqOscarJ/src/icq_direct.cpp | 1171 ++++++++ protocols/IcqOscarJ/src/icq_direct.h | 94 + protocols/IcqOscarJ/src/icq_directmsg.cpp | 361 +++ protocols/IcqOscarJ/src/icq_fieldnames.cpp | 595 ++++ protocols/IcqOscarJ/src/icq_fieldnames.h | 49 + protocols/IcqOscarJ/src/icq_filerequests.cpp | 218 ++ protocols/IcqOscarJ/src/icq_filetransfer.cpp | 515 ++++ protocols/IcqOscarJ/src/icq_firstrun.cpp | 124 + protocols/IcqOscarJ/src/icq_http.cpp | 212 ++ protocols/IcqOscarJ/src/icq_http.h | 48 + protocols/IcqOscarJ/src/icq_infoupdate.cpp | 401 +++ protocols/IcqOscarJ/src/icq_menu.cpp | 263 ++ protocols/IcqOscarJ/src/icq_opts.cpp | 617 +++++ protocols/IcqOscarJ/src/icq_packet.cpp | 898 ++++++ protocols/IcqOscarJ/src/icq_packet.h | 120 + protocols/IcqOscarJ/src/icq_popups.cpp | 323 +++ protocols/IcqOscarJ/src/icq_popups.h | 41 + protocols/IcqOscarJ/src/icq_proto.cpp | 2375 ++++++++++++++++ protocols/IcqOscarJ/src/icq_proto.h | 984 +++++++ protocols/IcqOscarJ/src/icq_rates.cpp | 529 ++++ protocols/IcqOscarJ/src/icq_rates.h | 146 + protocols/IcqOscarJ/src/icq_server.cpp | 444 +++ protocols/IcqOscarJ/src/icq_server.h | 71 + protocols/IcqOscarJ/src/icq_servlist.cpp | 2829 +++++++++++++++++++ protocols/IcqOscarJ/src/icq_servlist.h | 172 ++ protocols/IcqOscarJ/src/icq_uploadui.cpp | 1019 +++++++ protocols/IcqOscarJ/src/icq_xstatus.cpp | 1381 ++++++++++ protocols/IcqOscarJ/src/icq_xtraz.cpp | 466 ++++ protocols/IcqOscarJ/src/icqosc_svcs.cpp | 816 ++++++ protocols/IcqOscarJ/src/icqosc_svcs.h | 39 + protocols/IcqOscarJ/src/icqoscar.cpp | 35 + protocols/IcqOscarJ/src/icqoscar.h | 134 + protocols/IcqOscarJ/src/init.cpp | 245 ++ protocols/IcqOscarJ/src/init.h | 40 + protocols/IcqOscarJ/src/log.cpp | 161 ++ protocols/IcqOscarJ/src/log.h | 38 + protocols/IcqOscarJ/src/oscar_filetransfer.cpp | 2447 +++++++++++++++++ protocols/IcqOscarJ/src/oscar_filetransfer.h | 164 ++ protocols/IcqOscarJ/src/resource.h | 176 ++ protocols/IcqOscarJ/src/stdpackets.cpp | 1895 +++++++++++++ protocols/IcqOscarJ/src/stdpackets.h | 43 + protocols/IcqOscarJ/src/tlv.cpp | 391 +++ protocols/IcqOscarJ/src/tlv.h | 81 + protocols/IcqOscarJ/src/utilities.cpp | 2183 +++++++++++++++ protocols/IcqOscarJ/src/utilities.h | 188 ++ protocols/IcqOscarJ/src/version.h | 3 + 95 files changed, 43940 insertions(+) create mode 100644 protocols/IcqOscarJ/src/UI/askauthentication.cpp create mode 100644 protocols/IcqOscarJ/src/UI/icqoscar.h create mode 100644 protocols/IcqOscarJ/src/UI/loginpassword.cpp create mode 100644 protocols/IcqOscarJ/src/UI/userinfotab.cpp create mode 100644 protocols/IcqOscarJ/src/capabilities.cpp create mode 100644 protocols/IcqOscarJ/src/capabilities.h create mode 100644 protocols/IcqOscarJ/src/chan_01login.cpp create mode 100644 protocols/IcqOscarJ/src/chan_02data.cpp create mode 100644 protocols/IcqOscarJ/src/chan_03error.cpp create mode 100644 protocols/IcqOscarJ/src/chan_04close.cpp create mode 100644 protocols/IcqOscarJ/src/chan_05ping.cpp create mode 100644 protocols/IcqOscarJ/src/changeinfo/changeinfo.h create mode 100644 protocols/IcqOscarJ/src/changeinfo/constants.cpp create mode 100644 protocols/IcqOscarJ/src/changeinfo/db.cpp create mode 100644 protocols/IcqOscarJ/src/changeinfo/dlgproc.cpp create mode 100644 protocols/IcqOscarJ/src/changeinfo/editlist.cpp create mode 100644 protocols/IcqOscarJ/src/changeinfo/editstring.cpp create mode 100644 protocols/IcqOscarJ/src/changeinfo/icqoscar.h create mode 100644 protocols/IcqOscarJ/src/changeinfo/main.cpp create mode 100644 protocols/IcqOscarJ/src/changeinfo/upload.cpp create mode 100644 protocols/IcqOscarJ/src/channels.h create mode 100644 protocols/IcqOscarJ/src/cookies.cpp create mode 100644 protocols/IcqOscarJ/src/cookies.h create mode 100644 protocols/IcqOscarJ/src/directpackets.cpp create mode 100644 protocols/IcqOscarJ/src/fam_01service.cpp create mode 100644 protocols/IcqOscarJ/src/fam_02location.cpp create mode 100644 protocols/IcqOscarJ/src/fam_03buddy.cpp create mode 100644 protocols/IcqOscarJ/src/fam_04message.cpp create mode 100644 protocols/IcqOscarJ/src/fam_09bos.cpp create mode 100644 protocols/IcqOscarJ/src/fam_0alookup.cpp create mode 100644 protocols/IcqOscarJ/src/fam_0bstatus.cpp create mode 100644 protocols/IcqOscarJ/src/fam_13servclist.cpp create mode 100644 protocols/IcqOscarJ/src/fam_15icqserver.cpp create mode 100644 protocols/IcqOscarJ/src/fam_17signon.cpp create mode 100644 protocols/IcqOscarJ/src/families.h create mode 100644 protocols/IcqOscarJ/src/globals.h create mode 100644 protocols/IcqOscarJ/src/guids.h create mode 100644 protocols/IcqOscarJ/src/i18n.cpp create mode 100644 protocols/IcqOscarJ/src/i18n.h create mode 100644 protocols/IcqOscarJ/src/iconlib.cpp create mode 100644 protocols/IcqOscarJ/src/iconlib.h create mode 100644 protocols/IcqOscarJ/src/icq_advsearch.cpp create mode 100644 protocols/IcqOscarJ/src/icq_advsearch.h create mode 100644 protocols/IcqOscarJ/src/icq_avatar.cpp create mode 100644 protocols/IcqOscarJ/src/icq_avatar.h create mode 100644 protocols/IcqOscarJ/src/icq_clients.cpp create mode 100644 protocols/IcqOscarJ/src/icq_constants.h create mode 100644 protocols/IcqOscarJ/src/icq_db.cpp create mode 100644 protocols/IcqOscarJ/src/icq_db.h create mode 100644 protocols/IcqOscarJ/src/icq_direct.cpp create mode 100644 protocols/IcqOscarJ/src/icq_direct.h create mode 100644 protocols/IcqOscarJ/src/icq_directmsg.cpp create mode 100644 protocols/IcqOscarJ/src/icq_fieldnames.cpp create mode 100644 protocols/IcqOscarJ/src/icq_fieldnames.h create mode 100644 protocols/IcqOscarJ/src/icq_filerequests.cpp create mode 100644 protocols/IcqOscarJ/src/icq_filetransfer.cpp create mode 100644 protocols/IcqOscarJ/src/icq_firstrun.cpp create mode 100644 protocols/IcqOscarJ/src/icq_http.cpp create mode 100644 protocols/IcqOscarJ/src/icq_http.h create mode 100644 protocols/IcqOscarJ/src/icq_infoupdate.cpp create mode 100644 protocols/IcqOscarJ/src/icq_menu.cpp create mode 100644 protocols/IcqOscarJ/src/icq_opts.cpp create mode 100644 protocols/IcqOscarJ/src/icq_packet.cpp create mode 100644 protocols/IcqOscarJ/src/icq_packet.h create mode 100644 protocols/IcqOscarJ/src/icq_popups.cpp create mode 100644 protocols/IcqOscarJ/src/icq_popups.h create mode 100644 protocols/IcqOscarJ/src/icq_proto.cpp create mode 100644 protocols/IcqOscarJ/src/icq_proto.h create mode 100644 protocols/IcqOscarJ/src/icq_rates.cpp create mode 100644 protocols/IcqOscarJ/src/icq_rates.h create mode 100644 protocols/IcqOscarJ/src/icq_server.cpp create mode 100644 protocols/IcqOscarJ/src/icq_server.h create mode 100644 protocols/IcqOscarJ/src/icq_servlist.cpp create mode 100644 protocols/IcqOscarJ/src/icq_servlist.h create mode 100644 protocols/IcqOscarJ/src/icq_uploadui.cpp create mode 100644 protocols/IcqOscarJ/src/icq_xstatus.cpp create mode 100644 protocols/IcqOscarJ/src/icq_xtraz.cpp create mode 100644 protocols/IcqOscarJ/src/icqosc_svcs.cpp create mode 100644 protocols/IcqOscarJ/src/icqosc_svcs.h create mode 100644 protocols/IcqOscarJ/src/icqoscar.cpp create mode 100644 protocols/IcqOscarJ/src/icqoscar.h create mode 100644 protocols/IcqOscarJ/src/init.cpp create mode 100644 protocols/IcqOscarJ/src/init.h create mode 100644 protocols/IcqOscarJ/src/log.cpp create mode 100644 protocols/IcqOscarJ/src/log.h create mode 100644 protocols/IcqOscarJ/src/oscar_filetransfer.cpp create mode 100644 protocols/IcqOscarJ/src/oscar_filetransfer.h create mode 100644 protocols/IcqOscarJ/src/resource.h create mode 100644 protocols/IcqOscarJ/src/stdpackets.cpp create mode 100644 protocols/IcqOscarJ/src/stdpackets.h create mode 100644 protocols/IcqOscarJ/src/tlv.cpp create mode 100644 protocols/IcqOscarJ/src/tlv.h create mode 100644 protocols/IcqOscarJ/src/utilities.cpp create mode 100644 protocols/IcqOscarJ/src/utilities.h create mode 100644 protocols/IcqOscarJ/src/version.h (limited to 'protocols/IcqOscarJ/src') diff --git a/protocols/IcqOscarJ/src/UI/askauthentication.cpp b/protocols/IcqOscarJ/src/UI/askauthentication.cpp new file mode 100644 index 0000000000..818d5ce6de --- /dev/null +++ b/protocols/IcqOscarJ/src/UI/askauthentication.cpp @@ -0,0 +1,96 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2008 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +struct AskAuthParam +{ + CIcqProto* ppro; + HANDLE hContact; +}; + +static INT_PTR CALLBACK AskAuthProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + AskAuthParam* dat = (AskAuthParam*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch (msg) { + case WM_INITDIALOG: + dat = (AskAuthParam*)lParam; + if (!dat->hContact || !dat->ppro->icqOnline()) + EndDialog(hwndDlg, 0); + + TranslateDialogDefault(hwndDlg); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); + SendDlgItemMessage(hwndDlg, IDC_EDITAUTH, EM_LIMITTEXT, (WPARAM)255, 0); + SetDlgItemText(hwndDlg, IDC_EDITAUTH, TranslateT("Please authorize me to add you to my contact list.")); + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + if (dat->ppro->icqOnline()) + { + DWORD dwUin; + uid_str szUid; + if ( dat->ppro->getContactUid(dat->hContact, &dwUin, &szUid)) + return TRUE; // Invalid contact + + char* szReason = GetDlgItemTextUtf(hwndDlg, IDC_EDITAUTH); + dat->ppro->icq_sendAuthReqServ(dwUin, szUid, szReason); + SAFE_FREE((void**)&szReason); + + // auth bug fix (thx Bio) + if (dat->ppro->m_bSsiEnabled && dwUin) + dat->ppro->resetServContactAuthState(dat->hContact, dwUin); + + EndDialog(hwndDlg, 0); + } + return TRUE; + + case IDCANCEL: + EndDialog(hwndDlg, 0); + return TRUE; + } + + break; + + case WM_CLOSE: + EndDialog(hwndDlg,0); + return TRUE; + } + + return FALSE; +} + +INT_PTR CIcqProto::RequestAuthorization(WPARAM wParam, LPARAM lParam) +{ + AskAuthParam param = { this, (HANDLE)wParam }; + DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_ASKAUTH), NULL, AskAuthProc, (LPARAM)¶m); + return 0; +} diff --git a/protocols/IcqOscarJ/src/UI/icqoscar.h b/protocols/IcqOscarJ/src/UI/icqoscar.h new file mode 100644 index 0000000000..77283f6f7f --- /dev/null +++ b/protocols/IcqOscarJ/src/UI/icqoscar.h @@ -0,0 +1,2 @@ +/* For MinGW sake */ +#include "../icqoscar.h" diff --git a/protocols/IcqOscarJ/src/UI/loginpassword.cpp b/protocols/IcqOscarJ/src/UI/loginpassword.cpp new file mode 100644 index 0000000000..ab65212331 --- /dev/null +++ b/protocols/IcqOscarJ/src/UI/loginpassword.cpp @@ -0,0 +1,97 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2009 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +INT_PTR CALLBACK LoginPasswdDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + CIcqProto* ppro = (CIcqProto*)GetWindowLongPtr( hwndDlg, GWLP_USERDATA ); + + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + + ppro = (CIcqProto*)lParam; + SetWindowLongPtr( hwndDlg, GWLP_USERDATA, lParam ); + { + SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)ppro->m_hIconProtocol->GetIcon(true)); + SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)ppro->m_hIconProtocol->GetIcon()); + + DWORD dwUin = ppro->getContactUin(NULL); + + char pszUIN[MAX_PATH], str[MAX_PATH]; + null_snprintf(pszUIN, 128, ICQTranslateUtfStatic(LPGEN("Enter a password for UIN %u:"), str, MAX_PATH), dwUin); + SetDlgItemTextUtf(hwndDlg, IDC_INSTRUCTION, pszUIN); + + SendDlgItemMessage(hwndDlg, IDC_LOGINPW, EM_LIMITTEXT, PASSWORDMAXLEN - 1, 0); + + CheckDlgButton(hwndDlg, IDC_SAVEPASS, ppro->getSettingByte(NULL, "RememberPass", 0)); + } + break; + + case WM_DESTROY: + ppro->m_hIconProtocol->ReleaseIcon(true); + ppro->m_hIconProtocol->ReleaseIcon(); + break; + + case WM_CLOSE: + EndDialog(hwndDlg, 0); + break; + + case WM_COMMAND: + { + switch (LOWORD(wParam)) { + case IDOK: + ppro->m_bRememberPwd = (BYTE)IsDlgButtonChecked(hwndDlg, IDC_SAVEPASS); + ppro->setSettingByte(NULL, "RememberPass", ppro->m_bRememberPwd); + + GetDlgItemTextA(hwndDlg, IDC_LOGINPW, ppro->m_szPassword, sizeof(ppro->m_szPassword)); + + ppro->icq_login(ppro->m_szPassword); + + EndDialog(hwndDlg, IDOK); + break; + + case IDCANCEL: + ppro->SetCurrentStatus(ID_STATUS_OFFLINE); + EndDialog(hwndDlg, IDCANCEL); + break; + } + } + break; + } + + return FALSE; +} + +void CIcqProto::RequestPassword() +{ + DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_LOGINPW), NULL, LoginPasswdDlgProc, LPARAM(this)); +} diff --git a/protocols/IcqOscarJ/src/UI/userinfotab.cpp b/protocols/IcqOscarJ/src/UI/userinfotab.cpp new file mode 100644 index 0000000000..91d28c0e04 --- /dev/null +++ b/protocols/IcqOscarJ/src/UI/userinfotab.cpp @@ -0,0 +1,315 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Code for User details ICQ specific pages +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +#define SVS_NORMAL 0 +#define SVS_ZEROISUNSPEC 2 +#define SVS_IP 3 +#define SVS_SIGNED 6 +#define SVS_ICQVERSION 8 +#define SVS_TIMESTAMP 9 +#define SVS_STATUSID 10 + +char* MirandaVersionToString(char* szStr, int bUnicode, int v, int m); + +extern const char *nameXStatus[]; + + +///////////////////////////////////////////////////////////////////////////////////////// + +static void SetValue(CIcqProto* ppro, HWND hwndDlg, int idCtrl, HANDLE hContact, char* szModule, char* szSetting, int special) +{ + DBVARIANT dbv = {0}; + char str[MAX_PATH]; + char* pstr = NULL; + int unspecified = 0; + int bUtf = 0, bDbv = 0, bAlloc = 0; + + dbv.type = DBVT_DELETED; + + if ((hContact == NULL) && ((int)szModule<0x100)) + { + dbv.type = (BYTE)szModule; + + switch((int)szModule) { + case DBVT_BYTE: + dbv.cVal = (BYTE)szSetting; + break; + case DBVT_WORD: + dbv.wVal = (WORD)szSetting; + break; + case DBVT_DWORD: + dbv.dVal = (DWORD)szSetting; + break; + case DBVT_ASCIIZ: + dbv.pszVal = pstr = szSetting; + break; + default: + unspecified = 1; + dbv.type = DBVT_DELETED; + } + } + else + { + if (szModule == NULL) + unspecified = 1; + else + { + unspecified = DBGetContactSetting(hContact, szModule, szSetting, &dbv); + bDbv = 1; + } + } + + if (!unspecified) + { + switch (dbv.type) { + case DBVT_BYTE: + 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_ICQVERSION) + { + if (dbv.wVal != 0) + { + char szExtra[80]; + + null_snprintf(str, 250, "%d", dbv.wVal); + pstr = str; + + if (hContact && ppro->IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 1)) + { + ICQTranslateUtfStatic(LPGEN(" (DC Established)"), szExtra, 80); + strcat(str, (char*)szExtra); + bUtf = 1; + } + } + else + unspecified = 1; + } + else if (special == SVS_STATUSID) + { + char *pXName; + char *pszStatus = MirandaStatusToStringUtf(dbv.wVal); + BYTE bXStatus = ppro->getContactXStatus(hContact); + + if (bXStatus) + { + pXName = ppro->getSettingStringUtf(hContact, DBSETTING_XSTATUS_NAME, NULL); + if (!strlennull(pXName)) + { // give default name + pXName = ICQTranslateUtf(nameXStatus[bXStatus-1]); + } + null_snprintf(str, sizeof(str), "%s (%s)", pszStatus, pXName); + SAFE_FREE((void**)&pXName); + } + else + null_snprintf(str, sizeof(str), pszStatus); + + bUtf = 1; + SAFE_FREE(&pszStatus); + pstr = str; + unspecified = 0; + } + 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_TIMESTAMP) + { + if (dbv.dVal == 0) + unspecified = 1; + else + pstr = time2text(dbv.dVal); + } + else + pstr = _itoa(special == SVS_SIGNED ? dbv.lVal:dbv.dVal, str, 10); + break; + + case DBVT_ASCIIZ: + case DBVT_WCHAR: + unspecified = (special == SVS_ZEROISUNSPEC && dbv.pszVal[0] == '\0'); + if (!unspecified && pstr != szSetting) + { + pstr = ppro->getSettingStringUtf(hContact, szModule, szSetting, NULL); + bUtf = 1; + bAlloc = 1; + } + if (idCtrl == IDC_UIN) + SetDlgItemTextUtf(hwndDlg, IDC_UINSTATIC, ICQTranslateUtfStatic(LPGEN("ScreenName:"), str, MAX_PATH)); + break; + + default: + pstr = str; + strcpy(str,"???"); + break; + } + } + + EnableDlgItem(hwndDlg, idCtrl, !unspecified); + if (unspecified) + SetDlgItemTextUtf(hwndDlg, idCtrl, ICQTranslateUtfStatic(LPGEN(""), str, MAX_PATH)); + else if (bUtf) + SetDlgItemTextUtf(hwndDlg, idCtrl, pstr); + else + SetDlgItemTextA(hwndDlg, idCtrl, pstr); + + if (bDbv) + ICQFreeVariant(&dbv); + + if (bAlloc) + SAFE_FREE(&pstr); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static INT_PTR CALLBACK IcqDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + break; + + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->idFrom) { + case 0: + switch (((LPNMHDR)lParam)->code) { + case PSN_PARAMCHANGED: + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (( PSHNOTIFY* )lParam )->lParam ); + break; + + case PSN_INFOCHANGED: + { + CIcqProto* ppro = (CIcqProto*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + if (!ppro) + break; + + char* szProto; + HANDLE hContact = (HANDLE)((LPPSHNOTIFY)lParam)->lParam; + + if (hContact == NULL) + szProto = ppro->m_szModuleName; + else + szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + + if (!szProto) + break; + + SetValue(ppro, hwndDlg, IDC_UIN, hContact, szProto, UNIQUEIDSETTING, SVS_NORMAL); + SetValue(ppro, hwndDlg, IDC_ONLINESINCE, hContact, szProto, "LogonTS", SVS_TIMESTAMP); + SetValue(ppro, hwndDlg, IDC_IDLETIME, hContact, szProto, "IdleTS", SVS_TIMESTAMP); + SetValue(ppro, hwndDlg, IDC_IP, hContact, szProto, "IP", SVS_IP); + SetValue(ppro, hwndDlg, IDC_REALIP, hContact, szProto, "RealIP", SVS_IP); + + if (hContact) + { + SetValue(ppro, hwndDlg, IDC_PORT, hContact, szProto, "UserPort", SVS_ZEROISUNSPEC); + SetValue(ppro, hwndDlg, IDC_VERSION, hContact, szProto, "Version", SVS_ICQVERSION); + SetValue(ppro, hwndDlg, IDC_MIRVER, hContact, szProto, "MirVer", SVS_ZEROISUNSPEC); + if (ppro->getSettingByte(hContact, "ClientID", 0)) + ppro->setSettingDword(hContact, "TickTS", 0); + SetValue(ppro, hwndDlg, IDC_SYSTEMUPTIME, hContact, szProto, "TickTS", SVS_TIMESTAMP); + SetValue(ppro, hwndDlg, IDC_STATUS, hContact, szProto, "Status", SVS_STATUSID); + } + else + { + char str[MAX_PATH]; + + SetValue(ppro, hwndDlg, IDC_PORT, hContact, (char*)DBVT_WORD, (char*)ppro->wListenPort, SVS_ZEROISUNSPEC); + SetValue(ppro, hwndDlg, IDC_VERSION, hContact, (char*)DBVT_WORD, (char*)ICQ_VERSION, SVS_ICQVERSION); + SetValue(ppro, hwndDlg, IDC_MIRVER, hContact, (char*)DBVT_ASCIIZ, MirandaVersionToString(str, TRUE, ICQ_PLUG_VERSION, CallService(MS_SYSTEM_GETVERSION,0,0)), SVS_ZEROISUNSPEC); + SetDlgItemTextUtf(hwndDlg, IDC_SUPTIME, ICQTranslateUtfStatic(LPGEN("Member since:"), str, MAX_PATH)); + SetValue(ppro, hwndDlg, IDC_SYSTEMUPTIME, hContact, szProto, "MemberTS", SVS_TIMESTAMP); + SetValue(ppro, hwndDlg, IDC_STATUS, hContact, (char*)DBVT_WORD, (char*)ppro->m_iStatus, SVS_STATUSID); + } + } + break; + } + break; + } + break; + + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDCANCEL: + SendMessage(GetParent(hwndDlg),msg,wParam,lParam); + break; + } + break; + } + + return FALSE; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +int CIcqProto::OnUserInfoInit(WPARAM wParam, LPARAM lParam) +{ + if ((!IsICQContact((HANDLE)lParam)) && lParam) + return 0; + + OPTIONSDIALOGPAGE odp = {0}; + odp.cbSize = sizeof(odp); + odp.flags = ODPF_TCHAR | ODPF_DONTTRANSLATE; + odp.hInstance = hInst; + odp.dwInitParam = LPARAM(this); + odp.pfnDlgProc = IcqDlgProc; + odp.position = -1900000000; + odp.ptszTitle = m_tszUserName; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_INFO_ICQ); + UserInfo_AddPage(wParam, &odp); + + if (!lParam) + { + TCHAR buf[200]; + null_snprintf(buf, SIZEOF(buf), TranslateT("%s Details"), m_tszUserName); + odp.ptszTitle = buf; + + odp.position = -1899999999; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_INFO_CHANGEINFO); + odp.pfnDlgProc = ChangeInfoDlgProc; + UserInfo_AddPage(wParam, &odp); + } + return 0; +} diff --git a/protocols/IcqOscarJ/src/capabilities.cpp b/protocols/IcqOscarJ/src/capabilities.cpp new file mode 100644 index 0000000000..f994f861af --- /dev/null +++ b/protocols/IcqOscarJ/src/capabilities.cpp @@ -0,0 +1,271 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Contains helper functions to handle oscar user capabilities. Scanning and +// adding capabilities are assumed to be more timecritical than looking up +// capabilites. During the login sequence there could possibly be many hundred +// scans but only a few lookups. So when you add or change something in this +// code you must have this in mind, dont do anything that will slow down the +// adding process too much. +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +struct icq_capability +{ + DWORD capID; // A bitmask, we use it in order to save database space + capstr capCLSID; // A binary representation of a oscar capability +}; + +static const icq_capability CapabilityRecord[] = +{ + {CAPF_SRV_RELAY, {CAP_SRV_RELAY }}, + {CAPF_UTF, {CAP_UTF }}, + {CAPF_RTF, {CAP_RTF }}, + {CAPF_CONTACTS, {CAP_CONTACTS }}, + {CAPF_TYPING, {CAP_TYPING }}, + {CAPF_ICQDIRECT, {CAP_ICQDIRECT }}, + {CAPF_XTRAZ, {CAP_XTRAZ }}, + {CAPF_OSCAR_FILE,{CAP_OSCAR_FILE}} +}; + +// Mask of all handled capabilities' flags +#define CapabilityFlagsMask (CAPF_SRV_RELAY | CAPF_UTF | CAPF_RTF | CAPF_CONTACTS | CAPF_TYPING | CAPF_ICQDIRECT | CAPF_XTRAZ | CAPF_OSCAR_FILE) + + +#ifdef _DEBUG +struct icq_capability_name +{ + DWORD capID; + const char* capName; +}; + +static const icq_capability_name CapabilityNames[] = +{ + {CAPF_SRV_RELAY, "ServerRelay"}, + {CAPF_UTF, "UTF8 Messages"}, + {CAPF_RTF, "RTF Messages"}, + {CAPF_CONTACTS, "Contact Transfer"}, + {CAPF_TYPING, "Typing Notifications"}, + {CAPF_ICQDIRECT, "Direct Connections"}, + {CAPF_XTRAZ, "Xtraz"}, + {CAPF_OSCAR_FILE, "File Transfers"}, + {CAPF_STATUS_MESSAGES,"Individual Status Messages"}, + {CAPF_STATUS_MOOD, "Mood"}, + {CAPF_XSTATUS, "Custom Status"} +}; + +void NetLog_CapabilityChange(CIcqProto *ppro, const char *szChange, DWORD fdwCapabilities) +{ + char szBuffer[MAX_PATH] = {0}; + + if (!fdwCapabilities) return; + + for (int nIndex = 0; nIndex < SIZEOF(CapabilityNames); nIndex++) + { + // Check if the current capability is present + if ((fdwCapabilities & CapabilityNames[nIndex].capID) == CapabilityNames[nIndex].capID) + { + if (strlennull(szBuffer)) + strcat(szBuffer, ", "); + strcat(szBuffer, CapabilityNames[nIndex].capName); + } + } + // Log the change + ppro->NetLog_Server("Capabilities: %s %s", szChange, szBuffer); +} +#endif + + +// Deletes all oscar capabilities for a given contact +void CIcqProto::ClearAllContactCapabilities(HANDLE hContact) +{ + setSettingDword(hContact, DBSETTING_CAPABILITIES, 0); +} + + +// Deletes one or many oscar capabilities for a given contact +void CIcqProto::ClearContactCapabilities(HANDLE hContact, DWORD fdwCapabilities) +{ + // Get current capability flags + DWORD fdwContactCaps = getSettingDword(hContact, DBSETTING_CAPABILITIES, 0); + + if (fdwContactCaps != (fdwContactCaps & ~fdwCapabilities)) + { +#ifdef _DEBUG + NetLog_CapabilityChange(this, "Removed", fdwCapabilities & fdwContactCaps); +#endif + // Clear unwanted capabilities + fdwContactCaps &= ~fdwCapabilities; + + // And write it back to disk + setSettingDword(hContact, DBSETTING_CAPABILITIES, fdwContactCaps); + } +} + + +// Sets one or many oscar capabilities for a given contact +void CIcqProto::SetContactCapabilities(HANDLE hContact, DWORD fdwCapabilities) +{ + // Get current capability flags + DWORD fdwContactCaps = getSettingDword(hContact, DBSETTING_CAPABILITIES, 0); + + if (fdwContactCaps != (fdwContactCaps | fdwCapabilities)) + { +#ifdef _DEBUG + NetLog_CapabilityChange(this, "Added", fdwCapabilities & ~fdwContactCaps); +#endif + // Update them + fdwContactCaps |= fdwCapabilities; + + // And write it back to disk + setSettingDword(hContact, DBSETTING_CAPABILITIES, fdwContactCaps); + } +} + + +// Returns true if the given contact supports the requested capabilites +BOOL CIcqProto::CheckContactCapabilities(HANDLE hContact, DWORD fdwCapabilities) +{ + // Get current capability flags + DWORD fdwContactCaps = getSettingDword(hContact, DBSETTING_CAPABILITIES, 0); + + // Check if all requested capabilities are supported + if ((fdwContactCaps & fdwCapabilities) == fdwCapabilities) + return TRUE; + + return FALSE; +} + + +// Scan capability against the capability buffer +capstr* MatchCapability(BYTE *buf, int bufsize, const capstr *cap, int capsize) +{ + while (bufsize >= BINARY_CAP_SIZE) // search the buffer for a capability + { + if (!memcmp(buf, cap, capsize)) + { + return (capstr*)buf; // give found capability for version info + } + else + { + buf += BINARY_CAP_SIZE; + bufsize -= BINARY_CAP_SIZE; + } + } + return 0; +} + + +// Scan short capability against the capability buffer +capstr* MatchShortCapability(BYTE *buf, int bufsize, const shortcapstr *cap) +{ + capstr fullCap; + + memcpy(fullCap, capShortCaps, BINARY_CAP_SIZE); + fullCap[2] = (*cap)[0]; + fullCap[3] = (*cap)[1]; + + return MatchCapability(buf, bufsize, &fullCap, BINARY_CAP_SIZE); +} + + +// Scans a binary buffer for OSCAR capabilities. +DWORD GetCapabilitiesFromBuffer(BYTE *pBuffer, int nLength) +{ + DWORD fdwCaps = 0; + + // Calculate the number of records + int nRecordSize = SIZEOF(CapabilityRecord); + + // Loop over all capabilities in the buffer and + // compare them to our own record of capabilities + for (int nIndex = 0; nIndex < nRecordSize; nIndex++) + { + if (MatchCapability(pBuffer, nLength, &CapabilityRecord[nIndex].capCLSID, BINARY_CAP_SIZE)) + { // Match, add capability flag + fdwCaps |= CapabilityRecord[nIndex].capID; + } + } + + return fdwCaps; +} + + +// Scans a binary buffer for oscar capabilities and adds them to the contact. +// You probably want to call ClearAllContactCapabilities() first. +void CIcqProto::AddCapabilitiesFromBuffer(HANDLE hContact, BYTE *pBuffer, int nLength) +{ + // Get current capability flags + DWORD fdwContactCaps = getSettingDword(hContact, DBSETTING_CAPABILITIES, 0); + // Get capability flags from buffer + DWORD fdwCapabilities = GetCapabilitiesFromBuffer(pBuffer, nLength); + + if (fdwContactCaps != (fdwContactCaps | fdwCapabilities)) + { +#ifdef _DEBUG + NetLog_CapabilityChange(this, "Added", fdwCapabilities & ~fdwContactCaps); +#endif + // Add capability flags from buffer + fdwContactCaps |= fdwCapabilities; + + // And write them back to database + setSettingDword(hContact, DBSETTING_CAPABILITIES, fdwContactCaps); + } +} + + +// Scans a binary buffer for oscar capabilities and adds them to the contact. +// You probably want to call ClearAllContactCapabilities() first. +void CIcqProto::SetCapabilitiesFromBuffer(HANDLE hContact, BYTE *pBuffer, int nLength, BOOL bReset) +{ + // Get current capability flags + DWORD fdwContactCaps = bReset ? 0 : getSettingDword(hContact, DBSETTING_CAPABILITIES, 0); + // Get capability flags from buffer + DWORD fdwCapabilities = GetCapabilitiesFromBuffer(pBuffer, nLength); + +#ifdef _DEBUG + if (bReset) + NetLog_CapabilityChange(this, "Set", fdwCapabilities); + else + { + NetLog_CapabilityChange(this, "Removed", fdwContactCaps & ~fdwCapabilities & CapabilityFlagsMask); + NetLog_CapabilityChange(this, "Added", fdwCapabilities & ~fdwContactCaps); + } +#endif + + if (fdwCapabilities != (fdwContactCaps & ~CapabilityFlagsMask)) + { // Get current unmanaged capability flags + fdwContactCaps &= ~CapabilityFlagsMask; + + // Add capability flags from buffer + fdwContactCaps |= fdwCapabilities; + + // And write them back to database + setSettingDword(hContact, DBSETTING_CAPABILITIES, fdwContactCaps); + } +} diff --git a/protocols/IcqOscarJ/src/capabilities.h b/protocols/IcqOscarJ/src/capabilities.h new file mode 100644 index 0000000000..3b6590dd09 --- /dev/null +++ b/protocols/IcqOscarJ/src/capabilities.h @@ -0,0 +1,45 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Contains helper functions to handle OSCAR user capabilities. +// +// ----------------------------------------------------------------------------- + +#ifndef __CAPABILITIES_H +#define __CAPABILITIES_H + + +// capabilities +typedef BYTE capstr[BINARY_CAP_SIZE]; +typedef BYTE shortcapstr[BINARY_SHORT_CAP_SIZE]; + +extern const capstr capShortCaps; + +capstr* MatchCapability(BYTE *buf, int bufsize, const capstr *cap, int capsize = BINARY_CAP_SIZE); +capstr* MatchShortCapability(BYTE *buf, int bufsize, const shortcapstr *cap); + + +#endif /* __CAPABILITIES_H */ diff --git a/protocols/IcqOscarJ/src/chan_01login.cpp b/protocols/IcqOscarJ/src/chan_01login.cpp new file mode 100644 index 0000000000..3c4c4febc6 --- /dev/null +++ b/protocols/IcqOscarJ/src/chan_01login.cpp @@ -0,0 +1,105 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2009 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +void CIcqProto::handleLoginChannel(BYTE *buf, WORD datalen, serverthread_info *info) +{ + icq_packet packet; + +#ifdef _DEBUG + NetLog_Server("Received SRV_HELLO from %s", info->isLoginServer ? "login server" : "communication server"); +#endif + + // isLoginServer is "1" if we just received SRV_HELLO + if (info->isLoginServer) + { + if (m_bSecureLogin) + { + char szUin[UINMAXLEN]; + WORD wUinLen; + +#ifdef _DEBUG + NetLog_Server("Sending %s to %s", "CLI_HELLO", "login server"); +#endif + packet.wLen = 12; + write_flap(&packet, ICQ_LOGIN_CHAN); + packDWord(&packet, 0x00000001); + packTLVDWord(&packet, 0x8003, 0x00100000); // unknown + sendServPacket(&packet); // greet login server + + wUinLen = strlennull(strUID(m_dwLocalUIN, szUin)); +#ifdef _DEBUG + NetLog_Server("Sending %s to %s", "ICQ_SIGNON_AUTH_REQUEST", "login server"); +#endif + + serverPacketInit(&packet, (WORD)(14 + wUinLen)); + packFNACHeader(&packet, ICQ_AUTHORIZATION_FAMILY, ICQ_SIGNON_AUTH_REQUEST, 0, 0); + packTLV(&packet, 0x0001, wUinLen, (LPBYTE)szUin); + sendServPacket(&packet); // request login digest + } + else + { + sendClientAuth((char*)info->szAuthKey, info->wAuthKeyLen, FALSE); +#ifdef _DEBUG + NetLog_Server("Sent CLI_IDENT to %s", "login server"); +#endif + } + + info->isLoginServer = 0; + if (info->cookieDataLen) + { + SAFE_FREE((void**)&info->cookieData); + info->cookieDataLen = 0; + } + } + else + { + if (info->cookieDataLen) + { + wLocalSequence = generate_flap_sequence(); + + serverCookieInit(&packet, info->cookieData, (WORD)info->cookieDataLen); + sendServPacket(&packet); + +#ifdef _DEBUG + NetLog_Server("Sent CLI_IDENT to %s", "communication server"); +#endif + + SAFE_FREE((void**)&info->cookieData); + info->cookieDataLen = 0; + } + else + { + // We need a cookie to identify us to the communication server + NetLog_Server("Error: Connected to %s without a cookie!", "communication server"); + } + } +} diff --git a/protocols/IcqOscarJ/src/chan_02data.cpp b/protocols/IcqOscarJ/src/chan_02data.cpp new file mode 100644 index 0000000000..d55e0262e9 --- /dev/null +++ b/protocols/IcqOscarJ/src/chan_02data.cpp @@ -0,0 +1,222 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Handle channel 2 (Data) packets +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +void CIcqProto::handleDataChannel(BYTE *pBuffer, WORD wBufferLength, serverthread_info *info) +{ + snac_header snacHeader = {0}; + + if (!unpackSnacHeader(&snacHeader, &pBuffer, &wBufferLength) || !snacHeader.bValid) + { + NetLog_Server("Error: Failed to parse SNAC header"); + } + else + { +#ifdef _DEBUG + if (snacHeader.wFlags & 0x8000) + NetLog_Server(" Received SNAC(x%02X,x%02X), version %u", snacHeader.wFamily, snacHeader.wSubtype, snacHeader.wVersion); + else + NetLog_Server(" Received SNAC(x%02X,x%02X)", snacHeader.wFamily, snacHeader.wSubtype); +#endif + + switch (snacHeader.wFamily) { + + case ICQ_SERVICE_FAMILY: + handleServiceFam(pBuffer, wBufferLength, &snacHeader, info); + break; + + case ICQ_LOCATION_FAMILY: + handleLocationFam(pBuffer, wBufferLength, &snacHeader); + break; + + case ICQ_BUDDY_FAMILY: + handleBuddyFam(pBuffer, wBufferLength, &snacHeader, info); + break; + + case ICQ_MSG_FAMILY: + handleMsgFam(pBuffer, wBufferLength, &snacHeader); + break; + + case ICQ_BOS_FAMILY: + handleBosFam(pBuffer, wBufferLength, &snacHeader); + break; + + case ICQ_LOOKUP_FAMILY: + handleLookupFam(pBuffer, wBufferLength, &snacHeader); + break; + + case ICQ_STATS_FAMILY: + handleStatusFam(pBuffer, wBufferLength, &snacHeader); + break; + + case ICQ_LISTS_FAMILY: + handleServCListFam(pBuffer, wBufferLength, &snacHeader, info); + break; + + case ICQ_EXTENSIONS_FAMILY: + handleIcqExtensionsFam(pBuffer, wBufferLength, &snacHeader); + break; + + case ICQ_AUTHORIZATION_FAMILY: + handleAuthorizationFam(pBuffer, wBufferLength, &snacHeader, info); + break; + + default: + NetLog_Server("Ignoring SNAC(x%02X,x%02X) - FAMILYx%02X not implemented", snacHeader.wFamily, snacHeader.wSubtype, snacHeader.wFamily); + break; + + } + } +} + + +int unpackSnacHeader(snac_header *pSnacHeader, BYTE **pBuffer, WORD *pwBufferLength) +{ + WORD wRef1, wRef2; + + // Check header + if (!pSnacHeader) return 0; + + // 10 bytes is the minimum size of a header + if (*pwBufferLength < 10) + { + // Buffer overflow + pSnacHeader->bValid = FALSE; + return 1; + } + + // Unpack all the standard data + unpackWord(pBuffer, &(pSnacHeader->wFamily)); + unpackWord(pBuffer, &(pSnacHeader->wSubtype)); + unpackWord(pBuffer, &(pSnacHeader->wFlags)); + unpackWord(pBuffer, &wRef1); // unpack reference id (sequence) + unpackWord(pBuffer, &wRef2); // command + pSnacHeader->dwRef = wRef1 | (wRef2<<0x10); + + *pwBufferLength -= 10; + + // If flag bit 15 is set, we also have a version tag + // (...at least that is what I think it is) + if (pSnacHeader->wFlags & 0x8000) + { + if (*pwBufferLength >= 2) + { + WORD wExtraBytes = 0; + + unpackWord(pBuffer, &wExtraBytes); + *pwBufferLength -= 2; + + if (*pwBufferLength >= wExtraBytes) + { + if (wExtraBytes == 6) + { + *pBuffer += 4; // TLV type and length? + unpackWord(pBuffer, &(pSnacHeader->wVersion)); + *pwBufferLength -= wExtraBytes; + pSnacHeader->bValid = TRUE; + } + else if (wExtraBytes == 0x0E) + { + *pBuffer += 8; // TLV(2) - unknown + *pBuffer += 4; + unpackWord(pBuffer, &(pSnacHeader->wVersion)); + *pwBufferLength -= wExtraBytes; + pSnacHeader->bValid = TRUE; + } + else + { + *pBuffer += wExtraBytes; + *pwBufferLength -= wExtraBytes; + pSnacHeader->bValid = TRUE; + } + } + else + { + // Buffer overflow + pSnacHeader->bValid = FALSE; + } + } + else + { + // Buffer overflow + pSnacHeader->bValid = FALSE; + } + } + else + { + pSnacHeader->bValid = TRUE; + } + + return 1; +} + + +void CIcqProto::LogFamilyError(WORD wFamily, WORD wError) +{ + char *msg; + + switch(wError) { + case 0x01: msg = "Invalid SNAC header"; break; + case 0x02: msg = "Server rate limit exceeded"; break; + case 0x03: msg = "Client rate limit exceeded"; break; + case 0x04: msg = "Recipient is not logged in"; break; + case 0x05: msg = "Requested service unavailable"; break; + case 0x06: msg = "Requested service not defined"; break; + case 0x07: msg = "You sent obsolete SNAC"; break; + case 0x08: msg = "Not supported by server"; break; + case 0x09: msg = "Not supported by client"; break; + case 0x0A: msg = "Refused by client"; break; + case 0x0B: msg = "Reply too big"; break; + case 0x0C: msg = "Responses lost"; break; + case 0x0D: msg = "Request denied"; break; + case 0x0E: msg = "Incorrect SNAC format"; break; + case 0x0F: msg = "Insufficient rights"; break; + case 0x10: msg = "In local permit/deny (recipient blocked)"; break; + case 0x11: msg = "Sender is too evil"; break; + case 0x12: msg = "Receiver is too evil"; break; + case 0x13: msg = "User temporarily unavailable"; break; + case 0x14: msg = "No match"; break; + case 0x15: msg = "List overflow"; break; + case 0x16: msg = "Request ambiguous"; break; + case 0x17: msg = "Server queue full"; break; + case 0x18: msg = "Not while on AOL"; break; + case 0x19: msg = "Query failed"; break; + case 0x1A: msg = "Timeout"; break; + case 0x1C: msg = "General failure"; break; + case 0x1D: msg = "Progress"; break; + case 0x1E: msg = "In free area"; break; + case 0x1F: msg = "Restricted by parental controls"; break; + case 0x20: msg = "Remote restricted by parental controls"; break; + default: msg = ""; break; + } + + NetLog_Server("SNAC(x%02X,x01) - Error(%u): %s", wFamily, wError, msg); +} diff --git a/protocols/IcqOscarJ/src/chan_03error.cpp b/protocols/IcqOscarJ/src/chan_03error.cpp new file mode 100644 index 0000000000..71c6e2ea7c --- /dev/null +++ b/protocols/IcqOscarJ/src/chan_03error.cpp @@ -0,0 +1,35 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000,2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001,2002 Jon Keating, Richard Hughes +// Copyright © 2002,2003,2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004,2005 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +void CIcqProto::handleErrorChannel(unsigned char* buf, WORD datalen) +{ + NetLog_Server("Ignoring server packet on ERROR channel"); +} diff --git a/protocols/IcqOscarJ/src/chan_04close.cpp b/protocols/IcqOscarJ/src/chan_04close.cpp new file mode 100644 index 0000000000..3c9ee46350 --- /dev/null +++ b/protocols/IcqOscarJ/src/chan_04close.cpp @@ -0,0 +1,314 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2009 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +void CIcqProto::handleCloseChannel(BYTE *buf, WORD datalen, serverthread_info *info) +{ + oscar_tlv_chain *chain = NULL; + + // Parse server reply, prepare reconnection + if (!info->bLoggedIn && datalen && !info->newServerReady) + handleLoginReply(buf, datalen, info); + + if (info->isMigrating) + handleMigration(info); + + if ((!info->bLoggedIn || info->isMigrating) && info->newServerReady) + { + if (!connectNewServer(info)) + { // Connecting failed + if (info->isMigrating) + icq_LogUsingErrorCode(LOG_ERROR, GetLastError(), LPGEN("Unable to connect to migrated ICQ communication server")); + else + icq_LogUsingErrorCode(LOG_ERROR, GetLastError(), LPGEN("Unable to connect to ICQ communication server")); + + SetCurrentStatus(ID_STATUS_OFFLINE); + + info->isMigrating = 0; + } + info->newServerReady = 0; + + return; + } + + if (chain = readIntoTLVChain(&buf, datalen, 0)) + { + // TLV 9 errors (runtime errors?) + WORD wError = chain->getWord(0x09, 1); + if (wError) + { + SetCurrentStatus(ID_STATUS_OFFLINE); + + handleRuntimeError(wError); + } + + disposeChain(&chain); + } + // Server closed connection on error, or sign off + NetLib_CloseConnection(&hServerConn, TRUE); +} + + +void CIcqProto::handleLoginReply(BYTE *buf, WORD datalen, serverthread_info *info) +{ + oscar_tlv_chain *chain = NULL; + + icq_sendCloseConnection(); // imitate icq5 behaviour + + if (!(chain = readIntoTLVChain(&buf, datalen, 0))) + { + NetLog_Server("Error: Missing chain on close channel"); + NetLib_CloseConnection(&hServerConn, TRUE); + return; // Invalid data + } + + // TLV 8 errors (signon errors?) + WORD wError = chain->getWord(0x08, 1); + if (wError) + { + handleSignonError(wError); + + // we return only if the server did not gave us cookie (possible to connect with soft error) + if (!chain->getLength(0x06, 1)) + { + disposeChain(&chain); + SetCurrentStatus(ID_STATUS_OFFLINE); + icq_serverDisconnect(FALSE); + return; // Failure + } + } + + // We are in the login phase and no errors were reported. + // Extract communication server info. + info->newServer = chain->getString(0x05, 1); + info->newServerSSL = chain->getNumber(0x8E, 1); + info->cookieData = (BYTE*)chain->getString(0x06, 1); + info->cookieDataLen = chain->getLength(0x06, 1); + + // We dont need this anymore + disposeChain(&chain); + + if (!info->newServer || !info->cookieData) + { + icq_LogMessage(LOG_FATAL, LPGEN("You could not sign on because the server returned invalid data. Try again.")); + + SAFE_FREE(&info->newServer); + SAFE_FREE((void**)&info->cookieData); + info->cookieDataLen = 0; + + SetCurrentStatus(ID_STATUS_OFFLINE); + NetLib_CloseConnection(&hServerConn, TRUE); + return; // Failure + } + + NetLog_Server("Authenticated."); + info->newServerReady = 1; + + return; +} + + +int CIcqProto::connectNewServer(serverthread_info *info) +{ + int res = 0; + + /* Get the ip and port */ + WORD wServerPort = info->wServerPort; // prepare default port + parseServerAddress(info->newServer, &wServerPort); + + NETLIBOPENCONNECTION nloc = {0}; + nloc.flags = 0; + nloc.szHost = info->newServer; + nloc.wPort = wServerPort; + + if (!m_bGatewayMode) + { + NetLib_SafeCloseHandle(&info->hPacketRecver); + NetLib_CloseConnection(&hServerConn, TRUE); + + NetLog_Server("Closed connection to login server"); + + hServerConn = NetLib_OpenConnection(m_hServerNetlibUser, NULL, &nloc); + if (hServerConn && info->newServerSSL) + { /* Start SSL session if requested */ + if (!CallService(MS_NETLIB_STARTSSL, (WPARAM)hServerConn, 0)) + NetLib_CloseConnection(&hServerConn, FALSE); + } + + if (hServerConn) + { + /* Time to recreate the packet receiver */ + info->hPacketRecver = (HANDLE)CallService(MS_NETLIB_CREATEPACKETRECVER, (WPARAM)hServerConn, 0x2400); + if (!info->hPacketRecver) + { + NetLog_Server("Error: Failed to create packet receiver."); + } + else // we need to reset receiving structs + { + info->bReinitRecver = 1; + res = 1; + } + } + } + else + { // TODO: We should really do some checks here + NetLog_Server("Walking in Gateway to %s", info->newServer); + // TODO: This REQUIRES more work (most probably some kind of mid-netlib module) + icq_httpGatewayWalkTo(hServerConn, &nloc); + res = 1; + } + if (!res) SAFE_FREE((void**)&info->cookieData); + + // Free allocated memory + // NOTE: "cookie" will get freed when we have connected to the communication server. + SAFE_FREE(&info->newServer); + + return res; +} + + +void CIcqProto::handleMigration(serverthread_info *info) +{ + // Check the data that was saved when the migration was announced + NetLog_Server("Migrating to %s", info->newServer); + if (!info->newServer || !info->cookieData) + { + icq_LogMessage(LOG_FATAL, LPGEN("You have been disconnected from the ICQ network because the current server shut down.")); + + SAFE_FREE(&info->newServer); + SAFE_FREE((void**)&info->cookieData); + info->newServerReady = 0; + info->isMigrating = 0; + } +} + +void CIcqProto::handleSignonError(WORD wError) +{ + switch (wError) { + + case 0x01: // Unregistered uin + case 0x04: // Incorrect uin or password + case 0x05: // Mismatch uin or password + case 0x06: // Internal Client error (bad input to authorizer) + case 0x07: // Invalid account + BroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_WRONGPASSWORD); + ZeroMemory(m_szPassword, sizeof(m_szPassword)); + icq_LogFatalParam(LPGEN("Connection failed.\nYour ICQ number or password was rejected (%d)."), wError); + break; + + case 0x02: // Service temporarily unavailable + case 0x0D: // Bad database status + case 0x10: // Service temporarily offline + case 0x12: // Database send error + case 0x14: // Reservation map error + case 0x15: // Reservation link error + case 0x1A: // Reservation timeout + BroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_NOSERVER); + icq_LogFatalParam(LPGEN("Connection failed.\nThe server is temporarily unavailable (%d)."), wError); + break; + + case 0x16: // The users num connected from this IP has reached the maximum + case 0x17: // The users num connected from this IP has reached the maximum (reserved) + icq_LogFatalParam(LPGEN("Connection failed.\nServer has too many connections from your IP (%d)."), wError); + break; + + case 0x18: // Reservation rate limit exceeded + case 0x1D: // Rate limit exceeded + BroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_NOSERVER); + icq_LogFatalParam(LPGEN("Connection failed.\nYou have connected too quickly,\nplease wait and retry 10 to 20 minutes later (%d)."), wError); + break; + + case 0x1B: // You are using an older version of ICQ. Upgrade required + BroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_WRONGPROTOCOL); + icq_LogMessage(LOG_FATAL, LPGEN("Connection failed.\nThe server did not accept this client version.")); + break; + + case 0x1C: // You are using an older version of ICQ. Upgrade recommended + icq_LogMessage(LOG_WARNING, LPGEN("The server sent warning, this version is getting old.\nTry to look for a new one.")); + break; + + case 0x1E: // Can't register on the ICQ network + icq_LogMessage(LOG_FATAL, LPGEN("Connection failed.\nYou were rejected by the server for an unknown reason.\nThis can happen if the UIN is already connected.")); + break; + + case 0x0C: // Invalid database fields, MD5 login not supported + icq_LogMessage(LOG_FATAL, LPGEN("Connection failed.\nSecure (MD5) login is not supported on this account.")); + break; + + case 0: // No error + break; + + case 0x08: // Deleted account + case 0x09: // Expired account + case 0x0A: // No access to database + case 0x0B: // No access to resolver + case 0x0E: // Bad resolver status + case 0x0F: // Internal error + case 0x11: // Suspended account + case 0x13: // Database link error + case 0x19: // User too heavily warned + case 0x1F: // Token server timeout + case 0x20: // Invalid SecureID number + case 0x21: // MC error + case 0x22: // Age restriction + case 0x23: // RequireRevalidation + case 0x24: // Link rule rejected + case 0x25: // Missing information or bad SNAC format + case 0x26: // Link broken + case 0x27: // Invalid client IP + case 0x28: // Partner rejected + case 0x29: // SecureID missing + case 0x2A: // Blocked account | Bump user + + default: + icq_LogFatalParam(LPGEN("Connection failed.\nUnknown error during sign on: 0x%02x"), wError); + break; + } +} + + +void CIcqProto::handleRuntimeError(WORD wError) +{ + switch (wError) + { + + case 0x01: + { + BroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_OTHERLOCATION); + icq_LogMessage(LOG_FATAL, LPGEN("You have been disconnected from the ICQ network because you logged on from another location using the same ICQ number.")); + break; + } + + default: + icq_LogFatalParam(LPGEN("Unknown runtime error: 0x%02x"), wError); + break; + } +} diff --git a/protocols/IcqOscarJ/src/chan_05ping.cpp b/protocols/IcqOscarJ/src/chan_05ping.cpp new file mode 100644 index 0000000000..2a2b843509 --- /dev/null +++ b/protocols/IcqOscarJ/src/chan_05ping.cpp @@ -0,0 +1,89 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2009 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +void CIcqProto::handlePingChannel(BYTE *buf, WORD datalen) +{ + NetLog_Server("Warning: Ignoring server packet on PING channel"); +} + + +void __cdecl CIcqProto::KeepAliveThread(void *arg) +{ + serverthread_info *info = (serverthread_info*)arg; + icq_packet packet; + DWORD dwInterval = getSettingDword(NULL, "KeepAliveInterval", KEEPALIVE_INTERVAL); + + NetLog_Server("Keep alive thread starting."); + + info->hKeepAliveEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + + for (;;) + { + DWORD dwWait = ICQWaitForSingleObject(info->hKeepAliveEvent, dwInterval); + if (serverThreadHandle == NULL) // connection lost, end + break; + if (dwWait == WAIT_TIMEOUT) + { + // Send a keep alive packet to server + packet.wLen = 0; + write_flap(&packet, ICQ_PING_CHAN); + sendServPacket(&packet); + } + else if (dwWait == WAIT_IO_COMPLETION) + // Possible shutdown in progress + if (Miranda_Terminated()) break; + else + break; + } + + NetLog_Server("Keep alive thread ended."); + + CloseHandle(info->hKeepAliveEvent); + info->hKeepAliveEvent = NULL; +} + + +void CIcqProto::StartKeepAlive(serverthread_info *info) +{ + if (info->hKeepAliveEvent) // start only once + return; + + if (getSettingByte(NULL, "KeepAlive", DEFAULT_KEEPALIVE_ENABLED)) + CloseHandle( ForkThreadEx(&CIcqProto::KeepAliveThread, info)); +} + + +void CIcqProto::StopKeepAlive(serverthread_info *info) +{ // finish keep alive thread + if (info->hKeepAliveEvent) + SetEvent(info->hKeepAliveEvent); +} diff --git a/protocols/IcqOscarJ/src/changeinfo/changeinfo.h b/protocols/IcqOscarJ/src/changeinfo/changeinfo.h new file mode 100644 index 0000000000..ecc4fc3106 --- /dev/null +++ b/protocols/IcqOscarJ/src/changeinfo/changeinfo.h @@ -0,0 +1,122 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2001-2004 Richard Hughes, Martin Öberg +// Copyright © 2004-2010 Joe Kucera, Bio +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// ChangeInfo Plugin stuff +// +// ----------------------------------------------------------------------------- + +#ifndef __CHANGEINFO_H +#define __CHANGEINFO_H + + +#ifndef AW_SLIDE +#define SPI_GETCOMBOBOXANIMATION 0x1004 +#define AW_SLIDE 0x40000 +#define AW_ACTIVATE 0x20000 +#define AW_VER_POSITIVE 0x4 +#define UDM_SETPOS32 (WM_USER+113) +#define UDM_GETPOS32 (WM_USER+114) +#endif + + +#define LI_DIVIDER 0 +#define LI_STRING 1 +#define LI_LIST 2 +#define LI_LONGSTRING 3 +#define LI_NUMBER 4 +#define LIM_TYPE 0x0000FFFF +#define LIF_ZEROISVALID 0x80000000 +#define LIF_SIGNED 0x40000000 +#define LIF_PASSWORD 0x20000000 +#define LIF_CHANGEONLY 0x10000000 + +struct SettingItem +{ + const char *szDescription; + unsigned displayType; //LI_ constant + int dbType; //DBVT_ constant + const char *szDbSetting; + const void *pList; +}; + +struct SettingItemData +{ + LPARAM value; + int changed; +}; + +// contants.c +extern const SettingItem setting[]; +extern const int settingCount; + +//dlgproc.c +struct ChangeInfoData : public MZeroedObject +{ + HWND hwndDlg; + CIcqProto *ppro; + HFONT hListFont; + HWND hwndList; + int editTopIndex; + int iEditItem; + char Password[PASSWORDMAXLEN]; + + SettingItemData *settingData; + + HANDLE hAckHook; + HANDLE hUpload[2]; + + ChangeInfoData() { settingData = (SettingItemData*)SAFE_MALLOC(sizeof(SettingItemData) * settingCount); hAckHook = NULL; hUpload[0] = NULL; hUpload[1] = NULL;} + ~ChangeInfoData() { SAFE_FREE((void**)&settingData); } + + char* GetItemSettingText(int i, char *buf, size_t buf_size); + void PaintItemSetting(HDC hdc, RECT *rc, int i, UINT itemState); + + //db.cpp + void LoadSettingsFromDb(int keepChanged); + void FreeStoredDbSettings(void); + int ChangesMade(void); + void ClearChangeFlags(void); + int SaveSettingsToDb(HWND hwndDlg); + + //upload.cpp + int UploadSettings(void); + + //editstring.cpp + void BeginStringEdit(int iItem,RECT *rc,int i,WORD wVKey); + void EndStringEdit(int save); + //editlist.cpp + void BeginListEdit(int iItem, RECT *rc, int iSetting, WORD wVKey); + void EndListEdit(int save); +}; + +INT_PTR CALLBACK ChangeInfoDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); + +//editstring.c +int IsStringEditWindow(HWND hwnd); +char* BinaryToEscapes(char *str); + +//editlist.c +int IsListEditWindow(HWND hwnd); + +#endif /* __CHANGEINFO_H */ diff --git a/protocols/IcqOscarJ/src/changeinfo/constants.cpp b/protocols/IcqOscarJ/src/changeinfo/constants.cpp new file mode 100644 index 0000000000..92403a74f0 --- /dev/null +++ b/protocols/IcqOscarJ/src/changeinfo/constants.cpp @@ -0,0 +1,198 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2001-2004 Richard Hughes, Martin Öberg +// Copyright © 2004-2009 Joe Kucera, Bio +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// ChangeInfo Plugin stuff +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +static FieldNamesItem timezones[]={ + {24 ,LPGEN("GMT-12:00 Eniwetok; Kwajalein")}, + {23 ,LPGEN("GMT-11:30")}, + {22 ,LPGEN("GMT-11:00 Midway Island; Samoa")}, + {21 ,LPGEN("GMT-10:30")}, + {20 ,LPGEN("GMT-10:00 Hawaii")}, + {19 ,LPGEN("GMT-9:30")}, + {18 ,LPGEN("GMT-9:00 Alaska")}, + {17 ,LPGEN("GMT-8:30")}, + {16 ,LPGEN("GMT-8:00 Pacific Time; Tijuana")}, + {15 ,LPGEN("GMT-7:30")}, + {14 ,LPGEN("GMT-7:00 Arizona; Mountain Time")}, + {13 ,LPGEN("GMT-6:30")}, + {12 ,LPGEN("GMT-6:00 Central Time; Central America; Saskatchewan")}, + {11 ,LPGEN("GMT-5:30")}, + {10 ,LPGEN("GMT-5:00 Eastern Time; Bogota; Lima; Quito")}, + {9 ,LPGEN("GMT-4:30")}, + {8 ,LPGEN("GMT-4:00 Atlantic Time; Santiago; Caracas; La Paz")}, + {7 ,LPGEN("GMT-3:30 Newfoundland")}, + {6 ,LPGEN("GMT-3:00 Greenland; Buenos Aires; Georgetown")}, + {5 ,LPGEN("GMT-2:30")}, + {4 ,LPGEN("GMT-2:00 Mid-Atlantic")}, + {3 ,LPGEN("GMT-1:30")}, + {2 ,LPGEN("GMT-1:00 Cape Verde Islands; Azores")}, + {1 ,LPGEN("GMT-0:30")}, + {0 ,LPGEN("GMT+0:00 London; Dublin; Edinburgh; Lisbon; Casablanca")}, + {-1 ,LPGEN("GMT+0:30")}, + {-2 ,LPGEN("GMT+1:00 Central European Time; West Central Africa; Warsaw")}, + {-3 ,LPGEN("GMT+1:30")}, + {-4 ,LPGEN("GMT+2:00 Jerusalem; Helsinki; Harare; Cairo; Bucharest; Athens")}, + {-5 ,LPGEN("GMT+2:30")}, + {-6 ,LPGEN("GMT+3:00 Moscow; St. Petersburg; Nairobi; Kuwait; Baghdad")}, + {-7 ,LPGEN("GMT+3:30 Tehran")}, + {-8 ,LPGEN("GMT+4:00 Baku; Tbilisi; Yerevan; Abu Dhabi; Muscat")}, + {-9 ,LPGEN("GMT+4:30 Kabul")}, + {-10 ,LPGEN("GMT+5:00 Calcutta; Chennai; Mumbai; New Delhi; Ekaterinburg")}, + {-11 ,LPGEN("GMT+5:30")}, + {-12 ,LPGEN("GMT+6:00 Astana; Dhaka; Almaty; Novosibirsk; Sri Jayawardenepura")}, + {-13 ,LPGEN("GMT+6:30 Rangoon")}, + {-14 ,LPGEN("GMT+7:00 Bankok; Hanoi; Jakarta; Krasnoyarsk")}, + {-15 ,LPGEN("GMT+7:30")}, + {-16 ,LPGEN("GMT+8:00 Perth; Taipei; Singapore; Hong Kong; Beijing")}, + {-17 ,LPGEN("GMT+8:30")}, + {-18 ,LPGEN("GMT+9:00 Tokyo; Osaka; Seoul; Sapporo; Yakutsk")}, + {-19 ,LPGEN("GMT+9:30 Darwin; Adelaide")}, + {-20 ,LPGEN("GMT+10:00 East Australia; Guam; Vladivostok")}, + {-21 ,LPGEN("GMT+10:30")}, + {-22 ,LPGEN("GMT+11:00 Magadan; Solomon Is.; New Caledonia")}, + {-23 ,LPGEN("GMT+11:30")}, + {-24 ,LPGEN("GMT+12:00 Auckland; Wellington; Fiji; Kamchatka; Marshall Is.")}, + {-100,NULL} +}; + + +static FieldNamesItem months[]={ + {1, LPGEN("January")}, + {2, LPGEN("February")}, + {3, LPGEN("March")}, + {4, LPGEN("April")}, + {5, LPGEN("May")}, + {6, LPGEN("June")}, + {7, LPGEN("July")}, + {8, LPGEN("August")}, + {9, LPGEN("September")}, + {10,LPGEN("October")}, + {11,LPGEN("November")}, + {12,LPGEN("December")}, + {0, NULL} +}; + + +const int ageRange[]={13,0x7FFF}; // 14, 130 +const int yearRange[]={1753,0x7FFF}; // 1880, 2000 +const int dayRange[]={1,31}; + + +const SettingItem setting[]={ + //personal + {LPGEN("Personal"), LI_DIVIDER}, + {LPGEN("Nickname"), LI_STRING, DBVT_UTF8, "Nick"}, + {LPGEN("First name"), LI_STRING, DBVT_UTF8, "FirstName"}, + {LPGEN("Last name"), LI_STRING, DBVT_UTF8, "LastName"}, +// {LPGEN("Age"), LI_NUMBER, DBVT_WORD, "Age", ageRange}, + {LPGEN("Gender"), LI_LIST, DBVT_BYTE, "Gender", genderField}, + {LPGEN("About"), LI_LONGSTRING, DBVT_UTF8, "About"}, + //password + {LPGEN("Password"), LI_DIVIDER}, + {LPGEN("Password"), LI_STRING|LIF_PASSWORD,DBVT_ASCIIZ, "Password"}, + //contact + {LPGEN("Contact"), LI_DIVIDER}, + {LPGEN("Primary e-mail"), LI_STRING, DBVT_ASCIIZ, "e-mail0"}, + {LPGEN("Secondary e-mail"), LI_STRING, DBVT_ASCIIZ, "e-mail1"}, + {LPGEN("Tertiary e-mail"), LI_STRING, DBVT_ASCIIZ, "e-mail2"}, + {LPGEN("Homepage"), LI_STRING, DBVT_ASCIIZ, "Homepage"}, + {LPGEN("Street"), LI_STRING, DBVT_UTF8, "Street"}, + {LPGEN("City"), LI_STRING, DBVT_UTF8, "City"}, + {LPGEN("State"), LI_STRING, DBVT_UTF8, "State"}, + {LPGEN("ZIP/postcode"), LI_STRING, DBVT_UTF8, "ZIP"}, + {LPGEN("Country"), LI_LIST, DBVT_WORD, "Country", countryField}, + {LPGEN("Phone number"), LI_STRING, DBVT_ASCIIZ, "Phone"}, + {LPGEN("Fax number"), LI_STRING, DBVT_ASCIIZ, "Fax"}, + {LPGEN("Cellular number"),LI_STRING, DBVT_ASCIIZ, "Cellular"}, + //more + {LPGEN("Personal Detail"),LI_DIVIDER}, + {LPGEN("Timezone"), LI_LIST|LIF_ZEROISVALID|LIF_SIGNED,DBVT_BYTE, "Timezone", timezones}, + {LPGEN("Year of birth"), LI_NUMBER, DBVT_WORD, "BirthYear", yearRange}, + {LPGEN("Month of birth"), LI_LIST, DBVT_BYTE, "BirthMonth", months}, + {LPGEN("Day of birth"), LI_NUMBER, DBVT_BYTE, "BirthDay", dayRange}, + {LPGEN("Marital Status"), LI_LIST, DBVT_BYTE, "MaritalStatus", maritalField}, + {LPGEN("Spoken language 1"), LI_LIST, DBVT_BYTE, "Language1", languageField}, + {LPGEN("Spoken language 2"), LI_LIST, DBVT_BYTE, "Language2", languageField}, + {LPGEN("Spoken language 3"), LI_LIST, DBVT_BYTE, "Language3", languageField}, + //more + {LPGEN("Originally from"),LI_DIVIDER}, + {LPGEN("Street"), LI_STRING, DBVT_UTF8, "OriginStreet"}, + {LPGEN("City"), LI_STRING, DBVT_UTF8, "OriginCity"}, + {LPGEN("State"), LI_STRING, DBVT_UTF8, "OriginState"}, + {LPGEN("Country"), LI_LIST, DBVT_WORD, "OriginCountry", countryField}, + //study + {LPGEN("Education"), LI_DIVIDER}, + {LPGEN("Level"), LI_LIST, DBVT_WORD, "StudyLevel", studyLevelField}, + {LPGEN("Institute"), LI_STRING, DBVT_UTF8, "StudyInstitute"}, + {LPGEN("Degree"), LI_STRING, DBVT_UTF8, "StudyDegree"}, + {LPGEN("Graduation Year"),LI_NUMBER, DBVT_WORD, "StudyYear", yearRange}, + //work + {LPGEN("Work"), LI_DIVIDER}, + {LPGEN("Company name"), LI_STRING, DBVT_UTF8, "Company"}, + {LPGEN("Company homepage"),LI_STRING, DBVT_ASCIIZ, "CompanyHomepage"}, + {LPGEN("Company street"), LI_STRING, DBVT_UTF8, "CompanyStreet"}, + {LPGEN("Company city"), LI_STRING, DBVT_UTF8, "CompanyCity"}, + {LPGEN("Company state"), LI_STRING, DBVT_UTF8, "CompanyState"}, + {LPGEN("Company phone"), LI_STRING, DBVT_ASCIIZ, "CompanyPhone"}, + {LPGEN("Company fax"), LI_STRING, DBVT_ASCIIZ, "CompanyFax"}, + {LPGEN("Company ZIP/postcode"),LI_STRING,DBVT_UTF8, "CompanyZIP"}, + {LPGEN("Company country"),LI_LIST, DBVT_WORD, "CompanyCountry", countryField}, + {LPGEN("Company department"),LI_STRING, DBVT_UTF8, "CompanyDepartment"}, + {LPGEN("Company position"),LI_STRING, DBVT_UTF8, "CompanyPosition"}, + {LPGEN("Company industry"),LI_LIST, DBVT_WORD, "CompanyIndustry", industryField}, +// {LPGEN("Company occupation"),LI_LIST, DBVT_WORD, "CompanyOccupation", occupationField}, + //interests + {LPGEN("Personal Interests"), LI_DIVIDER}, + {LPGEN("Interest category 1"),LI_LIST, DBVT_WORD, "Interest0Cat", interestsField}, + {LPGEN("Interest areas 1"),LI_STRING, DBVT_ASCIIZ, "Interest0Text"}, + {LPGEN("Interest category 2"),LI_LIST, DBVT_WORD, "Interest1Cat", interestsField}, + {LPGEN("Interest areas 2"),LI_STRING, DBVT_ASCIIZ, "Interest1Text"}, + {LPGEN("Interest category 3"),LI_LIST, DBVT_WORD, "Interest2Cat", interestsField}, + {LPGEN("Interest areas 3"),LI_STRING, DBVT_ASCIIZ, "Interest2Text"}, + {LPGEN("Interest category 4"),LI_LIST, DBVT_WORD, "Interest3Cat", interestsField}, + {LPGEN("Interest areas 4"),LI_STRING, DBVT_ASCIIZ, "Interest3Text"}, + //pastbackground +// {LPGEN("Past Background"), LI_DIVIDER}, +// {LPGEN("Category 1"), LI_LIST, DBVT_ASCIIZ, "Past0", pastField}, +// {LPGEN("Past Background 1"),LI_STRING, DBVT_ASCIIZ, "Past0Text"}, +// {LPGEN("Category 2"), LI_LIST, DBVT_ASCIIZ, "Past1", pastField}, +// {LPGEN("Past Background 2"),LI_STRING, DBVT_ASCIIZ, "Past1Text"}, +// {LPGEN("Category 3"), LI_LIST, DBVT_ASCIIZ, "Past2", pastField}, +// {LPGEN("Past Background 3"),LI_STRING, DBVT_ASCIIZ, "Past2Text"}, + //affiliation +// {LPGEN("Affiliations"), LI_DIVIDER}, +// {LPGEN("Affiliation category 1"),LI_LIST,DBVT_ASCIIZ, "Affiliation0", affiliationField}, +// {LPGEN("Affiliation 1"), LI_STRING, DBVT_ASCIIZ, "Affiliation0Text"}, +// {LPGEN("Affiliation category 2"),LI_LIST,DBVT_ASCIIZ, "Affiliation1", affiliationField}, +// {LPGEN("Affiliation 2"), LI_STRING, DBVT_ASCIIZ, "Affiliation1Text"}, +// {LPGEN("Affiliation category 3"),LI_LIST,DBVT_ASCIIZ, "Affiliation2", affiliationField}, +// {LPGEN("Affiliation 3"), LI_STRING, DBVT_ASCIIZ, "Affiliation2Text"} +}; + +const int settingCount = SIZEOF(setting); diff --git a/protocols/IcqOscarJ/src/changeinfo/db.cpp b/protocols/IcqOscarJ/src/changeinfo/db.cpp new file mode 100644 index 0000000000..643dcaa034 --- /dev/null +++ b/protocols/IcqOscarJ/src/changeinfo/db.cpp @@ -0,0 +1,224 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2001-2004 Richard Hughes, Martin Öberg +// Copyright © 2004-2009 Joe Kucera, Bio +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// ChangeInfo Plugin stuff +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +void ChangeInfoData::LoadSettingsFromDb(int keepChanged) +{ + for (int i=0; i < settingCount; i++) + { + if (setting[i].displayType == LI_DIVIDER) continue; + if (keepChanged && settingData[i].changed) continue; + if (setting[i].dbType == DBVT_ASCIIZ || setting[i].dbType == DBVT_UTF8) + SAFE_FREE((void**)(char**)&settingData[i].value); + else if (!keepChanged) + settingData[i].value = 0; + + settingData[i].changed = 0; + + if (setting[i].displayType & LIF_PASSWORD) continue; + + DBVARIANT dbv = {DBVT_DELETED}; + if (!ppro->getSetting(NULL, setting[i].szDbSetting, &dbv)) + { + switch(dbv.type) { + case DBVT_ASCIIZ: + settingData[i].value = (LPARAM)ppro->getSettingStringUtf(NULL, setting[i].szDbSetting, NULL); + break; + + case DBVT_UTF8: + settingData[i].value = (LPARAM)null_strdup(dbv.pszVal); + break; + + case DBVT_WORD: + if (setting[i].displayType & LIF_SIGNED) + settingData[i].value = dbv.sVal; + else + settingData[i].value = dbv.wVal; + break; + + case DBVT_BYTE: + if (setting[i].displayType & LIF_SIGNED) + settingData[i].value = dbv.cVal; + else + settingData[i].value = dbv.bVal; + break; + +#ifdef _DEBUG + default: + MessageBoxA(NULL, "That's not supposed to happen either", "Huh?", MB_OK); + break; +#endif + } + ICQFreeVariant(&dbv); + } + + char buf[MAX_PATH]; + TCHAR tbuf[MAX_PATH]; + + if (utf8_to_tchar_static(GetItemSettingText(i, buf, SIZEOF(buf)), tbuf, SIZEOF(tbuf))) + ListView_SetItemText(hwndList, i, 1, tbuf); + } +} + + +void ChangeInfoData::FreeStoredDbSettings(void) +{ + for (int i=0; i < settingCount; i++ ) + if (setting[i].dbType == DBVT_ASCIIZ || setting[i].dbType == DBVT_UTF8) + SAFE_FREE((void**)&settingData[i].value); +} + + +int ChangeInfoData::ChangesMade(void) +{ + for (int i=0; i < settingCount; i++ ) + if (settingData[i].changed) + return 1; + return 0; +} + + +void ChangeInfoData::ClearChangeFlags(void) +{ + for (int i=0; i < settingCount; i++) + settingData[i].changed = 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +struct PwConfirmDlgParam +{ + CIcqProto* ppro; + char* Pass; +}; + +static INT_PTR CALLBACK PwConfirmDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + PwConfirmDlgParam* dat = (PwConfirmDlgParam*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch(msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); + SendDlgItemMessage(hwndDlg,IDC_PASSWORD,EM_LIMITTEXT,15,0); + return TRUE; + + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDOK: + { + char szTest[16]; + + GetDlgItemTextA(hwndDlg,IDC_OLDPASS,szTest,sizeof(szTest)); + + if (strcmpnull(szTest, dat->ppro->GetUserPassword(TRUE))) + { + MessageBoxUtf(hwndDlg, LPGEN("The password does not match your current password. Check Caps Lock and try again."), LPGEN("Change ICQ Details"), MB_OK); + SendDlgItemMessage(hwndDlg,IDC_OLDPASS,EM_SETSEL,0,(LPARAM)-1); + SetFocus(GetDlgItem(hwndDlg,IDC_OLDPASS)); + break; + } + + GetDlgItemTextA(hwndDlg,IDC_PASSWORD,szTest,sizeof(szTest)); + if(strcmpnull(szTest, dat->Pass)) + { + MessageBoxUtf(hwndDlg, LPGEN("The password does not match the password you originally entered. Check Caps Lock and try again."), LPGEN("Change ICQ Details"), MB_OK); + SendDlgItemMessage(hwndDlg,IDC_PASSWORD,EM_SETSEL,0,(LPARAM)-1); + SetFocus(GetDlgItem(hwndDlg,IDC_PASSWORD)); + break; + } + } + case IDCANCEL: + EndDialog(hwndDlg,wParam); + break; + } + break; + } + return FALSE; +} + + +int ChangeInfoData::SaveSettingsToDb(HWND hwndDlg) +{ + int ret = 1; + + for (int i = 0; i < settingCount; i++) + { + if (!settingData[i].changed) continue; + if (!(setting[i].displayType & LIF_ZEROISVALID) && settingData[i].value==0) + { + ppro->deleteSetting(NULL, setting[i].szDbSetting); + continue; + } + switch(setting[i].dbType) { + case DBVT_ASCIIZ: + if (setting[i].displayType & LIF_PASSWORD) + { + int nSettingLen = strlennull((char*)settingData[i].value); + + if (nSettingLen > 8 || nSettingLen < 1) + { + MessageBoxUtf(hwndDlg, LPGEN("The ICQ server does not support passwords longer than 8 characters. Please use a shorter password."), LPGEN("Change ICQ Details"), MB_OK); + ret=0; + break; + } + PwConfirmDlgParam param = { ppro, (char*)settingData[i].value }; + if (IDOK != DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_PWCONFIRM), hwndDlg, PwConfirmDlgProc, (LPARAM)¶m)) + { + ret = 0; + break; + } + strcpy(ppro->m_szPassword, (char*)settingData[i].value); + } + else { + if (*(char*)settingData[i].value) + ppro->setSettingStringUtf(NULL, setting[i].szDbSetting, (char*)settingData[i].value); + else + ppro->deleteSetting(NULL, setting[i].szDbSetting); + } + break; + + case DBVT_UTF8: + if (*(char*)settingData[i].value) + ppro->setSettingStringUtf(NULL, setting[i].szDbSetting, (char*)settingData[i].value); + else + ppro->deleteSetting(NULL, setting[i].szDbSetting); + break; + + case DBVT_WORD: + ppro->setSettingWord(NULL, setting[i].szDbSetting, (WORD)settingData[i].value); + break; + + case DBVT_BYTE: + ppro->setSettingByte(NULL, setting[i].szDbSetting, (BYTE)settingData[i].value); + break; + } + } + return ret; +} diff --git a/protocols/IcqOscarJ/src/changeinfo/dlgproc.cpp b/protocols/IcqOscarJ/src/changeinfo/dlgproc.cpp new file mode 100644 index 0000000000..404777aa97 --- /dev/null +++ b/protocols/IcqOscarJ/src/changeinfo/dlgproc.cpp @@ -0,0 +1,540 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2001-2004 Richard Hughes, Martin Öberg +// Copyright © 2004-2010 Joe Kucera, Bio +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// ChangeInfo Plugin stuff +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +#define DM_PROTOACK (WM_USER+10) + +static int DrawTextUtf(HDC hDC, char *text, LPRECT lpRect, UINT uFormat, LPSIZE lpSize) +{ + int res; + + WCHAR *tmp = make_unicode_string(text); + res = DrawTextW(hDC, tmp, -1, lpRect, uFormat); + if (lpSize) + GetTextExtentPoint32W(hDC, tmp, strlennull(tmp), lpSize); + SAFE_FREE((void**)&tmp); + + return res; +} + + +char* ChangeInfoData::GetItemSettingText(int i, char *buf, size_t bufsize) +{ + char *text = buf; + int alloced = 0; + + buf[0] = '\0'; + + if (settingData[i].value == 0 && !(setting[i].displayType & LIF_ZEROISVALID)) + { + if (setting[i].displayType & LIF_CHANGEONLY) + text = ICQTranslateUtfStatic(LPGEN(""), buf, bufsize); + else + text = ICQTranslateUtfStatic(LPGEN(""), buf, bufsize); + } + else + { + switch (setting[i].displayType & LIM_TYPE) { + case LI_STRING: + case LI_LONGSTRING: + text = BinaryToEscapes((char*)settingData[i].value); + alloced = 1; + break; + + case LI_NUMBER: + _itoa(settingData[i].value, text, 10); + break; + + case LI_LIST: + if (setting[i].dbType == DBVT_ASCIIZ) + text = ICQTranslateUtfStatic((char*)settingData[i].value, buf, bufsize); + else + { + text = ICQTranslateUtfStatic(LPGEN("Unknown value"), buf, bufsize); + + FieldNamesItem *list = (FieldNamesItem*)setting[i].pList; + for (int j=0; TRUE; j++) + if (list[j].code == settingData[i].value) + { + text = ICQTranslateUtfStatic(list[j].text, buf, bufsize); + break; + } + else if (!list[j].text) + { + if (list[j].code == settingData[i].value) + text = ICQTranslateUtfStatic("Unspecified", buf, bufsize); + break; + } + } + break; + } + } + if (setting[i].displayType & LIF_PASSWORD) + { + if (settingData[i].changed) + for (int j=0; text[j]; j++) text[j] = '*'; + else + { + if (alloced) + { + SAFE_FREE(&text); + alloced = 0; + } + text = "********"; + } + } + if (text != buf) + { + char *tmp = text; + + text = null_strcpy(buf, text, bufsize - 1); + if (alloced) + SAFE_FREE(&tmp); + } + + return text; +} + + +void ChangeInfoData::PaintItemSetting(HDC hdc, RECT *rc, int i, UINT itemState) +{ + char str[MAX_PATH]; + char *text = GetItemSettingText(i, str, SIZEOF(str)); + + if (settingData[i].value == 0 && !(setting[i].displayType & LIF_ZEROISVALID)) + SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT)); + + if ((setting[i].displayType & LIM_TYPE) == LI_LIST && (itemState & CDIS_SELECTED || iEditItem == i)) + { + RECT rcBtn; + + rcBtn = *rc; + rcBtn.left = rcBtn.right - rc->bottom + rc->top; + InflateRect(&rcBtn,-2,-2); + rc->right = rcBtn.left; + DrawFrameControl(hdc, &rcBtn, DFC_SCROLL, iEditItem == i ? DFCS_SCROLLDOWN|DFCS_PUSHED : DFCS_SCROLLDOWN); + } + DrawTextUtf(hdc, text, rc, DT_END_ELLIPSIS|DT_LEFT|DT_NOCLIP|DT_NOPREFIX|DT_SINGLELINE|DT_VCENTER, NULL); +} + + +static int ChangeInfoDlg_Resize(HWND hwndDlg, LPARAM lParam, UTILRESIZECONTROL *urc) +{ + switch (urc->wId) { + case IDC_LIST: + return RD_ANCHORX_WIDTH | RD_ANCHORY_HEIGHT; + + case IDC_SAVE: + return RD_ANCHORX_RIGHT | RD_ANCHORY_BOTTOM; + + case IDC_UPLOADING: + return RD_ANCHORX_WIDTH | RD_ANCHORY_BOTTOM; + } + + return RD_ANCHORX_LEFT | RD_ANCHORY_TOP; // default +} + + +INT_PTR CALLBACK ChangeInfoDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + ChangeInfoData* dat = (ChangeInfoData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch(msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + + dat = new ChangeInfoData(); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat); + + dat->hwndDlg = hwndDlg; + dat->ppro = (CIcqProto*)lParam; + dat->hwndList = GetDlgItem(hwndDlg, IDC_LIST); + + ListView_SetExtendedListViewStyle(dat->hwndList, LVS_EX_FULLROWSELECT); + dat->iEditItem = -1; + { + HFONT hFont; + LOGFONT lf; + + dat->hListFont = (HFONT)SendMessage(dat->hwndList, WM_GETFONT, 0, 0); + GetObject(dat->hListFont, sizeof(lf), &lf); + lf.lfHeight -= 5; + hFont = CreateFontIndirect(&lf); + SendMessage(dat->hwndList, WM_SETFONT, (WPARAM)hFont, 0); + } + { // Prepare ListView Columns + LV_COLUMN lvc = {0}; + RECT rc; + + GetClientRect(dat->hwndList, &rc); + rc.right -= GetSystemMetrics(SM_CXVSCROLL); + lvc.mask = LVCF_WIDTH; + lvc.cx = rc.right / 3; + ListView_InsertColumn(dat->hwndList, 0, &lvc); + lvc.cx = rc.right - lvc.cx; + ListView_InsertColumn(dat->hwndList, 1, &lvc); + } + { // Prepare Setting Items + LV_ITEM lvi = {0}; + lvi.mask = LVIF_PARAM | LVIF_TEXT; + + for (lvi.iItem = 0; lvi.iItem < settingCount; lvi.iItem++) + { + TCHAR text[MAX_PATH]; + + lvi.lParam = lvi.iItem; + lvi.pszText = text; + utf8_to_tchar_static(setting[lvi.iItem].szDescription, text, SIZEOF(text)); + ListView_InsertItem(dat->hwndList, &lvi); + } + } + + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + return TRUE; + + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->idFrom) { + case 0: + switch (((LPNMHDR)lParam)->code) { + case PSN_PARAMCHANGED: + dat->ppro = (CIcqProto*)((PSHNOTIFY*)lParam)->lParam; + dat->LoadSettingsFromDb(0); + { + char *pwd = dat->ppro->GetUserPassword(TRUE); + strcpy(dat->Password, (pwd) ? pwd : "" ); /// FIXME + } + break; + + case PSN_INFOCHANGED: + dat->LoadSettingsFromDb(1); + break; + + case PSN_KILLACTIVE: + dat->EndStringEdit(1); + dat->EndListEdit(1); + break; + + case PSN_APPLY: + if (dat->ChangesMade()) + { + if (IDYES!=MessageBoxUtf(hwndDlg, LPGEN("You've made some changes to your ICQ details but it has not been saved to the server. Are you sure you want to close this dialog?"), LPGEN("Change ICQ Details"), MB_YESNOCANCEL)) + { + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE); + return TRUE; + } + } + break; + } + break; + + case IDC_LIST: + switch (((LPNMHDR)lParam)->code) { + case LVN_GETDISPINFO: + if (dat->iEditItem != -1) + { + if (dat->editTopIndex != ListView_GetTopIndex(dat->hwndList)) + { + dat->EndStringEdit(1); + dat->EndListEdit(1); + } + } + break; + + case NM_CUSTOMDRAW: + { + LPNMLVCUSTOMDRAW cd=(LPNMLVCUSTOMDRAW)lParam; + + switch(cd->nmcd.dwDrawStage) { + case CDDS_PREPAINT: + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, CDRF_NOTIFYITEMDRAW); + return TRUE; + + case CDDS_ITEMPREPAINT: + { + RECT rcItem; + + if (dat->iEditItem != -1) + { + if (dat->editTopIndex != ListView_GetTopIndex(dat->hwndList)) + { + dat->EndStringEdit(1); + dat->EndListEdit(1); + } + } + + ListView_GetItemRect(dat->hwndList, cd->nmcd.dwItemSpec, &rcItem, LVIR_BOUNDS); + + if (GetWindowLongPtr(dat->hwndList, GWL_STYLE) & WS_DISABLED) + { // Disabled List + SetTextColor(cd->nmcd.hdc, cd->clrText); + FillRect(cd->nmcd.hdc, &rcItem, GetSysColorBrush(COLOR_3DFACE)); + } + else if ((cd->nmcd.uItemState & CDIS_SELECTED || dat->iEditItem == (int)cd->nmcd.dwItemSpec) + && setting[cd->nmcd.lItemlParam].displayType != LI_DIVIDER) + { // Selected item + SetTextColor(cd->nmcd.hdc, GetSysColor(COLOR_HIGHLIGHTTEXT)); + FillRect(cd->nmcd.hdc, &rcItem, GetSysColorBrush(COLOR_HIGHLIGHT)); + } + else + { // Unselected item + SetTextColor(cd->nmcd.hdc, GetSysColor(COLOR_WINDOWTEXT)); + FillRect(cd->nmcd.hdc, &rcItem, GetSysColorBrush(COLOR_WINDOW)); + } + + HFONT hoFont = (HFONT)SelectObject(cd->nmcd.hdc, dat->hListFont); + + if (setting[cd->nmcd.lItemlParam].displayType == LI_DIVIDER) + { + RECT rcLine; + SIZE textSize; + char str[MAX_PATH]; + char *szText = ICQTranslateUtfStatic(setting[cd->nmcd.lItemlParam].szDescription, str, MAX_PATH); + + SetTextColor(cd->nmcd.hdc, GetSysColor(COLOR_3DSHADOW)); + DrawTextUtf(cd->nmcd.hdc, szText, &rcItem, DT_CENTER|DT_NOCLIP|DT_NOPREFIX|DT_SINGLELINE|DT_VCENTER, &textSize); + rcLine.top = (rcItem.top + rcItem.bottom) / 2 - 1; + rcLine.bottom = rcLine.top + 2; + rcLine.left = rcItem.left + 3; + rcLine.right = (rcItem.left + rcItem.right - textSize.cx) / 2 - 3; + DrawEdge(cd->nmcd.hdc, &rcLine, BDR_SUNKENOUTER, BF_RECT); + rcLine.left = (rcItem.left + rcItem.right + textSize.cx) / 2 + 3; + rcLine.right = rcItem.right - 3; + DrawEdge(cd->nmcd.hdc, &rcLine, BDR_SUNKENOUTER, BF_RECT); + } + else + { + RECT rcItemDescr, rcItemValue; + char str[MAX_PATH]; + + ListView_GetSubItemRect(dat->hwndList, cd->nmcd.dwItemSpec, 0, LVIR_BOUNDS, &rcItemDescr); + ListView_GetSubItemRect(dat->hwndList, cd->nmcd.dwItemSpec, 1, LVIR_BOUNDS, &rcItemValue); + + rcItemDescr.right = rcItemValue.left; + rcItemDescr.left += 2; + DrawTextUtf(cd->nmcd.hdc, ICQTranslateUtfStatic(setting[cd->nmcd.lItemlParam].szDescription, str, MAX_PATH), &rcItemDescr, DT_END_ELLIPSIS|DT_LEFT|DT_NOCLIP|DT_NOPREFIX|DT_SINGLELINE|DT_VCENTER, NULL); + + dat->PaintItemSetting(cd->nmcd.hdc, &rcItemValue, cd->nmcd.lItemlParam, cd->nmcd.uItemState); + } + SelectObject(cd->nmcd.hdc, hoFont); + + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, CDRF_SKIPDEFAULT); + + return TRUE; + } + } + break; + } + case NM_CLICK: + { + LPNMLISTVIEW nm=(LPNMLISTVIEW)lParam; + LV_ITEM lvi; + RECT rc; + + dat->EndStringEdit(1); + dat->EndListEdit(1); + if (nm->iSubItem != 1) break; + lvi.mask = LVIF_PARAM|LVIF_STATE; + lvi.stateMask = 0xFFFFFFFF; + lvi.iItem = nm->iItem; lvi.iSubItem = nm->iSubItem; + ListView_GetItem(dat->hwndList, &lvi); + if (!(lvi.state & LVIS_SELECTED)) break; + ListView_EnsureVisible(dat->hwndList, lvi.iItem, FALSE); + ListView_GetSubItemRect(dat->hwndList, lvi.iItem, lvi.iSubItem, LVIR_BOUNDS, &rc); + dat->editTopIndex = ListView_GetTopIndex(dat->hwndList); + switch (setting[lvi.lParam].displayType & LIM_TYPE) { + case LI_STRING: + case LI_LONGSTRING: + case LI_NUMBER: + dat->BeginStringEdit(nm->iItem, &rc, lvi. lParam, 0); + break; + case LI_LIST: + dat->BeginListEdit(nm->iItem, &rc, lvi. lParam, 0); + break; + } + break; + } + case LVN_KEYDOWN: + { + LPNMLVKEYDOWN nm=(LPNMLVKEYDOWN)lParam; + LV_ITEM lvi; + RECT rc; + + dat->EndStringEdit(1); + dat->EndListEdit(1); + if(nm->wVKey==VK_SPACE || nm->wVKey==VK_RETURN || nm->wVKey==VK_F2) nm->wVKey=0; + if(nm->wVKey && (nm->wVKey<'0' || (nm->wVKey>'9' && nm->wVKey<'A') || (nm->wVKey>'Z' && nm->wVKeywVKey>=VK_F1)) + break; + lvi.mask=LVIF_PARAM|LVIF_STATE; + lvi.stateMask=0xFFFFFFFF; + lvi.iItem = ListView_GetNextItem(dat->hwndList, -1, LVNI_ALL|LVNI_SELECTED); + if (lvi.iItem==-1) break; + lvi.iSubItem=1; + ListView_GetItem(dat->hwndList,&lvi); + ListView_EnsureVisible(dat->hwndList,lvi.iItem,FALSE); + ListView_GetSubItemRect(dat->hwndList,lvi.iItem,lvi.iSubItem,LVIR_BOUNDS,&rc); + dat->editTopIndex = ListView_GetTopIndex(dat->hwndList); + switch(setting[lvi.lParam].displayType & LIM_TYPE) { + case LI_STRING: + case LI_LONGSTRING: + case LI_NUMBER: + dat->BeginStringEdit(lvi.iItem,&rc,lvi.lParam,nm->wVKey); + break; + case LI_LIST: + dat->BeginListEdit(lvi.iItem,&rc,lvi.lParam,nm->wVKey); + break; + } + SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, TRUE); + return TRUE; + } + case NM_KILLFOCUS: + if (!IsStringEditWindow(GetFocus())) dat->EndStringEdit(1); + if (!IsListEditWindow(GetFocus())) dat->EndListEdit(1); + break; + } + break; + } + break; + case WM_KILLFOCUS: + dat->EndStringEdit(1); + dat->EndListEdit(1); + break; + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDCANCEL: + SendMessage(GetParent(hwndDlg), msg, wParam, lParam); + break; + + case IDC_SAVE: + if (!dat->SaveSettingsToDb(hwndDlg)) + break; + + EnableDlgItem(hwndDlg, IDC_SAVE, FALSE); + EnableDlgItem(hwndDlg, IDC_LIST, FALSE); + { + char str[MAX_PATH]; + SetDlgItemTextUtf(hwndDlg, IDC_UPLOADING, ICQTranslateUtfStatic(LPGEN("Upload in progress..."), str, MAX_PATH)); + } + EnableDlgItem(hwndDlg, IDC_UPLOADING, TRUE); + ShowDlgItem(hwndDlg, IDC_UPLOADING, SW_SHOW); + dat->hAckHook = HookEventMessage(ME_PROTO_ACK, hwndDlg, DM_PROTOACK); + + if (!dat->UploadSettings()) + { + EnableDlgItem(hwndDlg, IDC_SAVE, TRUE); + EnableDlgItem(hwndDlg, IDC_LIST, TRUE); + ShowDlgItem(hwndDlg, IDC_UPLOADING, SW_HIDE); + UnhookEvent(dat->hAckHook); + dat->hAckHook = NULL; + } + break; + } + break; + + case WM_SIZE: + { // make the dlg resizeable + UTILRESIZEDIALOG urd = {0}; + + if (IsIconic(hwndDlg)) break; + urd.cbSize = sizeof(urd); + urd.hInstance = hInst; + urd.hwndDlg = hwndDlg; + urd.lParam = 0; // user-defined + urd.lpTemplate = MAKEINTRESOURCEA(IDD_INFO_CHANGEINFO); + urd.pfnResizer = ChangeInfoDlg_Resize; + CallService(MS_UTILS_RESIZEDIALOG, 0, (LPARAM) &urd); + + { // update listview column widths + RECT rc; + + GetClientRect(dat->hwndList, &rc); + rc.right -= GetSystemMetrics(SM_CXVSCROLL); + ListView_SetColumnWidth(dat->hwndList, 0, rc.right / 3); + ListView_SetColumnWidth(dat->hwndList, 1, rc.right - rc.right / 3); + } + break; + } + + case DM_PROTOACK: + { + ACKDATA *ack=(ACKDATA*)lParam; + int i,done; + char str[MAX_PATH]; + char buf[MAX_PATH]; + + if (ack->type != ACKTYPE_SETINFO) break; + if (ack->result == ACKRESULT_SUCCESS) + { + for (i=0; i < SIZEOF(dat->hUpload); i++) + if (dat->hUpload[i] && ack->hProcess == dat->hUpload[i]) break; + + if (i == SIZEOF(dat->hUpload)) break; + dat->hUpload[i] = NULL; + for (done = 0, i = 0; i < SIZEOF(dat->hUpload); i++) + done += dat->hUpload[i] == NULL; + null_snprintf(buf, sizeof(buf), "%s%d%%", ICQTranslateUtfStatic(LPGEN("Upload in progress..."), str, MAX_PATH), 100*done/(SIZEOF(dat->hUpload))); + SetDlgItemTextUtf(hwndDlg, IDC_UPLOADING, buf); + if (done < SIZEOF(dat->hUpload)) break; + + dat->ClearChangeFlags(); + UnhookEvent(dat->hAckHook); + dat->hAckHook = NULL; + EnableDlgItem(hwndDlg, IDC_LIST, TRUE); + EnableDlgItem(hwndDlg, IDC_UPLOADING, FALSE); + SetDlgItemTextUtf(hwndDlg, IDC_UPLOADING, ICQTranslateUtfStatic(LPGEN("Upload complete"), str, MAX_PATH)); + SendMessage(GetParent(hwndDlg), PSM_FORCECHANGED, 0, 0); + } + else if (ack->result==ACKRESULT_FAILED) + { + UnhookEvent(dat->hAckHook); + dat->hAckHook = NULL; + EnableDlgItem(hwndDlg, IDC_LIST, TRUE); + EnableDlgItem(hwndDlg, IDC_UPLOADING, FALSE); + SetDlgItemTextUtf(hwndDlg, IDC_UPLOADING, ICQTranslateUtfStatic(LPGEN("Upload FAILED"), str, MAX_PATH)); + SendMessage(GetParent(hwndDlg), PSM_FORCECHANGED, 0, 0); + EnableDlgItem(hwndDlg, IDC_SAVE, TRUE); + } + break; + } + case WM_DESTROY: + if (dat->hAckHook) + { + UnhookEvent(dat->hAckHook); + dat->hAckHook = NULL; + } + { + HFONT hFont = (HFONT)SendMessage(dat->hwndList, WM_GETFONT, 0, 0); + DeleteObject(hFont); + } + dat->FreeStoredDbSettings(); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0); + delete dat; + break; + } + return FALSE; +} diff --git a/protocols/IcqOscarJ/src/changeinfo/editlist.cpp b/protocols/IcqOscarJ/src/changeinfo/editlist.cpp new file mode 100644 index 0000000000..f2a1470fd3 --- /dev/null +++ b/protocols/IcqOscarJ/src/changeinfo/editlist.cpp @@ -0,0 +1,201 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2001-2004 Richard Hughes, Martin Öberg +// Copyright © 2004-2009 Joe Kucera, Bio +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// ChangeInfo Plugin stuff +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +static ChangeInfoData *dataListEdit = NULL; +static HWND hwndListEdit = NULL; +static BOOL (WINAPI *MyAnimateWindow)(HWND,DWORD,DWORD); +static WNDPROC OldListEditProc; + +static LRESULT CALLBACK ListEditSubclassProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) +{ + switch(msg) + { + case WM_LBUTTONUP: + CallWindowProc(OldListEditProc, hwnd, msg, wParam, lParam); + { + POINT pt; + + pt.x = (short)LOWORD(lParam); + pt.y = (short)HIWORD(lParam); + ClientToScreen(hwnd, &pt); + if (SendMessage(hwnd, WM_NCHITTEST, 0, MAKELPARAM(pt.x, pt.y)) == HTVSCROLL) break; + } + { + int i = SendMessage(hwnd, LB_GETCURSEL, 0, 0); + + if (dataListEdit) + dataListEdit->EndListEdit(i != LB_ERR); + } + return 0; + + case WM_CHAR: + if (wParam != '\r') break; + { + int i = SendMessage(hwnd, LB_GETCURSEL, 0, 0); + + if (dataListEdit) + dataListEdit->EndListEdit(i != LB_ERR); + } + return 0; + case WM_KILLFOCUS: + if (dataListEdit) + dataListEdit->EndListEdit(1); + return 0; + } + return CallWindowProc(OldListEditProc, hwnd, msg, wParam, lParam); +} + + +void ChangeInfoData::BeginListEdit(int iItem, RECT *rc, int iSetting, WORD wVKey) +{ + int j,n; + POINT pt; + int itemHeight; + char str[MAX_PATH]; + + if (dataListEdit) + dataListEdit->EndListEdit(0); + + pt.x=pt.y=0; + ClientToScreen(hwndList,&pt); + OffsetRect(rc,pt.x,pt.y); + InflateRect(rc,-2,-2); + rc->left-=2; + iEditItem = iItem; + ListView_RedrawItems(hwndList, iEditItem, iEditItem); + UpdateWindow(hwndList); + + dataListEdit = this; + hwndListEdit = CreateWindowEx(WS_EX_TOOLWINDOW|WS_EX_TOPMOST, _T("LISTBOX"), _T(""), WS_POPUP|WS_BORDER|WS_VSCROLL, rc->left, rc->bottom, rc->right - rc->left, 150, NULL, NULL, hInst, NULL); + SendMessage(hwndListEdit, WM_SETFONT, (WPARAM)hListFont, 0); + itemHeight = SendMessage(hwndListEdit, LB_GETITEMHEIGHT, 0, 0); + + FieldNamesItem *list = (FieldNamesItem*)setting[iSetting].pList; + + if (list == countryField) + { // some country codes were changed leaving old details uknown, convert it for the user + if (settingData[iSetting].value == 420) settingData[iSetting].value = 42; // conversion of obsolete codes (OMG!) + else if (settingData[iSetting].value == 421) settingData[iSetting].value = 4201; + else if (settingData[iSetting].value == 102) settingData[iSetting].value = 1201; + } + + n = ListBoxAddStringUtf(hwndListEdit, "Unspecified"); + for (j=0; ; j++) + if (!list[j].text) + { + SendMessage(hwndListEdit, LB_SETITEMDATA, n, j); + if ((settingData[iSetting].value == 0 && list[j].code == 0) + || (setting[iSetting].dbType != DBVT_ASCIIZ && settingData[iSetting].value == list[j].code)) + SendMessage(hwndListEdit, LB_SETCURSEL, n, 0); + break; + } + + for (j=0; list[j].text; j++) + { + n = ListBoxAddStringUtf(hwndListEdit, list[j].text); + SendMessage(hwndListEdit, LB_SETITEMDATA, n, j); + if ((setting[iSetting].dbType == DBVT_ASCIIZ && (!strcmpnull((char*)settingData[iSetting].value, list[j].text)) + || (setting[iSetting].dbType == DBVT_ASCIIZ && (!strcmpnull((char*)settingData[iSetting].value, ICQTranslateUtfStatic(list[j].text, str, MAX_PATH)))) + || ((char*)settingData[iSetting].value == NULL && list[j].code == 0)) + || (setting[iSetting].dbType != DBVT_ASCIIZ && settingData[iSetting].value == list[j].code)) + SendMessage(hwndListEdit, LB_SETCURSEL, n, 0); + } + SendMessage(hwndListEdit, LB_SETTOPINDEX, SendMessage(hwndListEdit, LB_GETCURSEL, 0, 0) - 3, 0); + int listCount = SendMessage(hwndListEdit, LB_GETCOUNT, 0, 0); + if (itemHeight * listCount < 150) + SetWindowPos(hwndListEdit, 0, 0, 0, rc->right - rc->left, itemHeight * listCount + GetSystemMetrics(SM_CYBORDER) * 2, SWP_NOZORDER|SWP_NOMOVE); + OldListEditProc = (WNDPROC)SetWindowLongPtr(hwndListEdit, GWLP_WNDPROC, (LONG_PTR)ListEditSubclassProc); + if (MyAnimateWindow = (BOOL (WINAPI*)(HWND,DWORD,DWORD))GetProcAddress(GetModuleHandleA("user32"), "AnimateWindow")) + { + BOOL enabled; + + SystemParametersInfo(SPI_GETCOMBOBOXANIMATION, 0, &enabled, FALSE); + if (enabled) MyAnimateWindow(hwndListEdit, 200, AW_SLIDE|AW_ACTIVATE|AW_VER_POSITIVE); + } + ShowWindow(hwndListEdit, SW_SHOW); + SetFocus(hwndListEdit); + if (wVKey) + PostMessage(hwndListEdit, WM_KEYDOWN, wVKey, 0); +} + + +void ChangeInfoData::EndListEdit(int save) +{ + if (hwndListEdit == NULL || iEditItem == -1 || this != dataListEdit) return; + if (save) + { + int iItem = SendMessage(hwndListEdit, LB_GETCURSEL, 0, 0); + int i = SendMessage(hwndListEdit, LB_GETITEMDATA, iItem, 0); + + if (setting[iEditItem].dbType == DBVT_ASCIIZ) + { + char *szNewValue = (((FieldNamesItem*)setting[iEditItem].pList)[i].text); + if (((FieldNamesItem*)setting[iEditItem].pList)[i].code || setting[iEditItem].displayType & LIF_ZEROISVALID) + { + settingData[iEditItem].changed = strcmpnull(szNewValue, (char*)settingData[iEditItem].value); + SAFE_FREE((void**)&settingData[iEditItem].value); + settingData[iEditItem].value = (LPARAM)null_strdup(szNewValue); + } + else + { + settingData[iEditItem].changed = (char*)settingData[iEditItem].value!=NULL; + SAFE_FREE((void**)&settingData[iEditItem].value); + } + } + else + { + settingData[iEditItem].changed = ((FieldNamesItem*)setting[iEditItem].pList)[i].code != settingData[iEditItem].value; + settingData[iEditItem].value = ((FieldNamesItem*)setting[iEditItem].pList)[i].code; + } + if (settingData[iEditItem].changed) + { + char buf[MAX_PATH]; + TCHAR tbuf[MAX_PATH]; + + if (utf8_to_tchar_static(ICQTranslateUtfStatic(((FieldNamesItem*)setting[iEditItem].pList)[i].text, buf, SIZEOF(buf)), tbuf, SIZEOF(buf))) + ListView_SetItemText(hwndList, iEditItem, 1, tbuf); + + EnableDlgItem(GetParent(hwndList), IDC_SAVE, TRUE); + + } + } + ListView_RedrawItems(hwndList, iEditItem, iEditItem); + iEditItem = -1; + dataListEdit = NULL; + DestroyWindow(hwndListEdit); + hwndListEdit = NULL; +} + + +int IsListEditWindow(HWND hwnd) +{ + if (hwnd == hwndListEdit) return 1; + return 0; +} diff --git a/protocols/IcqOscarJ/src/changeinfo/editstring.cpp b/protocols/IcqOscarJ/src/changeinfo/editstring.cpp new file mode 100644 index 0000000000..288351e8d3 --- /dev/null +++ b/protocols/IcqOscarJ/src/changeinfo/editstring.cpp @@ -0,0 +1,367 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2001-2004 Richard Hughes, Martin Öberg +// Copyright © 2004-2009 Joe Kucera, Bio +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// ChangeInfo Plugin stuff +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +static ChangeInfoData *dataStringEdit = NULL; +static WNDPROC OldStringEditProc, OldExpandButtonProc; +static HWND hwndEdit = NULL, hwndExpandButton = NULL, hwndUpDown = NULL; + +static const char escapes[]={'a','\a', +'b','\b', +'e',27, +'f','\f', +'r','\r', +'t','\t', +'v','\v', +'\\','\\'}; + +static void EscapesToMultiline(WCHAR *str,PDWORD selStart,PDWORD selEnd) +{ //converts "\\n" and "\\t" to "\r\n" and "\t" because a multi-line edit box can show them properly + DWORD i; + + for(i=0; *str; str++, i++) + { + if (*str != '\\') continue; + if (str[1] == 'n') + { + *str++ = '\r'; + i++; + *str = '\n'; + } + else if (str[1] == 't') + { + *str = '\t'; + memmove(str+1, str+2, sizeof(WCHAR)*(strlennull(str)-1)); + + if (*selStart>i) --*selStart; + if (*selEnd>i) --*selEnd; + } + } +} + + + +static void EscapesToBinary(char *str) +{ + for (;*str;str++) + { + if (*str!='\\') continue; + if(str[1]=='n') {*str++='\r'; *str='\n'; continue;} + if(str[1]=='0') + { + char *codeend; + *str=(char)strtol(str+1,&codeend,8); + if (*str==0) {*str='\\'; continue;} + memmove(str+1,codeend,strlennull(codeend)+1); + continue; + } + for(int i=0;i=' ') + { + *pout++=*str; + continue; + } + if(str[0]=='\r' && str[1]=='\n') + { + *pout++='\\'; + *pout++='n'; + str++; + continue; + } + if(extra<3) + { + extra+=8; len+=8; + pout=out=(char*)SAFE_REALLOC(out,len); + } + *pout++='\\'; + for(i = 0; i < SIZEOF(escapes); i += 2) + if (*str==escapes[i+1]) + { + *pout++=escapes[i]; + extra--; + break; + } + if(i < SIZEOF(escapes)) continue; + *pout++='0'; extra--; + if (*str>=8) + { + *pout++=(*str>>3)+'0'; + extra--; + } + *pout++=(*str&7)+'0'; extra--; + } + *pout='\0'; + return out; +} + + + +static LRESULT CALLBACK StringEditSubclassProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) +{ + switch(msg) + { + case WM_KEYDOWN: + if (wParam==VK_ESCAPE) + { + if (dataStringEdit) + dataStringEdit->EndStringEdit(0); + return 0; + } + if (wParam==VK_RETURN) + { + if (GetWindowLongPtr(hwnd, GWL_STYLE) & ES_MULTILINE && !(GetKeyState(VK_CONTROL) & 0x8000)) break; + if (dataStringEdit) + dataStringEdit->EndStringEdit(1); + return 0; + } + break; + + case WM_GETDLGCODE: + return DLGC_WANTALLKEYS|CallWindowProc(OldStringEditProc, hwnd, msg, wParam, lParam); + + case WM_KILLFOCUS: + if ((HWND)wParam == hwndExpandButton) break; + if (dataStringEdit) + dataStringEdit->EndStringEdit(1); + return 0; + } + return CallWindowProc(OldStringEditProc, hwnd, msg, wParam, lParam); +} + + + +static LRESULT CALLBACK ExpandButtonSubclassProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) +{ + switch(msg) + { + case WM_LBUTTONUP: + if(GetCapture()==hwnd) + { + //do expand + RECT rcStart,rcEnd; + DWORD selStart,selEnd; + WCHAR *text; + BOOL animEnabled=TRUE; + + GetWindowRect(hwndEdit,&rcStart); + InflateRect(&rcStart,2,2); + + text = GetWindowTextUcs(hwndEdit); + SendMessage(hwndEdit,EM_GETSEL,(WPARAM)&selStart,(LPARAM)&selEnd); + DestroyWindow(hwndEdit); + EscapesToMultiline(text,&selStart,&selEnd); + hwndEdit=CreateWindowExA(WS_EX_TOOLWINDOW,"EDIT","",WS_POPUP|WS_BORDER|WS_VISIBLE|ES_WANTRETURN|ES_AUTOVSCROLL|WS_VSCROLL|ES_MULTILINE,rcStart.left,rcStart.top,rcStart.right-rcStart.left,rcStart.bottom-rcStart.top,NULL,NULL,hInst,NULL); + SetWindowTextUcs(hwndEdit, text); + OldStringEditProc=(WNDPROC)SetWindowLongPtr(hwndEdit,GWLP_WNDPROC,(LONG_PTR)StringEditSubclassProc); + SendMessage(hwndEdit,WM_SETFONT,(WPARAM)dataStringEdit->hListFont,0); + SendMessage(hwndEdit,EM_SETSEL,selStart,selEnd); + SetFocus(hwndEdit); + + rcEnd.left=rcStart.left; rcEnd.top=rcStart.top; + rcEnd.right=rcEnd.left+350; + rcEnd.bottom=rcEnd.top+150; + if (LOBYTE(LOWORD(GetVersion()))>4 || HIBYTE(LOWORD(GetVersion()))>0) + SystemParametersInfo(SPI_GETCOMBOBOXANIMATION,0,&animEnabled,FALSE); + if(animEnabled) + { + DWORD startTime,timeNow; + startTime=GetTickCount(); + for (;;) + { + UpdateWindow(hwndEdit); + timeNow=GetTickCount(); + if(timeNow>startTime+200) break; + SetWindowPos(hwndEdit,0,rcEnd.left,rcEnd.top,(rcEnd.right-rcStart.right)*(timeNow-startTime)/200+rcStart.right-rcEnd.left,(rcEnd.bottom-rcStart.bottom)*(timeNow-startTime)/200+rcStart.bottom-rcEnd.top,SWP_NOZORDER); + } + } + SetWindowPos(hwndEdit,0,rcEnd.left,rcEnd.top,rcEnd.right-rcEnd.left,rcEnd.bottom-rcEnd.top,SWP_NOZORDER); + + DestroyWindow(hwnd); + hwndExpandButton=NULL; + + SAFE_FREE((void**)&text); + } + break; + } + return CallWindowProc(OldExpandButtonProc,hwnd,msg,wParam,lParam); +} + + +void ChangeInfoData::BeginStringEdit(int iItem, RECT *rc, int i, WORD wVKey) +{ + char *szValue; + char str[80]; + int alloced=0; + + EndStringEdit(0); + InflateRect(rc,-2,-2); + rc->left-=2; + if (setting[i].displayType & LIF_PASSWORD && !settingData[i].changed) + szValue = " "; + else if ((setting[i].displayType & LIM_TYPE) == LI_NUMBER) + { + szValue = str; + null_snprintf(str, sizeof(str), "%d", settingData[i].value ); + } + else if (settingData[i].value) + { + szValue = BinaryToEscapes((char*)settingData[i].value); + alloced = 1; + } + else szValue = ""; + + iEditItem = iItem; + + if ((setting[i].displayType & LIM_TYPE)==LI_LONGSTRING) + { + rc->right-=rc->bottom-rc->top; + hwndExpandButton=CreateWindowA("BUTTON","",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON|BS_ICON,rc->right,rc->top,rc->bottom-rc->top,rc->bottom-rc->top,hwndList,NULL,hInst,NULL); + SendMessage(hwndExpandButton,BM_SETIMAGE,IMAGE_ICON,(LPARAM)LoadImage(hInst,MAKEINTRESOURCE(IDI_EXPANDSTRINGEDIT),IMAGE_ICON,0,0,LR_SHARED)); + OldExpandButtonProc=(WNDPROC)SetWindowLongPtr(hwndExpandButton,GWLP_WNDPROC,(LONG_PTR)ExpandButtonSubclassProc); + } + + dataStringEdit = this; + hwndEdit = CreateWindow(_T("EDIT"),_T(""),WS_VISIBLE|WS_CHILD|ES_AUTOHSCROLL|((setting[i].displayType&LIM_TYPE)==LI_NUMBER?ES_NUMBER:0)|(setting[i].displayType&LIF_PASSWORD?ES_PASSWORD:0),rc->left,rc->top,rc->right-rc->left,rc->bottom-rc->top,hwndList,NULL,hInst,NULL); + SetWindowTextUtf(hwndEdit, szValue); + if (alloced) SAFE_FREE(&szValue); + OldStringEditProc=(WNDPROC)SetWindowLongPtr(hwndEdit,GWLP_WNDPROC,(LONG_PTR)StringEditSubclassProc); + SendMessage(hwndEdit,WM_SETFONT,(WPARAM)hListFont,0); + if ((setting[i].displayType & LIM_TYPE) == LI_NUMBER) + { + int *range= (int*)setting[i].pList; + RECT rcUpDown; + hwndUpDown=CreateWindow(UPDOWN_CLASS,_T(""),WS_VISIBLE|WS_CHILD|UDS_AUTOBUDDY|UDS_ALIGNRIGHT|UDS_HOTTRACK|UDS_NOTHOUSANDS|UDS_SETBUDDYINT,0,0,0,0,hwndList,NULL,hInst,NULL); + SendMessage(hwndUpDown, UDM_SETRANGE32, range[0], range[1]); + SendMessage(hwndUpDown, UDM_SETPOS32, 0, settingData[i].value); + if (!(setting[i].displayType & LIF_ZEROISVALID) && settingData[i].value==0) + SetWindowTextA(hwndEdit, ""); + GetClientRect(hwndUpDown, &rcUpDown); + rc->right -= rcUpDown.right; + SetWindowPos(hwndEdit,0,0,0,rc->right-rc->left,rc->bottom-rc->top,SWP_NOZORDER|SWP_NOMOVE); + } + SendMessage(hwndEdit,EM_SETSEL,0,(LPARAM)-1); + SetFocus(hwndEdit); + PostMessage(hwndEdit,WM_KEYDOWN,wVKey,0); +} + + +void ChangeInfoData::EndStringEdit(int save) +{ + if (hwndEdit == NULL || iEditItem == -1 || this != dataStringEdit) return; + if (save) + { + char *text = (char*)SAFE_MALLOC(GetWindowTextLength(hwndEdit)+1); + + GetWindowTextA(hwndEdit,(char*)text,GetWindowTextLength(hwndEdit)+1); + EscapesToBinary(text); + if ((setting[iEditItem].displayType&LIM_TYPE)==LI_NUMBER) + { + LPARAM newValue; + int *range=(int*)setting[iEditItem].pList; + newValue = atoi(text); + if (newValue) + { + if (newValuerange[1]) newValue=range[1]; + } + settingData[iEditItem].changed = settingData[iEditItem].value != newValue; + settingData[iEditItem].value = newValue; + SAFE_FREE(&text); + } + else + { + if (!(setting[iEditItem].displayType & LIF_PASSWORD)) + { + SAFE_FREE(&text); + text = GetWindowTextUtf(hwndEdit); + EscapesToBinary(text); + } + if ((setting[iEditItem].displayType & LIF_PASSWORD && strcmpnull(text," ")) || + (!(setting[iEditItem].displayType & LIF_PASSWORD) && strcmpnull(text, (char*)settingData[iEditItem].value) && (strlennull(text) + strlennull((char*)settingData[iEditItem].value)))) + { + SAFE_FREE((void**)&settingData[iEditItem].value); + if (strlennull(text)) + settingData[iEditItem].value = (LPARAM)text; + else + { + settingData[iEditItem].value = 0; + SAFE_FREE(&text); + } + settingData[iEditItem].changed = 1; + } + } + if (settingData[iEditItem].changed) + { + TCHAR tbuf[MAX_PATH]; + + GetWindowText(hwndEdit, tbuf, SIZEOF(tbuf)); + ListView_SetItemText(hwndList, iEditItem, 1, tbuf); + + EnableDlgItem(hwndDlg, IDC_SAVE, TRUE); + } + } + ListView_RedrawItems(hwndList, iEditItem, iEditItem); + iEditItem = -1; + dataStringEdit = NULL; + DestroyWindow(hwndEdit); + hwndEdit = NULL; + if (hwndExpandButton) DestroyWindow(hwndExpandButton); + hwndExpandButton = NULL; + if (hwndUpDown) DestroyWindow(hwndUpDown); + hwndUpDown = NULL; +} + + + +int IsStringEditWindow(HWND hwnd) +{ + if (hwnd == hwndEdit) return 1; + if (hwnd == hwndExpandButton) return 1; + if (hwnd == hwndUpDown) return 1; + return 0; +} diff --git a/protocols/IcqOscarJ/src/changeinfo/icqoscar.h b/protocols/IcqOscarJ/src/changeinfo/icqoscar.h new file mode 100644 index 0000000000..77283f6f7f --- /dev/null +++ b/protocols/IcqOscarJ/src/changeinfo/icqoscar.h @@ -0,0 +1,2 @@ +/* For MinGW sake */ +#include "../icqoscar.h" diff --git a/protocols/IcqOscarJ/src/changeinfo/main.cpp b/protocols/IcqOscarJ/src/changeinfo/main.cpp new file mode 100644 index 0000000000..22669be3aa --- /dev/null +++ b/protocols/IcqOscarJ/src/changeinfo/main.cpp @@ -0,0 +1,28 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2001-2004 Richard Hughes, Martin Öberg +// Copyright © 2004-2008 Joe Kucera, Bio +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// ChangeInfo Plugin stuff +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" diff --git a/protocols/IcqOscarJ/src/changeinfo/upload.cpp b/protocols/IcqOscarJ/src/changeinfo/upload.cpp new file mode 100644 index 0000000000..a468dc9f2b --- /dev/null +++ b/protocols/IcqOscarJ/src/changeinfo/upload.cpp @@ -0,0 +1,91 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2001-2004 Richard Hughes, Martin Öberg +// Copyright © 2004-2009 Joe Kucera, Bio +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// ChangeInfo Plugin stuff +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +int CIcqProto::StringToListItemId(const char *szSetting,int def) +{ + int i; + + for(i=0;iicqOnline()) + { + MessageBoxUtf(hwndDlg, LPGEN("You are not currently connected to the ICQ network. You must be online in order to update your information on the server."), LPGEN("Change ICQ Details"), MB_OK); + return 0; + } + + hUpload[0] = (HANDLE)ppro->ChangeInfoEx(CIXT_FULL, 0); + + //password + char* tmp = ppro->GetUserPassword(TRUE); + if (tmp) + { + if (strlennull(Password) > 0 && strcmpnull(Password, tmp)) + { + hUpload[1] = (HANDLE)ppro->icq_changeUserPasswordServ(tmp); + char szPwd[PASSWORDMAXLEN] = {0}; + + if (ppro->GetUserStoredPassword(szPwd, sizeof(szPwd))) + { // password is stored in DB, update + char ptmp[PASSWORDMAXLEN]; + + strcpy(ptmp, tmp); + + CallService(MS_DB_CRYPT_ENCODESTRING, sizeof(ptmp), (LPARAM)ptmp); + + ppro->setSettingString(NULL, "Password", ptmp); + } + } + } + + return 1; +} diff --git a/protocols/IcqOscarJ/src/channels.h b/protocols/IcqOscarJ/src/channels.h new file mode 100644 index 0000000000..ba10483b56 --- /dev/null +++ b/protocols/IcqOscarJ/src/channels.h @@ -0,0 +1,47 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Header for FLAP Channel packet handlers +// +// ----------------------------------------------------------------------------- +#ifndef __CHANNELS_H +#define __CHANNELS_H + + +struct snac_header +{ + BOOL bValid; + WORD wFamily; + WORD wSubtype; + WORD wFlags; + DWORD dwRef; + WORD wVersion; +}; + +int unpackSnacHeader(snac_header *pSnacHeader, BYTE **pBuffer, WORD *pwBufferLength); + + +#endif /* __CHANNELS_H */ diff --git a/protocols/IcqOscarJ/src/cookies.cpp b/protocols/IcqOscarJ/src/cookies.cpp new file mode 100644 index 0000000000..87ffb4bc07 --- /dev/null +++ b/protocols/IcqOscarJ/src/cookies.cpp @@ -0,0 +1,301 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Handles packet & message cookies +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +#define INVALID_COOKIE_INDEX -1 + +void CIcqProto::RemoveExpiredCookies() +{ + time_t tNow = time(NULL); + + for (int i = cookies.getCount()-1; i >= 0; i--) + { + icq_cookie_info *cookie = cookies[i]; + + if ((cookie->dwTime + COOKIE_TIMEOUT) < tNow) + { + cookies.remove(i); + SAFE_FREE((void**)&cookie); + } + } +} + + +// Generate and allocate cookie +DWORD CIcqProto::AllocateCookie(BYTE bType, WORD wIdent, HANDLE hContact, void *pvExtra) +{ + icq_lock l(cookieMutex); + + DWORD dwThisSeq = wCookieSeq++; + dwThisSeq &= 0x7FFF; + dwThisSeq |= wIdent<<0x10; + + icq_cookie_info* p = (icq_cookie_info*)SAFE_MALLOC(sizeof(icq_cookie_info)); + if (p) + { + p->bType = bType; + p->dwCookie = dwThisSeq; + p->hContact = hContact; + p->pvExtra = pvExtra; + p->dwTime = time(NULL); + cookies.insert(p); + } + return dwThisSeq; +} + + +DWORD CIcqProto::GenerateCookie(WORD wIdent) +{ + icq_lock l(cookieMutex); + + DWORD dwThisSeq = wCookieSeq++; + dwThisSeq &= 0x7FFF; + dwThisSeq |= wIdent<<0x10; + + return dwThisSeq; +} + + +int CIcqProto::GetCookieType(DWORD dwCookie) +{ + icq_lock l(cookieMutex); + + int i = cookies.getIndex(( icq_cookie_info* )&dwCookie ); + if ( i != INVALID_COOKIE_INDEX ) + i = cookies[i]->bType; + + return i; +} + + +int CIcqProto::FindCookie(DWORD dwCookie, HANDLE *phContact, void **ppvExtra) +{ + icq_lock l(cookieMutex); + + int i = cookies.getIndex(( icq_cookie_info* )&dwCookie ); + if (i != INVALID_COOKIE_INDEX) + { + if (phContact) + *phContact = cookies[i]->hContact; + if (ppvExtra) + *ppvExtra = cookies[i]->pvExtra; + + // Cookie found + return 1; + } + + return 0; +} + + +int CIcqProto::FindCookieByData(void *pvExtra, DWORD *pdwCookie, HANDLE *phContact) +{ + icq_lock l(cookieMutex); + + for (int i = 0; i < cookies.getCount(); i++) + { + if (pvExtra == cookies[i]->pvExtra) + { + if (phContact) + *phContact = cookies[i]->hContact; + if (pdwCookie) + *pdwCookie = cookies[i]->dwCookie; + + // Cookie found + return 1; + } + } + + return 0; +} + + +int CIcqProto::FindCookieByType(BYTE bType, DWORD *pdwCookie, HANDLE *phContact, void** ppvExtra) +{ + icq_lock l(cookieMutex); + + for (int i = 0; i < cookies.getCount(); i++) + { + if (bType == cookies[i]->bType) + { + if (pdwCookie) + *pdwCookie = cookies[i]->dwCookie; + if (phContact) + *phContact = cookies[i]->hContact; + if (ppvExtra) + *ppvExtra = cookies[i]->pvExtra; + + // Cookie found + return 1; + } + } + + return 0; +} + + +int CIcqProto::FindMessageCookie(DWORD dwMsgID1, DWORD dwMsgID2, DWORD *pdwCookie, HANDLE *phContact, cookie_message_data **ppvExtra) +{ + icq_lock l(cookieMutex); + + for (int i = 0; i < cookies.getCount(); i++) + { + if (cookies[i]->bType == CKT_MESSAGE || cookies[i]->bType == CKT_FILE || cookies[i]->bType == CKT_REVERSEDIRECT) + { // message cookie found + cookie_message_data *pCookie = (cookie_message_data*)cookies[i]->pvExtra; + + if (pCookie->dwMsgID1 == dwMsgID1 && pCookie->dwMsgID2 == dwMsgID2) + { + if (phContact) + *phContact = cookies[i]->hContact; + if (pdwCookie) + *pdwCookie = cookies[i]->dwCookie; + if (ppvExtra) + *ppvExtra = pCookie; + + // Cookie found + return 1; + } + } + } + + return 0; +} + + +void CIcqProto::FreeCookie(DWORD dwCookie) +{ + icq_lock l(cookieMutex); + + int i = cookies.getIndex((icq_cookie_info*)&dwCookie); + if (i != INVALID_COOKIE_INDEX) + { // Cookie found, remove from list + icq_cookie_info *cookie = cookies[i]; + + cookies.remove(i); + SAFE_FREE((void**)&cookie); + } + + RemoveExpiredCookies(); +} + + +void CIcqProto::FreeCookieByData(BYTE bType, void *pvExtra) +{ + icq_lock l(cookieMutex); + + for (int i = 0; i < cookies.getCount(); i++) + { + icq_cookie_info *cookie = cookies[i]; + + if (bType == cookie->bType && pvExtra == cookie->pvExtra) + { // Cookie found, remove from list + cookies.remove(i); + SAFE_FREE((void**)&cookie); + break; + } + } + + RemoveExpiredCookies(); +} + + +void CIcqProto::ReleaseCookie(DWORD dwCookie) +{ + icq_lock l(cookieMutex); + + int i = cookies.getIndex(( icq_cookie_info* )&dwCookie ); + if (i != INVALID_COOKIE_INDEX) + { // Cookie found, remove from list + icq_cookie_info *cookie = cookies[i]; + + cookies.remove(i); + SAFE_FREE((void**)&cookie->pvExtra); + SAFE_FREE((void**)&cookie); + } + RemoveExpiredCookies(); +} + + +void CIcqProto::InitMessageCookie(cookie_message_data *pCookie) +{ + DWORD dwMsgID1; + DWORD dwMsgID2; + + do + { // ensure that message ids are unique + dwMsgID1 = time(NULL); + dwMsgID2 = RandRange(0, 0x0FFFF); + } while (FindMessageCookie(dwMsgID1, dwMsgID2, NULL, NULL, NULL)); + + if (pCookie) + { + pCookie->dwMsgID1 = dwMsgID1; + pCookie->dwMsgID2 = dwMsgID2; + } +} + + +cookie_message_data* CIcqProto::CreateMessageCookie(WORD bMsgType, BYTE bAckType) +{ + cookie_message_data *pCookie = (cookie_message_data*)SAFE_MALLOC(sizeof(cookie_message_data)); + if (pCookie) + { + pCookie->bMessageType = bMsgType; + pCookie->nAckType = bAckType; + + InitMessageCookie(pCookie); + } + return pCookie; +} + + +cookie_message_data* CIcqProto::CreateMessageCookieData(BYTE bMsgType, HANDLE hContact, DWORD dwUin, int bUseSrvRelay) +{ + BYTE bAckType; + WORD wStatus = getContactStatus(hContact); + + if (!getSettingByte(hContact, "SlowSend", getSettingByte(NULL, "SlowSend", DEFAULT_SLOWSEND)) || + (!dwUin && wStatus == ID_STATUS_OFFLINE)) + bAckType = ACKTYPE_NONE; + else if (bUseSrvRelay) + bAckType = ACKTYPE_CLIENT; + else + bAckType = ACKTYPE_SERVER; + + cookie_message_data* pCookieData = CreateMessageCookie(bMsgType, bAckType); + + // set flag for offline messages - to allow proper error handling + if (wStatus == ID_STATUS_OFFLINE || wStatus == ID_STATUS_INVISIBLE) + pCookieData->isOffline = TRUE; + + return pCookieData; +} diff --git a/protocols/IcqOscarJ/src/cookies.h b/protocols/IcqOscarJ/src/cookies.h new file mode 100644 index 0000000000..05e0d170eb --- /dev/null +++ b/protocols/IcqOscarJ/src/cookies.h @@ -0,0 +1,148 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#ifndef __COOKIES_H +#define __COOKIES_H + + +#define CKT_MESSAGE 0x01 +#define CKT_FILE 0x02 +#define CKT_SEARCH 0x04 +#define CKT_SERVERLIST 0x08 +#define CKT_SERVICEREQUEST 0x0A +#define CKT_REVERSEDIRECT 0x0C +#define CKT_FAMILYSPECIAL 0x10 +#define CKT_OFFLINEMESSAGE 0x12 +#define CKT_DIRECTORY_QUERY 0x18 +#define CKT_DIRECTORY_UPDATE 0x19 +#define CKT_AVATAR 0x20 + +struct CIcqProto; + +/* Basic structure used to hold operation cookies list */ +struct icq_cookie_info +{ + DWORD dwCookie; + HANDLE hContact; + void *pvExtra; + time_t dwTime; + BYTE bType; +}; + + +/* Specific structures to hold request specific data - pvExtra */ + +struct cookie_family_request +{ + WORD wFamily; + void (CIcqProto::*familyHandler)(HANDLE hConn, char* cookie, WORD cookieLen); +}; + + +struct cookie_offline_messages +{ + int nMessages; + int nMissed; +}; + + +#define ACKTYPE_NONE 0 +#define ACKTYPE_SERVER 1 +#define ACKTYPE_CLIENT 2 + +struct cookie_message_data +{ + DWORD dwMsgID1; + DWORD dwMsgID2; + WORD bMessageType; + BYTE nAckType; + BYTE isOffline; +}; + +#define REQUESTTYPE_OWNER 0 +#define REQUESTTYPE_USERAUTO 1 +#define REQUESTTYPE_USERMINIMAL 2 +#define REQUESTTYPE_USERDETAILED 3 +#define REQUESTTYPE_PROFILE 4 + +struct cookie_fam15_data +{ + BYTE bRequestType; +}; + + +#define SEARCHTYPE_UID 0 +#define SEARCHTYPE_EMAIL 1 +#define SEARCHTYPE_NAMES 2 +#define SEARCHTYPE_DETAILS 4 + +struct cookie_search +{ + BYTE bSearchType; + char* szObject; + DWORD dwMainId; + DWORD dwStatus; +}; + + +struct cookie_avatar +{ + DWORD dwUin; + HANDLE hContact; + unsigned int hashlen; + BYTE *hash; + unsigned int cbData; + TCHAR *szFile; +}; + + +struct cookie_reverse_connect: public cookie_message_data +{ + HANDLE hContact; + DWORD dwUin; + int type; + void *ft; +}; + + +#define DIRECTORYREQUEST_INFOUSER 0x01 +#define DIRECTORYREQUEST_INFOOWNER 0x02 +#define DIRECTORYREQUEST_INFOMULTI 0x03 +#define DIRECTORYREQUEST_SEARCH 0x08 +#define DIRECTORYREQUEST_UPDATEOWNER 0x10 +#define DIRECTORYREQUEST_UPDATENOTE 0x11 +#define DIRECTORYREQUEST_UPDATEPRIVACY 0x12 + +struct cookie_directory_data +{ + BYTE bRequestType; +}; + + +#endif /* __COOKIES_H */ diff --git a/protocols/IcqOscarJ/src/directpackets.cpp b/protocols/IcqOscarJ/src/directpackets.cpp new file mode 100644 index 0000000000..594f005675 --- /dev/null +++ b/protocols/IcqOscarJ/src/directpackets.cpp @@ -0,0 +1,293 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +void EncryptDirectPacket(directconnect* dc, icq_packet* p); + +void packEmptyMsg(icq_packet *packet); + +static void packDirectMsgHeader(icq_packet* packet, WORD wDataLen, WORD wCommand, DWORD dwCookie, BYTE bMsgType, BYTE bMsgFlags, WORD wX1, WORD wX2) +{ + directPacketInit(packet, 29 + wDataLen); + packByte(packet, 2); /* channel */ + packLEDWord(packet, 0); /* space for crypto */ + packLEWord(packet, wCommand); + packLEWord(packet, 14); /* unknown */ + packLEWord(packet, (WORD)dwCookie); + packLEDWord(packet, 0); /* unknown */ + packLEDWord(packet, 0); /* unknown */ + packLEDWord(packet, 0); /* unknown */ + packByte(packet, bMsgType); + packByte(packet, bMsgFlags); + packLEWord(packet, wX1); /* unknown. Is 1 for getawaymsg, 0 otherwise */ + packLEWord(packet, wX2); // this is probably priority +} + + +void CIcqProto::icq_sendDirectMsgAck(directconnect* dc, WORD wCookie, BYTE bMsgType, BYTE bMsgFlags, char* szCap) +{ + icq_packet packet; + + packDirectMsgHeader(&packet, (WORD)(bMsgType==MTYPE_PLAIN ? (szCap ? 53 : 11) : 3), DIRECT_ACK, wCookie, bMsgType, bMsgFlags, 0, 0); + packEmptyMsg(&packet); /* empty message */ + + if (bMsgType == MTYPE_PLAIN) + { + packMsgColorInfo(&packet); + + if (szCap) + { + packLEDWord(&packet, 0x26); /* CLSID length */ + packBuffer(&packet, (LPBYTE)szCap, 0x26); /* GUID */ + } + } + EncryptDirectPacket(dc, &packet); + sendDirectPacket(dc, &packet); + + NetLog_Direct("Sent acknowledgement thru direct connection"); +} + + +DWORD CIcqProto::icq_sendGetAwayMsgDirect(HANDLE hContact, int type) +{ + icq_packet packet; + DWORD dwCookie; + cookie_message_data *pCookieData; + + if (getSettingWord(hContact, "Version", 0) == 9) + return 0; // v9 DC protocol does not support this message + + pCookieData = CreateMessageCookie(MTYPE_AUTOAWAY, (BYTE)type); + dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData); + + packDirectMsgHeader(&packet, 3, DIRECT_MESSAGE, dwCookie, (BYTE)type, 3, 1, 0); + packEmptyMsg(&packet); // message + + return (SendDirectMessage(hContact, &packet)) ? dwCookie : 0; +} + + +void CIcqProto::icq_sendAwayMsgReplyDirect(directconnect* dc, WORD wCookie, BYTE msgType, const char** szMsg) +{ + icq_packet packet; + + if (validateStatusMessageRequest(dc->hContact, msgType)) + { + NotifyEventHooks(m_modeMsgsEvent, (WPARAM)msgType, (LPARAM)dc->dwRemoteUin); + + icq_lock l(m_modeMsgsMutex); + + if (szMsg && *szMsg) + { + // prepare Ansi message - only Ansi supported + WORD wMsgLen = strlennull(*szMsg) + 1; + char *szAnsiMsg = (char*)_alloca(wMsgLen); + + utf8_decode_static(*szMsg, szAnsiMsg, wMsgLen); + wMsgLen = strlennull(szAnsiMsg); + packDirectMsgHeader(&packet, (WORD)(3 + wMsgLen), DIRECT_ACK, wCookie, msgType, 3, 0, 0); + packLEWord(&packet, (WORD)(wMsgLen + 1)); + packBuffer(&packet, (LPBYTE)szAnsiMsg, (WORD)(wMsgLen + 1)); + EncryptDirectPacket(dc, &packet); + + sendDirectPacket(dc, &packet); + } + } +} + + +void CIcqProto::icq_sendFileAcceptDirect(HANDLE hContact, filetransfer* ft) +{ + // v7 packet + icq_packet packet; + + packDirectMsgHeader(&packet, 18, DIRECT_ACK, ft->dwCookie, MTYPE_FILEREQ, 0, 0, 0); + packLEWord(&packet, 1); // description + packByte(&packet, 0); + packWord(&packet, wListenPort); + packLEWord(&packet, 0); + packLEWord(&packet, 1); // filename + packByte(&packet, 0); // TODO: really send filename + packLEDWord(&packet, ft->dwTotalSize); // file size + packLEDWord(&packet, wListenPort); // FIXME: ideally we want to open a new port for this + + SendDirectMessage(hContact, &packet); + + NetLog_Direct("Sent file accept direct, port %u", wListenPort); +} + + +void CIcqProto::icq_sendFileDenyDirect(HANDLE hContact, filetransfer *ft, const char *szReason) +{ + // v7 packet + icq_packet packet; + char *szReasonAnsi = NULL; + int cbReasonAnsi = 0; + + if (!utf8_decode(szReason, &szReasonAnsi)) + szReasonAnsi = NULL; + else + cbReasonAnsi = strlennull(szReasonAnsi); + + packDirectMsgHeader(&packet, (WORD)(18 + cbReasonAnsi), DIRECT_ACK, ft->dwCookie, MTYPE_FILEREQ, 0, 1, 0); + packLEWord(&packet, (WORD)(1 + cbReasonAnsi)); // description + if (szReasonAnsi) packBuffer(&packet, (LPBYTE)szReasonAnsi, (WORD)cbReasonAnsi); + packByte(&packet, 0); + packWord(&packet, 0); + packLEWord(&packet, 0); + packLEWord(&packet, 1); // filename + packByte(&packet, 0); // TODO: really send filename + packLEDWord(&packet, 0); // file size + packLEDWord(&packet, 0); + + SAFE_FREE(&szReasonAnsi); + + SendDirectMessage(hContact, &packet); + + NetLog_Direct("Sent file deny direct."); +} + + +int CIcqProto::icq_sendFileSendDirectv7(filetransfer *ft, const char *pszFiles) +{ + icq_packet packet; + char *szFilesAnsi = NULL; + WORD wDescrLen = strlennull(ft->szDescription), wFilesLen = 0; + + if (!utf8_decode(pszFiles, &szFilesAnsi)) + szFilesAnsi = NULL; + else + wFilesLen = strlennull(szFilesAnsi); + + packDirectMsgHeader(&packet, (WORD)(18 + wDescrLen + wFilesLen), DIRECT_MESSAGE, (WORD)ft->dwCookie, MTYPE_FILEREQ, 0, 0, 0); + packLEWord(&packet, (WORD)(wDescrLen + 1)); + packBuffer(&packet, (LPBYTE)ft->szDescription, (WORD)(wDescrLen + 1)); + packLEDWord(&packet, 0); // listen port + packLEWord(&packet, (WORD)(wFilesLen + 1)); + packBuffer(&packet, (LPBYTE)szFilesAnsi, (WORD)(wFilesLen + 1)); + packLEDWord(&packet, ft->dwTotalSize); + packLEDWord(&packet, 0); // listen port (again) + + SAFE_FREE(&szFilesAnsi); + + NetLog_Direct("Sending v%u file transfer request direct", 7); + + return SendDirectMessage(ft->hContact, &packet); +} + + +int CIcqProto::icq_sendFileSendDirectv8(filetransfer *ft, const char *pszFiles) +{ + icq_packet packet; + char *szFilesAnsi = NULL; + WORD wDescrLen = strlennull(ft->szDescription), wFilesLen = 0; + + if (!utf8_decode(pszFiles, &szFilesAnsi)) + szFilesAnsi = NULL; + else + wFilesLen = strlennull(szFilesAnsi); + + packDirectMsgHeader(&packet, (WORD)(0x2E + 22 + wDescrLen + wFilesLen + 1), DIRECT_MESSAGE, (WORD)ft->dwCookie, MTYPE_PLUGIN, 0, 0, 0); + packEmptyMsg(&packet); // message + packPluginTypeId(&packet, MTYPE_FILEREQ); + + packLEDWord(&packet, (WORD)(18 + wDescrLen + wFilesLen + 1)); // Remaining length + packLEDWord(&packet, wDescrLen); // Description + packBuffer(&packet, (LPBYTE)ft->szDescription, wDescrLen); + packWord(&packet, 0x8c82); // Unknown (port?), seen 0x80F6 + packWord(&packet, 0x0222); // Unknown, seen 0x2e01 + packLEWord(&packet, (WORD)(wFilesLen + 1)); + packBuffer(&packet, (LPBYTE)szFilesAnsi, (WORD)(wFilesLen + 1)); + packLEDWord(&packet, ft->dwTotalSize); + packLEDWord(&packet, 0x0008c82); // Unknown, (seen 0xf680 ~33000) + + SAFE_FREE(&szFilesAnsi); + + NetLog_Direct("Sending v%u file transfer request direct", 8); + + return SendDirectMessage(ft->hContact, &packet); +} + + +DWORD CIcqProto::icq_SendDirectMessage(HANDLE hContact, const char *szMessage, int nBodyLength, WORD wPriority, cookie_message_data *pCookieData, char *szCap) +{ + icq_packet packet; + DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData); + + // Pack the standard header + packDirectMsgHeader(&packet, (WORD)(nBodyLength + (szCap ? 53:11)), DIRECT_MESSAGE, dwCookie, (BYTE)pCookieData->bMessageType, 0, 0, 0); + + packLEWord(&packet, (WORD)(nBodyLength+1)); // Length of message + packBuffer(&packet, (LPBYTE)szMessage, (WORD)(nBodyLength+1)); // Message + packMsgColorInfo(&packet); + if (szCap) + { + packLEDWord(&packet, 0x00000026); // length of GUID + packBuffer(&packet, (LPBYTE)szCap, 0x26); // UTF-8 GUID + } + + if (SendDirectMessage(hContact, &packet)) + return dwCookie; // Success + + FreeCookie(dwCookie); // release cookie + return 0; // Failure +} + +void CIcqProto::icq_sendXtrazRequestDirect(HANDLE hContact, DWORD dwCookie, char* szBody, int nBodyLen, WORD wType) +{ + icq_packet packet; + + packDirectMsgHeader(&packet, (WORD)(11 + getPluginTypeIdLen(wType) + nBodyLen), DIRECT_MESSAGE, dwCookie, MTYPE_PLUGIN, 0, 0, 1); + packEmptyMsg(&packet); // message (unused) + packPluginTypeId(&packet, wType); + + packLEDWord(&packet, nBodyLen + 4); + packLEDWord(&packet, nBodyLen); + packBuffer(&packet, (LPBYTE)szBody, (WORD)nBodyLen); + + SendDirectMessage(hContact, &packet); +} + +void CIcqProto::icq_sendXtrazResponseDirect(HANDLE hContact, WORD wCookie, char* szBody, int nBodyLen, WORD wType) +{ + icq_packet packet; + + packDirectMsgHeader(&packet, (WORD)(getPluginTypeIdLen(wType) + 11 + nBodyLen), DIRECT_ACK, wCookie, MTYPE_PLUGIN, 0, 0, 0); + // + packEmptyMsg(&packet); // Message (unused) + + packPluginTypeId(&packet, wType); + + packLEDWord(&packet, nBodyLen + 4); + packLEDWord(&packet, nBodyLen); + packBuffer(&packet, (LPBYTE)szBody, (WORD)nBodyLen); + + SendDirectMessage(hContact, &packet); +} diff --git a/protocols/IcqOscarJ/src/fam_01service.cpp b/protocols/IcqOscarJ/src/fam_01service.cpp new file mode 100644 index 0000000000..9bc06f19ab --- /dev/null +++ b/protocols/IcqOscarJ/src/fam_01service.cpp @@ -0,0 +1,983 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Handles packets from Service family +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" +#include + +extern capstr capXStatus[]; + +void CIcqProto::handleServiceFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader, serverthread_info *info) +{ + icq_packet packet; + + switch (pSnacHeader->wSubtype) { + + case ICQ_SERVER_READY: +#ifdef _DEBUG + NetLog_Server("Server is ready and is requesting my Family versions"); + NetLog_Server("Sending my Families"); +#endif + + // This packet is a response to SRV_FAMILIES SNAC(1,3). + // This tells the server which SNAC families and their corresponding + // versions which the client understands. This also seems to identify + // the client as an ICQ vice AIM client to the server. + // Miranda mimics the behaviour of ICQ 6 + serverPacketInit(&packet, 54); + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_FAMILIES); + packDWord(&packet, 0x00220001); + packDWord(&packet, 0x00010004); + packDWord(&packet, 0x00130004); + packDWord(&packet, 0x00020001); + packDWord(&packet, 0x00030001); + packDWord(&packet, 0x00150001); + packDWord(&packet, 0x00040001); + packDWord(&packet, 0x00060001); + packDWord(&packet, 0x00090001); + packDWord(&packet, 0x000a0001); + packDWord(&packet, 0x000b0001); + sendServPacket(&packet); + break; + + case ICQ_SERVER_FAMILIES2: + /* This is a reply to CLI_FAMILIES and it tells the client which families and their versions that this server understands. + * We send a rate request packet */ +#ifdef _DEBUG + NetLog_Server("Server told me his Family versions"); + NetLog_Server("Requesting Rate Information"); +#endif + serverPacketInit(&packet, 10); + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_REQ_RATE_INFO); + sendServPacket(&packet); + break; + + case ICQ_SERVER_RATE_INFO: +#ifdef _DEBUG + NetLog_Server("Server sent Rate Info"); +#endif + /* init rates management */ + m_rates = new rates(this, pBuffer, wBufferLength); + /* ack rate levels */ +#ifdef _DEBUG + NetLog_Server("Sending Rate Info Ack"); +#endif + m_rates->initAckPacket(&packet); + sendServPacket(&packet); + + /* CLI_REQINFO - This command requests from the server certain information about the client that is stored on the server. */ +#ifdef _DEBUG + NetLog_Server("Sending CLI_REQINFO"); +#endif + serverPacketInit(&packet, 10); + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_REQINFO); + sendServPacket(&packet); + + if (m_bSsiEnabled) + { + cookie_servlist_action* ack; + DWORD dwCookie; + + DWORD dwLastUpdate = getSettingDword(NULL, "SrvLastUpdate", 0); + WORD wRecordCount = getSettingWord(NULL, "SrvRecordCount", 0); + + // CLI_REQLISTS - we want to use SSI +#ifdef _DEBUG + NetLog_Server("Requesting roster rights"); +#endif + serverPacketInit(&packet, 16); + packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_REQLISTS); + packTLVWord(&packet, 0x0B, 0x000F); // mimic ICQ 6 + sendServPacket(&packet); + + if (!wRecordCount) // CLI_REQROSTER + { // we do not have any data - request full list +#ifdef _DEBUG + NetLog_Server("Requesting full roster"); +#endif + serverPacketInit(&packet, 10); + ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + if (ack) + { // we try to use standalone cookie if available + ack->dwAction = SSA_CHECK_ROSTER; // loading list + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_CLI_REQUEST, 0, ack); + } + else // if not use that old fake + dwCookie = ICQ_LISTS_CLI_REQUEST<<0x10; + + packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_REQUEST, 0, dwCookie); + sendServPacket(&packet); + } + else // CLI_CHECKROSTER + { +#ifdef _DEBUG + NetLog_Server("Requesting roster check"); +#endif + serverPacketInit(&packet, 16); + ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + if (ack) // TODO: rewrite - use get list service for empty list + { // we try to use standalone cookie if available + ack->dwAction = SSA_CHECK_ROSTER; // loading list + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_CLI_CHECK, 0, ack); + } + else // if not use that old fake + dwCookie = ICQ_LISTS_CLI_CHECK<<0x10; + + packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_CHECK, 0, dwCookie); + // check if it was not changed elsewhere (force reload, set that setting to zero) + if (IsServerGroupsDefined()) + { + packDWord(&packet, dwLastUpdate); // last saved time + packWord(&packet, wRecordCount); // number of records saved + } + else + { // we need to get groups info into DB, force receive list + packDWord(&packet, 0); // last saved time + packWord(&packet, 0); // number of records saved + } + sendServPacket(&packet); + } + } + + // CLI_REQLOCATION +#ifdef _DEBUG + NetLog_Server("Requesting Location rights"); +#endif + serverPacketInit(&packet, 10); + packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_CLI_REQ_RIGHTS); + sendServPacket(&packet); + + // CLI_REQBUDDY +#ifdef _DEBUG + NetLog_Server("Requesting Client-side contactlist rights"); +#endif + serverPacketInit(&packet, 16); + packFNACHeader(&packet, ICQ_BUDDY_FAMILY, ICQ_USER_CLI_REQBUDDY); + // Query flags: 1 = Enable Avatars + // 2 = Enable offline status message notification + // 4 = Enable Avatars for offline contacts + // 8 = Use reject for not authorized contacts + packTLVWord(&packet, 0x05, 0x0007); + sendServPacket(&packet); + + // CLI_REQICBM +#ifdef _DEBUG + NetLog_Server("Sending CLI_REQICBM"); +#endif + serverPacketInit(&packet, 10); + packFNACHeader(&packet, ICQ_MSG_FAMILY, ICQ_MSG_CLI_REQICBM); + sendServPacket(&packet); + + // CLI_REQBOS +#ifdef _DEBUG + NetLog_Server("Sending CLI_REQBOS"); +#endif + serverPacketInit(&packet, 10); + packFNACHeader(&packet, ICQ_BOS_FAMILY, ICQ_PRIVACY_REQ_RIGHTS); + sendServPacket(&packet); + break; + + case ICQ_SERVER_PAUSE: + NetLog_Server("Server is going down in a few seconds... (Flags: %u)", pSnacHeader->wFlags); + // This is the list of groups that we want to have on the next server + serverPacketInit(&packet, 30); + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_PAUSE_ACK); + packWord(&packet,ICQ_SERVICE_FAMILY); + packWord(&packet,ICQ_LISTS_FAMILY); + packWord(&packet,ICQ_LOCATION_FAMILY); + packWord(&packet,ICQ_BUDDY_FAMILY); + packWord(&packet,ICQ_EXTENSIONS_FAMILY); + packWord(&packet,ICQ_MSG_FAMILY); + packWord(&packet,0x06); + packWord(&packet,ICQ_BOS_FAMILY); + packWord(&packet,ICQ_LOOKUP_FAMILY); + packWord(&packet,ICQ_STATS_FAMILY); + sendServPacket(&packet); +#ifdef _DEBUG + NetLog_Server("Sent server pause ack"); +#endif + break; + + case ICQ_SERVER_MIGRATIONREQ: + { +#ifdef _DEBUG + NetLog_Server("Server migration requested (Flags: %u)", pSnacHeader->wFlags); +#endif + pBuffer += 2; // Unknown, seen: 0 + wBufferLength -= 2; + + oscar_tlv_chain *chain = readIntoTLVChain(&pBuffer, wBufferLength, 0); + + if (info->cookieDataLen > 0) + SAFE_FREE((void**)&info->cookieData); + + info->newServer = chain->getString(0x05, 1); + info->newServerSSL = chain->getNumber(0x8E, 1); + info->cookieData = (BYTE*)chain->getString(0x06, 1); + info->cookieDataLen = chain->getLength(0x06, 1); + + disposeChain(&chain); + + if (!info->newServer || !info->cookieData) + { + icq_LogMessage(LOG_FATAL, LPGEN("A server migration has failed because the server returned invalid data. You must reconnect manually.")); + SAFE_FREE(&info->newServer); + SAFE_FREE((void**)&info->cookieData); + info->cookieDataLen = 0; + info->newServerReady = 0; + return; + } + + NetLog_Server("Migration has started. New server will be %s", info->newServer); + + m_iDesiredStatus = m_iStatus; + SetCurrentStatus(ID_STATUS_CONNECTING); // revert to connecting state + + info->newServerReady = 1; + info->isMigrating = 1; + } + break; + + case ICQ_SERVER_NAME_INFO: // This is the reply to CLI_REQINFO + { + BYTE bUinLen; + oscar_tlv_chain *chain; + +#ifdef _DEBUG + NetLog_Server("Received self info"); +#endif + unpackByte(&pBuffer, &bUinLen); + pBuffer += bUinLen; + pBuffer += 4; /* warning level & user class */ + wBufferLength -= 5 + bUinLen; + + if (pSnacHeader->dwRef == ICQ_CLIENT_REQINFO<<0x10) + { // This is during the login sequence + DWORD dwValue; + + // TLV(x01) User type? + // TLV(x0C) Empty CLI2CLI Direct connection info + // TLV(x0A) External IP + // TLV(x0F) Number of seconds that user has been online + // TLV(x03) The online since time. + // TLV(x0A) External IP again + // TLV(x22) Unknown + // TLV(x1E) Unknown: empty. + // TLV(x05) Member of ICQ since. + // TLV(x14) Unknown + chain = readIntoTLVChain(&pBuffer, wBufferLength, 0); + + // Save external IP + dwValue = chain->getDWord(0x0A, 1); + setSettingDword(NULL, "IP", dwValue); + + // Save member since timestamp + dwValue = chain->getDWord(0x05, 1); + if (dwValue) setSettingDword(NULL, "MemberTS", dwValue); + + dwValue = chain->getDWord(0x03, 1); + setSettingDword(NULL, "LogonTS", dwValue ? dwValue : time(NULL)); + + disposeChain(&chain); + + // If we are in SSI mode, this is sent after the list is acked instead + // to make sure that we don't set status before seing the visibility code + if (!m_bSsiEnabled || info->isMigrating) + handleServUINSettings(wListenPort, info); + } + else if (m_hNotifyNameInfoEvent) + // Just notify that the set status note & mood process is finished + SetEvent(m_hNotifyNameInfoEvent); + } + break; + + case ICQ_SERVER_RATE_CHANGE: + + if (wBufferLength >= 2) + { + WORD wStatus; + WORD wClass; + DWORD dwLevel; + // We now have global rate management, although controlled are only some + // areas. This should not arrive in most cases. If it does, update our + // local rate levels & issue broadcast. + unpackWord(&pBuffer, &wStatus); + unpackWord(&pBuffer, &wClass); + pBuffer += 20; + unpackDWord(&pBuffer, &dwLevel); + + m_ratesMutex->Enter(); + m_rates->updateLevel(wClass, dwLevel); + m_ratesMutex->Leave(); + + if (wStatus == 2 || wStatus == 3) + { // this is only the simplest solution, needs rate management to every section + BroadcastAck(NULL, ICQACKTYPE_RATEWARNING, ACKRESULT_STATUS, (HANDLE)wClass, wStatus); + if (wStatus == 2) + NetLog_Server("Rates #%u: Alert", wClass); + else + NetLog_Server("Rates #%u: Limit", wClass); + } + else if (wStatus == 4) + { + BroadcastAck(NULL, ICQACKTYPE_RATEWARNING, ACKRESULT_STATUS, (HANDLE)wClass, wStatus); + NetLog_Server("Rates #%u: Clear", wClass); + } + } + + break; + + case ICQ_SERVER_REDIRECT_SERVICE: // reply to family request, got new connection point + { + oscar_tlv_chain *pChain = NULL; + cookie_family_request *pCookieData; + + if (!(pChain = readIntoTLVChain(&pBuffer, wBufferLength, 0))) + { + NetLog_Server("Received Broken Redirect Service SNAC(1,5)."); + break; + } + WORD wFamily = pChain->getWord(0x0D, 1); + + // pick request data + if ((!FindCookie(pSnacHeader->dwRef, NULL, (void**)&pCookieData)) || (pCookieData->wFamily != wFamily)) + { + disposeChain(&pChain); + NetLog_Server("Received unexpected SNAC(1,5), skipping."); + break; + } + + FreeCookie(pSnacHeader->dwRef); + + { // new family entry point received + char *pServer = pChain->getString(0x05, 1); + BYTE bServerSSL = pChain->getNumber(0x8E, 1); + char *pCookie = pChain->getString(0x06, 1); + WORD wCookieLen = pChain->getLength(0x06, 1); + + if (!pServer || !pCookie) + { + NetLog_Server("Server returned invalid data, family unavailable."); + + SAFE_FREE(&pServer); + SAFE_FREE(&pCookie); + SAFE_FREE((void**)&pCookieData); + disposeChain(&pChain); + break; + } + + // Get new family server ip and port + WORD wPort = info->wServerPort; // get default port + parseServerAddress(pServer, &wPort); + + // establish connection + NETLIBOPENCONNECTION nloc = {0}; + if (m_bGatewayMode) + nloc.flags |= NLOCF_HTTPGATEWAY; + nloc.szHost = pServer; + nloc.wPort = wPort; + + HANDLE hConnection = NetLib_OpenConnection(m_hServerNetlibUser, wFamily == ICQ_AVATAR_FAMILY ? "Avatar " : NULL, &nloc); + + if (hConnection == NULL) + { + NetLog_Server("Unable to connect to ICQ new family server."); + } // we want the handler to be called even if the connecting failed + else if (bServerSSL) + { /* Start SSL session if requested */ +#ifdef _DEBUG + NetLog_Server("(%d) Starting SSL negotiation", CallService(MS_NETLIB_GETSOCKET, (WPARAM)hConnection, 0)); +#endif + if (!CallService(MS_NETLIB_STARTSSL, (WPARAM)hConnection, 0)) + { + NetLog_Server("Unable to connect to ICQ new family server, SSL could not be negotiated"); + NetLib_CloseConnection(&hConnection, FALSE); + } + } + + (this->*pCookieData->familyHandler)(hConnection, pCookie, wCookieLen); + + // Free allocated memory + // NOTE: "cookie" will get freed when we have connected to the avatar server. + disposeChain(&pChain); + SAFE_FREE(&pServer); + SAFE_FREE((void**)&pCookieData); + } + + break; + } + + case ICQ_SERVER_EXTSTATUS: // our session data + { +#ifdef _DEBUG + NetLog_Server("Received owner session data."); +#endif + while (wBufferLength > 4) + { // loop thru all items + WORD itemType = pBuffer[0] * 0x10 | pBuffer[1]; + BYTE itemFlags = pBuffer[2]; + BYTE itemLen = pBuffer[3]; + + if (itemType == AVATAR_HASH_PHOTO) /// TODO: handle photo item + { // skip photo item +#ifdef _DEBUG + NetLog_Server("Photo item recognized"); +#endif + } + else if ((itemType == AVATAR_HASH_STATIC || itemType == AVATAR_HASH_FLASH) && (itemLen >= 0x10)) + { +#ifdef _DEBUG + NetLog_Server("Avatar item recognized"); +#endif + if (m_bAvatarsEnabled && !info->bMyAvatarInited) // signal the server after login + { // this refreshes avatar state - it used to work automatically, but now it does not + if (getSettingByte(NULL, "ForceOurAvatar", 0)) + { // keep our avatar + TCHAR *file = GetOwnAvatarFileName(); + SetMyAvatar(0, (LPARAM)file); + SAFE_FREE(&file); + } + else // only change avatar hash to the same one + { + BYTE hash[0x14]; + + memcpy(hash, pBuffer, 0x14); + hash[2] = 1; // update image status + updateServAvatarHash(hash, 0x14); + } + info->bMyAvatarInited = TRUE; + break; + } + // process owner avatar hash changed notification + handleAvatarOwnerHash(itemType, itemFlags, pBuffer, itemLen + 4); + } + else if (itemType == 0x02) + { +#ifdef _DEBUG + NetLog_Server("Status message item recognized"); +#endif + } + else if (itemType == 0x0E) + { +#ifdef _DEBUG + NetLog_Server("Status mood item recognized"); +#endif + } + + // move to next item + if (wBufferLength >= itemLen + 4) + { + wBufferLength -= itemLen + 4; + pBuffer += itemLen + 4; + } + else + { + pBuffer += wBufferLength; + wBufferLength = 0; + } + } + break; + } + + case ICQ_ERROR: + { // Something went wrong, probably the request for avatar family failed + WORD wError; + + if (wBufferLength >= 2) + unpackWord(&pBuffer, &wError); + else + wError = 0; + + LogFamilyError(ICQ_SERVICE_FAMILY, wError); + break; + } + + // Stuff we don't care about + case ICQ_SERVER_MOTD: +#ifdef _DEBUG + NetLog_Server("Server message of the day"); +#endif + break; + + default: + NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_SERVICE_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + + } +} + + +char* CIcqProto::buildUinList(int subtype, WORD wMaxLen, HANDLE* hContactResume) +{ + char* szList; + HANDLE hContact; + WORD wCurrentLen = 0; + DWORD dwUIN; + uid_str szUID; + char szLen[2]; + int add; + + szList = (char*)SAFE_MALLOC(CallService(MS_DB_CONTACT_GETCOUNT, 0, 0) * UINMAXLEN); + szLen[1] = '\0'; + + if (*hContactResume) + hContact = *hContactResume; + else + hContact = FindFirstContact(); + + while (hContact != NULL) + { + if (!getContactUid(hContact, &dwUIN, &szUID)) + { + szLen[0] = strlennull(strUID(dwUIN, szUID)); + + switch (subtype) + { + + case BUL_VISIBLE: + add = ID_STATUS_ONLINE == getSettingWord(hContact, "ApparentMode", 0); + break; + + case BUL_INVISIBLE: + add = ID_STATUS_OFFLINE == getSettingWord(hContact, "ApparentMode", 0); + break; + + case BUL_TEMPVISIBLE: + add = getSettingByte(hContact, "TemporaryVisible", 0); + // clear temporary flag + // Here we assume that all temporary contacts will be in one packet + setSettingByte(hContact, "TemporaryVisible", 0); + break; + + default: + add = 1; + + // If we are in SS mode, we only add those contacts that are + // not in our SS list, or are awaiting authorization, to our + // client side list + if (m_bSsiEnabled && getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0) && + !getSettingByte(hContact, "Auth", 0)) + add = 0; + + // Never add hidden contacts to CS list + if (DBGetContactSettingByte(hContact, "CList", "Hidden", 0)) + add = 0; + + break; + } + + if (add) + { + wCurrentLen += szLen[0] + 1; + if (wCurrentLen > wMaxLen) + { + *hContactResume = hContact; + return szList; + } + + strcat(szList, szLen); + strcat(szList, szUID); + } + } + + hContact = FindNextContact(hContact); + } + *hContactResume = NULL; + + return szList; +} + + +void CIcqProto::sendEntireListServ(WORD wFamily, WORD wSubtype, int listType) +{ + HANDLE hResumeContact = NULL; + + do + { // server doesn't seem to be able to cope with packets larger than 8k + // send only about 100contacts per packet + char *szList = buildUinList(listType, 0x3E8, &hResumeContact); + int nListLen = strlennull(szList); + + if (nListLen) + { + icq_packet packet; + + serverPacketInit(&packet, (WORD)(nListLen + 10)); + packFNACHeader(&packet, wFamily, wSubtype); + packBuffer(&packet, (LPBYTE)szList, (WORD)nListLen); + sendServPacket(&packet); + } + + SAFE_FREE((void**)&szList); + } + while (hResumeContact); +} + + +static void packShortCapability(icq_packet *packet, WORD wCapability) +{ // pack standard capability + DWORD dwQ1 = 0x09460000 | wCapability; + + packDWord(packet, dwQ1); + packDWord(packet, 0x4c7f11d1); + packDWord(packet, 0x82224445); + packDWord(packet, 0x53540000); +} + + +// CLI_SETUSERINFO +void CIcqProto::setUserInfo() +{ + icq_packet packet; + WORD wAdditionalData = 0; + BYTE bXStatus = getContactXStatus(NULL); + + if (m_bAimEnabled) + wAdditionalData += 16; +#ifdef DBG_CAPMTN + wAdditionalData += 16; +#endif + if (m_bUtfEnabled) + wAdditionalData += 16; +#ifdef DBG_NEWCAPS + wAdditionalData += 16; +#endif +#ifdef DBG_CAPXTRAZ + wAdditionalData += 16; +#endif +#ifdef DBG_OSCARFT + wAdditionalData += 16; +#endif + if (m_bAvatarsEnabled) + wAdditionalData += 16; + if (m_bXStatusEnabled && bXStatus != 0) + wAdditionalData += 16; +#ifdef DBG_CAPHTML + wAdditionalData += 16; +#endif +#ifdef DBG_AIMCONTACTSEND + wAdditionalData += 16; +#endif + + wAdditionalData += (WORD)CustomCapList.size() * 16; + + //MIM/PackName + bool bHasPackName = false; + DBVARIANT dbv; + if ( !DBGetContactSettingString(NULL, "ICQCaps", "PackName", &dbv )) { + //MIM/PackName + bHasPackName = true; + wAdditionalData += 16; + } + + serverPacketInit(&packet, (WORD)(62 + wAdditionalData)); + packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_SET_USER_INFO); + + /* TLV(5): capability data */ + packWord(&packet, 0x0005); + packWord(&packet, (WORD)(48 + wAdditionalData)); + +#ifdef DBG_CAPMTN + { + packDWord(&packet, 0x563FC809); // CAP_TYPING + packDWord(&packet, 0x0B6F41BD); + packDWord(&packet, 0x9F794226); + packDWord(&packet, 0x09DFA2F3); + } +#endif + { + packShortCapability(&packet, 0x1349); // AIM_CAPS_ICQSERVERRELAY + } + if (m_bUtfEnabled) + { + packShortCapability(&packet, 0x134E); // CAP_UTF8MSGS + } // Broadcasts the capability to receive UTF8 encoded messages +#ifdef DBG_NEWCAPS + { + packShortCapability(&packet, 0x0000); // CAP_SHORTCAPS + } // Tells server we understand to new format of caps +#endif +#ifdef DBG_CAPXTRAZ + { + packDWord(&packet, 0x1a093c6c); // CAP_XTRAZ + packDWord(&packet, 0xd7fd4ec5); // Broadcasts the capability to handle + packDWord(&packet, 0x9d51a647); // Xtraz + packDWord(&packet, 0x4e34f5a0); + } +#endif + if (m_bAvatarsEnabled) + { + packShortCapability(&packet, 0x134C); // CAP_DEVILS + } +#ifdef DBG_OSCARFT + { + packShortCapability(&packet, 0x1343); // CAP_AIM_FILE + } // Broadcasts the capability to receive Oscar File Transfers +#endif + if (m_bAimEnabled) + { + packShortCapability(&packet, 0x134D); // CAP_AIM_COMPATIBLE + } // Tells the server we can speak to AIM +#ifdef DBG_AIMCONTACTSEND + { + packShortCapability(&packet, 0x134B); // CAP_SENDBUDDYLIST + } +#endif + if (m_bXStatusEnabled && bXStatus != 0) + { + packBuffer(&packet, capXStatus[bXStatus-1], BINARY_CAP_SIZE); + } + + packShortCapability(&packet, 0x1344); // CAP_ICQDIRECT + +#ifdef DBG_CAPHTML + packShortCapability(&packet, 0x0002); // CAP_HTMLMSGS +#endif + + packDWord(&packet, 0x4D697261); // Miranda Signature + packDWord(&packet, 0x6E64614E); + + int v[4] = { MIRANDA_VERSION_FILEVERSION }; + packWord(&packet, v[0]); + packWord(&packet, v[1]); + packWord(&packet, v[2]); + packWord(&packet, v[3]); + + //MIM/PackName + if ( bHasPackName ) { + packBuffer(&packet, (BYTE*)dbv.pszVal, 0x10); + ICQFreeVariant(&dbv); + } + + if(!CustomCapList.empty()) + { + for(std::list::iterator it = CustomCapList.begin(), end = CustomCapList.end(); it != end; ++it) + packBuffer(&packet, (BYTE*)(*it)->caps, 0x10); + } + + sendServPacket(&packet); +} + + +void CIcqProto::handleServUINSettings(int nPort, serverthread_info *info) +{ + icq_packet packet; + + setUserInfo(); + + /* SNAC 3,4: Tell server who's on our list (deprecated) */ + /* SNAC 3,15: Try to add unauthorised contacts to temporary list */ + sendEntireListServ(ICQ_BUDDY_FAMILY, ICQ_USER_ADDTOTEMPLIST, BUL_ALLCONTACTS); + + if (m_iDesiredStatus == ID_STATUS_INVISIBLE) + { + /* Tell server who's on our visible list (deprecated) */ + if (!m_bSsiEnabled) + sendEntireListServ(ICQ_BOS_FAMILY, ICQ_CLI_ADDVISIBLE, BUL_VISIBLE); + else + updateServVisibilityCode(3); + } + + if (m_iDesiredStatus != ID_STATUS_INVISIBLE) + { + /* Tell server who's on our invisible list (deprecated) */ + if (!m_bSsiEnabled) + sendEntireListServ(ICQ_BOS_FAMILY, ICQ_CLI_ADDINVISIBLE, BUL_INVISIBLE); + else + updateServVisibilityCode(4); + } + + // SNAC 1,1E: Set status + { + DWORD dwDirectCookie = rand() ^ (rand() << 16); + + // Get status + WORD wStatus = MirandaStatusToIcq(m_iDesiredStatus); + + // Get status note & mood + char *szStatusNote = PrepareStatusNote(m_iDesiredStatus); + BYTE bXStatus = getContactXStatus(NULL); + char szMoodData[32]; + + // prepare mood id + if (m_bMoodsEnabled && bXStatus && moodXStatus[bXStatus-1] != -1) + null_snprintf(szMoodData, SIZEOF(szMoodData), "icqmood%d", moodXStatus[bXStatus-1]); + else + szMoodData[0] = '\0'; + + //! Tricky code, this ensures that the status note will be saved to the directory + SetStatusNote(szStatusNote, m_bGatewayMode ? 5000 : 2500, TRUE); + + WORD wStatusNoteLen = strlennull(szStatusNote); + WORD wStatusMoodLen = strlennull(szMoodData); + WORD wSessionDataLen = (wStatusNoteLen ? wStatusNoteLen + 4 : 0) + 4 + wStatusMoodLen + 4; + + serverPacketInit(&packet, (WORD)(71 + (wSessionDataLen ? wSessionDataLen + 4 : 0))); + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_SET_STATUS); + packDWord(&packet, 0x00060004); // TLV 6: Status mode and security flags + packWord(&packet, GetMyStatusFlags()); // Status flags + packWord(&packet, wStatus); // Status + packTLVWord(&packet, 0x0008, 0x0A06); // TLV 8: Independent Status Messages + packDWord(&packet, 0x000c0025); // TLV C: Direct connection info + packDWord(&packet, getSettingDword(NULL, "RealIP", 0)); + packDWord(&packet, nPort); + packByte(&packet, DC_TYPE); // TCP/FLAG firewall settings + packWord(&packet, ICQ_VERSION); + packDWord(&packet, dwDirectCookie); // DC Cookie + packDWord(&packet, WEBFRONTPORT); // Web front port + packDWord(&packet, CLIENTFEATURES); // Client features + packDWord(&packet, 0x7fffffff); // Abused timestamp + packDWord(&packet, ICQ_PLUG_VERSION); // Abused timestamp + if (ServiceExists("SecureIM/IsContactSecured")) + packDWord(&packet, 0x5AFEC0DE); // SecureIM Abuse + else + packDWord(&packet, 0x00000000); // Timestamp + packWord(&packet, 0x0000); // Unknown + packTLVWord(&packet, 0x001F, 0x0000); + + if (wSessionDataLen) + { // Pack session data + packWord(&packet, 0x1D); // TLV 1D + packWord(&packet, wSessionDataLen); // TLV length + packWord(&packet, 0x02); // Item Type + if (wStatusNoteLen) + { + packWord(&packet, 0x400 | (WORD)(wStatusNoteLen + 4)); // Flags + Item Length + packWord(&packet, wStatusNoteLen); // Text Length + packBuffer(&packet, (LPBYTE)szStatusNote, wStatusNoteLen); + packWord(&packet, 0); // Encoding not specified (utf-8 is default) + } + else + packWord(&packet, 0); // Flags + Item Length + packWord(&packet, 0x0E); // Item Type + packWord(&packet, wStatusMoodLen); // Flags + Item Length + if (wStatusMoodLen) + packBuffer(&packet, (LPBYTE)szMoodData, wStatusMoodLen); // Mood + + // Save current status note & mood + setSettingStringUtf(NULL, DBSETTING_STATUS_NOTE, szStatusNote); + setSettingString(NULL, DBSETTING_STATUS_MOOD, szMoodData); + } + // Release memory + SAFE_FREE(&szStatusNote); + + sendServPacket(&packet); + } + + /* SNAC 1,11 */ + serverPacketInit(&packet, 14); + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_SET_IDLE); + packDWord(&packet, 0x00000000); + + sendServPacket(&packet); + m_bIdleAllow = 0; + + // Change status + SetCurrentStatus(m_iDesiredStatus); + + // Finish Login sequence + serverPacketInit(&packet, 98); + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_READY); + packDWord(&packet, 0x00220001); // imitate ICQ 6 behaviour + packDWord(&packet, 0x0110164f); + packDWord(&packet, 0x00010004); + packDWord(&packet, 0x0110164f); + packDWord(&packet, 0x00130004); + packDWord(&packet, 0x0110164f); + packDWord(&packet, 0x00020001); + packDWord(&packet, 0x0110164f); + packDWord(&packet, 0x00030001); + packDWord(&packet, 0x0110164f); + packDWord(&packet, 0x00150001); + packDWord(&packet, 0x0110164f); + packDWord(&packet, 0x00040001); + packDWord(&packet, 0x0110164f); + packDWord(&packet, 0x00060001); + packDWord(&packet, 0x0110164f); + packDWord(&packet, 0x00090001); + packDWord(&packet, 0x0110164f); + packDWord(&packet, 0x000A0001); + packDWord(&packet, 0x0110164f); + packDWord(&packet, 0x000B0001); + packDWord(&packet, 0x0110164f); + + sendServPacket(&packet); + + NetLog_Server(" *** Yeehah, login sequence complete"); + + // login sequence is complete enter logged-in mode + info->bLoggedIn = 1; + m_bConnectionLost = FALSE; + + // enable auto info-update routine + icq_EnableUserLookup(TRUE); + + if (!info->isMigrating) + { /* Get Offline Messages Reqeust */ + cookie_offline_messages *ack = (cookie_offline_messages*)SAFE_MALLOC(sizeof(cookie_offline_messages)); + if (ack) + { + DWORD dwCookie = AllocateCookie(CKT_OFFLINEMESSAGE, ICQ_MSG_CLI_REQ_OFFLINE, 0, ack); + + serverPacketInit(&packet, 10); + packFNACHeader(&packet, ICQ_MSG_FAMILY, ICQ_MSG_CLI_REQ_OFFLINE, 0, dwCookie); + + sendServPacket(&packet); + } + else + icq_LogMessage(LOG_WARNING, LPGEN("Failed to request offline messages. They may be received next time you log in.")); + + // Update our information from the server + sendOwnerInfoRequest(); + + // Request info updates on all contacts + icq_RescanInfoUpdate(); + + // Start sending Keep-Alive packets + StartKeepAlive(info); + + if (m_bAvatarsEnabled) + { // Send SNAC 1,4 - request avatar family 0x10 connection + icq_requestnewfamily(ICQ_AVATAR_FAMILY, &CIcqProto::StartAvatarThread); + + m_avatarsConnectionPending = TRUE; + NetLog_Server("Requesting Avatar family entry point."); + } + } + info->isMigrating = 0; + + if (m_bAimEnabled) + { + char **szAwayMsg = NULL; + icq_lock l(m_modeMsgsMutex); + + szAwayMsg = MirandaStatusToAwayMsg(m_iStatus); + if (szAwayMsg) + icq_sendSetAimAwayMsgServ(*szAwayMsg); + } +} diff --git a/protocols/IcqOscarJ/src/fam_02location.cpp b/protocols/IcqOscarJ/src/fam_02location.cpp new file mode 100644 index 0000000000..2398cf18c6 --- /dev/null +++ b/protocols/IcqOscarJ/src/fam_02location.cpp @@ -0,0 +1,312 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Handles packets from Location family +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +extern const char* cliSpamBot; + +void CIcqProto::handleLocationFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader) +{ + switch (pSnacHeader->wSubtype) { + + case ICQ_LOCATION_RIGHTS_REPLY: // Reply to CLI_REQLOCATION + NetLog_Server("Server sent SNAC(x02,x03) - SRV_LOCATION_RIGHTS_REPLY"); + break; + + case ICQ_LOCATION_USR_INFO_REPLY: // AIM user info reply + handleLocationUserInfoReply(pBuffer, wBufferLength, pSnacHeader->dwRef); + break; + + case ICQ_ERROR: + { + WORD wError; + HANDLE hCookieContact; + cookie_fam15_data *pCookieData; + + + if (wBufferLength >= 2) + unpackWord(&pBuffer, &wError); + else + wError = 0; + + if (wError == 4) + { + if (FindCookie(pSnacHeader->dwRef, &hCookieContact, (void**)&pCookieData) && !getContactUin(hCookieContact) && pCookieData->bRequestType == REQUESTTYPE_PROFILE) + { + BroadcastAck(hCookieContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0); + + ReleaseCookie(pSnacHeader->dwRef); + } + } + + LogFamilyError(ICQ_LOCATION_FAMILY, wError); + break; + } + + default: + NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_LOCATION_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + } +} + +static char* AimApplyEncoding(char* pszStr, const char* pszEncoding) +{ // decode encoding to ANSI only + if (pszStr && pszEncoding) + { + const char *szEnc = strstrnull(pszEncoding, "charset="); + + if (szEnc) + { // decode custom encoding to Utf-8 + char *szStr = ApplyEncoding(pszStr, szEnc + 9); + // decode utf-8 to ansi + char *szRes = NULL; + + SAFE_FREE((void**)&pszStr); + utf8_decode(szStr, &szRes); + SAFE_FREE((void**)&szStr); + + return szRes; + } + } + return pszStr; +} + +void CIcqProto::handleLocationUserInfoReply(BYTE* buf, WORD wLen, DWORD dwCookie) +{ + HANDLE hContact; + DWORD dwUIN; + uid_str szUID; + WORD wTLVCount; + WORD wWarningLevel; + HANDLE hCookieContact; + WORD status; + cookie_message_data *pCookieData; + + // Unpack the sender's user ID + if (!unpackUID(&buf, &wLen, &dwUIN, &szUID)) return; + + // Syntax check + if (wLen < 4) + return; + + // Warning level? + unpackWord(&buf, &wWarningLevel); + wLen -= 2; + + // TLV count + unpackWord(&buf, &wTLVCount); + wLen -= 2; + + // Determine contact + hContact = HContactFromUID(dwUIN, szUID, NULL); + + // Ignore away status if the user is not already on our list + if (hContact == INVALID_HANDLE_VALUE) + { +#ifdef _DEBUG + NetLog_Server("Ignoring away reply (%s)", strUID(dwUIN, szUID)); +#endif + return; + } + + if (!FindCookie(dwCookie, &hCookieContact, (void**)&pCookieData)) + { + NetLog_Server("Error: Received unexpected away reply from %s", strUID(dwUIN, szUID)); + return; + } + + if (hContact != hCookieContact) + { + NetLog_Server("Error: Away reply Contact does not match Cookie Contact(0x%x != 0x%x)", hContact, hCookieContact); + + ReleaseCookie(dwCookie); // This could be a bad idea, but I think it is safe + return; + } + + switch (GetCookieType(dwCookie)) + { + case CKT_FAMILYSPECIAL: + { + ReleaseCookie(dwCookie); + + // Read user info TLVs + { + oscar_tlv_chain* pChain; + BYTE *tmp; + char *szMsg = NULL; + + // Syntax check + if (wLen < 4) + return; + + tmp = buf; + // Get general chain + if (!(pChain = readIntoTLVChain(&buf, wLen, wTLVCount))) + return; + + disposeChain(&pChain); + + wLen -= (buf - tmp); + + // Get extra chain + if (pChain = readIntoTLVChain(&buf, wLen, 2)) + { + oscar_tlv *pTLV; + char *szEncoding = NULL; + + // Get Profile encoding TLV + + pTLV = pChain->getTLV(0x05, 1); + if (pTLV && (pTLV->wLen > 0)) + { + // store client capabilities + BYTE* capBuf = pTLV->pData; + WORD capLen = pTLV->wLen; + DBCONTACTWRITESETTING dbcws; + dbcws.value.type = DBVT_BLOB; + dbcws.value.cpbVal = capLen; + dbcws.value.pbVal = capBuf; + dbcws.szModule = m_szModuleName; + dbcws.szSetting = "CapBuf"; + CallService(MS_DB_CONTACT_WRITESETTING, (WPARAM)hContact, (LPARAM)&dbcws); + } + else + deleteSetting(hContact, "CapBuf"); + + pTLV = pChain->getTLV(0x01, 1); + if (pTLV && (pTLV->wLen >= 1)) + { + szEncoding = (char*)_alloca(pTLV->wLen + 1); + memcpy(szEncoding, pTLV->pData, pTLV->wLen); + szEncoding[pTLV->wLen] = '\0'; + } + // Get Profile info TLV + pTLV = pChain->getTLV(0x02, 1); + if (pTLV && (pTLV->wLen >= 1)) + { + szMsg = (char*)SAFE_MALLOC(pTLV->wLen + 2); + memcpy(szMsg, pTLV->pData, pTLV->wLen); + szMsg[pTLV->wLen] = '\0'; + szMsg[pTLV->wLen + 1] = '\0'; + szMsg = AimApplyEncoding(szMsg, szEncoding); + szMsg = EliminateHtml(szMsg, pTLV->wLen); + } + // Free TLV chain + disposeChain(&pChain); + } + + setSettingString(hContact, "About", szMsg); + BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, (HANDLE)1 ,0); + + SAFE_FREE((void**)&szMsg); + } + break; + } + + default: // away message + { + status = AwayMsgTypeToStatus(pCookieData->nAckType); + if (status == ID_STATUS_OFFLINE) + { + NetLog_Server("SNAC(2.6) Ignoring unknown status message from %s", strUID(dwUIN, szUID)); + + ReleaseCookie(dwCookie); + return; + } + + ReleaseCookie(dwCookie); + + // Read user info TLVs + { + oscar_tlv_chain* pChain; + oscar_tlv* pTLV; + BYTE *tmp; + char *szMsg = NULL; + CCSDATA ccs; + PROTORECVEVENT pre; + + // Syntax check + if (wLen < 4) + return; + + tmp = buf; + // Get general chain + if (!(pChain = readIntoTLVChain(&buf, wLen, wTLVCount))) + return; + + disposeChain(&pChain); + + wLen -= (buf - tmp); + + // Get extra chain + if (pChain = readIntoTLVChain(&buf, wLen, 2)) + { + char* szEncoding = NULL; + + // Get Away encoding TLV + pTLV = pChain->getTLV(0x03, 1); + if (pTLV && (pTLV->wLen >= 1)) + { + szEncoding = (char*)_alloca(pTLV->wLen + 1); + memcpy(szEncoding, pTLV->pData, pTLV->wLen); + szEncoding[pTLV->wLen] = '\0'; + } + // Get Away info TLV + pTLV = pChain->getTLV(0x04, 1); + if (pTLV && (pTLV->wLen >= 1)) + { + szMsg = (char*)SAFE_MALLOC(pTLV->wLen + 2); + memcpy(szMsg, pTLV->pData, pTLV->wLen); + szMsg[pTLV->wLen] = '\0'; + szMsg[pTLV->wLen + 1] = '\0'; + szMsg = AimApplyEncoding(szMsg, szEncoding); + szMsg = EliminateHtml(szMsg, pTLV->wLen); + } + // Free TLV chain + disposeChain(&pChain); + } + + ccs.szProtoService = PSR_AWAYMSG; + ccs.hContact = hContact; + ccs.wParam = status; + ccs.lParam = (LPARAM)⪯ + pre.flags = 0; + pre.szMessage = szMsg?szMsg:(char *)""; + pre.timestamp = time(NULL); + pre.lParam = dwCookie; + + CallService(MS_PROTO_CHAINRECV,0,(LPARAM)&ccs); + + SAFE_FREE((void**)&szMsg); + } + break; + } + } +} diff --git a/protocols/IcqOscarJ/src/fam_03buddy.cpp b/protocols/IcqOscarJ/src/fam_03buddy.cpp new file mode 100644 index 0000000000..caa41d966c --- /dev/null +++ b/protocols/IcqOscarJ/src/fam_03buddy.cpp @@ -0,0 +1,787 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Handles packets from Buddy family +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +extern const char* cliSpamBot; + +void CIcqProto::handleBuddyFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader, serverthread_info *info) +{ + switch (pSnacHeader->wSubtype) + { + case ICQ_USER_ONLINE: + handleUserOnline(pBuffer, wBufferLength, info); + break; + + case ICQ_USER_OFFLINE: + handleUserOffline(pBuffer, wBufferLength); + break; + + case ICQ_USER_SRV_REPLYBUDDY: + handleReplyBuddy(pBuffer, wBufferLength); + break; + + case ICQ_USER_NOTIFY_REJECTED: + handleNotifyRejected(pBuffer, wBufferLength); + break; + + case ICQ_ERROR: + { + WORD wError; + + if (wBufferLength >= 2) + unpackWord(&pBuffer, &wError); + else + wError = 0; + + LogFamilyError(ICQ_BUDDY_FAMILY, wError); + break; + } + + default: + NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_BUDDY_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + } +} + + +void CIcqProto::handleReplyBuddy(BYTE *buf, WORD wPackLen) +{ + oscar_tlv_chain *pChain = readIntoTLVChain(&buf, wPackLen, 0); + + if (pChain) + { + DWORD wMaxUins = pChain->getWord(1, 1); + DWORD wMaxWatchers = pChain->getWord(2, 1); + DWORD wMaxTemporary = pChain->getWord(4, 1); + + NetLog_Server("MaxUINs %u", wMaxUins); + NetLog_Server("MaxWatchers %u", wMaxWatchers); + NetLog_Server("MaxTemporary %u", wMaxTemporary); + + disposeChain(&pChain); + } + else + { + NetLog_Server("Error: Malformed BuddyReply"); + } +} + + +int unpackSessionDataItem(oscar_tlv_chain *pChain, WORD wItemType, BYTE **ppItemData, WORD *pwItemSize, BYTE *pbItemFlags) +{ + oscar_tlv *tlv = pChain->getTLV(0x1D, 1); + int len = 0; + BYTE *data; + + if (tlv) + { + len = tlv->wLen; + data = tlv->pData; + } + + while (len >= 4) + { // parse session data items one by one + WORD itemType; + BYTE itemFlags; + BYTE itemLen; + + unpackWord(&data, &itemType); + unpackByte(&data, &itemFlags); + unpackByte(&data, &itemLen); + len -= 4; + + // just some validity check + if (itemLen > len) + itemLen = len; + + if (itemType == wItemType) + { // found the requested item + if (ppItemData) + *ppItemData = data; + if (pwItemSize) + *pwItemSize = itemLen; + if (pbItemFlags) + *pbItemFlags = itemFlags; + + return 1; // Success + } + data += itemLen; + len -= itemLen; + } + return 0; +} + + +// TLV(1) User class +// TLV(3) Signon time +// TLV(4) Idle time (in minutes) +// TLV(5) Member since +// TLV(6) New status +// TLV(8) Status Capabilities +// TLV(A) External IP +// TLV(C) DC Info +// TLV(D) Capabilities +// TLV(F) Session timer (in seconds) +// TLV(14) Instance number (AIM only) +// TLV(19) Short capabilities +// TLV(1D) Session Data (Avatar, Mood, etc.) +// TLV(1F) User class (upper bytes) +// TLV(26) AIM Profile update time +// TLV(27) AIM Away message update time +// TLV(29) Away status since +// TLV(2B) URL to protocol icon +// TLV(2F) unknown key +// TLV(30) unknown timestamp + +void CIcqProto::handleUserOnline(BYTE *buf, WORD wLen, serverthread_info *info) +{ + DWORD dwPort = 0; + DWORD dwRealIP = 0; + DWORD dwUIN; + uid_str szUID; + DWORD dwDirectConnCookie = 0; + DWORD dwWebPort = 0; + DWORD dwFT1 = 0, dwFT2 = 0, dwFT3 = 0; + const char *szClient = NULL; + BYTE bClientId = 0; + WORD wVersion = 0; + WORD wTLVCount; + WORD wWarningLevel; + WORD wStatusFlags; + WORD wStatus = 0, wOldStatus = 0; + BYTE nTCPFlag = 0; + char szStrBuf[MAX_PATH]; + + // Unpack the sender's user ID + if (!unpackUID(&buf, &wLen, &dwUIN, &szUID)) return; + + // Syntax check + if (wLen < 4) + return; + + // Warning level? + unpackWord(&buf, &wWarningLevel); + wLen -= 2; + + // TLV count + unpackWord(&buf, &wTLVCount); + wLen -= 2; + + // notify that the set status note & mood process is finished + if (m_hNotifyNameInfoEvent) + SetEvent(m_hNotifyNameInfoEvent); + + // Ignore status notification if the user is not already on our list + HANDLE hContact = HContactFromUID(dwUIN, szUID, NULL); + if (hContact == INVALID_HANDLE_VALUE) + { +#ifdef _DEBUG + NetLog_Server("Ignoring user online (%s)", strUID(dwUIN, szUID)); +#endif + return; + } + + // Read user info TLVs + oscar_tlv_chain *pChain; + oscar_tlv *pTLV; + + // Syntax check + if (wLen < 4) + return; + + // Get chain + if (!(pChain = readIntoTLVChain(&buf, wLen, wTLVCount))) + return; + + // Get Class word + WORD wClass = pChain->getWord(0x01, 1); + int nIsICQ = wClass & CLASS_ICQ; + + if (dwUIN) + { + // Get DC info TLV + pTLV = pChain->getTLV(0x0C, 1); + if (pTLV && (pTLV->wLen >= 15)) + { + BYTE *pBuffer = pTLV->pData; + + nIsICQ = TRUE; + + unpackDWord(&pBuffer, &dwRealIP); + unpackDWord(&pBuffer, &dwPort); + unpackByte(&pBuffer, &nTCPFlag); + unpackWord(&pBuffer, &wVersion); + unpackDWord(&pBuffer, &dwDirectConnCookie); + unpackDWord(&pBuffer, &dwWebPort); // Web front port + pBuffer += 4; // Client features + + // Get faked time signatures, used to identify clients + if (pTLV->wLen >= 0x23) + { + unpackDWord(&pBuffer, &dwFT1); + unpackDWord(&pBuffer, &dwFT2); + unpackDWord(&pBuffer, &dwFT3); + } + } + else + { + // This client doesnt want DCs + } + + // Get Status info TLV + pTLV = pChain->getTLV(0x06, 1); + if (pTLV && (pTLV->wLen >= 4)) + { + BYTE *pBuffer = pTLV->pData; + + unpackWord(&pBuffer, &wStatusFlags); + unpackWord(&pBuffer, &wStatus); + } + else if (!nIsICQ) + { + // Connected thru AIM client, guess by user class + if (wClass & CLASS_AWAY) + wStatus = ID_STATUS_AWAY; + else if (wClass & CLASS_WIRELESS) + wStatus = ID_STATUS_ONTHEPHONE; + else + wStatus = ID_STATUS_ONLINE; + + wStatusFlags = 0; + } + else + { + // Huh? No status TLV? Lets guess then... + wStatusFlags = 0; + wStatus = ICQ_STATUS_ONLINE; + } + } + else + { + nIsICQ = FALSE; + + if (wClass & CLASS_AWAY) + wStatus = ID_STATUS_AWAY; + else if (wClass & CLASS_WIRELESS) + wStatus = ID_STATUS_ONTHEPHONE; + else + wStatus = ID_STATUS_ONLINE; + + wStatusFlags = 0; + } + +#ifdef _DEBUG + NetLog_Server("Flags are %x", wStatusFlags); + NetLog_Server("Status is %x", wStatus); +#endif + + // Get IP TLV + DWORD dwIP = pChain->getDWord(0x0A, 1); + + // Get Online Since TLV + DWORD dwOnlineSince = pChain->getDWord(0x03, 1); + + // Get Away Since TLV + DWORD dwAwaySince = pChain->getDWord(0x29, 1); + + // Get Member Since TLV + DWORD dwMemberSince = pChain->getDWord(0x05, 1); + + // Get Idle timer TLV + WORD wIdleTimer = pChain->getWord(0x04, 1); + time_t tIdleTS = 0; + if (wIdleTimer) + { + time(&tIdleTS); + tIdleTS -= (wIdleTimer*60); + }; + +#ifdef _DEBUG + if (wIdleTimer) + NetLog_Server("Idle timer is %u.", wIdleTimer); + NetLog_Server("Online since %s", time2text(dwOnlineSince)); + if (dwAwaySince) + NetLog_Server("Status was set on %s", time2text(dwAwaySince)); +#endif + + // Check client capabilities + if (hContact != NULL) + { + wOldStatus = getContactStatus(hContact); + + // Collect all Capability info from TLV chain + BYTE *capBuf = NULL; + WORD capLen = 0; + + // Get Location Capability Info TLVs + oscar_tlv *pFullTLV = pChain->getTLV(0x0D, 1); + oscar_tlv *pShortTLV = pChain->getTLV(0x19, 1); + + if (pFullTLV && (pFullTLV->wLen >= BINARY_CAP_SIZE)) + capLen += pFullTLV->wLen; + + if (pShortTLV && (pShortTLV->wLen >= 2)) + capLen += (pShortTLV->wLen * 8); + + capBuf = (BYTE*)_alloca(capLen + BINARY_CAP_SIZE); + + if (capLen) + { + BYTE *pCapability = capBuf; + + capLen = 0; // we need to recount that + + if (pFullTLV && (pFullTLV->wLen >= BINARY_CAP_SIZE)) + { // copy classic Capabilities + BYTE *cData = pFullTLV->pData; + int cLen = pFullTLV->wLen; + + while (cLen) + { // be impervious to duplicates (AOL sends them sometimes) + if (!capLen || !MatchCapability(capBuf, capLen, (capstr*)cData, BINARY_CAP_SIZE)) + { // not present, add + memcpy(pCapability, cData, BINARY_CAP_SIZE); + capLen += BINARY_CAP_SIZE; + pCapability += BINARY_CAP_SIZE; + } + cData += BINARY_CAP_SIZE; + cLen -= BINARY_CAP_SIZE; + } + } + + if (pShortTLV && (pShortTLV->wLen >= 2)) + { // copy short Capabilities + capstr tmp; + BYTE *cData = pShortTLV->pData; + int cLen = pShortTLV->wLen; + + memcpy(tmp, capShortCaps, BINARY_CAP_SIZE); + while (cLen) + { // be impervious to duplicates (AOL sends them sometimes) + tmp[2] = cData[0]; + tmp[3] = cData[1]; + + if (!capLen || !MatchCapability(capBuf, capLen, &tmp, BINARY_CAP_SIZE)) + { // not present, add + memcpy(pCapability, tmp, BINARY_CAP_SIZE); + capLen += BINARY_CAP_SIZE; + pCapability += BINARY_CAP_SIZE; + } + cData += 2; + cLen -= 2; + } + } +#ifdef _DEBUG + NetLog_Server("Detected %d capability items.", capLen / BINARY_CAP_SIZE); +#endif + } + + if (capLen) + { // Update the contact's capabilies if present in packet + SetCapabilitiesFromBuffer(hContact, capBuf, capLen, wOldStatus == ID_STATUS_OFFLINE); + + char *szCurrentClient = wOldStatus == ID_STATUS_OFFLINE ? NULL : getSettingStringUtf(hContact, "MirVer", NULL); + + szClient = detectUserClient(hContact, nIsICQ, wClass, dwOnlineSince, szCurrentClient, wVersion, dwFT1, dwFT2, dwFT3, nTCPFlag, dwDirectConnCookie, dwWebPort, capBuf, capLen, &bClientId, szStrBuf); + // Check if the client changed, if not do not change + if (szCurrentClient && !strcmpnull(szCurrentClient, szClient)) + szClient = (const char*)-1; + SAFE_FREE(&szCurrentClient); + } + else if (wOldStatus == ID_STATUS_OFFLINE) + { + // Remove the contact's capabilities if coming from offline + ClearAllContactCapabilities(hContact); + + // no capability + NetLog_Server("No capability info TLVs"); + + szClient = detectUserClient(hContact, nIsICQ, wClass, dwOnlineSince, NULL, wVersion, dwFT1, dwFT2, dwFT3, nTCPFlag, dwDirectConnCookie, dwWebPort, NULL, capLen, &bClientId, szStrBuf); + } + else + { + // Capabilities not present in update packet, do not touch + szClient = (const char*)-1; // we don't want to client be overwritten + } + + // handle Xtraz status + char *moodData = NULL; + WORD moodSize = 0; + + unpackSessionDataItem(pChain, 0x0E, (BYTE**)&moodData, &moodSize, NULL); + if (capLen || wOldStatus == ID_STATUS_OFFLINE) + handleXStatusCaps(dwUIN, szUID, hContact, capBuf, capLen, moodData, moodSize); + else + handleXStatusCaps(dwUIN, szUID, hContact, NULL, 0, moodData, moodSize); + + // Determine support for extended status messages + if (pChain->getWord(0x08, 1) == 0x0A06) + SetContactCapabilities(hContact, CAPF_STATUS_MESSAGES); + else if (wOldStatus == ID_STATUS_OFFLINE) + ClearContactCapabilities(hContact, CAPF_STATUS_MESSAGES); + +#ifdef _DEBUG + if (wOldStatus == ID_STATUS_OFFLINE) + { + if (CheckContactCapabilities(hContact, CAPF_SRV_RELAY)) + NetLog_Server("Supports advanced messages"); + else + NetLog_Server("Does NOT support advanced messages"); + } +#endif + + if (!nIsICQ) + { + // AIM clients does not advertise these, but do support them + SetContactCapabilities(hContact, CAPF_UTF | CAPF_TYPING); + // Server relayed messages are only supported by ICQ clients + ClearContactCapabilities(hContact, CAPF_SRV_RELAY); + + if (dwUIN && wOldStatus == ID_STATUS_OFFLINE) + NetLog_Server("Logged in with AIM client"); + } + + if (nIsICQ && wVersion < 8) + { + ClearContactCapabilities(hContact, CAPF_SRV_RELAY); + if (wOldStatus == ID_STATUS_OFFLINE) + NetLog_Server("Forcing simple messages due to compability issues"); + } + + // Process Avatar Hash + pTLV = pChain->getTLV(0x1D, 1); + if (pTLV) + handleAvatarContactHash(dwUIN, szUID, hContact, pTLV->pData, pTLV->wLen, wOldStatus); + else + handleAvatarContactHash(dwUIN, szUID, hContact, NULL, 0, wOldStatus); + + // Process Status Note + parseStatusNote(dwUIN, szUID, hContact, pChain); + } + // Free TLV chain + disposeChain(&pChain); + + // Save contacts details in database + if (hContact != NULL) + { + setSettingDword(hContact, "LogonTS", dwOnlineSince); + setSettingDword(hContact, "AwayTS", dwAwaySince); + setSettingDword(hContact, "IdleTS", tIdleTS); + + if (dwMemberSince) + setSettingDword(hContact, "MemberTS", dwMemberSince); + + if (nIsICQ) + { // on AIM these are not used + setSettingDword(hContact, "DirectCookie", dwDirectConnCookie); + setSettingByte(hContact, "DCType", (BYTE)nTCPFlag); + setSettingWord(hContact, "UserPort", (WORD)(dwPort & 0xffff)); + setSettingWord(hContact, "Version", wVersion); + } + else + { + deleteSetting(hContact, "DirectCookie"); + deleteSetting(hContact, "DCType"); + deleteSetting(hContact, "UserPort"); + deleteSetting(hContact, "Version"); + } + + if (!szClient) + { + // if no detection, set uknown + szClient = (nIsICQ ? "Unknown" : "Unknown AIM"); + } + if (szClient != (char*)-1) + { + setSettingStringUtf(hContact, "MirVer", szClient); + setSettingByte(hContact, "ClientID", bClientId); + } + + if (wOldStatus == ID_STATUS_OFFLINE) + { + setSettingDword(hContact, "IP", dwIP); + setSettingDword(hContact, "RealIP", dwRealIP); + } + else + { // if not first notification only write significant information + if (dwIP) + setSettingDword(hContact, "IP", dwIP); + if (dwRealIP) + setSettingDword(hContact, "RealIP", dwRealIP); + } + setSettingWord(hContact, "Status", (WORD)IcqStatusToMiranda(wStatus)); + + // Update info? + if (dwUIN) + { // check if the local copy of user details is up-to-date + if (IsMetaInfoChanged(hContact)) + icq_QueueUser(hContact); + } + } + + if (wOldStatus != IcqStatusToMiranda(wStatus)) + { // And a small log notice... if status was changed + if (nIsICQ) + NetLog_Server("%u changed status to %s (v%d).", dwUIN, MirandaStatusToString(IcqStatusToMiranda(wStatus)), wVersion); + else + NetLog_Server("%s changed status to %s.", strUID(dwUIN, szUID), MirandaStatusToString(IcqStatusToMiranda(wStatus))); + } +#ifdef _DEBUG + else + { + if (nIsICQ) + NetLog_Server("%u has status %s (v%d).", dwUIN, MirandaStatusToString(IcqStatusToMiranda(wStatus)), wVersion); + else + NetLog_Server("%s has status %s.", strUID(dwUIN, szUID), MirandaStatusToString(IcqStatusToMiranda(wStatus))); + } +#endif + + if (szClient == cliSpamBot) + { + if (getSettingByte(NULL, "KillSpambots", DEFAULT_KILLSPAM_ENABLED) && DBGetContactSettingByte(hContact, "CList", "NotOnList", 0)) + { // kill spammer + icq_DequeueUser(dwUIN); + icq_sendRemoveContact(dwUIN, NULL); + AddToSpammerList(dwUIN); + if (getSettingByte(NULL, "PopupsSpamEnabled", DEFAULT_SPAM_POPUPS_ENABLED)) + ShowPopUpMsg(hContact, LPGEN("Spambot Detected"), LPGEN("Contact deleted & further events blocked."), POPTYPE_SPAM); + CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0); + + NetLog_Server("Contact %u deleted", dwUIN); + } + } +} + +void CIcqProto::handleUserOffline(BYTE *buf, WORD wLen) +{ + DWORD dwUIN; + uid_str szUID; + + do { + oscar_tlv_chain *pChain = NULL; + WORD wTLVCount; + DWORD dwAwaySince; + + // Unpack the sender's user ID + if (!unpackUID(&buf, &wLen, &dwUIN, &szUID)) return; + + // Warning level? + buf += 2; + + // TLV Count + unpackWord(&buf, &wTLVCount); + wLen -= 4; + + // Skip the TLV chain + while (wTLVCount && wLen >= 4) + { + WORD wTLVType; + WORD wTLVLen; + + unpackWord(&buf, &wTLVType); + unpackWord(&buf, &wTLVLen); + wLen -= 4; + + // stop parsing overflowed packet + if (wTLVLen > wLen) + { + disposeChain(&pChain); + return; + } + + if (wTLVType == 0x1D) + { // read only TLV with Session data into chain + BYTE *pTLV = buf - 4; + disposeChain(&pChain); + pChain = readIntoTLVChain(&pTLV, wLen + 4, 1); + } + else if (wTLVType == 0x29 && wTLVLen == sizeof(DWORD)) + { // get Away Since value + BYTE *pData = buf; + unpackDWord(&pData, &dwAwaySince); + } + + buf += wTLVLen; + wLen -= wTLVLen; + wTLVCount--; + } + + // Determine contact + HANDLE hContact = HContactFromUID(dwUIN, szUID, NULL); + + // Skip contacts that are not already on our list or are already offline + if (hContact != INVALID_HANDLE_VALUE) + { + WORD wOldStatus = getContactStatus(hContact); + + // Process Avatar Hash + oscar_tlv *pAvatarTLV = pChain ? pChain->getTLV(0x1D, 1) : NULL; + if (pAvatarTLV) + handleAvatarContactHash(dwUIN, szUID, hContact, pAvatarTLV->pData, pAvatarTLV->wLen, wOldStatus); + else + handleAvatarContactHash(dwUIN, szUID, hContact, NULL, 0, wOldStatus); + + // Process Status Note (offline status note) + parseStatusNote(dwUIN, szUID, hContact, pChain); + + // Update status times + setSettingDword(hContact, "IdleTS", 0); + setSettingDword(hContact, "AwayTS", dwAwaySince); + + // Clear custom status & mood + char tmp = NULL; + handleXStatusCaps(dwUIN, szUID, hContact, (BYTE*)&tmp, 0, &tmp, 0); + + if (wOldStatus != ID_STATUS_OFFLINE) + { + NetLog_Server("%s went offline.", strUID(dwUIN, szUID)); + + setSettingWord(hContact, "Status", ID_STATUS_OFFLINE); + // close Direct Connections to that user + CloseContactDirectConns(hContact); + // Reset DC status + setSettingByte(hContact, "DCStatus", 0); + } +#ifdef _DEBUG + else + NetLog_Server("%s is offline.", strUID(dwUIN, szUID)); +#endif + } + + // Release memory + disposeChain(&pChain); + } + while (wLen >= 1); +} + + +void CIcqProto::parseStatusNote(DWORD dwUin, char *szUid, HANDLE hContact, oscar_tlv_chain *pChain) +{ + DWORD dwStatusNoteTS = time(NULL); + BYTE *pStatusNoteTS, *pStatusNote; + WORD wStatusNoteTSLen, wStatusNoteLen; + BYTE bStatusNoteFlags; + + if (unpackSessionDataItem(pChain, 0x0D, &pStatusNoteTS, &wStatusNoteTSLen, NULL) && wStatusNoteTSLen == sizeof(DWORD)) + unpackDWord(&pStatusNoteTS, &dwStatusNoteTS); + + // Get Status Note session item + if (unpackSessionDataItem(pChain, 0x02, &pStatusNote, &wStatusNoteLen, &bStatusNoteFlags)) + { + char *szStatusNote = NULL; + + if ((bStatusNoteFlags & 4) == 4 && wStatusNoteLen >= 4) + { + BYTE *buf = pStatusNote; + WORD buflen = wStatusNoteLen - 2; + WORD wTextLen; + + unpackWord(&buf, &wTextLen); + if (wTextLen > buflen) + wTextLen = buflen; + + if (wTextLen > 0) + { + szStatusNote = (char*)_alloca(wStatusNoteLen + 1); + unpackString(&buf, szStatusNote, wTextLen); + szStatusNote[wTextLen] = '\0'; + buflen -= wTextLen; + + WORD wEncodingType = 0; + char *szEncoding = NULL; + + if (buflen >= 2) + unpackWord(&buf, &wEncodingType); + + if (wEncodingType == 1 && buflen > 6) + { // Encoding specified + buf += 2; + buflen -= 2; + unpackWord(&buf, &wTextLen); + if (wTextLen > buflen) + wTextLen = buflen; + szEncoding = (char*)_alloca(wTextLen + 1); + unpackString(&buf, szEncoding, wTextLen); + szEncoding[wTextLen] = '\0'; + } + else if (UTF8_IsValid(szStatusNote)) + szEncoding = "utf-8"; + + szStatusNote = ApplyEncoding(szStatusNote, szEncoding); + } + } + // Check if the status note was changed + if (dwStatusNoteTS > getSettingDword(hContact, DBSETTING_STATUS_NOTE_TIME, 0)) + { + DBVARIANT dbv = {DBVT_DELETED}; + + if (strlennull(szStatusNote) || (!getSettingString(hContact, DBSETTING_STATUS_NOTE, &dbv) && (dbv.type == DBVT_ASCIIZ || dbv.type == DBVT_UTF8) && strlennull(dbv.pszVal))) + NetLog_Server("%s changed status note to \"%s\"", strUID(dwUin, szUid), szStatusNote ? szStatusNote : ""); + + ICQFreeVariant(&dbv); + + if (szStatusNote) + setSettingStringUtf(hContact, DBSETTING_STATUS_NOTE, szStatusNote); + else + deleteSetting(hContact, DBSETTING_STATUS_NOTE); + setSettingDword(hContact, DBSETTING_STATUS_NOTE_TIME, dwStatusNoteTS); + + if (getContactXStatus(hContact) != 0 || !CheckContactCapabilities(hContact, CAPF_STATUS_MESSAGES)) { + setStatusMsgVar(hContact, szStatusNote, false); + + TCHAR* tszNote = mir_utf8decodeT(szStatusNote); + BroadcastAck(hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, NULL, (LPARAM)tszNote); + mir_free(tszNote); + } + } + SAFE_FREE(&szStatusNote); + } + else + { + if (getContactStatus(hContact) == ID_STATUS_OFFLINE) + { + setStatusMsgVar(hContact, NULL, false); + deleteSetting(hContact, DBSETTING_STATUS_NOTE); + setSettingDword(hContact, DBSETTING_STATUS_NOTE_TIME, dwStatusNoteTS); + } + } +} + + +void CIcqProto::handleNotifyRejected(BYTE *buf, WORD wPackLen) +{ + DWORD dwUIN; + uid_str szUID; + + while (wPackLen) + if (unpackUID(&buf, &wPackLen, &dwUIN, &szUID)) + NetLog_Server("%s status notification rejected.", strUID(dwUIN, szUID)); +} diff --git a/protocols/IcqOscarJ/src/fam_04message.cpp b/protocols/IcqOscarJ/src/fam_04message.cpp new file mode 100644 index 0000000000..7d1bc6a829 --- /dev/null +++ b/protocols/IcqOscarJ/src/fam_04message.cpp @@ -0,0 +1,3043 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Handles packets from Family 4 ICBM Messages +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +void CIcqProto::handleMsgFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader) +{ + switch (pSnacHeader->wSubtype) { + + case ICQ_MSG_SRV_ERROR: // SNAC(4, 0x01) + handleRecvServMsgError(pBuffer, wBufferLength, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + + case ICQ_MSG_SRV_REPLYICBM: // SNAC(4, 0x05) SRV_REPLYICBM + handleReplyICBM(pBuffer, wBufferLength, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + + case ICQ_MSG_SRV_RECV: // SNAC(4, 0x07) + handleRecvServMsg(pBuffer, wBufferLength, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + + case ICQ_MSG_SRV_MISSED_MESSAGE: // SNAC(4, 0x0A) + handleMissedMsg(pBuffer, wBufferLength, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + + case ICQ_MSG_RESPONSE: // SNAC(4, 0x0B) + handleRecvMsgResponse(pBuffer, wBufferLength, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + + case ICQ_MSG_SRV_ACK: // SNAC(4, 0x0C) Server acknowledgements + handleServerAck(pBuffer, wBufferLength, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + + case ICQ_MSG_MTN: // SNAC(4, 0x14) Typing notifications + handleTypingNotification(pBuffer, wBufferLength, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + + case ICQ_MSG_SRV_OFFLINE_REPLY: // SNAC(4, 0x17) Offline Messages response + handleOffineMessagesReply(pBuffer, wBufferLength, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + + default: + NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_MSG_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + } +} + + +static void setMsgChannelParams(CIcqProto *ppro, WORD wChan, DWORD dwFlags) +{ + icq_packet packet; + + // Set message parameters for channel wChan (CLI_SET_ICBM_PARAMS) + serverPacketInit(&packet, 26); + packFNACHeader(&packet, ICQ_MSG_FAMILY, ICQ_MSG_CLI_SETPARAMS); + packWord(&packet, wChan); // Channel + packDWord(&packet, dwFlags); // Flags + packWord(&packet, MAX_MESSAGESNACSIZE); // Max message snac size + packWord(&packet, 0x03E7); // Max sender warning level + packWord(&packet, 0x03E7); // Max receiver warning level + packWord(&packet, CLIENTRATELIMIT); // Minimum message interval in seconds + packWord(&packet, 0x0000); // Unknown + ppro->sendServPacket(&packet); +} + + +void CIcqProto::handleReplyICBM(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef) +{ // we don't care about the stuff, just change the params + DWORD dwFlags = 0x00000303; + +#ifdef DBG_CAPHTML + dwFlags |= 0x00000400; +#endif +#ifdef DBG_CAPMTN + dwFlags |= 0x00000008; +#endif + // Set message parameters for all channels (imitate ICQ 6) + setMsgChannelParams(this, 0x0000, dwFlags); +} + + +void CIcqProto::handleRecvServMsg(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef) +{ + DWORD dwUin; + DWORD dwMsgID1; + DWORD dwMsgID2; + WORD wTLVCount; + WORD wMessageFormat; + uid_str szUID; + + if (wLen < 11) + { // just do some basic packet checking + NetLog_Server("Error: Malformed message thru server"); + return; + } + + // These two values are some kind of reference, we need to save + // them to send file request responses for example + unpackLEDWord(&buf, &dwMsgID1); // TODO: msg cookies should be main + wLen -= 4; + unpackLEDWord(&buf, &dwMsgID2); + wLen -= 4; + + // The message type used: + unpackWord(&buf, &wMessageFormat); // 0x0001: Simple message format + wLen -= 2; // 0x0002: Advanced message format + // 0x0004: 'New' message format + // Sender UIN + if (!unpackUID(&buf, &wLen, &dwUin, &szUID)) return; + + if (dwUin && IsOnSpammerList(dwUin)) + { + NetLog_Server("Ignored Message from known Spammer"); + return; + } + + if (wLen < 4) + { // just do some basic packet checking + NetLog_Server("Error: Malformed message thru server"); + return; + } + + // Warning level? + buf += 2; + wLen -= 2; + + // Number of following TLVs, until msg-format dependant TLVs + unpackWord(&buf, &wTLVCount); + wLen -= 2; + if (wTLVCount > 0) + { + // Save current buffer pointer so we can calculate + // how much data we have left after the chain read. + BYTE *pBufStart = buf; + oscar_tlv_chain *chain = readIntoTLVChain(&buf, wLen, wTLVCount); + + // This chain contains info that is filled in by the server. + // TLV(1): unknown + // TLV(2): date: on since + // TLV(3): date: on since + // TLV(4): unknown, usually 0000. Not in file-req or auto-msg-req + // TLV(6): sender's status + // TLV(F): a time in seconds, unknown + + disposeChain(&chain); + + // Update wLen + wLen -= buf - pBufStart; + } + + + // This is where the format specific data begins + + switch (wMessageFormat) { + + case 1: // Simple message format + handleRecvServMsgType1(buf, wLen, dwUin, szUID, dwMsgID1, dwMsgID2, dwRef); + break; + + case 2: // Encapsulated messages + handleRecvServMsgType2(buf, wLen, dwUin, szUID, dwMsgID1, dwMsgID2, dwRef); + break; + + case 4: // Typed messages + handleRecvServMsgType4(buf, wLen, dwUin, szUID, dwMsgID1, dwMsgID2, dwRef); + break; + + default: + NetLog_Server("Unknown format message thru server - Ref %u, Type: %u, UID: %s", dwRef, wMessageFormat, strUID(dwUin, szUID)); + break; + + } +} + + +char* CIcqProto::convertMsgToUserSpecificUtf(HANDLE hContact, const char *szMsg) +{ + WORD wCP = getSettingWord(hContact, "CodePage", m_wAnsiCodepage); + char *usMsg = NULL; + + if (wCP != CP_ACP) + usMsg = ansi_to_utf8_codepage(szMsg, wCP); + + return usMsg; +} + + +void CIcqProto::handleRecvServMsgType1(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, DWORD dwRef) +{ + WORD wTLVType; + WORD wTLVLen; + BYTE* pMsgTLV; + + if (wLen < 4) + { // just perform basic structure check + NetLog_Server("Message (format %u) - Ignoring empty message", 1); + return; + } + + // Unpack the first TLV(2) + unpackTypedTLV(buf, wLen, 2, &wTLVType, &wTLVLen, &pMsgTLV); + NetLog_Server("Message (format %u) - UID: %s", 1, strUID(dwUin, szUID)); + + // It must be TLV(2) + if (wTLVType == 2) + { + BYTE *pDataBuf = pMsgTLV; + oscar_tlv_chain *pChain = readIntoTLVChain(&pDataBuf, wTLVLen, 0); + + // TLV(2) contains yet another TLV chain with the following TLVs: + // TLV(1281): Capability + // TLV(257): This TLV contains the actual message (can be fragmented) + + if (pChain) + { + oscar_tlv* pMessageTLV; + oscar_tlv* pCapabilityTLV; + WORD wMsgPart = 1; + + // Find the capability TLV + pCapabilityTLV = pChain->getTLV(0x0501, 1); + if (pCapabilityTLV && (pCapabilityTLV->wLen > 0)) + { + WORD wDataLen; + BYTE *pDataBuf; + + wDataLen = pCapabilityTLV->wLen; + pDataBuf = pCapabilityTLV->pData; + + if (wDataLen > 0) + NetLog_Server("Message (format 1) - Message has %d caps.", wDataLen); + } + else + NetLog_Server("Message (format 1) - No message cap."); + + { // Parse the message parts, usually only one 0x0101 TLV containing the message, + // but in some cases there can be more 0x0101 TLVs containing message parts in + // different encodings (just like the new format of Offline Messages). + DWORD dwRecvTime; + char* szMsg = NULL; + CCSDATA ccs; + PROTORECVEVENT pre = {0}; + int bAdded; + + HANDLE hContact = HContactFromUID(dwUin, szUID, &bAdded); + + while (pMessageTLV = pChain->getTLV(0x0101, wMsgPart)) + { // Loop thru all message parts + if (pMessageTLV->wLen > 4) + { + WORD wMsgLen; + BYTE *pMsgBuf; + WORD wEncoding; + WORD wCodePage; + char *szMsgPart = NULL; + int bMsgPartUnicode = FALSE; + + // The message begins with a encoding specification + // The first WORD is believed to have the following meaning: + // 0x00: US-ASCII + // 0x02: Unicode UCS-2 Big Endian encoding + // 0x03: local 8bit encoding + pMsgBuf = pMessageTLV->pData; + unpackWord(&pMsgBuf, &wEncoding); + unpackWord(&pMsgBuf, &wCodePage); + + wMsgLen = pMessageTLV->wLen - 4; + NetLog_Server("Message (format 1) - Part %d: Encoding is 0x%X, page is 0x%X", wMsgPart, wEncoding, wCodePage); + + switch (wEncoding) { + + case 2: // UCS-2 + { + WCHAR* usMsgPart = (WCHAR*)SAFE_MALLOC(wMsgLen + 2); + + unpackWideString(&pMsgBuf, usMsgPart, wMsgLen); + usMsgPart[wMsgLen/sizeof(WCHAR)] = 0; + + szMsgPart = make_utf8_string(usMsgPart); + if (!IsUSASCII(szMsgPart, strlennull(szMsgPart))) + bMsgPartUnicode = TRUE; + SAFE_FREE(&usMsgPart); + + break; + } + + case 0: // us-ascii + case 3: // ANSI + default: + { + // Copy the message text into a new proper string. + szMsgPart = (char*)SAFE_MALLOC(wMsgLen + 1); + memcpy(szMsgPart, pMsgBuf, wMsgLen); + szMsgPart[wMsgLen] = '\0'; + + break; + } + } + // Check if the new part is compatible with the message + if (!pre.flags && bMsgPartUnicode) + { // make the resulting message utf-8 encoded - need to append utf-8 encoded part + if (szMsg) + { // not necessary to convert - appending first part, only set flags + char *szUtfMsg = ansi_to_utf8_codepage(szMsg, getSettingWord(hContact, "CodePage", m_wAnsiCodepage)); + + SAFE_FREE(&szMsg); + szMsg = szUtfMsg; + } + pre.flags = PREF_UTF; + } + if (!bMsgPartUnicode && pre.flags == PREF_UTF) + { // convert message part to utf-8 and append + char *szUtfPart = ansi_to_utf8_codepage((char*)szMsgPart, getSettingWord(hContact, "CodePage", m_wAnsiCodepage)); + + SAFE_FREE(&szMsgPart); + szMsgPart = szUtfPart; + } + // Append the new message part + szMsg = (char*)SAFE_REALLOC(szMsg, strlennull(szMsg) + strlennull(szMsgPart) + 1); + + strcat(szMsg, szMsgPart); + SAFE_FREE(&szMsgPart); + } + wMsgPart++; + } + if (strlennull(szMsg)) + { + if (_strnicmp(szMsg, "", 6) == 0) + { // strip HTML formating from AIM message + szMsg = EliminateHtml(szMsg, strlennull(szMsg)); + } + + if (!pre.flags && !IsUSASCII(szMsg, strlennull(szMsg))) + { // message is Ansi and contains national characters, create Unicode part by codepage + char *usMsg = convertMsgToUserSpecificUtf(hContact, szMsg); + if (usMsg) + { + SAFE_FREE(&szMsg); + szMsg = usMsg; + pre.flags = PREF_UTF; + } + } + + dwRecvTime = (DWORD)time(NULL); + + { // Check if the message was received as offline + cookie_offline_messages *cookie; + + if (!(dwRef & 0x80000000) && FindCookie(dwRef, NULL, (void**)&cookie)) + { + WORD wTimeTLVType, wTimeTLVLen; + BYTE *pTimeTLV; + + cookie->nMessages++; + + unpackTypedTLV(buf, wLen, 0x16, &wTimeTLVType, &wTimeTLVLen, &pTimeTLV); + if (pTimeTLV && wTimeTLVType == 0x16 && wTimeTLVLen == 4) + { // found Offline timestamp + BYTE *pBuf = pTimeTLV; + + unpackDWord(&pBuf, &dwRecvTime); + NetLog_Server("Message (format %u) - Offline timestamp is %s", 1, time2text(dwRecvTime)); + } + SAFE_FREE((void**)&pTimeTLV); + } + } + // Create and send the message event + ccs.szProtoService = PSR_MESSAGE; + ccs.hContact = hContact; + ccs.wParam = 0; + ccs.lParam = (LPARAM)⪯ + pre.timestamp = dwRecvTime; + pre.szMessage = (char *)szMsg; + CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs); + + NetLog_Server("Message (format 1) received"); + + // Save tick value + setSettingDword(ccs.hContact, "TickTS", time(NULL) - (dwMsgID1/1000)); + } + else + NetLog_Server("Message (format %u) - Ignoring empty message", 1); + + SAFE_FREE(&szMsg); + } + + // Free the chain memory + disposeChain(&pChain); + } + else + NetLog_Server("Failed to read TLV chain in message (format 1)"); + } + else + NetLog_Server("Unsupported TLV (%u) in message (format %u)", wTLVType, 1); + + SAFE_FREE((void**)&pMsgTLV); +} + + +void CIcqProto::handleRecvServMsgType2(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, DWORD dwRef) +{ + WORD wTLVType; + WORD wTLVLen; + BYTE *pDataBuf = NULL; + BYTE *pBuf; + + if (wLen < 4) + { + NetLog_Server("Message (format %u) - Ignoring empty message", 2); + return; + } + + // Unpack the first TLV(5) + unpackTypedTLV(buf, wLen, 5, &wTLVType, &wTLVLen, &pDataBuf); + NetLog_Server("Message (format %u) - UID: %s", 2, strUID(dwUin, szUID)); + pBuf = pDataBuf; + + // It must be TLV(5) + if (wTLVType == 5) + { + WORD wCommand; + oscar_tlv_chain* chain; + oscar_tlv* tlv; + DWORD q1,q2,q3,q4; + + if (wTLVLen < 26) + { // just check if all basic data is there + NetLog_Server("Message (format %u) - Ignoring empty message", 2); + SAFE_FREE((void**)&pBuf); + return; + } + + unpackWord(&pDataBuf, &wCommand); + wTLVLen -= 2; // Command 0x0000 - Normal message/file send request +#ifdef _DEBUG // 0x0001 - Abort request + NetLog_Server("Command is %u", wCommand); // 0x0002 - Acknowledge request +#endif + + // Some stuff we don't use + pDataBuf += 8; // dwID1 and dwID2 again + wTLVLen -= 8; + unpackDWord(&pDataBuf, &q1); + unpackDWord(&pDataBuf, &q2); + unpackDWord(&pDataBuf, &q3); + unpackDWord(&pDataBuf, &q4); // Message Capability + wTLVLen -= 16; + + if (CompareGUIDs(q1,q2,q3,q4, MCAP_SRV_RELAY_FMT)) + { // we surely have at least 4 bytes for TLV chain + HANDLE hContact = HContactFromUID(dwUin, szUID, NULL); + + if (wCommand == 1) + { + NetLog_Server("Cannot handle abort messages yet... :("); + SAFE_FREE((void**)&pBuf); + return; + } + + if (wTLVLen < 4) + { // just check if at least one tlv is there + NetLog_Server("Message (format %u) - Ignoring empty message", 2); + SAFE_FREE((void**)&pBuf); + return; + } + + // This TLV chain may contain the following TLVs: + // TLV(A): Acktype 0x0000 - normal message + // 0x0001 - file request / abort request + // 0x0002 - file ack + // TLV(F): Unknown + // TLV(3): External IP + // TLV(5): DC port (not to use for filetransfers) + // TLV(0x2711): The next message level + + chain = readIntoTLVChain(&pDataBuf, wTLVLen, 0); + if (!chain) + { // sanity check + NetLog_Server("Message (format %u) - Invalid data", 2); + SAFE_FREE((void**)&pBuf); + return; + } + + WORD wAckType = chain->getWord(0x0A, 1); + + // Update the saved DC info (if contact already exists) + if (hContact != INVALID_HANDLE_VALUE) + { + DWORD dwIP, dwExternalIP; + WORD wPort; + + if (dwExternalIP = chain->getDWord(0x03, 1)) + setSettingDword(hContact, "RealIP", dwExternalIP); + if (dwIP = chain->getDWord(0x04, 1)) + setSettingDword(hContact, "IP", dwIP); + if (wPort = chain->getWord(0x05, 1)) + setSettingWord(hContact, "UserPort", wPort); + + // Save tick value + BYTE bClientID = getSettingByte(hContact, "ClientID", 0); + if (bClientID == CLID_GENERIC || bClientID == CLID_ICQ6) + setSettingDword(hContact, "TickTS", time(NULL) - (dwMsgID1/1000)); + else + setSettingDword(hContact, "TickTS", 0); + } + + // Parse the next message level + if (tlv = chain->getTLV(0x2711, 1)) + { + parseServRelayData(tlv->pData, tlv->wLen, hContact, dwUin, szUID, dwMsgID1, dwMsgID2, wAckType); + } + else + { + NetLog_Server("Warning, no 0x2711 TLV in message (format 2)"); + } + // Clean up + disposeChain(&chain); + } + else if (CompareGUIDs(q1,q2,q3,q4, MCAP_REVERSE_DC_REQ)) + { // Handle reverse DC request + if (wCommand == 1) + { + NetLog_Server("Cannot handle abort messages yet... :("); + SAFE_FREE((void**)&pBuf); + return; + } + if (wTLVLen < 4) + { // just check if at least one tlv is there + NetLog_Server("Message (format %u) - Ignoring empty message", 2); + SAFE_FREE((void**)&pBuf); + return; + } + if (!dwUin) + { // AIM cannot send this, just sanity + NetLog_Server("Error: Malformed UIN in packet"); + SAFE_FREE((void**)&pBuf); + return; + } + chain = readIntoTLVChain(&pDataBuf, wTLVLen, 0); + if (!chain) + { // Malformed packet + NetLog_Server("Error: Malformed data in packet"); + SAFE_FREE((void**)&pBuf); + return; + } + + WORD wAckType = chain->getWord(0x0A, 1); + // Parse the next message level + if (tlv = chain->getTLV(0x2711, 1)) + { + if (tlv->wLen == 0x1B) + { + BYTE *buf = tlv->pData; + DWORD dwUin; + + unpackLEDWord(&buf, &dwUin); + + HANDLE hContact = HContactFromUIN(dwUin, NULL); + if (hContact == INVALID_HANDLE_VALUE) + { + NetLog_Server("Error: %s from unknown contact %u", "Reverse Connect Request", dwUin); + } + else + { + DWORD dwIp, dwPort; + WORD wVersion; + BYTE bMode; + + unpackDWord(&buf, &dwIp); + unpackLEDWord(&buf, &dwPort); + unpackByte(&buf, &bMode); + buf += 4; // unknown + if (dwPort) + buf += 4; // port, again? + else + unpackLEDWord(&buf, &dwPort); + unpackLEWord(&buf, &wVersion); + + setSettingDword(hContact, "IP", dwIp); + setSettingWord(hContact, "UserPort", (WORD)dwPort); + setSettingByte(hContact, "DCType", bMode); + setSettingWord(hContact, "Version", wVersion); + if (wVersion > 6) + { + cookie_reverse_connect *pCookie = (cookie_reverse_connect*)SAFE_MALLOC(sizeof(cookie_reverse_connect)); + + unpackLEDWord(&buf, (DWORD*)&pCookie->ft); + pCookie->dwMsgID1 = dwMsgID1; + pCookie->dwMsgID2 = dwMsgID2; + + OpenDirectConnection(hContact, DIRECTCONN_REVERSE, (void*)pCookie); + } + else + NetLog_Server("Warning: Unsupported direct protocol version in %s", "Reverse Connect Request"); + } + } + else + { + NetLog_Server("Malformed %s", "Reverse Connect Request"); + } + } + else + { + NetLog_Server("Warning, no 0x2711 TLV in message (format 2)"); + } + // Clean up + disposeChain(&chain); + } + else if (CompareGUIDs(q1,q2,q3,q4, MCAP_FILE_TRANSFER)) + { // this is an OFT packet + handleRecvServMsgOFT(pDataBuf, wTLVLen, dwUin, szUID, dwMsgID1, dwMsgID2, wCommand); + } + else if (CompareGUIDs(q1,q2,q3,q4, MCAP_CONTACTS)) + { // this is Contacts Transfer + handleRecvServMsgContacts(pDataBuf, wTLVLen, dwUin, szUID, dwMsgID1, dwMsgID2, wCommand); + } + else // here should be detection of extra data streams (Xtraz) + { + NetLog_Server("Unknown Message Format Capability"); + } + } + else + { + NetLog_Server("Unsupported TLV (%u) in message (format %u)", wTLVType, 2); + } + + SAFE_FREE((void**)&pBuf); +} + + +void CIcqProto::parseServRelayData(BYTE *pDataBuf, WORD wLen, HANDLE hContact, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, WORD wAckType) +{ + WORD wId; + + if (wLen < 2) + { + NetLog_Server("Message (format %u) - Ignoring empty message", 2); + return; + } + + unpackLEWord(&pDataBuf, &wId); // Incorrect identification, but working + wLen -= 2; + + // Only 0x1B are real messages + if (wId == 0x001B) + { + WORD wVersion; + WORD wCookie; + DWORD dwGuid1,dwGuid2,dwGuid3,dwGuid4; + + if (wLen < 31) + { // just check if we have data to work with + NetLog_Server("Message (format %u) - Ignoring empty message", 2); + return; + } + + unpackLEWord(&pDataBuf, &wVersion); + wLen -= 2; + + if (hContact != INVALID_HANDLE_VALUE) + setSettingWord(hContact, "Version", wVersion); + + unpackDWord(&pDataBuf, &dwGuid1); // plugin type GUID + unpackDWord(&pDataBuf, &dwGuid2); + unpackDWord(&pDataBuf, &dwGuid3); + unpackDWord(&pDataBuf, &dwGuid4); + wLen -= 16; + + // Skip lots of unused stuff + pDataBuf += 9; + wLen -= 9; + + unpackLEWord(&pDataBuf, &wId); + wLen -= 2; + + unpackLEWord(&pDataBuf, &wCookie); + wLen -= 2; + + if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, PSIG_MESSAGE)) + { // is this a normal message ? + BYTE bMsgType; + BYTE bFlags; + WORD wStatus, wPritority; + WORD wMsgLen; + + if (wLen < 20) + { // check if there is everything that should be there + NetLog_Server("Message (format %u) - Ignoring empty message", 2); + return; + } + + pDataBuf += 12; /* all zeroes */ + wLen -= 12; + unpackByte(&pDataBuf, &bMsgType); + wLen -= 1; + unpackByte(&pDataBuf, &bFlags); + wLen -= 1; + + // Status + unpackLEWord(&pDataBuf, &wStatus); + wLen -= 2; + + // Priority + unpackLEWord(&pDataBuf, &wPritority); + wLen -= 2; + NetLog_Server("Priority: %u", wPritority); + + // Message + unpackLEWord(&pDataBuf, &wMsgLen); + wLen -= 2; + + // HANDLERS + switch (bMsgType) + { + // File messages, handled by the file module + case MTYPE_FILEREQ: + { + if (!dwUin) + { // AIM cannot send this, just sanity + NetLog_Server("Error: Malformed UIN in packet"); + return; + } + + char* szMsg = (char *)_alloca(wMsgLen + 1); + memcpy(szMsg, pDataBuf, wMsgLen); + szMsg[wMsgLen] = '\0'; + pDataBuf += wMsgLen; + wLen -= wMsgLen; + + if (wAckType == 0 || wAckType == 1) + { + // File requests 7 + handleFileRequest(pDataBuf, wLen, dwUin, wCookie, dwMsgID1, dwMsgID2, szMsg, 7, FALSE); + } + else if (wAckType == 2) + { + // File reply 7 + handleFileAck(pDataBuf, wLen, dwUin, wCookie, wStatus, szMsg); + } + else + { + NetLog_Server("Ignored strange file message"); + } + + break; + } + + // Chat messages, handled by the chat module + case MTYPE_CHAT: + { // TODO: this type is deprecated + break; + } + + // Plugin messages, need further parsing + case MTYPE_PLUGIN: + { + if (wLen < wMsgLen) + { // sanity check + NetLog_Server("Error: Malformed server Greeting message"); + return; + } + + parseServRelayPluginData(pDataBuf + wMsgLen, wLen - wMsgLen, hContact, dwUin, szUID, dwMsgID1, dwMsgID2, wAckType, bFlags, wStatus, wCookie, wVersion); + break; + } + + // Everything else + default: + { + if (!dwUin) + { // AIM cannot send this, just sanity + NetLog_Server("Error: Malformed UIN in packet"); + return; + } + message_ack_params pMsgAck = {0}; + + pMsgAck.bType = MAT_SERVER_ADVANCED; + pMsgAck.dwUin = dwUin; + pMsgAck.dwMsgID1 = dwMsgID1; + pMsgAck.dwMsgID2 = dwMsgID2; + pMsgAck.wCookie = wCookie; + pMsgAck.msgType = bMsgType; + pMsgAck.bFlags = bFlags; + handleMessageTypes(dwUin, szUID, time(NULL), dwMsgID1, dwMsgID2, wCookie, wVersion, bMsgType, bFlags, wAckType, wLen, wMsgLen, (char*)pDataBuf, 0, &pMsgAck); + break; + } + } + } + else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, PSIG_INFO_PLUGIN)) + { // info manager plugin - obsolete + if (!dwUin) + { // AIM cannot send this, just sanity + NetLog_Server("Error: Malformed UIN in packet"); + return; + } + + BYTE bMsgType; + BYTE bLevel; + + pDataBuf += 16; /* unused stuff */ + wLen -= 16; + unpackByte(&pDataBuf, &bMsgType); + wLen -= 1; + + pDataBuf += 3; // unknown + wLen -= 3; + unpackByte(&pDataBuf, &bLevel); + if (bLevel != 0 || wLen < 16) + { + NetLog_Server("Invalid %s Manager Plugin message from %u", "Info", dwUin); + return; + } + unpackDWord(&pDataBuf, &dwGuid1); // plugin request GUID + unpackDWord(&pDataBuf, &dwGuid2); + unpackDWord(&pDataBuf, &dwGuid3); + unpackDWord(&pDataBuf, &dwGuid4); + wLen -= 16; + + if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, PMSG_QUERY_INFO)) + { + NetLog_Server("User %u requests our %s plugin list. NOT SUPPORTED", dwUin, "info"); + } + else + NetLog_Server("Unknown %s Manager message from %u", "Info", dwUin); + } + else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, PSIG_STATUS_PLUGIN)) + { // status manager plugin - obsolete + if (!dwUin) + { // AIM cannot send this, just sanity + NetLog_Server("Error: Malformed UIN in packet"); + return; + } + + BYTE bMsgType; + BYTE bLevel; + + pDataBuf += 16; /* unused stuff */ + wLen -= 16; + unpackByte(&pDataBuf, &bMsgType); + wLen -= 1; + + pDataBuf += 3; // unknown + wLen -= 3; + unpackByte(&pDataBuf, &bLevel); + if (bLevel != 0 || wLen < 16) + { + NetLog_Server("Invalid %s Manager Plugin message from %u", "Status", dwUin); + return; + } + unpackDWord(&pDataBuf, &dwGuid1); // plugin request GUID + unpackDWord(&pDataBuf, &dwGuid2); + unpackDWord(&pDataBuf, &dwGuid3); + unpackDWord(&pDataBuf, &dwGuid4); + wLen -= 16; + + if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, PMSG_QUERY_STATUS)) + NetLog_Server("User %u requests our %s plugin list. NOT SUPPORTED", dwUin, "status"); + else + NetLog_Server("Unknown %s Manager message from %u", "Status", dwUin); + } + else + NetLog_Server("Unknown signature (%08x-%08x-%08x-%08x) in message (format 2)", dwGuid1, dwGuid2, dwGuid3, dwGuid4); + } + else + NetLog_Server("Unknown wId1 (%u) in message (format 2)", wId); +} + + +void CIcqProto::parseServRelayPluginData(BYTE *pDataBuf, WORD wLen, HANDLE hContact, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, WORD wAckType, BYTE bFlags, WORD wStatus, WORD wCookie, WORD wVersion) +{ + int nTypeId; + WORD wFunction; + + NetLog_Server("Parsing Greeting message through server"); + + // Message plugin identification + if (!unpackPluginTypeId(&pDataBuf, &wLen, &nTypeId, &wFunction, FALSE)) return; + + if (wLen > 8) + { + DWORD dwLengthToEnd; + DWORD dwDataLen; + + // Length of remaining data + unpackLEDWord(&pDataBuf, &dwLengthToEnd); + + // Length of message + unpackLEDWord(&pDataBuf, &dwDataLen); + wLen -= 8; + + if (dwDataLen > wLen) + dwDataLen = wLen; + + if (nTypeId == MTYPE_FILEREQ && wAckType == 2) + { + if (!dwUin) + { // AIM cannot send this, just sanity + NetLog_Server("Error: Malformed UIN in packet"); + return; + } + NetLog_Server("This is file ack"); + + char *szMsg = (char *)_alloca(dwDataLen + 1); + memcpy(szMsg, pDataBuf, dwDataLen); + szMsg[dwDataLen] = '\0'; + pDataBuf += dwDataLen; + wLen -= (WORD)dwDataLen; + + handleFileAck(pDataBuf, wLen, dwUin, wCookie, wStatus, szMsg); + } + else if (nTypeId == MTYPE_FILEREQ && wAckType == 1) + { + if (!dwUin) + { // AIM cannot send this, just sanity + NetLog_Server("Error: Malformed UIN in packet"); + return; + } + NetLog_Server("This is a file request"); + + char *szMsg = (char *)_alloca(dwDataLen + 1); + memcpy(szMsg, pDataBuf, dwDataLen); + szMsg[dwDataLen] = '\0'; + pDataBuf += dwDataLen; + wLen -= (WORD)dwDataLen; + + handleFileRequest(pDataBuf, wLen, dwUin, wCookie, dwMsgID1, dwMsgID2, szMsg, 8, FALSE); + } + else if (nTypeId == MTYPE_CHAT && wAckType == 1) + { // TODO: this is deprecated + if (!dwUin) + { // AIM cannot send this, just sanity + NetLog_Server("Error: Malformed UIN in packet"); + return; + } + NetLog_Server("This is a chat request"); + + char *szMsg = (char *)_alloca(dwDataLen + 1); + memcpy(szMsg, pDataBuf, dwDataLen); + szMsg[dwDataLen] = '\0'; + pDataBuf += dwDataLen; + wLen -= (WORD)dwDataLen; + + // handleChatRequest(pDataBuf, wLen, dwUin, wCookie, dwMsgID1, dwMsgID2, szMsg, 8); + } + else if (nTypeId == MTYPE_STATUSMSGEXT && wFunction >= 1 && wFunction <= 3) + { // handle extended status message request + int nMsgType = 0; + + switch (wFunction) + { + case 1: // Away + if (m_iStatus == ID_STATUS_ONLINE || m_iStatus == ID_STATUS_INVISIBLE) + nMsgType = MTYPE_AUTOONLINE; + else if (m_iStatus == ID_STATUS_AWAY) + nMsgType = MTYPE_AUTOAWAY; + else if (m_iStatus == ID_STATUS_FREECHAT) + nMsgType = MTYPE_AUTOFFC; + break; + + case 2: // Busy + if (m_iStatus == ID_STATUS_OCCUPIED) + nMsgType = MTYPE_AUTOBUSY; + else if (m_iStatus == ID_STATUS_DND) + nMsgType = MTYPE_AUTODND; + break; + + case 3: // N/A + if (m_iStatus == ID_STATUS_NA) + nMsgType = MTYPE_AUTONA; + } + handleMessageTypes(dwUin, szUID, time(NULL), dwMsgID1, dwMsgID2, wCookie, wVersion, nMsgType, bFlags, wAckType, dwLengthToEnd, 0, (char*)pDataBuf, MTF_PLUGIN | MTF_STATUS_EXTENDED, NULL); + } + else if (nTypeId) + { + if (!dwUin) + { // AIM cannot send this, just sanity + NetLog_Server("Error: Malformed UIN in packet"); + return; + } + message_ack_params pMsgAck = {0}; + + pMsgAck.bType = MAT_SERVER_ADVANCED; + pMsgAck.dwUin = dwUin; + pMsgAck.dwMsgID1 = dwMsgID1; + pMsgAck.dwMsgID2 = dwMsgID2; + pMsgAck.wCookie = wCookie; + pMsgAck.msgType = nTypeId; + pMsgAck.bFlags = bFlags; + handleMessageTypes(dwUin, szUID, time(NULL), dwMsgID1, dwMsgID2, wCookie, wVersion, nTypeId, bFlags, wAckType, dwLengthToEnd, (WORD)dwDataLen, (char*)pDataBuf, MTF_PLUGIN, &pMsgAck); + } + else + { + NetLog_Server("Unsupported plugin message type %d", nTypeId); + } + } + else + NetLog_Server("Error: Malformed server plugin message"); +} + + +void CIcqProto::handleRecvServMsgContacts(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwID1, DWORD dwID2, WORD wCommand) +{ + HANDLE hContact = HContactFromUID(dwUin, szUID, NULL); + + if (wCommand == 0) + { // received contacts + if (wLen < 4) + { // just check if at least one tlv is there + NetLog_Server("Message (format %u) - Ignoring empty contacts message", 2); + return; + } + oscar_tlv_chain *chain = readIntoTLVChain(&buf, wLen, 0); + if (!chain) + { // sanity check + NetLog_Server("Message (format %u) - Invalid data", 2); + return; + } + + WORD wAckType = chain->getWord(0x0A, 1); + + if (wAckType == 1) + { // it is really message containing contacts, parse them + oscar_tlv *tlvUins = chain->getTLV(0x2711, 1); + oscar_tlv *tlvNames = chain->getTLV(0x2712, 1); + + if (!tlvUins || tlvUins->wLen < 4) + { + NetLog_Server("Malformed '%s' message", "contacts"); + disposeChain(&chain); + return; + } + int nContacts = 0x10, iContact = 0; + ICQSEARCHRESULT **contacts = (ICQSEARCHRESULT**)SAFE_MALLOC(nContacts * sizeof(ICQSEARCHRESULT*)); + WORD wContactsGroup = 0; + int valid = 1; + BYTE *pBuffer = tlvUins->pData; + int nLen = tlvUins->wLen; + + while (nLen > 2) + { // parse UIDs + if (!wContactsGroup) + { + WORD wGroupLen; + + unpackWord(&pBuffer, &wGroupLen); + nLen -= 2; + if (nLen >= wGroupLen + 2) + { + pBuffer += wGroupLen; + unpackWord(&pBuffer, &wContactsGroup); + nLen -= wGroupLen + 2; + } + else + break; + } + else + { // group parsed, UIDs waiting + WORD wUidLen; + + unpackWord(&pBuffer, &wUidLen); + nLen -= 2; + if (nLen >= wUidLen) + { + char *szUid = (char*)SAFE_MALLOC(wUidLen + 1); + unpackString(&pBuffer, szUid, wUidLen); + nLen -= wUidLen; + + if (iContact >= nContacts) + { // the list is too small, resize it + nContacts += 0x10; + contacts = (ICQSEARCHRESULT**)SAFE_REALLOC(contacts, nContacts * sizeof(ICQSEARCHRESULT*)); + } + contacts[iContact] = (ICQSEARCHRESULT*)SAFE_MALLOC(sizeof(ICQSEARCHRESULT)); + contacts[iContact]->hdr.cbSize = sizeof(ICQSEARCHRESULT); + contacts[iContact]->hdr.flags = PSR_TCHAR; + contacts[iContact]->hdr.nick = null_strdup(_T("")); + contacts[iContact]->hdr.id = ansi_to_tchar(szUid); + + if (IsStringUIN(szUid)) + { // icq contact + contacts[iContact]->uin = atoi(szUid); + if (contacts[iContact]->uin == 0) + valid = 0; + } + else + { // aim contact + if (!strlennull(szUid)) + valid = 0; + } + iContact++; + + SAFE_FREE(&szUid); + } + else + { + if (wContactsGroup) valid = 0; + break; + } + + wContactsGroup--; + } + } + if (!iContact || !valid) + { + NetLog_Server("Malformed '%s' message", "contacts"); + disposeChain(&chain); + for (int i = 0; i < iContact; i++) + { + SAFE_FREE(&contacts[i]->hdr.id); + SAFE_FREE(&contacts[i]->hdr.nick); + SAFE_FREE((void**)&contacts[i]); + } + SAFE_FREE((void**)&contacts); + return; + } + nContacts = iContact; + if (tlvNames && tlvNames->wLen >= 4) + { // parse names, if available + pBuffer = tlvNames->pData; + nLen = tlvNames->wLen; + iContact = 0; + + while (nLen > 2) + { // parse Names + if (!wContactsGroup) + { + WORD wGroupLen; + + unpackWord(&pBuffer, &wGroupLen); + nLen -= 2; + if (nLen >= wGroupLen + 2) + { + pBuffer += wGroupLen; + unpackWord(&pBuffer, &wContactsGroup); + nLen -= wGroupLen + 2; + } + else + break; + } + else + { // group parsed, Names waiting + WORD wNickLen; + + unpackWord(&pBuffer, &wNickLen); + nLen -= 2; + if (nLen >= wNickLen) + { + WORD wNickTLV, wNickTLVLen; + char *pNick = NULL; + + unpackTypedTLV(pBuffer, wNickLen, 0x01, &wNickTLV, &wNickTLVLen, (LPBYTE*)&pNick); + if (wNickTLV == 0x01) + { + SAFE_FREE(&contacts[iContact]->hdr.nick); + contacts[iContact]->hdr.nick = utf8_to_tchar(pNick); + } + else + SAFE_FREE(&pNick); + pBuffer += wNickLen; + nLen -= wNickLen; + + iContact++; + if (iContact >= nContacts) break; + } + else + break; + + wContactsGroup--; + } + } + } + + if (!valid) + { + NetLog_Server("Malformed '%s' message", "contacts"); + } + else + { + int bAdded; + CCSDATA ccs; + PROTORECVEVENT pre = {0}; + + hContact = HContactFromUID(dwUin, szUID, &bAdded); + + // ack the message + icq_sendContactsAck(dwUin, szUID, dwID1, dwID2); + + ccs.szProtoService = PSR_CONTACTS; + ccs.hContact = hContact; + ccs.wParam = 0; + ccs.lParam = (LPARAM)⪯ + pre.timestamp = (DWORD)time(NULL); + pre.szMessage = (char *)contacts; + pre.lParam = nContacts; + pre.flags = PREF_TCHAR; + CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs); + } + + for (int i = 0; i < iContact; i++) + { + SAFE_FREE(&contacts[i]->hdr.id); + SAFE_FREE(&contacts[i]->hdr.nick); + SAFE_FREE((void**)&contacts[i]); + } + SAFE_FREE((void**)&contacts); + } + else + NetLog_Server("Error: Received unknown contacts message, ignoring."); + // Clean up + disposeChain(&chain); + } + else if (wCommand == 1) + { + NetLog_Server("Cannot handle abort messages yet... :("); + return; + } + else if (wCommand == 2) + { // acknowledgement + DWORD dwCookie; + HANDLE hCookieContact; + + if (FindMessageCookie(dwID1, dwID2, &dwCookie, &hCookieContact, NULL)) + { + if (hCookieContact != hContact) + NetLog_Server("Warning: Ack Contact does not match Cookie Contact(0x%x != 0x%x)", hContact, hCookieContact); + + BroadcastAck(hContact, ACKTYPE_CONTACTS, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0); + + ReleaseCookie(dwCookie); + } + else + NetLog_Server("Warning: Unexpected Contact Transfer ack from %s", strUID(dwUin, szUID)); + } +} + + +void CIcqProto::handleRecvServMsgType4(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, DWORD dwRef) +{ + WORD wTLVType; + WORD wTLVLen; + BYTE* pDataBuf; + DWORD dwUin2; + + if (wLen < 2) + { + NetLog_Server("Message (format %u) - Ignoring empty message", 4); + return; + } + + // Unpack the first TLV(5) + unpackTypedTLV(buf, wLen, 5, &wTLVType, &wTLVLen, &pDataBuf); + NetLog_Server("Message (format %u) - UID: %s", 4, strUID(dwUin, szUID)); + + // It must be TLV(5) + if (wTLVType == 5) + { + BYTE bMsgType; + BYTE bFlags; + BYTE* pmsg = pDataBuf; + WORD wMsgLen; + + + unpackLEDWord(&pmsg, &dwUin2); + + if (dwUin2 == dwUin) + { + unpackByte(&pmsg, &bMsgType); + unpackByte(&pmsg, &bFlags); + unpackLEWord(&pmsg, &wMsgLen); + + if (bMsgType == 0 && wMsgLen == 1) + { + NetLog_Server("User %u probably checks his ignore state.", dwUin); + } + else + { + cookie_offline_messages *cookie; + DWORD dwRecvTime = (DWORD)time(NULL); + + if (!(dwRef & 0x80000000) && FindCookie(dwRef, NULL, (void**)&cookie)) + { + WORD wTimeTLVType, wTimeTLVLen; + BYTE *pTimeTLV = NULL; + + cookie->nMessages++; + + unpackTypedTLV(buf, wLen, 0x16, &wTimeTLVType, &wTimeTLVLen, &pTimeTLV); + if (pTimeTLV && wTimeTLVType == 0x16 && wTimeTLVLen == 4) + { // found Offline timestamp + BYTE *pBuf = pTimeTLV; + + unpackDWord(&pBuf, &dwRecvTime); + NetLog_Server("Message (format %u) - Offline timestamp is %s", 4, time2text(dwRecvTime)); + } + SAFE_FREE((void**)&pTimeTLV); + } + + if (bMsgType == MTYPE_PLUGIN) + { + WORD wLen = wTLVLen - 8; + int typeId; + + NetLog_Server("Parsing Greeting message through server"); + + pmsg += wMsgLen; + wLen -= wMsgLen; + + if (unpackPluginTypeId(&pmsg, &wLen, &typeId, NULL, FALSE) && wLen > 8) + { + DWORD dwLengthToEnd; + DWORD dwDataLen; + + // Length of remaining data + unpackLEDWord(&pmsg, &dwLengthToEnd); + + // Length of message + unpackLEDWord(&pmsg, &dwDataLen); + wLen -= 8; + + if (dwDataLen > wLen) + dwDataLen = wLen; + + if (typeId) + { + uid_str szUID; + handleMessageTypes(dwUin, szUID, dwRecvTime, dwMsgID1, dwMsgID2, 0, 0, typeId, bFlags, 0, dwLengthToEnd, (WORD)dwDataLen, (char*)pmsg, MTF_PLUGIN, NULL); + } + else + { + NetLog_Server("Unsupported plugin message type %d", typeId); + } + } + } + else + { + uid_str szUID; + handleMessageTypes(dwUin, szUID, dwRecvTime, dwMsgID1, dwMsgID2, 0, 0, bMsgType, bFlags, 0, wTLVLen - 8, wMsgLen, (char*)pmsg, 0, NULL); + } + } + } + else + { + NetLog_Server("Ignoring spoofed TYPE4 message thru server from %d", dwUin); + } + } + else + { + NetLog_Server("Unsupported TLV (%u) in message (format %u)", wTLVType, 4); + } + + SAFE_FREE((void**)&pDataBuf); +} + + +// +// Helper functions +// + +static int TypeGUIDToTypeId(DWORD dwGuid1, DWORD dwGuid2, DWORD dwGuid3, DWORD dwGuid4, WORD wType) +{ + int nTypeID = MTYPE_UNKNOWN; + + if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_STATUSMSGEXT)) + { + nTypeID = MTYPE_STATUSMSGEXT; + } + else if (wType==MGTYPE_UNDEFINED) + { + if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, PSIG_MESSAGE)) + { // icq6 message ack + nTypeID = MTYPE_PLAIN; + } + } + else if (wType==MGTYPE_STANDARD_SEND) + { + if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_WEBURL)) + { + nTypeID = MTYPE_URL; + } + else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_CONTACTS)) + { + nTypeID = MTYPE_CONTACTS; + } + else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_CHAT)) + { + nTypeID = MTYPE_CHAT; + } + else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_FILE)) + { + nTypeID = MTYPE_FILEREQ; + } + else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_GREETING_CARD)) + { + nTypeID = MTYPE_GREETINGCARD; + } + else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_MESSAGE)) + { + nTypeID = MTYPE_MESSAGE; + } + else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_SMS_MESSAGE)) + { + nTypeID = MTYPE_SMS_MESSAGE; + } + } + else if (wType==MGTYPE_CONTACTS_REQUEST) + { + if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_CONTACTS)) + { + nTypeID = MTYPE_REQUESTCONTACTS; + } + else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_XTRAZ_SCRIPT)) + { + nTypeID = MTYPE_SCRIPT_DATA; + } + } + else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_XTRAZ_SCRIPT)) + { + if (wType==MGTYPE_SCRIPT_INVITATION) + { + nTypeID = MTYPE_SCRIPT_INVITATION; + } + else if (wType==MGTYPE_SCRIPT_NOTIFY) + { + nTypeID = MTYPE_SCRIPT_NOTIFY; + } + } + + return nTypeID; +} + + +int CIcqProto::unpackPluginTypeId(BYTE **pBuffer, WORD *pwLen, int *pTypeId, WORD *pFunctionId, BOOL bThruDC) +{ + WORD wLen = *pwLen; + WORD wInfoLen; + DWORD dwPluginNameLen; + DWORD q1,q2,q3,q4; + WORD qt; + + if (wLen < 24) + return 0; // Failure + + unpackLEWord(pBuffer, &wInfoLen); + + unpackDWord(pBuffer, &q1); // get data GUID & function id + unpackDWord(pBuffer, &q2); + unpackDWord(pBuffer, &q3); + unpackDWord(pBuffer, &q4); + unpackLEWord(pBuffer, &qt); + wLen -= 20; + + if (pFunctionId) *pFunctionId = qt; + + unpackLEDWord(pBuffer, &dwPluginNameLen); + wLen -= 4; + + if (dwPluginNameLen > wLen) + { // check for malformed plugin name + dwPluginNameLen = wLen; + NetLog_Uni(bThruDC, "Warning: malformed size of plugin name."); + } + char *szPluginName = (char *)_alloca(dwPluginNameLen + 1); + memcpy(szPluginName, *pBuffer, dwPluginNameLen); + szPluginName[dwPluginNameLen] = '\0'; + wLen -= (WORD)dwPluginNameLen; + + *pBuffer += dwPluginNameLen; + + int typeId = TypeGUIDToTypeId(q1, q2, q3, q4, qt); + if (!typeId) + NetLog_Uni(bThruDC, "Error: Unknown type {%08x-%08x-%08x-%08x:%04x}: %s", q1,q2,q3,q4,qt, szPluginName); + + if (wInfoLen >= 22 + dwPluginNameLen) + { // sanity checking + wInfoLen -= (WORD)(22 + dwPluginNameLen); + + // check if enough data is available - skip remaining bytes of info block + if (wLen >= wInfoLen) + { + *pBuffer += wInfoLen; + wLen -= wInfoLen; + } + } + + *pwLen = wLen; + *pTypeId = typeId; + + return 1; // Success +} + + +int getPluginTypeIdLen(int nTypeID) +{ + switch (nTypeID) + { + case MTYPE_SCRIPT_NOTIFY: + return 0x51; + + case MTYPE_FILEREQ: + return 0x2B; + + case MTYPE_AUTOONLINE: + case MTYPE_AUTOAWAY: + case MTYPE_AUTOBUSY: + case MTYPE_AUTODND: + case MTYPE_AUTOFFC: + return 0x3C; + + case MTYPE_AUTONA: + return 0x3B; + + default: + return 0; + } +} + + +void packPluginTypeId(icq_packet *packet, int nTypeID) +{ + switch (nTypeID) + { + case MTYPE_SCRIPT_NOTIFY: + packLEWord(packet, 0x04f); // Length + + packGUID(packet, MGTYPE_XTRAZ_SCRIPT); // Message Type GUID + packLEWord(packet, MGTYPE_SCRIPT_NOTIFY); // Function ID + packLEDWord(packet, 0x002a); // Request type string + packBuffer(packet, (LPBYTE)"Script Plug-in: Remote Notification Arrive", 0x002a); + + packDWord(packet, 0x00000100); // Unknown binary stuff + packDWord(packet, 0x00000000); + packDWord(packet, 0x00000000); + packWord(packet, 0x0000); + packByte(packet, 0x00); + + break; + + case MTYPE_FILEREQ: + packLEWord(packet, 0x029); // Length + + packGUID(packet, MGTYPE_FILE); // Message Type GUID + packWord(packet, 0x0000); // Unknown + packLEDWord(packet, 0x0004); // Request type string + packBuffer(packet, (LPBYTE)"File", 0x0004); + + packDWord(packet, 0x00000100); // More unknown binary stuff + packDWord(packet, 0x00010000); + packDWord(packet, 0x00000000); + packWord(packet, 0x0000); + packByte(packet, 0x00); + + break; + + case MTYPE_AUTOONLINE: + case MTYPE_AUTOAWAY: + case MTYPE_AUTOFFC: + packLEWord(packet, 0x03A); // Length + + packGUID(packet, MGTYPE_STATUSMSGEXT); // Message Type GUID + + packLEWord(packet, 1); // Function ID + packLEDWord(packet, 0x13); // Request type string + packBuffer(packet, (LPBYTE)"Away Status Message", 0x13); + + packDWord(packet, 0x01000000); // Unknown binary stuff + packDWord(packet, 0x00000000); + packDWord(packet, 0x00000000); + packDWord(packet, 0x00000000); + packByte(packet, 0x00); + + break; + + case MTYPE_AUTOBUSY: + case MTYPE_AUTODND: + packLEWord(packet, 0x03A); // Length + + packGUID(packet, MGTYPE_STATUSMSGEXT); // Message Type GUID + + packLEWord(packet, 2); // Function ID + packLEDWord(packet, 0x13); // Request type string + packBuffer(packet, (LPBYTE)"Busy Status Message", 0x13); + + packDWord(packet, 0x02000000); // Unknown binary stuff + packDWord(packet, 0x00000000); + packDWord(packet, 0x00000000); + packDWord(packet, 0x00000000); + packByte(packet, 0x00); + + break; + + case MTYPE_AUTONA: + packLEWord(packet, 0x039); // Length + + packGUID(packet, MGTYPE_STATUSMSGEXT); // Message Type GUID + + packLEWord(packet, 3); // Function ID + packLEDWord(packet, 0x12); // Request type string + packBuffer(packet, (LPBYTE)"N/A Status Message", 0x12); + + packDWord(packet, 0x03000000); // Unknown binary stuff + packDWord(packet, 0x00000000); + packDWord(packet, 0x00000000); + packDWord(packet, 0x00000000); + packByte(packet, 0x00); + + break; + } +} + + +void CIcqProto::handleStatusMsgReply(const char *szPrefix, HANDLE hContact, DWORD dwUin, WORD wVersion, int bMsgType, WORD wCookie, const char *szMsg, int nMsgFlags) +{ + CCSDATA ccs; + PROTORECVEVENT pre = {0}; + + if (hContact == INVALID_HANDLE_VALUE) + { + NetLog_Server("%sIgnoring status message from unknown contact %u", szPrefix, dwUin); + return; + } + + int status = AwayMsgTypeToStatus(bMsgType); + if (status == ID_STATUS_OFFLINE) + { + NetLog_Server("%sIgnoring unknown status message from %u", szPrefix, dwUin); + return; + } + + // it is probably UTF-8 status reply + if (wVersion == 9 || (nMsgFlags & MTF_PLUGIN) && wVersion == 10) + { + if (UTF8_IsValid(szMsg)) pre.flags |= PREF_UTF; + } + + ccs.szProtoService = PSR_AWAYMSG; + ccs.hContact = hContact; + ccs.wParam = status; + ccs.lParam = (LPARAM)⪯ + pre.szMessage = (char*)szMsg; + pre.timestamp = time(NULL); + pre.lParam = wCookie; + + CallService(MS_PROTO_CHAINRECV,0,(LPARAM)&ccs); +} + + +HANDLE CIcqProto::handleMessageAck(DWORD dwUin, char *szUID, WORD wCookie, WORD wVersion, int type, WORD wMsgLen, PBYTE buf, BYTE bFlags, int nMsgFlags) +{ + if (bFlags == 3) + { + HANDLE hCookieContact; + cookie_message_data *pCookieData = NULL; + + HANDLE hContact = HContactFromUID(dwUin, szUID, NULL); + + if (!FindCookie(wCookie, &hCookieContact, (void**)&pCookieData)) + { + NetLog_Server("%sIgnoring unrequested status message from %u", "handleMessageAck: ", dwUin); + + ReleaseCookie(wCookie); + return INVALID_HANDLE_VALUE; + } + + if (hContact != hCookieContact) + { + NetLog_Server("%sAck Contact does not match Cookie Contact(0x%x != 0x%x)", "handleMessageAck: ", hContact, hCookieContact); + + ReleaseCookie(wCookie); + return INVALID_HANDLE_VALUE; + } + ReleaseCookie(wCookie); + + handleStatusMsgReply("handleMessageAck: ", hContact, dwUin, wVersion, type, wCookie, (char*)buf, nMsgFlags); + } + else + { + // Should not happen + NetLog_Server("%sIgnored type %u ack message (this should not happen)", "handleMessageAck: ", type); + } + + return INVALID_HANDLE_VALUE; +} + + +/* this function send all acks from handleMessageTypes */ +void CIcqProto::sendMessageTypesAck(HANDLE hContact, int bUnicode, message_ack_params *pArgs) +{ + if (pArgs) + { + if ((pArgs->msgType == MTYPE_PLAIN && !CallService(MS_IGNORE_ISIGNORED, (WPARAM)hContact, IGNOREEVENT_MESSAGE)) + || (pArgs->msgType == MTYPE_URL && !CallService(MS_IGNORE_ISIGNORED, (WPARAM)hContact, IGNOREEVENT_URL)) + || pArgs->msgType == MTYPE_CONTACTS) + { + if (pArgs->bType == MAT_SERVER_ADVANCED) + { // Only ack message packets + icq_sendAdvancedMsgAck(pArgs->dwUin, pArgs->dwMsgID1, pArgs->dwMsgID2, pArgs->wCookie, (BYTE)pArgs->msgType, pArgs->bFlags); + } + else if (pArgs->bType == MAT_DIRECT) + { // Send acknowledgement + icq_sendDirectMsgAck(pArgs->pDC, pArgs->wCookie, (BYTE)pArgs->msgType, pArgs->bFlags, bUnicode ? (char *)CAP_UTF8MSGS : NULL); + } + } + } +} + + +/* this function also processes direct packets, so it should be bulletproof */ +/* pMsg points to the beginning of the message */ +void CIcqProto::handleMessageTypes(DWORD dwUin, char *szUID, DWORD dwTimestamp, DWORD dwMsgID, DWORD dwMsgID2, WORD wCookie, WORD wVersion, int type, int flags, WORD wAckType, DWORD dwDataLen, WORD wMsgLen, char *pMsg, int nMsgFlags, message_ack_params *pAckParams) +{ + HANDLE hContact = INVALID_HANDLE_VALUE; + BOOL bThruDC = (nMsgFlags & MTF_DIRECT) == MTF_DIRECT; + int bAdded; + + + if (dwDataLen < wMsgLen) + { + NetLog_Uni(bThruDC, "Ignoring overflowed message"); + return; + } + + if (wAckType == 2) + { + handleMessageAck(dwUin, szUID, wCookie, wVersion, type, wMsgLen, (LPBYTE)pMsg, (BYTE)flags, nMsgFlags); + return; + } + + char *szMsg = (char *)SAFE_MALLOC(wMsgLen + 1); + if (wMsgLen > 0) + { + memcpy(szMsg, pMsg, wMsgLen); + pMsg += wMsgLen; + dwDataLen -= wMsgLen; + } + szMsg[wMsgLen] = '\0'; + + + char* pszMsgField[2*MAX_CONTACTSSEND+1]; + int nMsgFields = 0; + + pszMsgField[0] = szMsg; + if (type == MTYPE_URL || type == MTYPE_AUTHREQ || type == MTYPE_ADDED || type == MTYPE_CONTACTS || type == MTYPE_EEXPRESS || type == MTYPE_WWP) + { + for (char *pszMsg=szMsg, nMsgFields=1; *pszMsg; pszMsg++) + { + if ((BYTE)*pszMsg == 0xFE) + { + *pszMsg = '\0'; + pszMsgField[nMsgFields++] = pszMsg + 1; + if (nMsgFields >= SIZEOF(pszMsgField)) + break; + } + } + } + + switch (type) { + + case MTYPE_PLAIN: /* plain message */ + { + CCSDATA ccs; + PROTORECVEVENT pre = {0}; + + // Check if this message is marked as UTF8 encoded + if (dwDataLen > 12) + { + DWORD dwGuidLen = 0; + int bDoubleMsg = 0; + + if (bThruDC) + { + DWORD dwExtraLen = *(DWORD*)pMsg; + + if (dwExtraLen < dwDataLen && !strncmp(szMsg, "{\\rtf", 5)) + { // it is icq5 sending us crap, get real message from it + WCHAR* usMsg = (WCHAR*)_alloca((dwExtraLen + 1)*sizeof(WCHAR)); + // make sure it is null-terminated + wcsncpy(usMsg, (WCHAR*)(pMsg + 4), dwExtraLen); + usMsg[dwExtraLen] = '\0'; + SAFE_FREE(&szMsg); + szMsg = (char*)make_utf8_string(usMsg); + + if (!IsUnicodeAscii(usMsg, dwExtraLen)) + pre.flags = PREF_UTF; // only mark real non-ascii messages as unicode + + bDoubleMsg = 1; + } + } + + if (!bDoubleMsg) + { + dwGuidLen = *(DWORD*)(pMsg+8); + dwDataLen -= 12; + pMsg += 12; + } + + while ((dwGuidLen >= 38) && (dwDataLen >= dwGuidLen)) + { + if (!strncmp(pMsg, CAP_UTF8MSGS, 38)) + { // Found UTF8 cap, convert message to ansi + pre.flags = PREF_UTF; + break; + } + else if (!strncmp(pMsg, CAP_RTFMSGS, 38)) + { // Found RichText cap + NetLog_Uni(bThruDC, "Warning: User %u sends us RichText.", dwUin); + break; + } + + dwGuidLen -= 38; + dwDataLen -= 38; + pMsg += 38; + } + } + + hContact = HContactFromUIN(dwUin, &bAdded); + sendMessageTypesAck(hContact, pre.flags & PREF_UTF, pAckParams); + + if (!pre.flags && !IsUSASCII(szMsg, strlennull(szMsg))) + { // message is Ansi and contains national characters, create Unicode part by codepage + char *usMsg = convertMsgToUserSpecificUtf(hContact, szMsg); + if (usMsg) + { + SAFE_FREE(&szMsg); + szMsg = (char*)usMsg; + pre.flags = PREF_UTF; + } + } + + ccs.szProtoService = PSR_MESSAGE; + ccs.hContact = hContact; + ccs.wParam = 0; + ccs.lParam = (LPARAM)⪯ + pre.timestamp = dwTimestamp; + pre.szMessage = (char *)szMsg; + + CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs); + } + break; + + case MTYPE_URL: + { + CCSDATA ccs; + PROTORECVEVENT pre = {0}; + + if (nMsgFields < 2) + { + NetLog_Uni(bThruDC, "Malformed '%s' message", "URL"); + break; + } + + hContact = HContactFromUIN(dwUin, &bAdded); + sendMessageTypesAck(hContact, 0, pAckParams); + + char *szTitle = ICQTranslateUtf(LPGEN("Incoming URL:")); + char *szDataDescr = ansi_to_utf8(pszMsgField[0]); + char *szDataUrl = ansi_to_utf8(pszMsgField[1]); + char *szBlob = (char *)SAFE_MALLOC(strlennull(szTitle) + strlennull(szDataDescr) + strlennull(szDataUrl) + 8); + strcpy(szBlob, szTitle); + strcat(szBlob, " "); + strcat(szBlob, szDataDescr); // Description + strcat(szBlob, "\r\n"); + strcat(szBlob, szDataUrl); // URL + SAFE_FREE(&szTitle); + SAFE_FREE(&szDataDescr); + SAFE_FREE(&szDataUrl); + + ccs.szProtoService = PSR_MESSAGE; + ccs.hContact = hContact; + ccs.wParam = 0; + ccs.lParam = (LPARAM)⪯ + pre.timestamp = dwTimestamp; + pre.szMessage = (char *)szBlob; + pre.flags = PREF_UTF; + + CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs); + + SAFE_FREE(&szBlob); + } + break; + + case MTYPE_AUTHREQ: /* auth request */ + /* format: nick FE first FE last FE email FE unk-char FE msg 00 */ + { + CCSDATA ccs; + PROTORECVEVENT pre = {0}; + char* szBlob; + char* pCurBlob; + + + if (nMsgFields < 6) + { + NetLog_Server("Malformed '%s' message", "auth req"); + break; + } + + ccs.szProtoService=PSR_AUTH; + ccs.hContact=hContact=HContactFromUIN(dwUin, &bAdded); + ccs.wParam=0; + ccs.lParam=(LPARAM)⪯ + pre.timestamp=dwTimestamp; + pre.lParam=sizeof(DWORD)+sizeof(HANDLE)+strlennull(pszMsgField[0])+strlennull(pszMsgField[1])+strlennull(pszMsgField[2])+strlennull(pszMsgField[3])+strlennull(pszMsgField[5])+5; + + /*blob is: uin(DWORD), hcontact(HANDLE), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ), reason(ASCIIZ)*/ + pCurBlob=szBlob=(char *)_alloca(pre.lParam); + memcpy(pCurBlob,&dwUin,sizeof(DWORD)); pCurBlob+=sizeof(DWORD); + memcpy(pCurBlob,&hContact,sizeof(HANDLE)); pCurBlob+=sizeof(HANDLE); + strcpy((char *)pCurBlob,pszMsgField[0]); pCurBlob+=strlennull((char *)pCurBlob)+1; + strcpy((char *)pCurBlob,pszMsgField[1]); pCurBlob+=strlennull((char *)pCurBlob)+1; + strcpy((char *)pCurBlob,pszMsgField[2]); pCurBlob+=strlennull((char *)pCurBlob)+1; + strcpy((char *)pCurBlob,pszMsgField[3]); pCurBlob+=strlennull((char *)pCurBlob)+1; + strcpy((char *)pCurBlob,pszMsgField[5]); + pre.szMessage=(char *)szBlob; + + CallService(MS_PROTO_CHAINRECV,0,(LPARAM)&ccs); + } + break; + + case MTYPE_ADDED: /* 'you were added' */ + /* format: nick FE first FE last FE email 00 */ + { + DWORD cbBlob; + PBYTE pBlob, pCurBlob; + + if (nMsgFields < 4) + { + NetLog_Server("Malformed '%s' message", "you were added"); + break; + } + + hContact = HContactFromUIN(dwUin, &bAdded); + + /*blob is: uin(DWORD), hcontact(HANDLE), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ) */ + cbBlob=sizeof(DWORD)*2+strlennull(pszMsgField[0])+strlennull(pszMsgField[1])+strlennull(pszMsgField[2])+strlennull(pszMsgField[3])+4; + pCurBlob=pBlob=(PBYTE)_alloca(cbBlob); + *(DWORD*)pCurBlob = dwUin; pCurBlob += sizeof(DWORD); + *(DWORD*)pCurBlob = DWORD(hContact); pCurBlob += sizeof(DWORD); + strcpy((char *)pCurBlob,pszMsgField[0]); pCurBlob += strlennull((char *)pCurBlob)+1; + strcpy((char *)pCurBlob,pszMsgField[1]); pCurBlob += strlennull((char *)pCurBlob)+1; + strcpy((char *)pCurBlob,pszMsgField[2]); pCurBlob += strlennull((char *)pCurBlob)+1; + strcpy((char *)pCurBlob,pszMsgField[3]); + + AddEvent(NULL, EVENTTYPE_ADDED, dwTimestamp, 0, cbBlob, pBlob); + } + break; + + case MTYPE_CONTACTS: + { + CCSDATA ccs; + PROTORECVEVENT pre = {0}; + char* pszNContactsEnd; + int nContacts; + int i; + + + if (nMsgFields < 3 + || (nContacts = strtol(pszMsgField[0], &pszNContactsEnd, 10)) == 0 + || pszNContactsEnd - pszMsgField[0] != (int)strlennull(pszMsgField[0]) + || nMsgFields < nContacts * 2 + 1) + { + NetLog_Uni(bThruDC, "Malformed '%s' message", "contacts"); + break; + } + + int valid = 1; + ICQSEARCHRESULT** isrList = (ICQSEARCHRESULT**)_alloca(nContacts * sizeof(ICQSEARCHRESULT*)); + for (i = 0; i < nContacts; i++) + { + isrList[i] = (ICQSEARCHRESULT*)SAFE_MALLOC(sizeof(ICQSEARCHRESULT)); + isrList[i]->hdr.cbSize = sizeof(ICQSEARCHRESULT); + isrList[i]->hdr.flags = PSR_TCHAR; + if (IsStringUIN(pszMsgField[1 + i * 2])) + { // icq contact + isrList[i]->uin = atoi(pszMsgField[1 + i * 2]); + if (isrList[i]->uin == 0) + valid = 0; + } + else + { // aim contact + if (!strlennull(pszMsgField[1 + i * 2])) + valid = 0; + } + isrList[i]->hdr.id = ansi_to_tchar(pszMsgField[1 + i * 2]); + isrList[i]->hdr.nick = ansi_to_tchar(pszMsgField[2 + i * 2]); + } + + if (!valid) + { + NetLog_Uni(bThruDC, "Malformed '%s' message", "contacts"); + } + else + { + hContact = HContactFromUIN(dwUin, &bAdded); + sendMessageTypesAck(hContact, 0, pAckParams); + + ccs.szProtoService = PSR_CONTACTS; + ccs.hContact = hContact; + ccs.wParam = 0; + ccs.lParam = (LPARAM)⪯ + pre.timestamp = dwTimestamp; + pre.szMessage = (char *)isrList; + pre.lParam = nContacts; + pre.flags = PREF_TCHAR; + CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs); + } + + for (i = 0; i < nContacts; i++) + { + SAFE_FREE(&isrList[i]->hdr.id); + SAFE_FREE(&isrList[i]->hdr.nick); + SAFE_FREE((void**)&isrList[i]); + } + } + break; + + case MTYPE_PLUGIN: // FIXME: this should be removed - it is never called + hContact = NULL; + + switch(dwUin) + { + case 1111: /* icqmail 'you've got mail' - not processed */ + break; + } + break; + + case MTYPE_SMS_MESSAGE: + /* it's a SMS message from a mobile - broadcast to SMS plugin */ + if (dwUin != 1002) + { + NetLog_Uni(bThruDC, "Malformed '%s' message", "SMS Mobile"); + break; + } + NetLog_Server("Received SMS Mobile message"); + + BroadcastAck(NULL, ICQACKTYPE_SMS, ACKRESULT_SUCCESS, NULL, (LPARAM)szMsg); + break; + + case MTYPE_STATUSMSGEXT: + /* it's either extended StatusMsg reply from icq2003b or a IcqWebMessage */ + if (dwUin == 1003) + { + NetLog_Server("Received ICQWebMessage - NOT SUPPORTED"); + } + break; + + case MTYPE_WWP: + /* format: fromname FE FE FE fromemail FE unknownbyte FE 'Sender IP: xxx.xxx.xxx.xxx' 0D 0A body */ + { + DWORD cbBlob; + PBYTE pBlob, pCurBlob; + + if (nMsgFields < 6) + { + NetLog_Server("Malformed '%s' message", "web pager"); + break; + } + + /*blob is: body(ASCIIZ), name(ASCIIZ), email(ASCIIZ) */ + cbBlob=strlennull(pszMsgField[0])+strlennull(pszMsgField[3])+strlennull(pszMsgField[5])+3; + pCurBlob=pBlob=(PBYTE)_alloca(cbBlob); + strcpy((char *)pCurBlob,pszMsgField[5]); pCurBlob+=strlennull((char *)pCurBlob)+1; + strcpy((char *)pCurBlob,pszMsgField[0]); pCurBlob+=strlennull((char *)pCurBlob)+1; + strcpy((char *)pCurBlob,pszMsgField[3]); + + AddEvent(NULL, ICQEVENTTYPE_WEBPAGER, dwTimestamp, 0, cbBlob, pBlob); + } + break; + + case MTYPE_EEXPRESS: + /* format: fromname FE FE FE fromemail FE unknownbyte FE body */ + { + DWORD cbBlob; + PBYTE pBlob, pCurBlob; + + if (nMsgFields < 6) + { + NetLog_Server("Malformed '%s' message", "e-mail express"); + break; + } + + /*blob is: body(ASCIIZ), name(ASCIIZ), email(ASCIIZ) */ + cbBlob=strlennull(pszMsgField[0])+strlennull(pszMsgField[3])+strlennull(pszMsgField[5])+3; + pCurBlob=pBlob=(PBYTE)_alloca(cbBlob); + strcpy((char *)pCurBlob,pszMsgField[5]); pCurBlob+=strlennull((char *)pCurBlob)+1; + strcpy((char *)pCurBlob,pszMsgField[0]); pCurBlob+=strlennull((char *)pCurBlob)+1; + strcpy((char *)pCurBlob,pszMsgField[3]); + + AddEvent(NULL, ICQEVENTTYPE_EMAILEXPRESS, dwTimestamp, 0, cbBlob, pBlob); + } + break; + + case MTYPE_REQUESTCONTACTS: + /* it's a contacts-request */ + NetLog_Uni(bThruDC, "Received %s from %u", "Request for Contacts", dwUin); + break; + + case MTYPE_GREETINGCARD: + /* it's a greeting card */ + NetLog_Uni(bThruDC, "Received %s from %u", "Greeting Card", dwUin); + break; + + case MTYPE_SCRIPT_NOTIFY: + /* it's a xtraz notify request */ + NetLog_Uni(bThruDC, "Received %s from %u", "Xtraz Notify Request", dwUin); + handleXtrazNotify(dwUin, dwMsgID, dwMsgID2, wCookie, szMsg, wMsgLen, bThruDC); + break; + + case MTYPE_SCRIPT_INVITATION: + /* it's a xtraz invitation to session */ + NetLog_Uni(bThruDC, "Received %s from %u", "Xtraz Invitation", dwUin); + handleXtrazInvitation(dwUin, dwMsgID, dwMsgID2, wCookie, szMsg, wMsgLen, bThruDC); + break; + + case MTYPE_SCRIPT_DATA: + /* it's a xtraz data packet */ + NetLog_Uni(bThruDC, "Received %s from %u", "Xtraz data packet", dwUin); + handleXtrazData(dwUin, dwMsgID, dwMsgID2, wCookie, szMsg, wMsgLen, bThruDC); + break; + + case MTYPE_AUTOONLINE: + case MTYPE_AUTOAWAY: + case MTYPE_AUTOBUSY: + case MTYPE_AUTONA: + case MTYPE_AUTODND: + case MTYPE_AUTOFFC: + { + char **szMsg = MirandaStatusToAwayMsg(AwayMsgTypeToStatus(type)); + if (szMsg) + { + struct rates_status_message_response: public rates_queue_item + { + protected: + virtual rates_queue_item* copyItem(rates_queue_item *aDest = NULL) + { + rates_status_message_response *pDest = (rates_status_message_response*)aDest; + if (!pDest) + pDest = new rates_status_message_response(ppro, wGroup); + + pDest->bExtended = bExtended; + pDest->dwMsgID1 = dwMsgID1; + pDest->dwMsgID2 = dwMsgID2; + pDest->wCookie = wCookie; + pDest->wVersion = wVersion; + pDest->nMsgType = nMsgType; + + return rates_queue_item::copyItem(pDest); + }; + public: + rates_status_message_response(CIcqProto *ppro, WORD wGroup): rates_queue_item(ppro, wGroup) { }; + virtual ~rates_status_message_response() { }; + + virtual void execute() + { + char **pszMsg = ppro->MirandaStatusToAwayMsg(AwayMsgTypeToStatus(nMsgType)); + if (bExtended) + ppro->icq_sendAwayMsgReplyServExt(dwUin, szUid, dwMsgID1, dwMsgID2, wCookie, wVersion, nMsgType, pszMsg); + else if (dwUin) + ppro->icq_sendAwayMsgReplyServ(dwUin, dwMsgID1, dwMsgID2, wCookie, wVersion, (BYTE)nMsgType, pszMsg); + else + ppro->NetLog_Server("Error: Malformed UIN in packet"); + }; + + BOOL bExtended; + DWORD dwMsgID1; + DWORD dwMsgID2; + WORD wCookie; + WORD wVersion; + int nMsgType; + }; + + m_ratesMutex->Enter(); + WORD wGroup = m_rates->getGroupFromSNAC(ICQ_MSG_FAMILY, ICQ_MSG_RESPONSE); + m_ratesMutex->Leave(); + + rates_status_message_response rr(this, wGroup); + rr.bExtended = (nMsgFlags & MTF_STATUS_EXTENDED) == MTF_STATUS_EXTENDED; + rr.hContact = hContact; + rr.dwUin = dwUin; + rr.szUid = szUID; + rr.dwMsgID1 = dwMsgID; + rr.dwMsgID2 = dwMsgID2; + rr.wCookie = wCookie; + rr.wVersion = wVersion; + rr.nMsgType = type; + + handleRateItem(&rr, RQT_RESPONSE); + } + + break; + } + + case MTYPE_FILEREQ: // Never happens + default: + NetLog_Uni(bThruDC, "Unprocessed message type %d", type); + break; + + } + + SAFE_FREE(&szMsg); +} + + +void CIcqProto::handleRecvMsgResponse(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef) +{ + DWORD dwUin; + uid_str szUid; + DWORD dwCookie; + WORD wMessageFormat; + WORD wStatus; + WORD bMsgType = 0; + BYTE bFlags; + WORD wLength; + HANDLE hCookieContact; + DWORD dwMsgID1, dwMsgID2; + WORD wVersion = 0; + cookie_message_data *pCookieData = NULL; + + + unpackLEDWord(&buf, &dwMsgID1); // Message ID + unpackLEDWord(&buf, &dwMsgID2); + wLen -= 8; + + unpackWord(&buf, &wMessageFormat); + wLen -= 2; + if (wMessageFormat != 2) + { + NetLog_Server("SNAC(4.B) Unknown type"); + return; + } + + if (!unpackUID(&buf, &wLen, &dwUin, &szUid)) return; + + HANDLE hContact = HContactFromUID(dwUin, szUid, NULL); + + buf += 2; // 3. unknown + wLen -= 2; + + if (!FindMessageCookie(dwMsgID1, dwMsgID2, &dwCookie, &hCookieContact, &pCookieData)) + { + NetLog_Server("SNAC(4.B) Received an ack that I did not ask for from (%u)", dwUin); + return; + } + + if (IsValidOscarTransfer(pCookieData)) + { // it is OFT response + handleRecvServResponseOFT(buf, wLen, dwUin, szUid, pCookieData); + return; + } + + if (!dwUin) + { // AIM cannot send this - just sanity + NetLog_Server("Error: Invalid UID in message response."); + return; + } + + // Length of sub chunk? + if (wLen >= 2) + { + unpackLEWord(&buf, &wLength); + wLen -= 2; + } + else + wLength = 0; + + if (wLength == 0x1b && pCookieData->bMessageType != MTYPE_REVERSE_REQUEST) + { // this can be v8 greeting message reply + WORD wCookie; + + unpackLEWord(&buf, &wVersion); + buf += 27; /* unknowns from the msg we sent */ + wLen -= 29; + + // Message sequence (SEQ2) + unpackLEWord(&buf, &wCookie); + wLen -= 2; + + // Unknown (12 bytes) + buf += 12; + wLen -= 12; + + // Message type + unpackByte(&buf, (BYTE*)&bMsgType); + unpackByte(&buf, &bFlags); + wLen -= 2; + + // Status + unpackLEWord(&buf, &wStatus); + wLen -= 2; + + // Priority? + buf += 2; + wLen -= 2; + + if (!FindCookie(wCookie, &hCookieContact, (void**)&pCookieData)) + { // use old reliable method + NetLog_Server("Warning: Invalid cookie in %s from (%u)", "message response", dwUin); + + if (pCookieData->bMessageType != MTYPE_AUTOAWAY && bFlags == 3) + { // most probably a broken ack of some kind (e.g. from R&Q), try to fix that + bMsgType = pCookieData->bMessageType; + bFlags = 0; + + NetLog_Server("Warning: Invalid message type in %s from (%u)", "message response", dwUin); + } + } + else if (bMsgType != MTYPE_PLUGIN && pCookieData->bMessageType != MTYPE_AUTOAWAY) + { // just because some clients break it... + dwCookie = wCookie; + + if (bMsgType != pCookieData->bMessageType) + NetLog_Server("Warning: Invalid message type in %s from (%u)", "message response", dwUin); + + bMsgType = pCookieData->bMessageType; + } + else if (pCookieData->bMessageType == MTYPE_AUTOAWAY && bMsgType != MTYPE_PLUGIN) + { + if (bMsgType != pCookieData->nAckType) + NetLog_Server("Warning: Invalid message type in %s from (%u)", "message response", dwUin); + } + } + else + { + bMsgType = pCookieData->bMessageType; + bFlags = 0; + } + + if (hCookieContact != hContact) + { + NetLog_Server("SNAC(4.B) Ack Contact does not match Cookie Contact(0x%x != 0x%x)", hContact, hCookieContact); + + ReleaseCookie(dwCookie); // This could be a bad idea, but I think it is safe + return; + } + + if (bFlags == 3) // A status message reply + { + handleStatusMsgReply("SNAC(4.B) ", hContact, dwUin, wVersion, bMsgType, (WORD)dwCookie, (char*)(buf + 2), 0); + } + else + { // An ack of some kind + int ackType; + + + if (hContact == NULL || hContact == INVALID_HANDLE_VALUE) + { + NetLog_Server("SNAC(4.B) Message from unknown contact (%u)", dwUin); + + ReleaseCookie(dwCookie); // This could be a bad idea, but I think it is safe + return; + } + + switch (bMsgType) { + + case MTYPE_FILEREQ: + { + char* szMsg; + WORD wMsgLen; + + // Message length + unpackLEWord(&buf, &wMsgLen); + wLen -= 2; + szMsg = (char *)_alloca(wMsgLen + 1); + szMsg[wMsgLen] = '\0'; + if (wMsgLen > 0) + { + memcpy(szMsg, buf, wMsgLen); + buf += wMsgLen; + wLen -= wMsgLen; + } + handleFileAck(buf, wLen, dwUin, dwCookie, wStatus, szMsg); + // No success protoack will be sent here, since all file requests + // will have been 'sent' when the server returns its ack + return; + } + + case MTYPE_PLUGIN: + { + WORD wMsgLen; + DWORD dwLengthToEnd; + DWORD dwDataLen; + int typeId; + WORD wFunctionId; + + + if (wLength != 0x1B) + { + NetLog_Server("Invalid Greeting %s", "message response"); + + ReleaseCookie(dwCookie); + return; + } + + NetLog_Server("Parsing Greeting %s", "message response"); + + // Message + unpackLEWord(&buf, &wMsgLen); + wLen -= 2; + buf += wMsgLen; + wLen -= wMsgLen; + + // This packet is malformed. Possibly a file accept from Miranda IM 0.1.2.1 + if (wLen < 20) + { + ReleaseCookie(dwCookie); + return; + } + + if (!unpackPluginTypeId(&buf, &wLen, &typeId, &wFunctionId, FALSE)) + { + ReleaseCookie(dwCookie); + return; + } + + if (wLen < 4) + { + NetLog_Server("Error: Invalid greeting %s", "message response"); + + ReleaseCookie(dwCookie); + return; + } + + // Length of remaining data + unpackLEDWord(&buf, &dwLengthToEnd); + wLen -= 4; + + if (wLen >= 4 && dwLengthToEnd > 0) + unpackLEDWord(&buf, &dwDataLen); // Length of message + else + dwDataLen = 0; + + + switch (typeId) + { + case MTYPE_PLAIN: + if (pCookieData && pCookieData->bMessageType == MTYPE_AUTOAWAY && dwLengthToEnd >= 4) + { // ICQ 6 invented this + char *szMsg = (char*)_alloca(dwDataLen + 1); + + if (dwDataLen > 0) + memcpy(szMsg, buf, dwDataLen); + szMsg[dwDataLen] = '\0'; + handleStatusMsgReply("SNAC(4.B) ", hContact, dwUin, wVersion, pCookieData->nAckType, (WORD)dwCookie, szMsg, 0); + + ReleaseCookie(dwCookie); + return; + } + else + ackType = ACKTYPE_MESSAGE; + break; + + case MTYPE_URL: + ackType = ACKTYPE_URL; + break; + + case MTYPE_CONTACTS: + ackType = ACKTYPE_CONTACTS; + break; + + case MTYPE_FILEREQ: + { + NetLog_Server("This is file ack"); + + char *szMsg = (char *)_alloca(dwDataLen + 1); + + if (dwDataLen > 0) + memcpy(szMsg, buf, dwDataLen); + szMsg[dwDataLen] = '\0'; + buf += dwDataLen; + wLen -= (WORD)dwDataLen; + + handleFileAck(buf, wLen, dwUin, dwCookie, wStatus, szMsg); + // No success protoack will be sent here, since all file requests + // will have been 'sent' when the server returns its ack + } + return; + + case MTYPE_SCRIPT_NOTIFY: + { + char *szMsg = (char*)_alloca(dwDataLen + 1); + + if (dwDataLen > 0) + memcpy(szMsg, buf, dwDataLen); + szMsg[dwDataLen] = '\0'; + + handleXtrazNotifyResponse(dwUin, hContact, (WORD)dwCookie, szMsg, dwDataLen); + + ReleaseCookie(dwCookie); + } + return; + + case MTYPE_STATUSMSGEXT: + { // handle Away Message response (ICQ 6) + char *szMsg = (char*)SAFE_MALLOC(dwDataLen + 1); + + if (dwDataLen > 0) + memcpy(szMsg, buf, dwDataLen); + szMsg[dwDataLen] = '\0'; + szMsg = EliminateHtml(szMsg, dwDataLen); + + handleStatusMsgReply("SNAC(4.B) ", hContact, dwUin, wVersion, pCookieData->nAckType, (WORD)dwCookie, szMsg, MTF_PLUGIN | MTF_STATUS_EXTENDED); + + SAFE_FREE(&szMsg); + + ReleaseCookie(dwCookie); + } + return; + + default: + NetLog_Server("Error: Unknown plugin message response, type %d.", typeId); + return; + } + } + break; + + case MTYPE_PLAIN: + ackType = ACKTYPE_MESSAGE; + break; + + case MTYPE_URL: + ackType = ACKTYPE_URL; + break; + + case MTYPE_AUTHOK: + case MTYPE_AUTHDENY: + ackType = ACKTYPE_AUTHREQ; + break; + + case MTYPE_ADDED: + ackType = ACKTYPE_ADDED; + break; + + case MTYPE_CONTACTS: + ackType = ACKTYPE_CONTACTS; + break; + + case MTYPE_REVERSE_REQUEST: + { + cookie_reverse_connect *pReverse = (cookie_reverse_connect*)pCookieData; + + if (pReverse->ft) + { + filetransfer *ft = (filetransfer*)pReverse->ft; + + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0); + } + NetLog_Server("Reverse Connect request failed"); + // Set DC status to failed + setSettingByte(hContact, "DCStatus", 2); + + ReleaseCookie(dwCookie); + } + return; + + case MTYPE_CHAT: + default: + NetLog_Server("SNAC(4.B) Unknown message type (%u) in switch", bMsgType); + return; + } + + if ((ackType == MTYPE_PLAIN && pCookieData && (pCookieData->nAckType == ACKTYPE_CLIENT)) || ackType != MTYPE_PLAIN) + { + BroadcastAck(hContact, ackType, ACKRESULT_SUCCESS, (HANDLE)(WORD)dwCookie, 0); + } + } + + ReleaseCookie(dwCookie); +} + + +// A response to a CLI_SENDMSG +void CIcqProto::handleRecvServMsgError(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwSequence) +{ + WORD wError; + char *pszErrorMessage; + HANDLE hContact; + cookie_message_data *pCookieData = NULL; + int nMessageType; + + + if (wLen < 2) + return; + + if (FindCookie((WORD)dwSequence, &hContact, (void**)&pCookieData)) + { // all packet cookies from msg family has command 0 in the queue + DWORD dwUin; + uid_str szUid; + + if (getContactUid(hContact, &dwUin, &szUid)) + { // Invalid contact + FreeCookie((WORD)dwSequence); + return; + } + + // Error code + unpackWord(&buf, &wError); + + if (wError == 9 && pCookieData->bMessageType == MTYPE_AUTOAWAY) + { // we failed to request away message the normal way, try it AIM way + icq_packet packet; + + serverPacketInit(&packet, (WORD)(13 + getUINLen(dwUin))); + packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_REQ_USER_INFO, 0, (WORD)dwSequence); + packWord(&packet, 0x03); + packUIN(&packet, dwUin); + + sendServPacket(&packet); + return; + } + + // Not all of these are actually used in family 4 + // This will be moved into a special error handling function later + switch (wError) { + + case 0x0002: // Server rate limit exceeded + pszErrorMessage = Translate("You are sending too fast. Wait a while and try again.\r\nSNAC(4.1) Error x02"); + break; + + case 0x0003: // Client rate limit exceeded + pszErrorMessage = Translate("You are sending too fast. Wait a while and try again.\r\nSNAC(4.1) Error x03"); + break; + + case 0x0004: // Recipient is not logged in (resend in a offline message) + if (pCookieData->bMessageType == MTYPE_PLAIN) + { + if (pCookieData->isOffline) + { // offline failed - most probably to AIM contact + pszErrorMessage = Translate("The contact does not support receiving offline messages."); + break; + } + // TODO: this needs better solution + setSettingWord(hContact, "Status", ID_STATUS_OFFLINE); + } + pszErrorMessage = Translate("The user has logged off. Select 'Retry' to send an offline message.\r\nSNAC(4.1) Error x04"); + break; + + case 0x0005: // Requested service unavailable + pszErrorMessage = Translate("The messaging service is temporarily unavailable. Wait a while and try again.\r\nSNAC(4.1) Error x05"); + break; + + case 0x0009: // Not supported by client (resend in a simpler format) + pszErrorMessage = Translate("The receiving client does not support this type of message.\r\nSNAC(4.1) Error x09"); + break; + + case 0x000A: // Refused by client + pszErrorMessage = Translate("You sent too long message. The receiving client does not support it.\r\nSNAC(4.1) Error x0A"); + break; + + case 0x000E: // Incorrect SNAC format + pszErrorMessage = Translate("The SNAC format was rejected by the server.\nSNAC(4.1) Error x0E"); + break; + + case 0x0013: // User temporarily unavailable + pszErrorMessage = Translate("The user is temporarily unavailable. Wait a while and try again.\r\nSNAC(4.1) Error x13"); + break; + + case 0x0001: // Invalid SNAC header + case 0x0006: // Requested service not defined + case 0x0007: // You sent obsolete SNAC + case 0x0008: // Not supported by server + case 0x000B: // Reply too big + case 0x000C: // Responses lost + case 0x000D: // Request denied + case 0x000F: // Insufficient rights + case 0x0010: // In local permit/deny (recipient blocked) + case 0x0011: // Sender too evil + case 0x0012: // Receiver too evil + case 0x0014: // No match + case 0x0015: // List overflow + case 0x0016: // Request ambiguous + case 0x0017: // Server queue full + case 0x0018: // Not while on AOL + default: + if (pszErrorMessage = (char*)_alloca(256)) + null_snprintf(pszErrorMessage, 256, Translate("SNAC(4.1) SENDMSG Error (x%02x)"), wError); + break; + } + + + switch (pCookieData->bMessageType) { + + case MTYPE_PLAIN: + nMessageType = ACKTYPE_MESSAGE; + break; + + case MTYPE_CHAT: + nMessageType = ACKTYPE_CHAT; + break; + + case MTYPE_FILEREQ: + nMessageType = ACKTYPE_FILE; + break; + + case MTYPE_URL: + nMessageType = ACKTYPE_URL; + break; + + case MTYPE_CONTACTS: + nMessageType = ACKTYPE_CONTACTS; + break; + + default: + nMessageType = -1; + break; + } + + if (nMessageType != -1) + { + BroadcastAck(hContact, nMessageType, ACKRESULT_FAILED, (HANDLE)(WORD)dwSequence, (LPARAM)pszErrorMessage); + } + else + { + NetLog_Server("Error: Message delivery to %u failed: %s", dwUin, pszErrorMessage); + } + + FreeCookie((WORD)dwSequence); + + if (pCookieData->bMessageType != MTYPE_FILEREQ) + SAFE_FREE((void**)&pCookieData); + } + else + { + unpackWord(&buf, &wError); + + LogFamilyError(ICQ_MSG_FAMILY, wError); + } +} + + +void CIcqProto::handleServerAck(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwSequence) +{ + DWORD dwUin; + uid_str szUID; + WORD wChannel; + cookie_message_data *pCookieData; + + + if (wLen < 13) + { + NetLog_Server("Ignoring SNAC(4,C) Packet to short"); + return; + } + + buf += 8; // Skip first 8 bytes + wLen -= 8; + + // Message channel + unpackWord(&buf, &wChannel); + wLen -= 2; + + // Sender + if (!unpackUID(&buf, &wLen, &dwUin, &szUID)) return; + + HANDLE hContact = HContactFromUID(dwUin, szUID, NULL); + + if (FindCookie((WORD)dwSequence, NULL, (void**)&pCookieData)) + { + // If the user requested a full ack, the + // server ack should be ignored here. + if (pCookieData && (pCookieData->nAckType == ACKTYPE_SERVER)) + { + if ((hContact != NULL) && (hContact != INVALID_HANDLE_VALUE)) + { + int ackType; + int ackRes = ACKRESULT_SUCCESS; + + switch (pCookieData->bMessageType) { + case MTYPE_PLAIN: + ackType = ACKTYPE_MESSAGE; + break; + + case MTYPE_CONTACTS: + ackType = ACKTYPE_CONTACTS; + break; + + case MTYPE_URL: + ackType = ACKTYPE_URL; + break; + + case MTYPE_FILEREQ: + ackType = ACKTYPE_FILE; + ackRes = ACKRESULT_SENTREQUEST; + // Note 1: We are not allowed to free the cookie here because it + // contains the filetransfer struct that we will need later + // Note 2: The cookiedata is NOT a message_cookie_data*, it is a + // filetransfer*. IMPORTANT! (it's one of those silly things) + break; + + default: + ackType = -1; + NetLog_Server("Error: Unknown message type %d in ack", pCookieData->bMessageType); + break; + } + if (ackType != -1) + BroadcastAck(hContact, ackType, ackRes, (HANDLE)(WORD)dwSequence, 0); + + if (pCookieData->bMessageType != MTYPE_FILEREQ) + SAFE_FREE((void**)&pCookieData); // this could be a bad idea, but I think it is safe + } + FreeCookie((WORD)dwSequence); + } + else if (pCookieData && (pCookieData->nAckType == ACKTYPE_CLIENT)) + NetLog_Server("Received a server ack, waiting for client ack."); + else + NetLog_Server("Ignored a server ack I did not ask for"); + } +} + + +void CIcqProto::handleMissedMsg(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef) +{ + DWORD dwUin; + uid_str szUid; + WORD wChannel; + WORD wWarningLevel; + WORD wCount; + WORD wError; + WORD wTLVCount; + + if (wLen < 14) + return; // Too short + + // Message channel? + unpackWord(&buf, &wChannel); + wLen -= 2; + + // Sender + if (!unpackUID(&buf, &wLen, &dwUin, &szUid)) return; + + if (wLen < 8) + return; // Too short + + // Warning level? + unpackWord(&buf, &wWarningLevel); + wLen -= 2; + + // TLV count + unpackWord(&buf, &wTLVCount); + wLen -= 2; + + // Read past user info TLVs + oscar_tlv_chain *pChain = readIntoTLVChain(&buf, (WORD)(wLen-4), wTLVCount); + if (pChain) + disposeChain(&pChain); + + if (wLen < 4) + return; // Too short + + // Number of missed messages + unpackWord(&buf, &wCount); + wLen -= 2; + + // Error code + unpackWord(&buf, &wError); + wLen -= 2; + + { // offline retrieval process in progress, note that we received missed message notification + cookie_offline_messages *cookie; + + if (FindCookieByType(CKT_OFFLINEMESSAGE, NULL, NULL, (void**)&cookie)) + cookie->nMissed++; + } + + switch (wError) { + + case 0: // The message was invalid + case 1: // The message was too long + case 2: // The sender has flooded the server + case 4: // You are too evil + break; + + default: + // 3 = Sender too evil (sender warn level > your max_msg_sevil) + return; + break; + } + + // Create event to notify user + int bAdded; + + AddEvent(HContactFromUID(dwUin, szUid, &bAdded), ICQEVENTTYPE_MISSEDMESSAGE, time(NULL), 0, sizeof(wError), (PBYTE)&wError); +} + + +void CIcqProto::handleOffineMessagesReply(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef) +{ + cookie_offline_messages *cookie; + + if (FindCookie(dwRef, NULL, (void**)&cookie)) + { + NetLog_Server("End of offline msgs, %u received", cookie->nMessages); + if (cookie->nMissed) + { // NASTY WORKAROUND!! + // The ICQ server has a bug that causes offline messages to be received again and again when some + // missed message notification is present (most probably it is not processed correctly and causes + // the server to fail the purging process); try to purge them using the old offline messages + // protocol. 2008/05/21 + NetLog_Server("Warning: Received %u missed message notifications, trying to fix the server.", cookie->nMissed); + + icq_packet packet; + // This will delete the messages stored on server + serverPacketInit(&packet, 24); + packFNACHeader(&packet, ICQ_EXTENSIONS_FAMILY, ICQ_META_CLI_REQUEST); + packWord(&packet, 1); // TLV Type + packWord(&packet, 10); // TLV Length + packLEWord(&packet, 8); // Data length + packLEDWord(&packet, m_dwLocalUIN); // My UIN + packLEWord(&packet, CLI_DELETE_OFFLINE_MSGS_REQ); // Ack offline msgs + packLEWord(&packet, 0x0000); // Request sequence number (we dont use this for now) + + // Send it + sendServPacket(&packet); + } + + ReleaseCookie(dwRef); + } + else + NetLog_Server("Error: Received unexpected end of offline msgs."); +} + + +void CIcqProto::handleTypingNotification(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef) +{ + DWORD dwUin; + uid_str szUid; + WORD wChannel; + WORD wNotification; + + if (wLen < 14) + { + NetLog_Server("Ignoring SNAC(4.x11) Packet to short"); + return; + } + +#ifndef DBG_CAPMTN + { + NetLog_Server("Ignoring unexpected typing notification"); + return; + } +#endif + + // The message ID, unused? + buf += 8; + wLen -= 8; + + // Message channel, unused? + unpackWord(&buf, &wChannel); + wLen -= 2; + + // Sender + if (!unpackUID(&buf, &wLen, &dwUin, &szUid)) return; + + HANDLE hContact = HContactFromUID(dwUin, szUid, NULL); + + if (hContact == INVALID_HANDLE_VALUE) return; + + // Typing notification code + unpackWord(&buf, &wNotification); + wLen -= 2; + + SetContactCapabilities(hContact, CAPF_TYPING); + + // Notify user + switch (wNotification) { + + case MTN_FINISHED: + case MTN_TYPED: + CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hContact, (LPARAM)PROTOTYPE_CONTACTTYPING_OFF); + NetLog_Server("%s has stopped typing (ch %u).", strUID(dwUin, szUid), wChannel); + break; + + case MTN_BEGUN: + CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hContact, (LPARAM)60); + NetLog_Server("%s is typing a message (ch %u).", strUID(dwUin, szUid), wChannel); + break; + + case MTN_WINDOW_CLOSED: + { + char szFormat[MAX_PATH]; + char szMsg[MAX_PATH]; + char *nick = NickFromHandleUtf(hContact); + + null_snprintf(szMsg, MAX_PATH, ICQTranslateUtfStatic(LPGEN("Contact \"%s\" has closed the message window."), szFormat, MAX_PATH), nick); + ShowPopUpMsg(hContact, ICQTranslateUtfStatic(LPGEN("ICQ Note"), szFormat, MAX_PATH), szMsg, LOG_NOTE); + SAFE_FREE((void**)&nick); + + NetLog_Server("%s has closed the message window.", strUID(dwUin, szUid)); + } + break; + + default: + NetLog_Server("Unknown typing notification from %s, type %u (ch %u)", strUID(dwUin, szUid), wNotification, wChannel); + break; + } +} + + +void CIcqProto::sendTypingNotification(HANDLE hContact, WORD wMTNCode) +{ + _ASSERTE((wMTNCode == MTN_FINISHED) || (wMTNCode == MTN_TYPED) || (wMTNCode == MTN_BEGUN) || (wMTNCode == MTN_WINDOW_CLOSED)); + + DWORD dwUin; + uid_str szUid; + if (getContactUid(hContact, &dwUin, &szUid)) + return; // Invalid contact + + WORD wLen = getUIDLen(dwUin, szUid); + + icq_packet packet; + serverPacketInit(&packet, 23 + wLen); + packFNACHeader(&packet, ICQ_MSG_FAMILY, ICQ_MSG_MTN); + packLEDWord(&packet, 0x0000); // Msg ID + packLEDWord(&packet, 0x0000); // Msg ID + packWord(&packet, 0x01); // Channel + packUID(&packet, dwUin, szUid); // User ID + packWord(&packet, wMTNCode); // Notification type + + sendServPacketAsync(&packet); +} diff --git a/protocols/IcqOscarJ/src/fam_09bos.cpp b/protocols/IcqOscarJ/src/fam_09bos.cpp new file mode 100644 index 0000000000..327e045a96 --- /dev/null +++ b/protocols/IcqOscarJ/src/fam_09bos.cpp @@ -0,0 +1,106 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2008 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +void CIcqProto::handleBosFam(unsigned char *pBuffer, WORD wBufferLength, snac_header* pSnacHeader) +{ + switch (pSnacHeader->wSubtype) { + + case ICQ_PRIVACY_RIGHTS_REPLY: // Reply to CLI_REQBOS + handlePrivacyRightsReply(pBuffer, wBufferLength); + break; + + case ICQ_ERROR: + { + WORD wError; + + if (wBufferLength >= 2) + unpackWord(&pBuffer, &wError); + else + wError = 0; + + LogFamilyError(ICQ_BOS_FAMILY, wError); + break; + } + + default: + NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_BOS_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + + } +} + +void CIcqProto::handlePrivacyRightsReply(unsigned char *pBuffer, WORD wBufferLength) +{ + if (wBufferLength >= 12) + { + oscar_tlv_chain* pChain = readIntoTLVChain(&pBuffer, wBufferLength, 0); + if (pChain) + { + WORD wMaxVisibleContacts; + WORD wMaxInvisibleContacts; + WORD wMaxTemporaryVisibleContacts; + + wMaxVisibleContacts = pChain->getWord(0x0001, 1); + wMaxInvisibleContacts = pChain->getWord(0x0002, 1); + wMaxTemporaryVisibleContacts = pChain->getWord(0x0003, 1); + + disposeChain(&pChain); + + NetLog_Server("PRIVACY: Max visible %u, max invisible %u, max temporary visible %u items.", wMaxVisibleContacts, wMaxInvisibleContacts, wMaxTemporaryVisibleContacts); + + // Success + return; + } + } + + // Failure + NetLog_Server("Warning: Malformed SRV_PRIVACY_RIGHTS_REPLY"); +} + +void CIcqProto::makeContactTemporaryVisible(HANDLE hContact) +{ + DWORD dwUin; + uid_str szUid; + + if (getSettingByte(hContact, "TemporaryVisible", 0)) + return; // already there + + if (getContactUid(hContact, &dwUin, &szUid)) + return; // Invalid contact + + icq_sendGenericContact(dwUin, szUid, ICQ_BOS_FAMILY, ICQ_CLI_ADDTEMPVISIBLE); + + setSettingByte(hContact, "TemporaryVisible", 1); + +#ifdef _DEBUG + NetLog_Server("Added contact %s to temporary visible list", strUID(dwUin, szUid)); +#endif +} diff --git a/protocols/IcqOscarJ/src/fam_0alookup.cpp b/protocols/IcqOscarJ/src/fam_0alookup.cpp new file mode 100644 index 0000000000..da77295ecb --- /dev/null +++ b/protocols/IcqOscarJ/src/fam_0alookup.cpp @@ -0,0 +1,130 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +void CIcqProto::handleLookupFam(BYTE *pBuffer, WORD wBufferLength, snac_header* pSnacHeader) +{ + switch (pSnacHeader->wSubtype) { + + case ICQ_LOOKUP_EMAIL_REPLY: // AIM search reply + handleLookupEmailReply(pBuffer, wBufferLength, pSnacHeader->dwRef); + break; + + case ICQ_ERROR: + { + WORD wError; + cookie_search *pCookie; + + if (wBufferLength >= 2) + unpackWord(&pBuffer, &wError); + else + wError = 0; + + if (FindCookie(pSnacHeader->dwRef, NULL, (void**)&pCookie)) + { + if (wError == 0x14) + NetLog_Server("Lookup: No results"); + + ReleaseLookupCookie(pSnacHeader->dwRef, pCookie); + + if (wError == 0x14) return; + } + + LogFamilyError(ICQ_LOOKUP_FAMILY, wError); + break; + } + + default: + NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_LOOKUP_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + } +} + +void CIcqProto::ReleaseLookupCookie(DWORD dwCookie, cookie_search *pCookie) +{ + FreeCookie(dwCookie); + SAFE_FREE(&pCookie->szObject); + + if (pCookie->dwMainId && !pCookie->dwStatus) + { // we need to wait for main search + pCookie->dwStatus = 1; + } + else + { // finish everything + if (pCookie->dwMainId) + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)pCookie->dwMainId, 0); + else // we are single + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0); + + SAFE_FREE((void**)&pCookie); + } +} + +void CIcqProto::handleLookupEmailReply(BYTE* buf, WORD wLen, DWORD dwCookie) +{ + ICQSEARCHRESULT sr = {0}; + oscar_tlv_chain *pChain; + cookie_search *pCookie; + + if (!FindCookie(dwCookie, NULL, (void**)&pCookie)) + { + NetLog_Server("Error: Received unexpected lookup reply"); + return; + } + + NetLog_Server("SNAC(0x0A,0x3): Lookup reply"); + + sr.hdr.cbSize = sizeof(sr); + sr.hdr.flags = PSR_TCHAR; + sr.hdr.email = ansi_to_tchar(pCookie->szObject); + + // Syntax check, read chain + if (wLen >= 4 && (pChain = readIntoTLVChain(&buf, wLen, 0))) + { + for (WORD i = 1; TRUE; i++) + { // collect the results + char *szUid = pChain->getString(0x01, i); + if (!szUid) break; + sr.hdr.id = ansi_to_tchar(szUid); + sr.hdr.nick = sr.hdr.id; + // broadcast the result + if (pCookie->dwMainId) + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)pCookie->dwMainId, (LPARAM)&sr); + else + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)dwCookie, (LPARAM)&sr); + SAFE_FREE(&sr.hdr.id); + SAFE_FREE(&szUid); + } + disposeChain(&pChain); + } + SAFE_FREE(&sr.hdr.email); + + ReleaseLookupCookie(dwCookie, pCookie); +} diff --git a/protocols/IcqOscarJ/src/fam_0bstatus.cpp b/protocols/IcqOscarJ/src/fam_0bstatus.cpp new file mode 100644 index 0000000000..7a979007e2 --- /dev/null +++ b/protocols/IcqOscarJ/src/fam_0bstatus.cpp @@ -0,0 +1,62 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000,2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001,2002 Jon Keating, Richard Hughes +// Copyright © 2002,2003,2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004,2005,2006 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +void CIcqProto::handleStatusFam(unsigned char *pBuffer, WORD wBufferLength, snac_header* pSnacHeader) +{ + switch (pSnacHeader->wSubtype) { + + case ICQ_STATS_MINREPORTINTERVAL: + { + WORD wInterval; + unpackWord(&pBuffer, &wInterval); + NetLog_Server("Server sent SNAC(x0B,x02) - SRV_SET_MINREPORTINTERVAL (Value: %u hours)", wInterval); + } + break; + + case ICQ_ERROR: + { + WORD wError; + + if (wBufferLength >= 2) + unpackWord(&pBuffer, &wError); + else + wError = 0; + + LogFamilyError(ICQ_STATS_FAMILY, wError); + break; + } + + default: + NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_STATS_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + } +} diff --git a/protocols/IcqOscarJ/src/fam_13servclist.cpp b/protocols/IcqOscarJ/src/fam_13servclist.cpp new file mode 100644 index 0000000000..5df30ee5ec --- /dev/null +++ b/protocols/IcqOscarJ/src/fam_13servclist.cpp @@ -0,0 +1,2068 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +static int unpackServerListItem(BYTE **pbuf, WORD *pwLen, char *pszRecordName, WORD *pwGroupId, WORD *pwItemId, WORD *pwItemType, WORD *pwTlvLength); + + +void CIcqProto::handleServCListFam(BYTE *pBuffer, WORD wBufferLength, snac_header* pSnacHeader, serverthread_info *info) +{ + switch (pSnacHeader->wSubtype) { + + case ICQ_LISTS_ACK: // UPDATE_ACK + if (wBufferLength >= 2) + { + WORD wError; + cookie_servlist_action* sc; + + unpackWord(&pBuffer, &wError); + + if (FindCookie(pSnacHeader->dwRef, NULL, (void**)&sc)) + { // look for action cookie +#ifdef _DEBUG + NetLog_Server("Received expected server list ack, action: %d, result: %d", sc->dwAction, wError); +#endif + FreeCookie(pSnacHeader->dwRef); // release cookie + + if (sc->dwAction == SSA_ACTION_GROUP) + { // group cookie, handle sub-items + int i; + +#ifdef _DEBUG + NetLog_Server("Server-List: Grouped action contains %d actions.", sc->dwGroupCount); +#endif + pBuffer -= 2; // revoke unpack + if (wBufferLength != 2*sc->dwGroupCount) + NetLog_Server("Error: Server list ack does not contain expected amount of result codes (%u != %u)", wBufferLength/2, sc->dwGroupCount); + + for (i = 0; i < sc->dwGroupCount; i++) + { + if (wBufferLength >= 2) + { // get proper result code + unpackWord(&pBuffer, &wError); + wBufferLength -= 2; + } + else // missing result code, give some special + wError = -1; + +#ifdef _DEBUG + NetLog_Server("Action: %d, ack result: %d", sc->pGroupItems[i]->dwAction, wError); +#endif + // call normal ack handler + handleServerCListAck(sc->pGroupItems[i], wError); + } + // Release cookie + SAFE_FREE((void**)&sc->pGroupItems); + SAFE_FREE((void**)&sc); + } + else // single ack + handleServerCListAck(sc, wError); + } + else + { + NetLog_Server("Received unexpected server list ack %u", wError); + } + } + break; + + case ICQ_LISTS_SRV_REPLYLISTS: + { /* received server-list rights */ + handleServerCListRightsReply(pBuffer, wBufferLength); + +#ifdef _DEBUG + NetLog_Server("Server sent SNAC(x13,x03) - SRV_REPLYLISTS"); +#endif + } + break; + + case ICQ_LISTS_LIST: // SRV_REPLYROSTER + { + cookie_servlist_action* sc; + BOOL blWork; + + blWork = bIsSyncingCL; + bIsSyncingCL = TRUE; // this is not used if cookie takes place + + if (FindCookie(pSnacHeader->dwRef, NULL, (void**)&sc)) + { // we do it by reliable cookie + if (!sc->lParam) + { // is this first packet ? + ResetSettingsOnListReload(); + sc->lParam = 1; + } + handleServerCListReply(pBuffer, wBufferLength, pSnacHeader->wFlags, info); + if (!(pSnacHeader->wFlags & 0x0001)) + { // was that last packet ? + ReleaseCookie(pSnacHeader->dwRef); // yes, release cookie + } + } + else + { // use old fake + if (!blWork) + { // this can fail on some crazy situations + ResetSettingsOnListReload(); + } + handleServerCListReply(pBuffer, wBufferLength, pSnacHeader->wFlags, info); + } + break; + } + + case ICQ_LISTS_UPTODATE: // SRV_REPLYROSTEROK + { + cookie_servlist_action* sc; + + bIsSyncingCL = FALSE; + + if (FindCookie(pSnacHeader->dwRef, NULL, (void**)&sc)) + { // we requested servlist check +#ifdef _DEBUG + NetLog_Server("Server stated roster is ok."); +#endif + ReleaseCookie(pSnacHeader->dwRef); + LoadServerIDs(); + } + else + NetLog_Server("Server sent unexpected SNAC(x13,x0F) - SRV_REPLYROSTEROK"); + + // This will activate the server side list + sendRosterAck(); // this must be here, cause of failures during cookie alloc + handleServUINSettings(wListenPort, info); + break; + } + + case ICQ_LISTS_CLI_MODIFYSTART: + NetLog_Server("Server sent SNAC(x13,x%02x) - %s", ICQ_LISTS_CLI_MODIFYSTART, "Server is modifying contact list"); + break; + + case ICQ_LISTS_CLI_MODIFYEND: + NetLog_Server("Server sent SNAC(x13,x%02x) - %s", ICQ_LISTS_CLI_MODIFYEND, "End of server modification"); + break; + + case ICQ_LISTS_ADDTOLIST: + case ICQ_LISTS_UPDATEGROUP: + case ICQ_LISTS_REMOVEFROMLIST: + { + int nItems = 0; + + while (wBufferLength >= 10) + { + WORD wGroupId, wItemId, wItemType, wTlvLen; + uid_str szRecordName; + + if (unpackServerListItem(&pBuffer, &wBufferLength, szRecordName, &wGroupId, &wItemId, &wItemType, &wTlvLen)) + { + BYTE *buf = pBuffer; + oscar_tlv_chain *pChain = NULL; + + nItems++; + + // parse possible item's data + if (wBufferLength >= wTlvLen && wTlvLen > 0) + { + pChain = readIntoTLVChain(&buf, wTlvLen, 0); + pBuffer += wTlvLen; + wBufferLength -= wTlvLen; + } + else if (wTlvLen > 0) + wBufferLength = 0; + + // process item change + if (pSnacHeader->wSubtype == ICQ_LISTS_ADDTOLIST) + handleServerCListItemAdd(szRecordName, wGroupId, wItemId, wItemType, pChain); + else if (pSnacHeader->wSubtype == ICQ_LISTS_UPDATEGROUP) + handleServerCListItemUpdate(szRecordName, wGroupId, wItemId, wItemType, pChain); + else if (pSnacHeader->wSubtype == ICQ_LISTS_REMOVEFROMLIST) + handleServerCListItemDelete(szRecordName, wGroupId, wItemId, wItemType, pChain); + + // release memory + disposeChain(&pChain); + } + } + { // log packet basics + char *szChange; + char szLogText[MAX_PATH]; + + if (pSnacHeader->wSubtype == ICQ_LISTS_ADDTOLIST) + szChange = "Server added %u item(s) to list"; + else if (pSnacHeader->wSubtype == ICQ_LISTS_UPDATEGROUP) + szChange = "Server updated %u item(s) on list"; + else if (pSnacHeader->wSubtype == ICQ_LISTS_REMOVEFROMLIST) + szChange = "Server removed %u item(s) from list"; + + null_snprintf(szLogText, MAX_PATH, szChange, nItems); + NetLog_Server("Server sent SNAC(x13,x%02x) - %s", pSnacHeader->wSubtype, szLogText); + } + } + break; + + case ICQ_LISTS_AUTHREQUEST: + handleRecvAuthRequest(pBuffer, wBufferLength); + break; + + case ICQ_LISTS_SRV_AUTHRESPONSE: + handleRecvAuthResponse(pBuffer, wBufferLength); + break; + + case ICQ_LISTS_AUTHGRANTED: + NetLog_Server("Server sent SNAC(x13,x%02x) - %s", ICQ_LISTS_AUTHGRANTED, "User granted us future authorization"); + break; + + case ICQ_LISTS_YOUWEREADDED: + handleRecvAdded(pBuffer, wBufferLength); + break; + + case ICQ_LISTS_ERROR: + if (wBufferLength >= 2) + { + WORD wError; + cookie_servlist_action* sc; + + unpackWord(&pBuffer, &wError); + + if (FindCookie(pSnacHeader->dwRef, NULL, (void**)&sc)) + { // look for action cookie +#ifdef _DEBUG + NetLog_Server("Received server list error, action: %d, result: %d", sc->dwAction, wError); +#endif + FreeCookie(pSnacHeader->dwRef); // release cookie + + if (sc->dwAction==SSA_CHECK_ROSTER) + { // the serv-list is unavailable turn it off + icq_LogMessage(LOG_ERROR, LPGEN("Server contact list is unavailable, Miranda will use local contact list.")); + m_bSsiEnabled = 0; + handleServUINSettings(wListenPort, info); + } + /// FIXME: properly release pending operations & cookie memory + SAFE_FREE((void**)&sc); + } + else + { + LogFamilyError(ICQ_LISTS_FAMILY, wError); + } + } + break; + + default: + NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_LISTS_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + } +} + + +static int unpackServerListItem(BYTE **pbuf, WORD *pwLen, char *pszRecordName, WORD *pwGroupId, WORD *pwItemId, WORD *pwItemType, WORD *pwTlvLength) +{ + WORD wRecordNameLen; + + // The name of the entry. If this is a group header, then this + // is the name of the group. If it is a plain contact list entry, + // then it's the UIN of the contact. + unpackWord(pbuf, &wRecordNameLen); + if (*pwLen < 10 + wRecordNameLen || wRecordNameLen >= MAX_PATH) + return 0; // Failure + + unpackString(pbuf, pszRecordName, wRecordNameLen); + if (pszRecordName) + pszRecordName[wRecordNameLen] = '\0'; + + // The group identifier this entry belongs to. If 0, this is meta information or + // a contact without a group + unpackWord(pbuf, pwGroupId); + + // The ID of this entry. Group headers have ID 0. Otherwise, this + // is a random number generated when the user is added to the + // contact list, or when the user is ignored. See CLI_ADDBUDDY. + unpackWord(pbuf, pwItemId); + + // This field indicates what type of entry this is + unpackWord(pbuf, pwItemType); + + // The length in bytes of the following TLV chain + unpackWord(pbuf, pwTlvLength); + + *pwLen -= wRecordNameLen + 10; + + return 1; // Success +} + + +void CIcqProto::handleServerCListRightsReply(BYTE *buf, WORD wLen) +{ /* received list rights, store the item limits for future use */ + oscar_tlv_chain* chain; + + memset(m_wServerListLimits, -1, sizeof(m_wServerListLimits)); + m_wServerListGroupMaxContacts = 0; + m_wServerListRecordNameMaxLength = 0xFFFF; + + if (chain = readIntoTLVChain(&buf, wLen, 0)) + { + oscar_tlv* pTLV; + + // determine max number of contacts in a group + m_wServerListGroupMaxContacts = chain->getWord(0x0C, 1); + // determine length limit for server-list item's name + m_wServerListRecordNameMaxLength = chain->getWord(0x06, 1); + + if (pTLV = chain->getTLV(0x04, 1)) + { // limits for item types + int i; + WORD *pLimits = (WORD*)pTLV->pData; + + for (i = 0; i < pTLV->wLen / 2; i++) + { + m_wServerListLimits[i] = (pLimits[i] & 0xFF) << 8 | (pLimits[i] >> 8); + + if (i + 1 >= SIZEOF(m_wServerListLimits)) break; + } + + NetLog_Server("SSI: Max %d contacts (%d per group), %d groups, %d permit, %d deny, %d ignore items.", m_wServerListLimits[SSI_ITEM_BUDDY], m_wServerListGroupMaxContacts, m_wServerListLimits[SSI_ITEM_GROUP], m_wServerListLimits[SSI_ITEM_PERMIT], m_wServerListLimits[SSI_ITEM_DENY], m_wServerListLimits[SSI_ITEM_IGNORE]); + } + + disposeChain(&chain); + } +} + + +DWORD CIcqProto::updateServerGroupData(WORD wGroupId, void *groupData, int groupSize, DWORD dwOperationFlags) +{ + DWORD dwCookie; + cookie_servlist_action* ack; + + ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + if (!ack) + { + NetLog_Server("Updating of group on server list failed (malloc error)"); + return 0; + } + ack->dwAction = SSA_GROUP_UPDATE; + ack->szGroupName = getServListGroupName(wGroupId); + ack->wGroupId = wGroupId; + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack); + + return icq_sendServerGroup(dwCookie, ICQ_LISTS_UPDATEGROUP, ack->wGroupId, ack->szGroupName, groupData, groupSize, dwOperationFlags); +} + + +void CIcqProto::handleServerCListAck(cookie_servlist_action* sc, WORD wError) +{ + switch (sc->dwAction) + { + case SSA_VISIBILITY: + { + if (wError) + NetLog_Server("Server visibility update failed, error %d", wError); + break; + } + case SSA_CONTACT_UPDATE: + { + servlistPendingRemoveContact(sc->hContact, sc->wContactId, sc->wGroupId, wError ? PENDING_RESULT_FAILED : PENDING_RESULT_SUCCESS); + if (wError) + { + NetLog_Server("Updating of server contact failed, error %d", wError); + icq_LogMessage(LOG_WARNING, LPGEN("Updating of server contact failed.")); + } + break; + } + case SSA_PRIVACY_ADD: + { + if (wError) + { + NetLog_Server("Adding of privacy item to server list failed, error %d", wError); + icq_LogMessage(LOG_WARNING, LPGEN("Adding of privacy item to server list failed.")); + } + break; + } + case SSA_PRIVACY_REMOVE: + { + if (wError) + { + NetLog_Server("Removing of privacy item from server list failed, error %d", wError); + icq_LogMessage(LOG_WARNING, LPGEN("Removing of privacy item from server list failed.")); + } + FreeServerID(sc->wContactId, SSIT_ITEM); // release server id + break; + } + case SSA_CONTACT_ADD: + { + if (wError) + { + if (wError == 0xE) // server refused to add contact w/o auth, add with + { + DWORD dwCookie; + + NetLog_Server("Contact could not be added without authorization, add with await auth flag."); + + setSettingByte(sc->hContact, "Auth", 1); // we need auth + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, sc->hContact, sc); + icq_sendServerContact(sc->hContact, dwCookie, ICQ_LISTS_ADDTOLIST, sc->wGroupId, sc->wContactId, SSOP_ITEM_ACTION | SSOF_CONTACT, 500, NULL); + + sc = NULL; // we do not want it to be freed now + break; + } + FreeServerID(sc->wContactId, SSIT_ITEM); + + NetLog_Server("Adding of contact to server list failed, error %d", wError); + icq_LogMessage(LOG_WARNING, LPGEN("Adding of contact to server list failed.")); + + servlistPendingRemoveContact(sc->hContact, 0, sc->wGroupId, PENDING_RESULT_FAILED); + + servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here + } + else + { + void* groupData; + int groupSize; + + setSettingWord(sc->hContact, DBSETTING_SERVLIST_ID, sc->wContactId); + setSettingWord(sc->hContact, DBSETTING_SERVLIST_GROUP, sc->wGroupId); + + servlistPendingRemoveContact(sc->hContact, sc->wContactId, sc->wGroupId, PENDING_RESULT_SUCCESS); + + if (groupData = collectBuddyGroup(sc->wGroupId, &groupSize)) + { // the group is not empty, just update it + updateServerGroupData(sc->wGroupId, groupData, groupSize, SSOF_END_OPERATION); + SAFE_FREE((void**)&groupData); + } + else + { // this should never happen + NetLog_Server("Group update failed."); + servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here + } + } + break; + } + case SSA_GROUP_ADD: + { + if (wError) + { + FreeServerID(sc->wGroupId, SSIT_GROUP); + NetLog_Server("Adding of group to server list failed, error %d", wError); + icq_LogMessage(LOG_WARNING, LPGEN("Adding of group to server list failed.")); + + servlistPendingRemoveGroup(sc->szGroup, 0, PENDING_RESULT_FAILED); + } + else // group added, we need to update master group + { + void* groupData; + int groupSize; + cookie_servlist_action* ack; + DWORD dwCookie; + + setServListGroupName(sc->wGroupId, sc->szGroupName); // add group to namelist + { // add group to known + char *szCListGroup = getServListGroupCListPath(sc->wGroupId); + + // create link to the original CList group + setServListGroupLinkID(sc->szGroup, sc->wGroupId); + + servlistPendingRemoveGroup(sc->szGroup, sc->wGroupId, PENDING_RESULT_SUCCESS); + SAFE_FREE((void**)&szCListGroup); + } + + groupData = collectGroups(&groupSize); + groupData = SAFE_REALLOC(groupData, groupSize+2); + *(((WORD*)groupData)+(groupSize>>1)) = sc->wGroupId; // add this new group id + groupSize += 2; + + ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + if (ack) + { + ack->dwAction = SSA_GROUP_UPDATE; + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack); + + icq_sendServerGroup(dwCookie, ICQ_LISTS_UPDATEGROUP, 0, ack->szGroupName, groupData, groupSize, SSOF_END_OPERATION); + } + else // end server modifications here + servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); + + SAFE_FREE((void**)&groupData); + } + if (sc->szGroup != sc->szGroupName) + SAFE_FREE((void**)&sc->szGroup); + + SAFE_FREE((void**)&sc->szGroupName); + break; + } + case SSA_CONTACT_REMOVE: + { + if (!wError) + { + void* groupData; + int groupSize; + + setSettingWord(sc->hContact, DBSETTING_SERVLIST_ID, 0); // clear the values + setSettingWord(sc->hContact, DBSETTING_SERVLIST_GROUP, 0); + + FreeServerID(sc->wContactId, SSIT_ITEM); + + servlistPendingRemoveContact(sc->hContact, 0, sc->wGroupId, PENDING_RESULT_SUCCESS); + + if (groupData = collectBuddyGroup(sc->wGroupId, &groupSize)) + { // the group is still not empty, just update it + updateServerGroupData(sc->wGroupId, groupData, groupSize, SSOF_END_OPERATION); + } + else // the group is empty, delete it + { + char *szGroup = getServListGroupCListPath(sc->wGroupId); + + servlistRemoveGroup(szGroup, sc->wGroupId); + SAFE_FREE((void**)&szGroup); + } + SAFE_FREE((void**)&groupData); // free the memory + } + else + { + NetLog_Server("Removing of contact from server list failed, error %d", wError); + icq_LogMessage(LOG_WARNING, LPGEN("Removing of contact from server list failed.")); + + servlistPendingRemoveContact(sc->hContact, sc->wContactId, sc->wGroupId, PENDING_RESULT_FAILED); + + servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here + } + break; + } + case SSA_GROUP_UPDATE: + { + if (wError) + { + NetLog_Server("Updating of group on server list failed, error %d", wError); + icq_LogMessage(LOG_WARNING, LPGEN("Updating of group on server list failed.")); + } + SAFE_FREE((void**)&sc->szGroupName); + break; + } + case SSA_GROUP_REMOVE: + { + SAFE_FREE((void**)&sc->szGroupName); + if (wError) + { + NetLog_Server("Removing of group from server list failed, error %d", wError); + icq_LogMessage(LOG_WARNING, LPGEN("Removing of group from server list failed.")); + + servlistPendingRemoveGroup(sc->szGroup, 0, PENDING_RESULT_FAILED); + + servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here + SAFE_FREE((void**)&sc->szGroup); + } + else // group removed, we need to update master group + { + void* groupData; + int groupSize; + DWORD dwCookie; + + setServListGroupName(sc->wGroupId, NULL); // clear group from namelist + FreeServerID(sc->wGroupId, SSIT_GROUP); + removeGroupPathLinks(sc->wGroupId); + + servlistPendingRemoveGroup(sc->szGroup, 0, PENDING_RESULT_SUCCESS); + SAFE_FREE((void**)&sc->szGroup); + + groupData = collectGroups(&groupSize); + sc->wGroupId = 0; + sc->dwAction = SSA_GROUP_UPDATE; + sc->szGroupName = NULL; + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, sc); + + icq_sendServerGroup(dwCookie, ICQ_LISTS_UPDATEGROUP, 0, sc->szGroupName, groupData, groupSize, SSOF_END_OPERATION); + // end server modifications here + + sc = NULL; // we do not want to be freed here + + SAFE_FREE((void**)&groupData); + } + break; + } + case SSA_CONTACT_SET_GROUP: + { // we moved contact to another group + if (sc->lParam == -1) + { // the first was an error + break; + } + if (wError) + { + if (wError == 0x0E && sc->lParam == 1) + { // second ack - adding failed with error 0x0E, try to add with AVAIT_AUTH flag + DWORD dwCookie; + + if (!getSettingByte(sc->hContact, "Auth", 0)) + { // we tried without AWAIT_AUTH, try again with it + NetLog_Server("Contact could not be added without authorization, add with await auth flag."); + + setSettingByte(sc->hContact, "Auth", 1); // we need auth + } + else + { // we tried with AWAIT_AUTH, try again without + NetLog_Server("Contact count not be added awaiting authorization, try authorized."); + + setSettingByte(sc->hContact, "Auth", 0); + } + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, sc->hContact, sc); + icq_sendServerContact(sc->hContact, dwCookie, ICQ_LISTS_ADDTOLIST, sc->wNewGroupId, sc->wNewContactId, SSOP_ITEM_ACTION | SSOF_CONTACT, 400, NULL); + + sc->lParam = 2; // do not cycle + sc = NULL; // we do not want to be freed here + break; + } + FreeServerID(sc->wNewContactId, SSIT_ITEM); + NetLog_Server("Moving of user to another group on server list failed, error %d", wError); + icq_LogMessage(LOG_ERROR, LPGEN("Moving of user to another group on server list failed.")); + + servlistPendingRemoveContact(sc->hContact, 0, (WORD)(sc->lParam ? sc->wGroupId : sc->wNewGroupId), PENDING_RESULT_FAILED); + + servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here + + if (!sc->lParam) // is this first ack ? + { + sc->lParam = -1; + sc = NULL; // this can't be freed here + } + break; + } + if (sc->lParam) // is this the second ack ? + { + void* groupData; + int groupSize; + int bEnd = 1; // shall we end the sever modifications + + setSettingWord(sc->hContact, DBSETTING_SERVLIST_ID, sc->wNewContactId); + setSettingWord(sc->hContact, DBSETTING_SERVLIST_GROUP, sc->wNewGroupId); + + servlistPendingRemoveContact(sc->hContact, sc->wNewContactId, sc->wNewGroupId, PENDING_RESULT_SUCCESS); + + if (groupData = collectBuddyGroup(sc->wGroupId, &groupSize)) // update the group we moved from + { // the group is still not empty, just update it + updateServerGroupData(sc->wGroupId, groupData, groupSize, 0); + SAFE_FREE((void**)&groupData); // free the memory + } + else + { // the group is empty, delete it + char* szGroup = getServListGroupCListPath(sc->wGroupId); + + servlistRemoveGroup(szGroup, sc->wGroupId); + SAFE_FREE((void**)&szGroup); + bEnd = 0; // here the modifications go on + } + + groupData = collectBuddyGroup(sc->wNewGroupId, &groupSize); // update the group we moved to + updateServerGroupData(sc->wNewGroupId, groupData, groupSize, bEnd ? SSOF_END_OPERATION : 0); + // end server modifications here + SAFE_FREE((void**)&groupData); + + } + else // contact was deleted from server-list + { + deleteSetting(sc->hContact, DBSETTING_SERVLIST_ID); + deleteSetting(sc->hContact, DBSETTING_SERVLIST_GROUP); + FreeServerID(sc->wContactId, SSIT_ITEM); // release old contact id + sc->lParam = 1; + sc = NULL; // wait for second ack + } + break; + } + case SSA_CONTACT_FIX_AUTH: + { + if (wError) + { // FIXME: something failed, we should handle it properly + } + break; + } + case SSA_GROUP_RENAME: + { + if (wError) + { + NetLog_Server("Renaming of server group failed, error %d", wError); + icq_LogMessage(LOG_WARNING, LPGEN("Renaming of server group failed.")); + + servlistPendingRemoveGroup(sc->szGroup, sc->wGroupId, PENDING_RESULT_FAILED); + } + else + { + setServListGroupName(sc->wGroupId, sc->szGroupName); + removeGroupPathLinks(sc->wGroupId); + { // add group to known + char *szCListGroup = getServListGroupCListPath(sc->wGroupId); + + /// FIXME: need to create link to the new group name before unique item name correction as well + setServListGroupLinkID(szCListGroup, sc->wGroupId); + SAFE_FREE((void**)&szCListGroup); + } + servlistPendingRemoveGroup(sc->szGroup, sc->wGroupId, PENDING_RESULT_SUCCESS); + } + SAFE_FREE((void**)&sc->szGroupName); + SAFE_FREE((void**)&sc->szGroup); + break; + } + case SSA_SETAVATAR: + { + if (wError) + { + NetLog_Server("Uploading of avatar hash failed."); + if (sc->wGroupId) // is avatar added or updated? + { + FreeServerID(sc->wContactId, SSIT_ITEM); + deleteSetting(NULL, DBSETTING_SERVLIST_AVATAR); // to fix old versions + } + } + else + { + setSettingWord(NULL, DBSETTING_SERVLIST_AVATAR, sc->wContactId); + } + break; + } + case SSA_REMOVEAVATAR: + { + if (wError) + NetLog_Server("Removing of avatar hash failed."); + else + { + FreeServerID(sc->wContactId, SSIT_ITEM); + deleteSetting(NULL, DBSETTING_SERVLIST_AVATAR); + } + break; + } + case SSA_SERVLIST_ACK: + { + BroadcastAck(sc->hContact, ICQACKTYPE_SERVERCLIST, wError?ACKRESULT_FAILED:ACKRESULT_SUCCESS, (HANDLE)sc->lParam, wError); + break; + } + case SSA_IMPORT: + { + if (wError) + NetLog_Server("Re-starting import sequence failed, error %d", wError); + else + { + setSettingWord(NULL, "SrvImportID", 0); + deleteSetting(NULL, "ImportTS"); + } + break; + } + default: + NetLog_Server("Server ack cookie type (%d) not recognized.", sc->dwAction); + } + SAFE_FREE((void**)&sc); // free the memory + + return; +} + + +HANDLE CIcqProto::HContactFromRecordName(const char* szRecordName, int *bAdded) +{ + HANDLE hContact = INVALID_HANDLE_VALUE; + + if (!IsStringUIN(szRecordName)) + { // probably AIM contact + hContact = HContactFromUID(0, szRecordName, bAdded); + } + else + { // this should be ICQ number + DWORD dwUin = atoi(szRecordName); + + hContact = HContactFromUIN(dwUin, bAdded); + } + return hContact; +} + + +int CIcqProto::getServerDataFromItemTLV(oscar_tlv_chain* pChain, unsigned char *buf) /// FIXME: need to keep original order +{ // get server-list item's TLV data + oscar_tlv_chain* list = pChain; + int datalen = 0; + icq_packet pBuf; + + // Initialize our handy data buffer + pBuf.wPlace = 0; + pBuf.pData = buf; + + while (list) + { // collect non-standard TLVs and save them to DB + if (list->tlv.wType != SSI_TLV_AWAITING_AUTH && + list->tlv.wType != SSI_TLV_NAME && + list->tlv.wType != SSI_TLV_COMMENT && + list->tlv.wType != SSI_TLV_METAINFO_TOKEN && + list->tlv.wType != SSI_TLV_METAINFO_TIME) + { // only TLVs which we do not handle on our own + packTLV(&pBuf, list->tlv.wType, list->tlv.wLen, list->tlv.pData); + + datalen += list->tlv.wLen + 4; + } + list = list->next; + } + return datalen; +} + + +void CIcqProto::handleServerCListReply(BYTE *buf, WORD wLen, WORD wFlags, serverthread_info *info) +{ + BYTE bySSIVersion; + WORD wRecordCount; + WORD wRecord; + WORD wGroupId; + WORD wItemId; + WORD wTlvType; + WORD wTlvLength; + BOOL bIsLastPacket; + uid_str szRecordName; + oscar_tlv_chain* pChain = NULL; + oscar_tlv* pTLV = NULL; + char *szActiveSrvGroup = NULL; + WORD wActiveSrvGroupId = -1; + + + // If flag bit 1 is set, this is not the last + // packet. If it is 0, this is the last packet + // and there will be a timestamp at the end. + if (wFlags & 0x0001) + bIsLastPacket = FALSE; + else + bIsLastPacket = TRUE; + + if (wLen < 3) + return; + + // Version number of SSI protocol? + unpackByte(&buf, &bySSIVersion); + wLen -= 1; + + // Total count of following entries. This is the size of the server + // side contact list and should be saved and sent with CLI_CHECKROSTER. + // NOTE: When the entries are split up in several packets, each packet + // has it's own count and they must be added to get the total size of + // server list. + unpackWord(&buf, &wRecordCount); + wLen -= 2; + NetLog_Server("SSI: number of entries is %u, version is %u", wRecordCount, bySSIVersion); + + + // Loop over all items in the packet + for (wRecord = 0; wRecord < wRecordCount; wRecord++) + { + NetLog_Server("SSI: parsing record %u", wRecord + 1); + + if (wLen < 10) + { // minimum: name length (zero), group ID, item ID, empty TLV + NetLog_Server("Warning: SSI parsing error (%d)", 0); + break; + } + + if (!unpackServerListItem(&buf, &wLen, szRecordName, &wGroupId, &wItemId, &wTlvType, &wTlvLength)) + { // unpack basic structure + NetLog_Server("Warning: SSI parsing error (%d)", 1); + break; + } + + NetLog_Server("Name: '%s', GroupID: %u, EntryID: %u, EntryType: %u, TLVlength: %u", + szRecordName, wGroupId, wItemId, wTlvType, wTlvLength); + + if (wLen < wTlvLength) + { + NetLog_Server("Warning: SSI parsing error (%d)", 2); + break; + } + + // Initialize the tlv chain + if (wTlvLength > 0) + { + pChain = readIntoTLVChain(&buf, wTlvLength, 0); + wLen -= wTlvLength; + } + else + { + pChain = NULL; + } + + + switch (wTlvType) + { + + case SSI_ITEM_BUDDY: + { + /* this is a contact */ + HANDLE hContact; + int bAdded; + + hContact = HContactFromRecordName(szRecordName, &bAdded); + + if (hContact != INVALID_HANDLE_VALUE) + { + int bRegroup = 0; + int bNicked = 0; + + if (bAdded) + { // Not already on list: added + NetLog_Server("SSI added new %s contact '%s'", "ICQ", szRecordName); + + AddJustAddedContact(hContact); + } + else + { // we should add new contacts and this contact was just added, show it + if (IsContactJustAdded(hContact)) + { + setContactHidden(hContact, 0); + bAdded = 1; // we want details for new contacts + } + else + NetLog_Server("SSI ignoring existing contact '%s'", szRecordName); + // Contact on server is always on list + DBWriteContactSettingByte(hContact, "CList", "NotOnList", 0); + } + + // Save group and item ID + setSettingWord(hContact, DBSETTING_SERVLIST_ID, wItemId); + setSettingWord(hContact, DBSETTING_SERVLIST_GROUP, wGroupId); + ReserveServerID(wItemId, SSIT_ITEM, 0); + + if (!bAdded && getSettingByte(NULL, "LoadServerDetails", DEFAULT_SS_LOAD)) + { // check if the contact has been moved on the server + if (wActiveSrvGroupId != wGroupId || !szActiveSrvGroup) + { + SAFE_FREE(&szActiveSrvGroup); + szActiveSrvGroup = getServListGroupCListPath(wGroupId); + wActiveSrvGroupId = wGroupId; + } + char *szLocalGroup = getContactCListGroup(hContact); + + if (!strlennull(szLocalGroup)) + { // no CListGroup + SAFE_FREE(&szLocalGroup); + + szLocalGroup = null_strdup(DEFAULT_SS_GROUP); + } + + if (strcmpnull(szActiveSrvGroup, szLocalGroup) && + (strlennull(szActiveSrvGroup) >= strlennull(szLocalGroup) || _strnicmp(szActiveSrvGroup, szLocalGroup, strlennull(szLocalGroup)))) + { // contact moved to new group or sub-group or not to master group + bRegroup = 1; + } + if (bRegroup && !stricmpnull(DEFAULT_SS_GROUP, szActiveSrvGroup)) /// TODO: invent something more clever for "root" group + { // is it the default "General" group ? + bRegroup = 0; // if yes, do not move to it - cause it would hide the contact + } + SAFE_FREE(&szLocalGroup); + } + + if (bRegroup || bAdded) + { // if we should load server details or contact was just added, update its group + if (wActiveSrvGroupId != wGroupId || !szActiveSrvGroup) + { + SAFE_FREE(&szActiveSrvGroup); + szActiveSrvGroup = getServListGroupCListPath(wGroupId); + wActiveSrvGroupId = wGroupId; + } + + if (szActiveSrvGroup) + { // try to get Miranda Group path from groupid, if succeeded save to db + moveContactToCListGroup(hContact, szActiveSrvGroup); + } + } + + if (pChain) + { // Look for nickname TLV and copy it to the db if necessary + if (pTLV = pChain->getTLV(SSI_TLV_NAME, 1)) + { + if (pTLV->pData && (pTLV->wLen > 0)) + { + char *pszNick; + WORD wNickLength; + + wNickLength = pTLV->wLen; + + pszNick = (char*)SAFE_MALLOC(wNickLength + 1); + // Copy buffer to utf-8 buffer + memcpy(pszNick, pTLV->pData, wNickLength); + pszNick[wNickLength] = 0; // Terminate string + + NetLog_Server("Nickname is '%s'", pszNick); + + bNicked = 1; + + // Write nickname to database + if (getSettingByte(NULL, "LoadServerDetails", DEFAULT_SS_LOAD) || bAdded) + { // if just added contact, save details always - does no harm + char *szOldNick; + + if (szOldNick = getSettingStringUtf(hContact, "CList", "MyHandle", NULL)) + { + if ((strcmpnull(szOldNick, pszNick)) && (strlennull(pszNick) > 0)) + { // check if the truncated nick changed, i.e. do not overwrite locally stored longer nick + if (strlennull(szOldNick) <= strlennull(pszNick) || strncmp(szOldNick, pszNick, null_strcut(szOldNick, MAX_SSI_TLV_NAME_SIZE))) + { + // Yes, we really do need to delete it first. Otherwise the CLUI nick + // cache isn't updated (I'll look into it) + DBDeleteContactSetting(hContact,"CList","MyHandle"); + setSettingStringUtf(hContact, "CList", "MyHandle", pszNick); + } + } + SAFE_FREE(&szOldNick); + } + else if (strlennull(pszNick) > 0) + { + DBDeleteContactSetting(hContact,"CList","MyHandle"); + setSettingStringUtf(hContact, "CList", "MyHandle", pszNick); + } + } + SAFE_FREE(&pszNick); + } + else + { + NetLog_Server("Invalid nickname"); + } + } + if (bAdded && !bNicked) + icq_QueueUser(hContact); // queue user without nick for fast auto info update + + // Look for comment TLV and copy it to the db if necessary + if (pTLV = pChain->getTLV(SSI_TLV_COMMENT, 1)) + { + if (pTLV->pData && (pTLV->wLen > 0)) + { + char *pszComment; + WORD wCommentLength; + + + wCommentLength = pTLV->wLen; + + pszComment = (char*)SAFE_MALLOC(wCommentLength + 1); + // Copy buffer to utf-8 buffer + memcpy(pszComment, pTLV->pData, wCommentLength); + pszComment[wCommentLength] = 0; // Terminate string + + NetLog_Server("Comment is '%s'", pszComment); + + // Write comment to database + if (getSettingByte(NULL, "LoadServerDetails", DEFAULT_SS_LOAD) || bAdded) + { // if just added contact, save details always - does no harm + char *szOldComment; + + if (szOldComment = getSettingStringUtf(hContact, "UserInfo", "MyNotes", NULL)) + { + if ((strcmpnull(szOldComment, pszComment)) && (strlennull(pszComment) > 0)) + { // check if the truncated comment changed, i.e. do not overwrite locally stored longer comment + if (strlennull(szOldComment) <= strlennull(pszComment) || strncmp((char*)szOldComment, (char*)pszComment, null_strcut(szOldComment, MAX_SSI_TLV_COMMENT_SIZE))) + { + setSettingStringUtf(hContact, "UserInfo", "MyNotes", pszComment); + } + } + SAFE_FREE((void**)&szOldComment); + } + else if (strlennull(pszComment) > 0) + { + setSettingStringUtf(hContact, "UserInfo", "MyNotes", pszComment); + } + } + SAFE_FREE((void**)&pszComment); + } + else + { + NetLog_Server("Invalid comment"); + } + } + + // Look for need-authorization TLV + if (pChain->getTLV(SSI_TLV_AWAITING_AUTH, 1)) + { + setSettingByte(hContact, "Auth", 1); + NetLog_Server("SSI contact need authorization"); + } + else + { + setSettingByte(hContact, "Auth", 0); + } + + if (pTLV = pChain->getTLV(SSI_TLV_METAINFO_TOKEN, 1)) + { + setSettingBlob(hContact, DBSETTING_METAINFO_TOKEN, pTLV->pData, pTLV->wLen); + if (pChain->getTLV(SSI_TLV_METAINFO_TIME, 1)) + setSettingDouble(hContact, DBSETTING_METAINFO_TIME, pChain->getDouble(SSI_TLV_METAINFO_TIME, 1)); + NetLog_Server("SSI contact has meta info token"); + } + else + { + deleteSetting(hContact, DBSETTING_METAINFO_TOKEN); + deleteSetting(hContact, DBSETTING_METAINFO_TIME); + } + + { // store server-list item's TLV data + BYTE* data = (BYTE*)SAFE_MALLOC(wTlvLength); + int datalen = getServerDataFromItemTLV(pChain, data); + + if (datalen > 0) + setSettingBlob(hContact, DBSETTING_SERVLIST_DATA, data, datalen); + else + deleteSetting(hContact, DBSETTING_SERVLIST_DATA); + + SAFE_FREE((void**)&data); + } + } + } + else + { // failed to add or other error + NetLog_Server("SSI failed to handle %s Item '%s'", "Buddy", szRecordName); + } + } + break; + + case SSI_ITEM_GROUP: + if ((wGroupId == 0) && (wItemId == 0)) + { + /* list of groups. wTlvType=1, data is TLV(C8) containing list of WORDs which */ + /* is the group ids + /* we don't need to use this. Our processing is on-the-fly */ + /* this record is always sent first in the first packet only, */ + } + else if (wGroupId != 0) + { + /* wGroupId != 0: a group record */ + if (wItemId == 0) + { /* no item ID: this is a group */ + /* pszRecordName is the name of the group */ + ReserveServerID(wGroupId, SSIT_GROUP, 0); + + setServListGroupName(wGroupId, szRecordName); + + NetLog_Server("Group %s added to known groups.", szRecordName); + + /* demangle full grouppath, set it to known */ + SAFE_FREE(&szActiveSrvGroup); + szActiveSrvGroup = getServListGroupCListPath(wGroupId); + wActiveSrvGroupId = wGroupId; + + /* TLV contains a TLV(C8) with a list of WORDs of contained contact IDs */ + /* our processing is good enough that we don't need this duplication */ + } + else + { + NetLog_Server("Unhandled type 0x01, wItemID != 0"); + } + } + else + { + NetLog_Server("Unhandled type 0x01"); + } + break; + + case SSI_ITEM_PERMIT: + { + /* item on visible list */ + /* wItemId not related to contact ID */ + /* pszRecordName is the UIN */ + HANDLE hContact; + int bAdded; + + hContact = HContactFromRecordName(szRecordName, &bAdded); + + if (hContact != INVALID_HANDLE_VALUE) + { + if (bAdded) + { + NetLog_Server("SSI added new %s contact '%s'", "Permit", szRecordName); + // It wasn't previously in the list, we hide it so it only appears in the visible list + setContactHidden(hContact, 1); + // Add it to the list, so it can be added properly if proper contact + AddJustAddedContact(hContact); + } + else + NetLog_Server("SSI %s contact already exists '%s'", "Permit", szRecordName); + + // Save permit ID + setSettingWord(hContact, DBSETTING_SERVLIST_PERMIT, wItemId); + ReserveServerID(wItemId, SSIT_ITEM, 0); + // Set apparent mode + setSettingWord(hContact, "ApparentMode", ID_STATUS_ONLINE); + NetLog_Server("Visible-contact (%s)", szRecordName); + } + else + { // failed to add or other error + NetLog_Server("SSI failed to handle %s Item '%s'", "Permit", szRecordName); + + ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); + } + } + break; + + case SSI_ITEM_DENY: + { + /* Item on invisible list */ + /* wItemId not related to contact ID */ + /* pszRecordName is the UIN */ + HANDLE hContact; + int bAdded; + + hContact = HContactFromRecordName(szRecordName, &bAdded); + + if (hContact != INVALID_HANDLE_VALUE) + { + if (bAdded) + { + /* not already on list: added */ + NetLog_Server("SSI added new %s contact '%s'", "Deny", szRecordName); + // It wasn't previously in the list, we hide it so it only appears in the visible list + setContactHidden(hContact, 1); + // Add it to the list, so it can be added properly if proper contact + AddJustAddedContact(hContact); + } + else + NetLog_Server("SSI %s contact already exists '%s'", "Deny", szRecordName); + + // Save Deny ID + setSettingWord(hContact, DBSETTING_SERVLIST_DENY, wItemId); + ReserveServerID(wItemId, SSIT_ITEM, 0); + + // Set apparent mode + setSettingWord(hContact, "ApparentMode", ID_STATUS_OFFLINE); + NetLog_Server("Invisible-contact (%s)", szRecordName); + } + else + { // failed to add or other error + NetLog_Server("SSI failed to handle %s Item '%s'", "Deny", szRecordName); + + ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); + } + } + break; + + case SSI_ITEM_VISIBILITY: /* My visibility settings */ + { + BYTE bVisibility; + + // Look for visibility TLV + if (bVisibility = pChain->getByte(SSI_TLV_VISIBILITY, 1)) + { // found it, store the id, we do not need current visibility - we do not rely on it + setSettingWord(NULL, DBSETTING_SERVLIST_PRIVACY, wItemId); + ReserveServerID(wItemId, SSIT_ITEM, 0); + + NetLog_Server("Visibility is %u", bVisibility); + } + else + ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); + } + break; + + case SSI_ITEM_IGNORE: + { + /* item on ignore list */ + /* wItemId not related to contact ID */ + /* pszRecordName is the UIN */ + HANDLE hContact; + int bAdded; + + hContact = HContactFromRecordName(szRecordName, &bAdded); + + if (hContact != INVALID_HANDLE_VALUE) + { + if (bAdded) + { + /* not already on list: add */ + NetLog_Server("SSI added new %s contact '%s'", "Ignore", szRecordName); + // It wasn't previously in the list, we hide it + setContactHidden(hContact, 1); + // Add it to the list, so it can be added properly if proper contact + AddJustAddedContact(hContact); + } + else + NetLog_Server("SSI %s contact already exists '%s'", "Ignore", szRecordName); + + // Save Ignore ID + setSettingWord(hContact, DBSETTING_SERVLIST_IGNORE, wItemId); + ReserveServerID(wItemId, SSIT_ITEM, 0); + + // Set apparent mode & ignore + setSettingWord(hContact, "ApparentMode", ID_STATUS_OFFLINE); + // set ignore all events + CallService(MS_IGNORE_IGNORE, (WPARAM)hContact, IGNOREEVENT_ALL); + NetLog_Server("Ignore-contact (%s)", szRecordName); + } + else + { // failed to add or other error + NetLog_Server("SSI failed to handle %s Item '%s'", "Ignore", szRecordName); + + ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); + } + } + break; + + case SSI_ITEM_UNKNOWN2: + NetLog_Server("SSI unknown type 0x11"); + + ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); + break; + + case SSI_ITEM_IMPORTTIME: + if (wGroupId == 0) + { + /* time our list was first imported */ + /* pszRecordName is "Import Time" */ + /* data is TLV(13) {TLV(D4) {time_t importTime}} */ + setSettingDword(NULL, "ImportTS", pChain->getDWord(SSI_TLV_TIMESTAMP, 1)); + setSettingWord(NULL, "SrvImportID", wItemId); + ReserveServerID(wItemId, SSIT_ITEM, 0); + NetLog_Server("SSI %s item recognized", "first import"); + } + break; + + case SSI_ITEM_BUDDYICON: + if (wGroupId == 0) + { + /* our avatar MD5-hash */ + /* pszRecordName is "1" */ + /* data is TLV(D5) hash */ + /* we ignore this, just save the id */ + /* cause we get the hash again after login */ + if (!strcmpnull(szRecordName, "12")) + { // need to handle Photo Item separately + setSettingWord(NULL, DBSETTING_SERVLIST_PHOTO, wItemId); + NetLog_Server("SSI %s item recognized", "Photo"); + } + else + { + setSettingWord(NULL, DBSETTING_SERVLIST_AVATAR, wItemId); + NetLog_Server("SSI %s item recognized", "Avatar"); + } + ReserveServerID(wItemId, SSIT_ITEM, 0); + } + break; + + case SSI_ITEM_METAINFO: + if (wGroupId == 0) + { + /* our meta info token & last update time */ + /* pszRecordName is "ICQ-MDIR" */ + /* data is TLV(15C) and TLV(15D) */ + oscar_tlv* pToken = pChain->getTLV(SSI_TLV_METAINFO_TOKEN, 1); + oscar_tlv* pTime = pChain->getTLV(SSI_TLV_METAINFO_TIME, 1); + if (pToken) + setSettingBlob(NULL, DBSETTING_METAINFO_TOKEN, pToken->pData, pToken->wLen); + if (pTime) + setSettingDouble(NULL, DBSETTING_METAINFO_TIME, pChain->getDouble(SSI_TLV_METAINFO_TIME, 1)); + + setSettingWord(NULL, DBSETTING_SERVLIST_METAINFO, wItemId); + ReserveServerID(wItemId, SSIT_ITEM, 0); + + NetLog_Server("SSI %s item recognized", "Meta info"); + } + break; + + case SSI_ITEM_CLIENTDATA: + if (wGroupId == 0) + { + /* ICQ2k ShortcutBar Items */ + /* data is TLV(CD) text */ + if (wItemId) + ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); + } + + case SSI_ITEM_SAVED: + case SSI_ITEM_PREAUTH: + break; + + default: + NetLog_Server("SSI unhandled item %2x", wTlvType); + + if (wItemId) + ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED); + break; + } + + disposeChain(&pChain); + } // end for + + // Release Memory + SAFE_FREE(&szActiveSrvGroup); + + NetLog_Server("Bytes left: %u", wLen); + + setSettingWord(NULL, "SrvRecordCount", (WORD)(wRecord + getSettingWord(NULL, "SrvRecordCount", 0))); + + if (bIsLastPacket) + { + // No contacts left to sync + bIsSyncingCL = FALSE; + + StoreServerIDs(); + + icq_RescanInfoUpdate(); + + if (wLen >= 4) + { + DWORD dwLastUpdateTime; + + /* finally we get a time_t of the last update time */ + unpackDWord(&buf, &dwLastUpdateTime); + setSettingDword(NULL, "SrvLastUpdate", dwLastUpdateTime); + NetLog_Server("Last update of server list was (%u) %s", dwLastUpdateTime, time2text(dwLastUpdateTime)); + + sendRosterAck(); + handleServUINSettings(wListenPort, info); + + servlistProcessLogin(); + } + else + { + NetLog_Server("Last packet missed update time..."); + } + if (getSettingWord(NULL, "SrvRecordCount", 0) == 0) + { // we got empty serv-list, create master group + cookie_servlist_action* ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + if (ack) + { + DWORD dwCookie; + + ack->dwAction = SSA_GROUP_UPDATE; + ack->szGroupName = null_strdup(""); + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, 0, ack); + icq_sendServerGroup(dwCookie, ICQ_LISTS_ADDTOLIST, 0, ack->szGroupName, NULL, 0, 0); + } + } + // serv-list sync finished, clear just added contacts + FlushJustAddedContacts(); + } + else + { + NetLog_Server("Waiting for more packets"); + } +} + + +void CIcqProto::handleServerCListItemAdd(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData) +{ + if (wItemType == SSI_ITEM_IMPORTTIME) + { + if (pItemData) + { + setSettingDword(NULL, "ImportTS", pItemData->getDWord(SSI_TLV_TIMESTAMP, 1)); + setSettingWord(NULL, "SrvImportID", wItemId); + ReserveServerID(wItemId, SSIT_ITEM, 0); + + NetLog_Server("Server added Import timestamp to list"); + + return; + } + } + // Reserve server-list ID + ReserveServerID(wItemId, wItemType == SSI_ITEM_GROUP ? SSIT_GROUP : SSIT_ITEM, SSIF_UNHANDLED); +} + + +void CIcqProto::handleServerCListItemUpdate(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData) +{ + HANDLE hContact = (wItemType == SSI_ITEM_BUDDY || wItemType == SSI_ITEM_DENY || wItemType == SSI_ITEM_PERMIT || wItemType == SSI_ITEM_IGNORE) ? HContactFromRecordName(szRecordName, NULL) : NULL; + + if (hContact != INVALID_HANDLE_VALUE && wItemType == SSI_ITEM_BUDDY) + { // a contact was updated on server + if (pItemData) + { + oscar_tlv* pAuth = pItemData->getTLV(SSI_TLV_AWAITING_AUTH, 1); + BYTE bAuth = getSettingByte(hContact, "Auth", 0); + + if (bAuth && !pAuth) + { // server authorized our contact + char str[MAX_PATH]; + char msg[MAX_PATH]; + char *nick = NickFromHandleUtf(hContact); + + setSettingByte(hContact, "Auth", 0); + null_snprintf(str, MAX_PATH, ICQTranslateUtfStatic(LPGEN("Contact \"%s\" was authorized in the server list."), msg, MAX_PATH), nick); + icq_LogMessage(LOG_WARNING, str); + SAFE_FREE(&nick); + } + else if (!bAuth && pAuth) + { // server took away authorization of our contact + char str[MAX_PATH]; + char msg[MAX_PATH]; + char *nick = NickFromHandleUtf(hContact); + + setSettingByte(hContact, "Auth", 1); + null_snprintf(str, MAX_PATH, ICQTranslateUtfStatic(LPGEN("Contact \"%s\" lost its authorization in the server list."), msg, MAX_PATH), nick); + icq_LogMessage(LOG_WARNING, str); + SAFE_FREE(&nick); + } + + { // update metainfo data + DBVARIANT dbv = {0}; + oscar_tlv *pToken = pItemData->getTLV(SSI_TLV_METAINFO_TOKEN, 1); + oscar_tlv *pTime = pItemData->getTLV(SSI_TLV_METAINFO_TIME, 1); + + if (!getSetting(hContact, DBSETTING_METAINFO_TOKEN, &dbv)) + { + if (!pToken || dbv.cpbVal != pToken->wLen || memcmp(dbv.pbVal, pToken->pData, dbv.cpbVal)) + { + if (!pToken) + NetLog_Server("Contact %s, meta info token removed", szRecordName); + else + NetLog_Server("Contact %s, meta info token changed", szRecordName); + + // user info was changed, refresh + if (IsMetaInfoChanged(hContact)) + icq_QueueUser(hContact); + } + + ICQFreeVariant(&dbv); + } + else if (pToken) + { + NetLog_Server("Contact %s, meta info token added", szRecordName); + + // user info was changed, refresh + if (IsMetaInfoChanged(hContact)) + icq_QueueUser(hContact); + } + + if (pToken) + setSettingBlob(hContact, DBSETTING_METAINFO_TOKEN, pToken->pData, pToken->wLen); + if (pTime) + setSettingDouble(hContact, DBSETTING_METAINFO_TIME, pItemData->getDouble(SSI_TLV_METAINFO_TIME, 1)); + } + + { // update server's data - otherwise consequent operations can fail with 0x0E + BYTE *data = (BYTE*)_alloca(pItemData->getChainLength()); + int datalen = getServerDataFromItemTLV(pItemData, data); + + if (datalen > 0) + setSettingBlob(hContact, DBSETTING_SERVLIST_DATA, data, datalen); + else + deleteSetting(hContact, DBSETTING_SERVLIST_DATA); + } + } + } + else if (wItemType == SSI_ITEM_METAINFO) + { // owner MetaInfo data updated + if (pItemData) + { + DBVARIANT dbv = {0}; + oscar_tlv *pToken = pItemData->getTLV(SSI_TLV_METAINFO_TOKEN, 1); + oscar_tlv *pTime = pItemData->getTLV(SSI_TLV_METAINFO_TIME, 1); + + if (!getSetting(hContact, DBSETTING_METAINFO_TOKEN, &dbv)) + { + if (!pToken || dbv.cpbVal != pToken->wLen || memcmp(dbv.pbVal, pToken->pData, dbv.cpbVal)) + { + if (!pToken) + NetLog_Server("Owner meta info token removed"); + else + NetLog_Server("Owner meta info token changed"); + } + + ICQFreeVariant(&dbv); + } + + if (pToken) + setSettingBlob(hContact, DBSETTING_METAINFO_TOKEN, pToken->pData, pToken->wLen); + if (pTime) + setSettingDouble(hContact, DBSETTING_METAINFO_TIME, pItemData->getDouble(SSI_TLV_METAINFO_TIME, 1)); + } + } + else if (wItemType == SSI_ITEM_GROUP) + { // group updated + NetLog_Server("Server updated our group \"%s\" on list", szRecordName); + } +} + + +void CIcqProto::handleServerCListItemDelete(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData) +{ + HANDLE hContact = (wItemType == SSI_ITEM_BUDDY || wItemType == SSI_ITEM_DENY || wItemType == SSI_ITEM_PERMIT || wItemType == SSI_ITEM_IGNORE) ? HContactFromRecordName(szRecordName, NULL) : NULL; + + if (hContact != INVALID_HANDLE_VALUE && wItemType == SSI_ITEM_BUDDY) + { // a contact was removed from our list + if (getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0) == wItemId) + { + deleteSetting(hContact, DBSETTING_SERVLIST_ID); + deleteSetting(hContact, DBSETTING_SERVLIST_GROUP); + deleteSetting(hContact, "Auth"); + + { + char str[MAX_PATH]; + char msg[MAX_PATH]; + char *nick = NickFromHandleUtf(hContact); + + null_snprintf(str, MAX_PATH, ICQTranslateUtfStatic(LPGEN("User \"%s\" was removed from server list."), msg, MAX_PATH), nick); + icq_LogMessage(LOG_WARNING, str); + SAFE_FREE(&nick); + } + } + } + // Release server-list ID + FreeServerID(wItemId, wItemType == SSI_ITEM_GROUP ? SSIT_GROUP : SSIT_ITEM); +} + + +void CIcqProto::handleRecvAuthRequest(unsigned char *buf, WORD wLen) +{ + DWORD dwUin; + uid_str szUid; + int bAdded; + + if (!unpackUID(&buf, &wLen, &dwUin, &szUid)) return; + + if (dwUin && IsOnSpammerList(dwUin)) + { + NetLog_Server("Ignored Message from known Spammer"); + return; + } + + WORD wReasonLen; + unpackWord(&buf, &wReasonLen); + wLen -= 2; + if (wReasonLen > wLen) + return; + + HANDLE hContact = HContactFromUID(dwUin, szUid, &bAdded); + CCSDATA ccs; + PROTORECVEVENT pre; + + ccs.szProtoService = PSR_AUTH; + ccs.hContact = hContact; + ccs.wParam = 0; + ccs.lParam = (LPARAM)⪯ + pre.flags = 0; + pre.timestamp = time(NULL); + pre.lParam = sizeof(DWORD) + sizeof(HANDLE) + 5; + // Prepare reason + char *szReason = (char*)SAFE_MALLOC(wReasonLen + 1); + int nReasonLen = 0; + if (szReason) + { + memcpy(szReason, buf, wReasonLen); + szReason[wReasonLen] = '\0'; + nReasonLen = strlennull(szReason); + + char *temp = (char*)_alloca(nReasonLen + 2); + if (!IsUSASCII(szReason, nReasonLen) && UTF8_IsValid(szReason) && utf8_decode_static(szReason, temp, nReasonLen + 1)) + pre.flags |= PREF_UTF; + } + // Read nick name from DB + char *szNick = NULL; + if (dwUin) + { + DBVARIANT dbv = { 0 }; + if (pre.flags & PREF_UTF) + szNick = getSettingStringUtf(hContact, "Nick", NULL); + else if (!getSettingString(hContact, "Nick", &dbv)) + { + szNick = null_strdup(dbv.pszVal); + ICQFreeVariant(&dbv); + } + } + else + szNick = null_strdup(szUid); + int nNickLen = strlennull(szNick); + + pre.lParam += nNickLen + nReasonLen; + + setSettingByte(ccs.hContact, "Grant", 1); + + /*blob is: uin(DWORD), hcontact(HANDLE), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ), reason(ASCIIZ)*/ + char *szBlob = (char *)_alloca(pre.lParam); + char *pCurBlob = szBlob; + memcpy(pCurBlob, &dwUin, sizeof(DWORD)); pCurBlob += sizeof(DWORD); + memcpy(pCurBlob, &hContact, sizeof(HANDLE)); pCurBlob += sizeof(HANDLE); + if (nNickLen) + { // if we have nick we add it, otherwise keep trailing zero + memcpy(pCurBlob, szNick, nNickLen); + pCurBlob += nNickLen; + } + *pCurBlob = 0; pCurBlob++; // Nick + *pCurBlob = 0; pCurBlob++; // FirstName + *pCurBlob = 0; pCurBlob++; // LastName + *pCurBlob = 0; pCurBlob++; // email + if (nReasonLen) + { + memcpy(pCurBlob, szReason, nReasonLen); + pCurBlob += nReasonLen; + } + *pCurBlob = 0; // Reason + pre.szMessage = szBlob; + + // TODO: Change for new auth system, include all known informations + CallService(MS_PROTO_CHAINRECV,0,(LPARAM)&ccs); + + SAFE_FREE(&szNick); + SAFE_FREE(&szReason); + return; +} + + +void CIcqProto::handleRecvAdded(unsigned char *buf, WORD wLen) +{ + DWORD dwUin; + uid_str szUid; + DWORD cbBlob; + PBYTE pBlob,pCurBlob; + int bAdded; + char* szNick; + int nNickLen; + DBVARIANT dbv = {0}; + + if (!unpackUID(&buf, &wLen, &dwUin, &szUid)) return; + + if (dwUin && IsOnSpammerList(dwUin)) + { + NetLog_Server("Ignored Message from known Spammer"); + return; + } + + HANDLE hContact = HContactFromUID(dwUin, szUid, &bAdded); + + cbBlob=sizeof(DWORD)*2+4; + + if (dwUin) + { + if (getSettingString(hContact, "Nick", &dbv)) + nNickLen = 0; + else + { + szNick = dbv.pszVal; + nNickLen = strlennull(szNick); + } + } + else + nNickLen = strlennull(szUid); + + cbBlob += nNickLen; + + pCurBlob=pBlob=(PBYTE)_alloca(cbBlob); + /*blob is: uin(DWORD), hContact(HANDLE), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ) */ + *(DWORD*)pCurBlob = dwUin; pCurBlob += sizeof(DWORD); + *(DWORD*)pCurBlob = DWORD(hContact); pCurBlob += sizeof(DWORD); + if (nNickLen && dwUin) + { // if we have nick we add it, otherwise keep trailing zero + memcpy(pCurBlob, szNick, nNickLen); + pCurBlob+=nNickLen; + } + else + { + memcpy(pCurBlob, szUid, nNickLen); + pCurBlob+=nNickLen; + } + *(char *)pCurBlob = 0; pCurBlob++; + *(char *)pCurBlob = 0; pCurBlob++; + *(char *)pCurBlob = 0; pCurBlob++; + *(char *)pCurBlob = 0; + // TODO: Change for new auth system + + AddEvent(NULL, EVENTTYPE_ADDED, time(NULL), 0, cbBlob, pBlob); +} + + +void CIcqProto::handleRecvAuthResponse(unsigned char *buf, WORD wLen) +{ + DWORD dwUin; + uid_str szUid; + char* szNick = NULL; + WORD nReasonLen; + char* szReason; + int bAdded; + + BYTE bResponse = 0xFF; + + if (!unpackUID(&buf, &wLen, &dwUin, &szUid)) return; + + if (dwUin && IsOnSpammerList(dwUin)) + { + NetLog_Server("Ignored Message from known Spammer"); + return; + } + + HANDLE hContact = HContactFromUID(dwUin, szUid, &bAdded); + + if (hContact != INVALID_HANDLE_VALUE) szNick = NickFromHandle(hContact); + + if (wLen > 0) + { + unpackByte(&buf, &bResponse); + wLen -= 1; + } + if (wLen >= 2) + { + unpackWord(&buf, &nReasonLen); + wLen -= 2; + if (wLen >= nReasonLen) + { + szReason = (char*)_alloca(nReasonLen+1); + unpackString(&buf, szReason, nReasonLen); + szReason[nReasonLen] = '\0'; + } + } + + switch (bResponse) + { + + case 0: + NetLog_Server("Authorization request %s by %s", "denied", strUID(dwUin, szUid)); + // TODO: Add to system history as soon as new auth system is ready + break; + + case 1: + setSettingByte(hContact, "Auth", 0); + NetLog_Server("Authorization request %s by %s", "granted", strUID(dwUin, szUid)); + // TODO: Add to system history as soon as new auth system is ready + break; + + default: + NetLog_Server("Unknown Authorization request response (%u) from %s", bResponse, strUID(dwUin, szUid)); + break; + + } + SAFE_FREE(&szNick); +} + + +// Updates the visibility code used while in SSI mode. If a server ID is +// not stored in the local DB, a new ID will be added to the server list. +// +// Possible values are: +// 01 - Allow all users to see you +// 02 - Block all users from seeing you +// 03 - Allow only users in the permit list to see you +// 04 - Block only users in the invisible list from seeing you +// 05 - Allow only users in the buddy list to see you +// +void CIcqProto::updateServVisibilityCode(BYTE bCode) +{ + icq_packet packet; + WORD wVisibilityID; + WORD wCommand; + + if ((bCode > 0) && (bCode < 6)) + { + cookie_servlist_action* ack; + DWORD dwCookie; + BYTE bVisibility = getSettingByte(NULL, "SrvVisibility", 0); + + if (bVisibility == bCode) // if no change was made, not necescary to update that + return; + setSettingByte(NULL, "SrvVisibility", bCode); + + // Do we have a known server visibility ID? We should, unless we just subscribed to the serv-list for the first time + if ((wVisibilityID = getSettingWord(NULL, DBSETTING_SERVLIST_PRIVACY, 0)) == 0) + { + // No, create a new random ID + wVisibilityID = GenerateServerID(SSIT_ITEM, 0); + setSettingWord(NULL, DBSETTING_SERVLIST_PRIVACY, wVisibilityID); + wCommand = ICQ_LISTS_ADDTOLIST; +#ifdef _DEBUG + NetLog_Server("Made new srvVisibilityID, id is %u, code is %u", wVisibilityID, bCode); +#endif + } + else + { +#ifdef _DEBUG + NetLog_Server("Reused srvVisibilityID, id is %u, code is %u", wVisibilityID, bCode); +#endif + wCommand = ICQ_LISTS_UPDATEGROUP; + } + + ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + if (!ack) + { + NetLog_Server("Cookie alloc failure."); + return; // out of memory, go away + } + ack->dwAction = SSA_VISIBILITY; // update visibility + dwCookie = AllocateCookie(CKT_SERVERLIST, wCommand, 0, ack); // take cookie + + // Build and send packet + serverPacketInit(&packet, 25); + packFNACHeader(&packet, ICQ_LISTS_FAMILY, wCommand, 0, dwCookie); + packWord(&packet, 0); // Name (null) + packWord(&packet, 0); // GroupID (0 if not relevant) + packWord(&packet, wVisibilityID); // EntryID + packWord(&packet, SSI_ITEM_VISIBILITY); // EntryType + packWord(&packet, 5); // Length in bytes of following TLV + packTLV(&packet, SSI_TLV_VISIBILITY, 1, &bCode); // TLV (Visibility) + sendServPacket(&packet); + // There is no need to send ICQ_LISTS_CLI_MODIFYSTART or + // ICQ_LISTS_CLI_MODIFYEND when modifying the visibility code + } +} + +// Updates the avatar hash used while in SSI mode. If a server ID is +// not stored in the local DB, a new ID will be added to the server list. +void CIcqProto::updateServAvatarHash(BYTE *pHash, int size) +{ + void** pDoubleObject = NULL; + void* doubleObject = NULL; + DWORD dwOperationFlags = 0; + WORD wAvatarID; + WORD wCommand; + DBVARIANT dbvHash; + int bResetHash = 0; + char szItemName[2] = {0, 0}; + + if (!getSetting(NULL, "AvatarHash", &dbvHash)) + { + szItemName[0] = 0x30 + dbvHash.pbVal[1]; + + if (memcmp(pHash, dbvHash.pbVal, 2) != 0) + { + /** add code to remove old hash from server */ + bResetHash = 1; + } + ICQFreeVariant(&dbvHash); + } + + if (bResetHash) // start update session + { // pair the packets (need to be send in the correct order + dwOperationFlags |= SSOF_BEGIN_OPERATION | SSOF_END_OPERATION; + pDoubleObject = &doubleObject; + } + + if (bResetHash || !pHash) + { + cookie_servlist_action* ack; + DWORD dwCookie; + + // Do we have a known server avatar ID? + if (wAvatarID = getSettingWord(NULL, DBSETTING_SERVLIST_AVATAR, 0)) + { + ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + if (!ack) + { + NetLog_Server("Cookie alloc failure."); + return; // out of memory, go away + } + ack->dwAction = SSA_REMOVEAVATAR; // update avatar hash + ack->wContactId = wAvatarID; + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_REMOVEFROMLIST, 0, ack); // take cookie + + icq_sendServerItem(dwCookie, ICQ_LISTS_REMOVEFROMLIST, 0, wAvatarID, szItemName, NULL, 0, SSI_ITEM_BUDDYICON, SSOP_ITEM_ACTION | dwOperationFlags, 400, pDoubleObject); + } + } + + if (pHash) + { + cookie_servlist_action* ack; + DWORD dwCookie; + WORD wTLVlen; + icq_packet pBuffer; + WORD hashsize = size - 2; + + // Do we have a known server avatar ID? We should, unless we just subscribed to the serv-list for the first time + if (bResetHash || (wAvatarID = getSettingWord(NULL, DBSETTING_SERVLIST_AVATAR, 0)) == 0) + { + // No, create a new random ID + wAvatarID = GenerateServerID(SSIT_ITEM, 0); + wCommand = ICQ_LISTS_ADDTOLIST; +#ifdef _DEBUG + NetLog_Server("Made new srvAvatarID, id is %u", wAvatarID); +#endif + } + else + { +#ifdef _DEBUG + NetLog_Server("Reused srvAvatarID, id is %u", wAvatarID); +#endif + wCommand = ICQ_LISTS_UPDATEGROUP; + } + + ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + if (!ack) + { + NetLog_Server("Cookie alloc failure."); + return; // out of memory, go away + } + ack->dwAction = SSA_SETAVATAR; // update avatar hash + ack->wContactId = wAvatarID; + dwCookie = AllocateCookie(CKT_SERVERLIST, wCommand, 0, ack); // take cookie + + szItemName[0] = 0x30 + pHash[1]; + + // Build the packet + wTLVlen = 8 + hashsize; + + // Initialize our handy data buffer + pBuffer.wPlace = 0; + pBuffer.pData = (BYTE *)_alloca(wTLVlen); + pBuffer.wLen = wTLVlen; + + packTLV(&pBuffer, SSI_TLV_NAME, 0, NULL); // TLV (Name) + packTLV(&pBuffer, SSI_TLV_AVATARHASH, hashsize, pHash + 2); // TLV (Hash) + + icq_sendServerItem(dwCookie, wCommand, 0, wAvatarID, szItemName, pBuffer.pData, wTLVlen, SSI_ITEM_BUDDYICON, SSOP_ITEM_ACTION | dwOperationFlags, 400, pDoubleObject); + // There is no need to send ICQ_LISTS_CLI_MODIFYSTART or + // ICQ_LISTS_CLI_MODIFYEND when modifying the avatar hash + } +} + +// Should be called before the server list is modified. When all +// modifications are done, call icq_sendServerEndOperation(). +// Called automatically thru server-list update board! +void CIcqProto::icq_sendServerBeginOperation(int bImport) +{ + icq_packet packet; + WORD wImportID = getSettingWord(NULL, "SrvImportID", 0); + + if (bImport && wImportID) + { // we should be importing, check if already have import item + if (getSettingDword(NULL, "ImportTS", 0) + 604800 < getSettingDword(NULL, "LogonTS", 0)) + { // is the timestamp week older, clear it and begin new import + DWORD dwCookie; + cookie_servlist_action* ack; + + if (ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action))) + { // we have cookie good, go on + ack->dwAction = SSA_IMPORT; + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_REMOVEFROMLIST, 0, ack); + + icq_sendSimpleItem(dwCookie, ICQ_LISTS_REMOVEFROMLIST, 0, "ImportTime", 0, wImportID, SSI_ITEM_IMPORTTIME, SSOP_ITEM_ACTION | SSOF_SEND_DIRECTLY, 100); + } + } + } + + serverPacketInit(&packet, (WORD)(bImport?14:10)); + packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_MODIFYSTART); + if (bImport) packDWord(&packet, 1<<0x10); + sendServPacket(&packet); +} + +// Should be called after the server list has been modified to inform +// the server that we are done. +// Called automatically thru server-list update board! +void CIcqProto::icq_sendServerEndOperation() +{ + icq_packet packet; + + serverPacketInit(&packet, 10); + packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_MODIFYEND); + sendServPacket(&packet); +} + +// Sent when the last roster packet has been received +void CIcqProto::sendRosterAck(void) +{ + icq_packet packet; + + serverPacketInit(&packet, 10); + packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_GOTLIST); + sendServPacket(&packet); + +#ifdef _DEBUG + NetLog_Server("Sent SNAC(x13,x07) - CLI_ROSTERACK"); +#endif +} diff --git a/protocols/IcqOscarJ/src/fam_15icqserver.cpp b/protocols/IcqOscarJ/src/fam_15icqserver.cpp new file mode 100644 index 0000000000..3f332e4f27 --- /dev/null +++ b/protocols/IcqOscarJ/src/fam_15icqserver.cpp @@ -0,0 +1,1201 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +void CIcqProto::handleIcqExtensionsFam(BYTE *pBuffer, WORD wBufferLength, snac_header* pSnacHeader) +{ + switch (pSnacHeader->wSubtype) { + + case ICQ_META_ERROR: + handleExtensionError(pBuffer, wBufferLength); + break; + + case ICQ_META_SRV_REPLY: + handleExtensionServerInfo(pBuffer, wBufferLength, pSnacHeader->wFlags); + break; + + default: + NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_EXTENSIONS_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + } +} + + +void CIcqProto::handleExtensionError(BYTE *buf, WORD wPackLen) +{ + WORD wErrorCode; + + if (wPackLen < 2) + wErrorCode = 0; + + if (wPackLen >= 2 && wPackLen <= 6) + unpackWord(&buf, &wErrorCode); + else + { // TODO: cookies need to be handled and freed here on error + oscar_tlv_chain *chain = NULL; + + unpackWord(&buf, &wErrorCode); + wPackLen -= 2; + chain = readIntoTLVChain(&buf, wPackLen, 0); + if (chain) + { + oscar_tlv* pTLV; + + pTLV = chain->getTLV(0x21, 1); // get meta error data + if (pTLV && pTLV->wLen >= 8) + { + BYTE *pBuffer = pTLV->pData; + WORD wData; + pBuffer += 6; + unpackLEWord(&pBuffer, &wData); // get request type + switch (wData) + { + case CLI_META_INFO_REQ: + if (pTLV->wLen >= 12) + { + WORD wSubType; + WORD wCookie; + + unpackWord(&pBuffer, &wCookie); + unpackLEWord(&pBuffer, &wSubType); + // more sofisticated detection, send ack + if (wSubType == META_REQUEST_FULL_INFO) + { + HANDLE hContact; + cookie_fam15_data *pCookieData = NULL; + int foundCookie; + + foundCookie = FindCookie(wCookie, &hContact, (void**)&pCookieData); + if (foundCookie && pCookieData) + { + BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0); + + ReleaseCookie(wCookie); // we do not leak cookie and memory + } + + NetLog_Server("Full info request error 0x%02x received", wErrorCode); + } + else if (wSubType == META_SET_PASSWORD_REQ) + { // failed to change user password, report to UI + BroadcastAck(NULL, ACKTYPE_SETINFO, ACKRESULT_FAILED, (HANDLE)wCookie, 0); + + NetLog_Server("Meta change password request failed, error 0x%02x", wErrorCode); + } + else + NetLog_Server("Meta request error 0x%02x received", wErrorCode); + } + else + NetLog_Server("Meta request error 0x%02x received", wErrorCode); + + break; + + default: + NetLog_Server("Unknown request 0x%02x error 0x%02x received", wData, wErrorCode); + } + disposeChain(&chain); + return; + } + disposeChain(&chain); + } + } + LogFamilyError(ICQ_EXTENSIONS_FAMILY, wErrorCode); +} + + +void CIcqProto::handleExtensionServerInfo(BYTE *buf, WORD wPackLen, WORD wFlags) +{ + oscar_tlv_chain *chain; + oscar_tlv *dataTlv; + + // The entire packet is encapsulated in a TLV type 1 + chain = readIntoTLVChain(&buf, wPackLen, 0); + if (chain == NULL) + { + NetLog_Server("Error: Broken snac 15/3 %d", 1); + return; + } + + dataTlv = chain->getTLV(0x0001, 1); + if (dataTlv == NULL) + { + disposeChain(&chain); + NetLog_Server("Error: Broken snac 15/3 %d", 2); + return; + } + BYTE *databuf = dataTlv->pData; + wPackLen -= 4; + + _ASSERTE(dataTlv->wLen == wPackLen); + _ASSERTE(wPackLen >= 10); + + if ((dataTlv->wLen == wPackLen) && (wPackLen >= 10)) + { + WORD wBytesRemaining; + WORD wRequestType; + WORD wCookie; + DWORD dwMyUin; + + unpackLEWord(&databuf, &wBytesRemaining); + unpackLEDWord(&databuf, &dwMyUin); + unpackLEWord(&databuf, &wRequestType); + unpackWord(&databuf, &wCookie); + + _ASSERTE(wBytesRemaining == (wPackLen - 2)); + if (wBytesRemaining == (wPackLen - 2)) + { + wPackLen -= 10; + switch (wRequestType) + { + case SRV_META_INFO_REPLY: // SRV_META request replies + handleExtensionMetaResponse(databuf, wPackLen, wCookie, wFlags); + break; + + default: + NetLog_Server("Warning: Ignoring Meta response - Unknown type %d", wRequestType); + break; + } + } + } + else + NetLog_Server("Error: Broken snac 15/3 %d", 3); + + if (chain) + disposeChain(&chain); +} + + +void CIcqProto::handleExtensionMetaResponse(BYTE *databuf, WORD wPacketLen, WORD wCookie, WORD wFlags) +{ + WORD wReplySubtype; + BYTE bResultCode; + + _ASSERTE(wPacketLen >= 3); + if (wPacketLen >= 3) + { + // Reply subtype + unpackLEWord(&databuf, &wReplySubtype); + wPacketLen -= 2; + + // Success byte + unpackByte(&databuf, &bResultCode); + wPacketLen -= 1; + + switch (wReplySubtype) + { + case META_SET_PASSWORD_ACK: + parseUserInfoUpdateAck(databuf, wPacketLen, wCookie, wReplySubtype, bResultCode); + break; + + case SRV_RANDOM_FOUND: + case SRV_USER_FOUND: + case SRV_LAST_USER_FOUND: + parseSearchReplies(databuf, wPacketLen, wCookie, wReplySubtype, bResultCode); + break; + + case META_PROCESSING_ERROR: // Meta processing error server reply + // Todo: We only use this as an SMS ack, that will have to change + { + // Terminate buffer + char *pszInfo = (char *)_alloca(wPacketLen + 1); + if (wPacketLen > 0) + memcpy(pszInfo, databuf, wPacketLen); + pszInfo[wPacketLen] = 0; + + BroadcastAck(NULL, ICQACKTYPE_SMS, ACKRESULT_FAILED, (HANDLE)wCookie, (LPARAM)pszInfo); + FreeCookie(wCookie); + break; + } + break; + + case META_SMS_DELIVERY_RECEIPT: + // Todo: This overlaps with META_SET_AFFINFO_ACK. + // Todo: Check what happens if result != A + if (wPacketLen > 8) + { + WORD wNetworkNameLen; + WORD wAckLen; + char *pszInfo; + + + databuf += 6; // Some unknowns + wPacketLen -= 6; + + unpackWord(&databuf, &wNetworkNameLen); + if (wPacketLen >= (wNetworkNameLen + 2)) + { + databuf += wNetworkNameLen; + wPacketLen -= wNetworkNameLen; + + unpackWord(&databuf, &wAckLen); + if (pszInfo = (char *)_alloca(wAckLen + 1)) + { + // Terminate buffer + if (wAckLen > 0) + memcpy(pszInfo, databuf, wAckLen); + pszInfo[wAckLen] = 0; + + BroadcastAck(NULL, ICQACKTYPE_SMS, ACKRESULT_SENTREQUEST, (HANDLE)wCookie, (LPARAM)pszInfo); + FreeCookie(wCookie); + + // Parsing success + break; + } + } + } + + // Parsing failure + NetLog_Server("Error: Failure parsing META_SMS_DELIVERY_RECEIPT"); + break; + + case META_DIRECTORY_DATA: + case META_DIRECTORY_RESPONSE: + if (bResultCode == 0x0A) + handleDirectoryQueryResponse(databuf, wPacketLen, wCookie, wReplySubtype, wFlags); + else + NetLog_Server("Error: Directory request failed, code %u", bResultCode); + break; + + case META_DIRECTORY_UPDATE_ACK: + if (bResultCode == 0x0A) + handleDirectoryUpdateResponse(databuf, wPacketLen, wCookie, wReplySubtype); + else + NetLog_Server("Error: Directory request failed, code %u", bResultCode); + break; + + default: + NetLog_Server("Warning: Ignored 15/03 replysubtype x%x", wReplySubtype); + // _ASSERTE(0); + break; + } + + // Success + return; + } + + // Failure + NetLog_Server("Warning: Broken 15/03 ExtensionMetaResponse"); +} + + +void CIcqProto::ReleaseSearchCookie(DWORD dwCookie, cookie_search *pCookie) +{ + if (pCookie) + { + FreeCookie(dwCookie); + if (pCookie->dwMainId) + { + if (pCookie->dwStatus) + { + SAFE_FREE((void**)&pCookie); + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0); + } + else + pCookie->dwStatus = 1; + } + else + { + SAFE_FREE((void**)&pCookie); + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0); + } + } + else + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0); +} + + +void CIcqProto::parseSearchReplies(unsigned char *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype, BYTE bResultCode) +{ + BYTE bParsingOK = FALSE; // For debugging purposes only + BOOL bLastUser = FALSE; + cookie_search *pCookie; + + if (!FindCookie(wCookie, NULL, (void**)&pCookie)) + { + NetLog_Server("Warning: Received unexpected search reply"); + pCookie = NULL; + } + + switch (wReplySubtype) + { + + case SRV_LAST_USER_FOUND: // Search: last user found reply + bLastUser = TRUE; + + case SRV_USER_FOUND: // Search: user found reply + if (bLastUser) + NetLog_Server("SNAC(0x15,0x3): Last search reply"); + else + NetLog_Server("SNAC(0x15,0x3): Search reply"); + + if (bResultCode == 0xA) + { + ICQSEARCHRESULT sr = {0}; + DWORD dwUin; + char szUin[UINMAXLEN]; + WORD wLen; + + sr.hdr.cbSize = sizeof(sr); + + // Remaining bytes + if (wPacketLen < 2) + break; + unpackLEWord(&databuf, &wLen); + wPacketLen -= 2; + + _ASSERTE(wLen <= wPacketLen); + if (wLen > wPacketLen) + break; + + // Uin + if (wPacketLen < 4) + break; + unpackLEDWord(&databuf, &dwUin); // Uin + wPacketLen -= 4; + sr.uin = dwUin; + _itoa(dwUin, szUin, 10); + sr.hdr.id = (FNAMECHAR*)szUin; + + // Nick + if (wPacketLen < 2) + break; + unpackLEWord(&databuf, &wLen); + wPacketLen -= 2; + if (wLen > 0) + { + if (wPacketLen < wLen || (databuf[wLen-1] != 0)) + break; + sr.hdr.nick = (FNAMECHAR*)databuf; + databuf += wLen; + } + else + { + sr.hdr.nick = NULL; + } + + // First name + if (wPacketLen < 2) + break; + unpackLEWord(&databuf, &wLen); + wPacketLen -= 2; + if (wLen > 0) + { + if (wPacketLen < wLen || (databuf[wLen-1] != 0)) + break; + sr.hdr.firstName = (FNAMECHAR*)databuf; + databuf += wLen; + } + else + { + sr.hdr.firstName = NULL; + } + + // Last name + if (wPacketLen < 2) + break; + unpackLEWord(&databuf, &wLen); + wPacketLen -= 2; + if (wLen > 0) + { + if (wPacketLen < wLen || (databuf[wLen-1] != 0)) + break; + sr.hdr.lastName = (FNAMECHAR*)databuf; + databuf += wLen; + } + else + { + sr.hdr.lastName = NULL; + } + + // E-mail name + if (wPacketLen < 2) + break; + unpackLEWord(&databuf, &wLen); + wPacketLen -= 2; + if (wLen > 0) + { + if (wPacketLen < wLen || (databuf[wLen-1] != 0)) + break; + sr.hdr.email = (FNAMECHAR*)databuf; + databuf += wLen; + } + else + { + sr.hdr.email = NULL; + } + + // Authentication needed flag + if (wPacketLen < 1) + break; + unpackByte(&databuf, &sr.auth); + + // Finally, broadcast the result + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)wCookie, (LPARAM)&sr); + + // Broadcast "Last result" ack if this was the last user found + if (wReplySubtype == SRV_LAST_USER_FOUND) + { + if (wPacketLen>=10) + { + DWORD dwLeft; + + databuf += 5; + unpackLEDWord(&databuf, &dwLeft); + if (dwLeft) + NetLog_Server("Warning: %d search results omitted", dwLeft); + } + ReleaseSearchCookie(wCookie, pCookie); + } + bParsingOK = TRUE; + } + else + { + // Failed search + NetLog_Server("SNAC(0x15,0x3): Search error %u", bResultCode); + + ReleaseSearchCookie(wCookie, pCookie); + + bParsingOK = TRUE; + } + break; + + case SRV_RANDOM_FOUND: // Random search server reply + default: + if (pCookie) + ReleaseCookie(wCookie); + break; + } + + // For debugging purposes only + if (!bParsingOK) + { + NetLog_Server("Warning: Parsing error in 15/03 search reply type x%x", wReplySubtype); + _ASSERTE(!bParsingOK); + } +} + + +void CIcqProto::parseUserInfoUpdateAck(unsigned char *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype, BYTE bResultCode) +{ + switch (wReplySubtype) { + case META_SET_PASSWORD_ACK: // Set user password server ack + + if (bResultCode == 0xA) + BroadcastAck(NULL, ACKTYPE_SETINFO, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); + else + BroadcastAck(NULL, ACKTYPE_SETINFO, ACKRESULT_FAILED, (HANDLE)wCookie, 0); + + FreeCookie(wCookie); + break; + + default: + NetLog_Server("Warning: Ignored 15/03 user info update ack type x%x", wReplySubtype); + break; + } +} + + +UserInfoRecordItem rEmail[] = { + {0x64, DBVT_ASCIIZ, "e-mail%u"} +}; + +UserInfoRecordItem rAddress[] = { + {0x64, DBVT_UTF8, "Street"}, + {0x6E, DBVT_UTF8, "City"}, + {0x78, DBVT_UTF8, "State"}, + {0x82, DBVT_UTF8, "ZIP"}, + {0x8C, DBVT_WORD, "Country"} +}; + +UserInfoRecordItem rOriginAddress[] = { + {0x64, DBVT_UTF8, "OriginStreet"}, + {0x6E, DBVT_UTF8, "OriginCity"}, + {0x78, DBVT_UTF8, "OriginState"}, + {0x8C, DBVT_WORD, "OriginCountry"} +}; + +UserInfoRecordItem rCompany[] = { + {0x64, DBVT_UTF8, "CompanyPosition"}, + {0x6E, DBVT_UTF8, "Company"}, + {0x7D, DBVT_UTF8, "CompanyDepartment"}, + {0x78, DBVT_ASCIIZ, "CompanyHomepage"}, + {0x82, DBVT_WORD, "CompanyIndustry"}, + {0xAA, DBVT_UTF8, "CompanyStreet"}, + {0xB4, DBVT_UTF8, "CompanyCity"}, + {0xBE, DBVT_UTF8, "CompanyState"}, + {0xC8, DBVT_UTF8, "CompanyZIP"}, + {0xD2, DBVT_WORD, "CompanyCountry"} +}; + +UserInfoRecordItem rEducation[] = { + {0x64, DBVT_WORD, "StudyLevel"}, + {0x6E, DBVT_UTF8, "StudyInstitute"}, + {0x78, DBVT_UTF8, "StudyDegree"}, + {0x8C, DBVT_WORD, "StudyYear"} +}; + +UserInfoRecordItem rInterest[] = { + {0x64, DBVT_UTF8, "Interest%uText"}, + {0x6E, DBVT_WORD, "Interest%uCat"} +}; + + +int CIcqProto::parseUserInfoRecord(HANDLE hContact, oscar_tlv *pData, UserInfoRecordItem pRecordDef[], int nRecordDef, int nMaxRecords) +{ + int nRecords = 0; + + if (pData && pData->wLen >= 2) + { + BYTE *pRecords = pData->pData; + WORD wRecordCount; + unpackWord(&pRecords, &wRecordCount); + oscar_tlv_record_list *cData = readIntoTLVRecordList(&pRecords, pData->wLen - 2, nMaxRecords > wRecordCount ? wRecordCount : nMaxRecords); + oscar_tlv_record_list *cDataItem = cData; + while (cDataItem) + { + oscar_tlv_chain *cItem = cDataItem->item; + + for (int i = 0; i < nRecordDef; i++) + { + char szItemKey[MAX_PATH]; + + null_snprintf(szItemKey, MAX_PATH, pRecordDef[i].szDbSetting, nRecords); + + switch (pRecordDef[i].dbType) + { + case DBVT_ASCIIZ: + writeDbInfoSettingTLVString(hContact, szItemKey, cItem, pRecordDef[i].wTLV); + break; + + case DBVT_UTF8: + writeDbInfoSettingTLVStringUtf(hContact, szItemKey, cItem, pRecordDef[i].wTLV); + break; + + case DBVT_WORD: + writeDbInfoSettingTLVWord(hContact, szItemKey, cItem, pRecordDef[i].wTLV); + break; + } + } + nRecords++; + + cDataItem = cDataItem->next; + } + // release memory + disposeRecordList(&cData); + } + // remove old data from database + if (!nRecords || nMaxRecords > 1) + for (int i = nRecords; i <= nMaxRecords; i++) + for (int j = 0; j < nRecordDef; j++) + { + char szItemKey[MAX_PATH]; + + null_snprintf(szItemKey, MAX_PATH, pRecordDef[j].szDbSetting, i); + + deleteSetting(hContact, szItemKey); + } + + return nRecords; +} + + +void CIcqProto::handleDirectoryQueryResponse(BYTE *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype, WORD wFlags) +{ + WORD wBytesRemaining = 0; + snac_header requestSnac = {0}; + BYTE requestResult; + +#ifdef _DEBUG + NetLog_Server("Received directory query response"); +#endif + if (wPacketLen >= 2) + unpackLEWord(&databuf, &wBytesRemaining); + wPacketLen -= 2; + _ASSERTE(wPacketLen == wBytesRemaining); + + if (!unpackSnacHeader(&requestSnac, &databuf, &wPacketLen) || !requestSnac.bValid) + { + NetLog_Server("Error: Failed to parse directory response"); + return; + } + + cookie_directory_data *pCookieData; + HANDLE hContact; + // check request cookie + if (!FindCookie(wCookie, &hContact, (void**)&pCookieData) || !pCookieData) + { + NetLog_Server("Warning: Ignoring unrequested directory reply type (x%x, x%x)", requestSnac.wFamily, requestSnac.wSubtype); + return; + } + /// FIXME: we should really check the snac contents according to cookie data here ?? + + // Check if this is the last packet for this request + BOOL bMoreDataFollows = wFlags&0x0001 && requestSnac.wFlags&0x0001; + + if (wPacketLen >= 3) + unpackByte(&databuf, &requestResult); + else + { + NetLog_Server("Error: Malformed directory response"); + if (!bMoreDataFollows) + ReleaseCookie(wCookie); + return; + } + if (requestResult != 1 && requestResult != 4) + { + NetLog_Server("Error: Directory request failed, status %u", requestResult); + + if (!bMoreDataFollows) + { + if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER) + BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0); + else if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH) + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); // should report error here, but Find/Add module does not support that + ReleaseCookie(wCookie); + } + return; + } + WORD wLen; + + unpackWord(&databuf, &wLen); + wPacketLen -= 3; + if (wLen) + NetLog_Server("Warning: Data in error message present!"); + + if (wPacketLen <= 0x16) + { // sanity check + NetLog_Server("Error: Malformed directory response"); + + if (!bMoreDataFollows) + { + if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER) + BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0); + else if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH) + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); // should report error here, but Find/Add module does not support that + ReleaseCookie(wCookie); + } + return; + } + databuf += 0x10; // unknown stuff + wPacketLen -= 0x10; + + DWORD dwItemCount; + WORD wPageCount; + + /// FIXME: check itemcount, pagecount against the cookie data ??? + + unpackDWord(&databuf, &dwItemCount); + unpackWord(&databuf, &wPageCount); + wPacketLen -= 6; + + if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH && !bMoreDataFollows) + NetLog_Server("Directory Search: %d contacts found (%u pages)", dwItemCount, wPageCount); + + if (wPacketLen <= 2) + { // sanity check, block expected + NetLog_Server("Error: Malformed directory response"); + + if (!bMoreDataFollows) + { + if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER) + BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0); + else if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH) + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); // should report error here, but Find/Add module does not support that + ReleaseCookie(wCookie); + } + return; + } + WORD wData; + + unpackWord(&databuf, &wData); // This probably the count of items following (a block) + wPacketLen -= 2; + if (wPacketLen >= 2 && wData >= 1) + { + unpackWord(&databuf, &wLen); // This is the size of the first item + wPacketLen -= 2; + } + + if (wData == 0 && pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH) + { + NetLog_Server("Directory Search: No contacts found"); + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); + ReleaseCookie(wCookie); + return; + } + + _ASSERTE(wData == 1 && wPacketLen == wLen); + if (wData != 1 || wPacketLen != wLen) + { + NetLog_Server("Error: Malformed directory response (missing data)"); + + if (!bMoreDataFollows) + { + if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER) + BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0); + else if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH) + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); // should report error here, but Find/Add module does not support that + ReleaseCookie(wCookie); + } + return; + } + oscar_tlv_chain *pDirectoryData = readIntoTLVChain(&databuf, wLen, -1); + if (pDirectoryData) + { + switch (pCookieData->bRequestType) + { + case DIRECTORYREQUEST_INFOOWNER: + parseDirectoryUserDetailsData(NULL, pDirectoryData, wCookie, pCookieData, wReplySubtype); + break; + + case DIRECTORYREQUEST_INFOUSER: + { + DWORD dwUin = 0; + char *szUid = pDirectoryData->getString(0x32, 1); + if (!szUid) + { + NetLog_Server("Error: Received unrecognized data from the directory"); + break; + } + + if (IsStringUIN(szUid)) + dwUin = atoi(szUid); + + if (hContact != HContactFromUID(dwUin, szUid, NULL)) + { + NetLog_Server("Error: Received data does not match cookie contact, ignoring."); + SAFE_FREE(&szUid); + break; + } + else + SAFE_FREE(&szUid); + } + + case DIRECTORYREQUEST_INFOMULTI: + parseDirectoryUserDetailsData(hContact, pDirectoryData, wCookie, pCookieData, wReplySubtype); + break; + + case DIRECTORYREQUEST_SEARCH: + parseDirectorySearchData(pDirectoryData, wCookie, pCookieData, wReplySubtype); + break; + + default: + NetLog_Server("Error: Unknown cookie type %x for directory response!", pCookieData->bRequestType); + } + disposeChain(&pDirectoryData); + } + else + NetLog_Server("Error: Failed parsing directory response"); + + // Release Memory + if (!bMoreDataFollows) + ReleaseCookie(wCookie); +} + + +static int calcAgeFromBirthDate(double dDate) +{ + if (dDate > 0) + { // date is stored as double with unit equal to a day, incrementing since 1/1/1900 0:00 GMT + SYSTEMTIME sDate = {0}; + if (VariantTimeToSystemTime(dDate + 2, &sDate)) + { + SYSTEMTIME sToday = {0}; + + GetLocalTime(&sToday); + + int nAge = sToday.wYear - sDate.wYear; + + if (sToday.wMonth < sDate.wMonth || (sToday.wMonth == sDate.wMonth && sToday.wDay < sDate.wDay)) + nAge--; + + return nAge; + } + } + return 0; +} + + +void CIcqProto::parseDirectoryUserDetailsData(HANDLE hContact, oscar_tlv_chain *cDetails, DWORD dwCookie, cookie_directory_data *pCookieData, WORD wReplySubType) +{ + oscar_tlv *pTLV; + WORD wRecordCount; + + if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOMULTI && !hContact) + { + DWORD dwUin = 0; + char *szUid = cDetails->getString(0x32, 1); + if (!szUid) + { + NetLog_Server("Error: Received unrecognized data from the directory"); + return; + } + + if (IsStringUIN(szUid)) + dwUin = atoi(szUid); + + hContact = HContactFromUID(dwUin, szUid, NULL); + if (hContact == INVALID_HANDLE_VALUE) + { + NetLog_Server("Error: Received details for unknown contact \"%s\"", szUid); + SAFE_FREE(&szUid); + return; + } +#ifdef _DEBUG + else + NetLog_Server("Received user info for %s from directory", szUid); +#endif + SAFE_FREE(&szUid); + } +#ifdef _DEBUG + else + { + char *szUid = cDetails->getString(0x32, 1); + + if (!hContact) + NetLog_Server("Received owner user info from directory"); + else + NetLog_Server("Received user info for %s from directory", szUid); + SAFE_FREE(&szUid); + } +#endif + + pTLV = cDetails->getTLV(0x50, 1); + if (pTLV && pTLV->wLen > 0) + writeDbInfoSettingTLVString(hContact, "e-mail", cDetails, 0x50); // Verified e-mail + else + writeDbInfoSettingTLVString(hContact, "e-mail", cDetails, 0x55); // Pending e-mail + + writeDbInfoSettingTLVStringUtf(hContact, "FirstName", cDetails, 0x64); + writeDbInfoSettingTLVStringUtf(hContact, "LastName", cDetails, 0x6E); + writeDbInfoSettingTLVStringUtf(hContact, "Nick", cDetails, 0x78); + // Home Address + parseUserInfoRecord(hContact, cDetails->getTLV(0x96, 1), rAddress, SIZEOF(rAddress), 1); + // Origin Address + parseUserInfoRecord(hContact, cDetails->getTLV(0xA0, 1), rOriginAddress, SIZEOF(rOriginAddress), 1); + // Phones + pTLV = cDetails->getTLV(0xC8, 1); + if (pTLV && pTLV->wLen >= 2) + { + BYTE *pRecords = pTLV->pData; + unpackWord(&pRecords, &wRecordCount); + oscar_tlv_record_list *cPhones = readIntoTLVRecordList(&pRecords, pTLV->wLen - 2, wRecordCount); + if (cPhones) + { + oscar_tlv_chain *cPhone; + cPhone = cPhones->getRecordByTLV(0x6E, 1); + writeDbInfoSettingTLVString(hContact, "Phone", cPhone, 0x64); + cPhone = cPhones->getRecordByTLV(0x6E, 2); + writeDbInfoSettingTLVString(hContact, "CompanyPhone", cPhone, 0x64); + cPhone = cPhones->getRecordByTLV(0x6E, 3); + writeDbInfoSettingTLVString(hContact, "Cellular", cPhone, 0x64); + cPhone = cPhones->getRecordByTLV(0x6E, 4); + writeDbInfoSettingTLVString(hContact, "Fax", cPhone, 0x64); + cPhone = cPhones->getRecordByTLV(0x6E, 5); + writeDbInfoSettingTLVString(hContact, "CompanyFax", cPhone, 0x64); + + disposeRecordList(&cPhones); + } + else + { // Remove old data when phones not available + deleteSetting(hContact, "Phone"); + deleteSetting(hContact, "CompanyPhone"); + deleteSetting(hContact, "Cellular"); + deleteSetting(hContact, "Fax"); + deleteSetting(hContact, "CompanyFax"); + } + } + else + { // Remove old data when phones not available + deleteSetting(hContact, "Phone"); + deleteSetting(hContact, "CompanyPhone"); + deleteSetting(hContact, "Cellular"); + deleteSetting(hContact, "Fax"); + deleteSetting(hContact, "CompanyFax"); + } + // Emails + parseUserInfoRecord(hContact, cDetails->getTLV(0x8C, 1), rEmail, SIZEOF(rEmail), 4); + + writeDbInfoSettingTLVByte(hContact, "Timezone", cDetails, 0x17C); + // Company + parseUserInfoRecord(hContact, cDetails->getTLV(0x118, 1), rCompany, SIZEOF(rCompany), 1); + // Education + parseUserInfoRecord(hContact, cDetails->getTLV(0x10E, 1), rEducation, SIZEOF(rEducation), 1); + + switch (cDetails->getNumber(0x82, 1)) + { + case 1: + setSettingByte(hContact, "Gender", 'F'); + break; + case 2: + setSettingByte(hContact, "Gender", 'M'); + break; + default: + deleteSetting(hContact, "Gender"); + } + + writeDbInfoSettingTLVString(hContact, "Homepage", cDetails, 0xFA); + writeDbInfoSettingTLVDate(hContact, "BirthYear", "BirthMonth", "BirthDay", cDetails, 0x1A4); + + writeDbInfoSettingTLVByte(hContact, "Language1", cDetails, 0xAA); + writeDbInfoSettingTLVByte(hContact, "Language2", cDetails, 0xB4); + writeDbInfoSettingTLVByte(hContact, "Language3", cDetails, 0xBE); + + writeDbInfoSettingTLVByte(hContact, "MaritalStatus", cDetails, 0x12C); + // Interests + parseUserInfoRecord(hContact, cDetails->getTLV(0x122, 1), rInterest, SIZEOF(rInterest), 4); + + writeDbInfoSettingTLVStringUtf(hContact, "About", cDetails, 0x186); + +// if (hContact) +// writeDbInfoSettingTLVStringUtf(hContact, DBSETTING_STATUS_NOTE, cDetails, 0x226); +// else + if (!hContact) + { // Owner contact needs special processing, in the database is current status note for the client + // We just received the last status note set on directory, if it differs call SetStatusNote() to + // ensure the directory will be updated (it should be in process anyway) + char *szClientStatusNote = getSettingStringUtf(hContact, DBSETTING_STATUS_NOTE, NULL); + char *szDirectoryStatusNote = cDetails->getString(0x226, 1); + + if (strcmpnull(szClientStatusNote, szDirectoryStatusNote)) + SetStatusNote(szClientStatusNote, 1000, TRUE); + + // Release memory + SAFE_FREE(&szDirectoryStatusNote); + SAFE_FREE(&szClientStatusNote); + } + + writeDbInfoSettingTLVByte(hContact, "PrivacyLevel", cDetails, 0x1F9); + + if (!hContact) + { + setSettingByte(hContact, "Auth", !cDetails->getByte(0x19A, 1)); + writeDbInfoSettingTLVByte(hContact, "WebAware", cDetails, 0x212); + writeDbInfoSettingTLVByte(hContact, "AllowSpam", cDetails, 0x1EA); + } + + writeDbInfoSettingTLVWord(hContact, "InfoCP", cDetails, 0x1C2); + + if (hContact) + { // Handle deprecated setting (Age & Birthdate are not separate fields anymore) + int nAge = calcAgeFromBirthDate(cDetails->getDouble(0x1A4, 1)); + + if (nAge) + setSettingWord(hContact, "Age", nAge); + else + deleteSetting(hContact, "Age"); + } + else // we do not need to calculate age for owner + deleteSetting(hContact, "Age"); + + { // Save user info last update time and privacy token + double dInfoTime; + BYTE pbEmptyMetaToken[0x10] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + int bHasMetaToken = FALSE; + + // Check if the details arrived with privacy token! + if ((pTLV = cDetails->getTLV(0x3C, 1)) && pTLV->wLen == 0x10 && memcmp(pTLV->pData, pbEmptyMetaToken, 0x10)) + bHasMetaToken = TRUE; + + // !Important, we need to save the MDir server-item time - it can be newer than the one from the directory + if ((dInfoTime = getSettingDouble(hContact, DBSETTING_METAINFO_TIME, 0)) > 0) + setSettingDouble(hContact, DBSETTING_METAINFO_SAVED, dInfoTime); + else if (bHasMetaToken || !hContact) + writeDbInfoSettingTLVDouble(hContact, DBSETTING_METAINFO_SAVED, cDetails, 0x1CC); + else + setSettingDword(hContact, DBSETTING_METAINFO_SAVED, time(NULL)); + } + + if (wReplySubType == META_DIRECTORY_RESPONSE) + if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER) + BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, (HANDLE)1 ,0); + + // Remove user from info update queue. Removing is fast so we always call this + // even if it is likely that the user is not queued at all. + if (hContact) + icq_DequeueUser(getContactUin(hContact)); +} + + +void CIcqProto::parseDirectorySearchData(oscar_tlv_chain *cDetails, DWORD dwCookie, cookie_directory_data *pCookieData, WORD wReplySubType) +{ + ICQSEARCHRESULT isr = {0}; + char *szUid = cDetails->getString(0x32, 1); // User ID + +#ifdef _DEBUG + NetLog_Server("Directory Search: Found user %s", szUid); +#endif + isr.hdr.cbSize = sizeof(ICQSEARCHRESULT); + isr.hdr.flags = PSR_TCHAR; + isr.hdr.id = ansi_to_tchar(szUid); + + if (IsStringUIN(szUid)) + isr.uin = atoi(szUid); + else + isr.uin = 0; + + SAFE_FREE(&szUid); + + oscar_tlv *pTLV = cDetails->getTLV(0x50, 1); + char *szData = NULL; + + if (pTLV && pTLV->wLen > 0) + szData = cDetails->getString(0x50, 1); // Verified e-mail + else + szData = cDetails->getString(0x55, 1); // Pending e-mail + if (strlennull(szData)) + isr.hdr.email = ansi_to_tchar(szData); + SAFE_FREE(&szData); + + szData = cDetails->getString(0x64, 1); // First Name + if (strlennull(szData)) + isr.hdr.firstName = utf8_to_tchar(szData); + SAFE_FREE(&szData); + + szData = cDetails->getString(0x6E, 1); // Last Name + if (strlennull(szData)) + isr.hdr.lastName = utf8_to_tchar(szData); + SAFE_FREE(&szData); + + szData = cDetails->getString(0x78, 1); // Nick + if (strlennull(szData)) + isr.hdr.nick = utf8_to_tchar(szData); + SAFE_FREE(&szData); + + switch (cDetails->getNumber(0x82, 1)) // Gender + { + case 1: + isr.gender = 'F'; + break; + case 2: + isr.gender = 'M'; + break; + } + + pTLV = cDetails->getTLV(0x96, 1); + if (pTLV && pTLV->wLen >= 4) + { + BYTE *buf = pTLV->pData; + oscar_tlv_chain *chain = readIntoTLVChain(&buf, pTLV->wLen, 0); + if (chain) + isr.country = chain->getDWord(0x8C, 1); // Home Country + disposeChain(&chain); + } + + isr.auth = !cDetails->getByte(0x19A, 1); // Require Authorization + isr.maritalStatus = cDetails->getNumber(0x12C, 1); // Marital Status + + // calculate Age if Birthdate is available + isr.age = calcAgeFromBirthDate(cDetails->getDouble(0x1A4, 1)); + + // Finally, broadcast the result + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)dwCookie, (LPARAM)&isr); + + // Release memory + SAFE_FREE(&isr.hdr.id); + SAFE_FREE(&isr.hdr.nick); + SAFE_FREE(&isr.hdr.firstName); + SAFE_FREE(&isr.hdr.lastName); + SAFE_FREE(&isr.hdr.email); + + // Search is over, broadcast final ack + if (wReplySubType == META_DIRECTORY_RESPONSE) + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0); +} + + +void CIcqProto::handleDirectoryUpdateResponse(BYTE *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype) +{ + WORD wBytesRemaining = 0; + snac_header requestSnac = {0}; + BYTE requestResult; + +#ifdef _DEBUG + NetLog_Server("Received directory update response"); +#endif + if (wPacketLen >= 2) + unpackLEWord(&databuf, &wBytesRemaining); + wPacketLen -= 2; + _ASSERTE(wPacketLen == wBytesRemaining); + + if (!unpackSnacHeader(&requestSnac, &databuf, &wPacketLen) || !requestSnac.bValid) + { + NetLog_Server("Error: Failed to parse directory response"); + return; + } + + cookie_directory_data *pCookieData; + HANDLE hContact; + // check request cookie + if (!FindCookie(wCookie, &hContact, (void**)&pCookieData) || !pCookieData) + { + NetLog_Server("Warning: Ignoring unrequested directory reply type (x%x, x%x)", requestSnac.wFamily, requestSnac.wSubtype); + return; + } + /// FIXME: we should really check the snac contents according to cookie data here ?? + + if (wPacketLen >= 3) + unpackByte(&databuf, &requestResult); + else + { + NetLog_Server("Error: Malformed directory response"); + ReleaseCookie(wCookie); + return; + } + if (requestResult != 1 && requestResult != 4) + { + NetLog_Server("Error: Directory request failed, status %u", requestResult); + + if (pCookieData->bRequestType == DIRECTORYREQUEST_UPDATEOWNER) + BroadcastAck(NULL, ACKTYPE_SETINFO, ACKRESULT_FAILED, (HANDLE)wCookie, 0); + + ReleaseCookie(wCookie); + return; + } + WORD wLen; + + unpackWord(&databuf, &wLen); + wPacketLen -= 3; + if (wLen) + NetLog_Server("Warning: Data in error message present!"); + + if (pCookieData->bRequestType == DIRECTORYREQUEST_UPDATEOWNER) + BroadcastAck(NULL, ACKTYPE_SETINFO, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); + if (wPacketLen == 0x18) + { + DWORD64 qwMetaTime; + BYTE pbEmptyMetaToken[0x10] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + + unpackQWord(&databuf, &qwMetaTime); + setSettingBlob(NULL, DBSETTING_METAINFO_TIME, (BYTE*)&qwMetaTime, 8); + + if (memcmp(databuf, pbEmptyMetaToken, 0x10)) + setSettingBlob(NULL, DBSETTING_METAINFO_TOKEN, databuf, 0x10); + } + ReleaseCookie(wCookie); +} diff --git a/protocols/IcqOscarJ/src/fam_17signon.cpp b/protocols/IcqOscarJ/src/fam_17signon.cpp new file mode 100644 index 0000000000..b35a432a48 --- /dev/null +++ b/protocols/IcqOscarJ/src/fam_17signon.cpp @@ -0,0 +1,175 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2009 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +void CIcqProto::handleAuthorizationFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader, serverthread_info *info) +{ + switch (pSnacHeader->wSubtype) { + + case ICQ_SIGNON_ERROR: + { + WORD wError; + + if (wBufferLength >= 2) + unpackWord(&pBuffer, &wError); + else + wError = 0; + + LogFamilyError(ICQ_AUTHORIZATION_FAMILY, wError); + break; + } + + case ICQ_SIGNON_AUTH_KEY: + handleAuthKeyResponse(pBuffer, wBufferLength, info); + break; + + case ICQ_SIGNON_LOGIN_REPLY: + handleLoginReply(pBuffer, wBufferLength, info); + break; + + default: + NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_AUTHORIZATION_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + } +} + +static void icq_encryptPassword(const char *szPassword, BYTE *encrypted) +{ + BYTE table[] = + { + 0xf3, 0x26, 0x81, 0xc4, + 0x39, 0x86, 0xdb, 0x92, + 0x71, 0xa3, 0xb9, 0xe6, + 0x53, 0x7a, 0x95, 0x7c + }; + + for (int i = 0; szPassword[i]; i++) + encrypted[i] = (szPassword[i] ^ table[i % 16]); +} + +void CIcqProto::sendClientAuth(const char *szKey, WORD wKeyLen, BOOL bSecure) +{ + char szUin[UINMAXLEN]; + WORD wUinLen; + icq_packet packet; + + wUinLen = strlennull(strUID(m_dwLocalUIN, szUin)); + + packet.wLen = 70 + sizeof(CLIENT_ID_STRING) + wUinLen + wKeyLen + (m_bSecureConnection ? 4 : 0); + + if (bSecure) + { + serverPacketInit(&packet, (WORD)(packet.wLen + 10)); + packFNACHeader(&packet, ICQ_AUTHORIZATION_FAMILY, ICQ_SIGNON_LOGIN_REQUEST, 0, 0); + } + else + { + write_flap(&packet, ICQ_LOGIN_CHAN); + packDWord(&packet, 0x00000001); + } + packTLV(&packet, 0x0001, wUinLen, (LPBYTE)szUin); + + if (bSecure) + { // Pack MD5 auth digest + packTLV(&packet, 0x0025, wKeyLen, (BYTE*)szKey); + packDWord(&packet, 0x004C0000); // empty TLV(0x4C): unknown + } + else + { // Pack old style password hash + BYTE hash[20]; + + icq_encryptPassword(szKey, hash); + packTLV(&packet, 0x0002, wKeyLen, hash); + } + + // Pack client identification details. + packTLV(&packet, 0x0003, (WORD)sizeof(CLIENT_ID_STRING)-1, (LPBYTE)CLIENT_ID_STRING); + packTLVWord(&packet, 0x0017, CLIENT_VERSION_MAJOR); + packTLVWord(&packet, 0x0018, CLIENT_VERSION_MINOR); + packTLVWord(&packet, 0x0019, CLIENT_VERSION_LESSER); + packTLVWord(&packet, 0x001a, CLIENT_VERSION_BUILD); + packTLVWord(&packet, 0x0016, CLIENT_ID_CODE); + packTLVDWord(&packet, 0x0014, CLIENT_DISTRIBUTION); + packTLV(&packet, 0x000f, 0x0002, (LPBYTE)CLIENT_LANGUAGE); + packTLV(&packet, 0x000e, 0x0002, (LPBYTE)CLIENT_COUNTRY); + packTLV(&packet, 0x0094, 0x0001, &m_bConnectionLost); // CLIENT_RECONNECT flag + if (m_bSecureConnection) + packDWord(&packet, 0x008C0000); // empty TLV(0x8C): use SSL + + sendServPacket(&packet); +} + +void CIcqProto::handleAuthKeyResponse(BYTE *buf, WORD wPacketLen, serverthread_info *info) +{ + WORD wKeyLen; + char szKey[64] = {0}; + mir_md5_state_t state; + mir_md5_byte_t digest[16]; + +#ifdef _DEBUG + NetLog_Server("Received %s", "ICQ_SIGNON_AUTH_KEY"); +#endif + + if (wPacketLen < 2) + { + NetLog_Server("Malformed %s", "ICQ_SIGNON_AUTH_KEY"); + icq_LogMessage(LOG_FATAL, LPGEN("Secure login failed.\nInvalid server response.")); + SetCurrentStatus(ID_STATUS_OFFLINE); + return; + } + + unpackWord(&buf, &wKeyLen); + wPacketLen -= 2; + + if (!wKeyLen || wKeyLen > wPacketLen || wKeyLen > sizeof(szKey)) + { + NetLog_Server("Invalid length in %s: %u", "ICQ_SIGNON_AUTH_KEY", wKeyLen); + icq_LogMessage(LOG_FATAL, LPGEN("Secure login failed.\nInvalid key length.")); + SetCurrentStatus(ID_STATUS_OFFLINE); + return; + } + + unpackString(&buf, szKey, wKeyLen); + + mir_md5_init(&state); + mir_md5_append(&state, info->szAuthKey, info->wAuthKeyLen); + mir_md5_finish(&state, digest); + + mir_md5_init(&state); + mir_md5_append(&state, (LPBYTE)szKey, wKeyLen); + mir_md5_append(&state, digest, 16); + mir_md5_append(&state, (LPBYTE)CLIENT_MD5_STRING, sizeof(CLIENT_MD5_STRING)-1); + mir_md5_finish(&state, digest); + +#ifdef _DEBUG + NetLog_Server("Sending ICQ_SIGNON_LOGIN_REQUEST to login server"); +#endif + sendClientAuth((char*)digest, 0x10, TRUE); +} diff --git a/protocols/IcqOscarJ/src/families.h b/protocols/IcqOscarJ/src/families.h new file mode 100644 index 0000000000..61ac38f17a --- /dev/null +++ b/protocols/IcqOscarJ/src/families.h @@ -0,0 +1,74 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Declaration for handlers of Channel 2 SNAC Families +// +// ----------------------------------------------------------------------------- +#ifndef __FAMILIES_H +#define __FAMILIES_H + + +struct message_ack_params +{ + BYTE bType; + DWORD dwUin; + DWORD dwMsgID1; + DWORD dwMsgID2; + directconnect *pDC; + WORD wCookie; + int msgType; + BYTE bFlags; +}; + +#define MAT_SERVER_ADVANCED 0 +#define MAT_DIRECT 1 + + +/* handleMessageTypes(): mMsgFlags constants */ +#define MTF_DIRECT 1 +#define MTF_PLUGIN 2 +#define MTF_STATUS_EXTENDED 4 + + +struct UserInfoRecordItem +{ + WORD wTLV; + int dbType; + char *szDbSetting; +}; + +/*---------* Functions *---------------*/ + +int getPluginTypeIdLen(int nTypeID); +void packPluginTypeId(icq_packet *packet, int nTypeID); + +#define BUL_ALLCONTACTS 0 +#define BUL_VISIBLE 1 +#define BUL_INVISIBLE 2 +#define BUL_TEMPVISIBLE 4 + + +#endif /* __FAMILIES_H */ diff --git a/protocols/IcqOscarJ/src/globals.h b/protocols/IcqOscarJ/src/globals.h new file mode 100644 index 0000000000..c0d8326689 --- /dev/null +++ b/protocols/IcqOscarJ/src/globals.h @@ -0,0 +1,57 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Contains global types & variables declarations. +// +// ----------------------------------------------------------------------------- + +#ifndef __GLOBALS_H +#define __GLOBALS_H + + +typedef char uid_str[MAX_PATH]; + +// from init.cpp +extern HINSTANCE hInst; +extern DWORD MIRANDA_VERSION; + +extern IcqIconHandle hStaticIcons[]; + +extern const int moodXStatus[]; + +// from fam_04message.cpp +struct icq_mode_messages +{ + char *szOnline; + char *szAway; + char *szNa; + char *szDnd; + char *szOccupied; + char *szFfc; +}; + + +#endif /* __GLOBALS_H */ diff --git a/protocols/IcqOscarJ/src/guids.h b/protocols/IcqOscarJ/src/guids.h new file mode 100644 index 0000000000..24073deec4 --- /dev/null +++ b/protocols/IcqOscarJ/src/guids.h @@ -0,0 +1,81 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Contains helper functions to handle oscar message GUIDs. +// +// ----------------------------------------------------------------------------- + +#ifndef __GUIDS_H +#define __GUIDS_H + + +typedef DWORD plugin_guid[4]; + +// Message Capability GUIDs +static const plugin_guid MCAP_SRV_RELAY_FMT = {MCAP_SRV_RELAY_FMT_s}; +static const plugin_guid MCAP_REVERSE_DC_REQ = {MCAP_REVERSE_DC_REQ_s}; +static const plugin_guid MCAP_FILE_TRANSFER = {MCAP_FILE_TRANSFER_s}; +static const plugin_guid MCAP_CONTACTS = {MCAP_CONTACTS_s}; + +// Plugin GUIDs +static const plugin_guid PSIG_MESSAGE = {PSIG_MESSAGE_s}; +static const plugin_guid PSIG_INFO_PLUGIN = {PSIG_INFO_PLUGIN_s}; +static const plugin_guid PSIG_STATUS_PLUGIN = {PSIG_STATUS_PLUGIN_s}; + +// Plugin Message GUIDs +static const plugin_guid PMSG_QUERY_INFO = {PMSG_QUERY_INFO_s}; +static const plugin_guid PMSG_QUERY_STATUS = {PMSG_QUERY_STATUS_s}; + +// Message GUIDs +static const plugin_guid MGTYPE_MESSAGE = {MGTYPE_MESSAGE_s}; +static const plugin_guid MGTYPE_STATUSMSGEXT = {MGTYPE_STATUSMSGEXT_s}; +static const plugin_guid MGTYPE_FILE = {MGTYPE_FILE_s}; +static const plugin_guid MGTYPE_WEBURL = {MGTYPE_WEBURL_s}; +static const plugin_guid MGTYPE_CONTACTS = {MGTYPE_CONTACTS_s}; +static const plugin_guid MGTYPE_GREETING_CARD = {MGTYPE_GREETING_CARD_s}; +static const plugin_guid MGTYPE_CHAT = {MGTYPE_CHAT_s}; +static const plugin_guid MGTYPE_SMS_MESSAGE = {MGTYPE_SMS_MESSAGE_s}; +static const plugin_guid MGTYPE_XTRAZ_SCRIPT = {MGTYPE_XTRAZ_SCRIPT_s}; + + +// make GUID checks easy +static BOOL CompareGUIDs(DWORD q1,DWORD q2,DWORD q3,DWORD q4, const plugin_guid guid) +{ + return ((q1 == guid[0]) && (q2 == guid[1]) && (q3 == guid[2]) && (q4 == guid[3]))?TRUE:FALSE; +} + + +// pack entire GUID into icq packet +static __inline void packGUID(icq_packet *packet, const plugin_guid guid) +{ + packDWord(packet, guid[0]); + packDWord(packet, guid[1]); + packDWord(packet, guid[2]); + packDWord(packet, guid[3]); +} + + +#endif /* __GUIDS_H */ diff --git a/protocols/IcqOscarJ/src/i18n.cpp b/protocols/IcqOscarJ/src/i18n.cpp new file mode 100644 index 0000000000..535feebcdf --- /dev/null +++ b/protocols/IcqOscarJ/src/i18n.cpp @@ -0,0 +1,541 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Contains helper functions to convert text messages between different +// character sets. +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +static BOOL bHasCP_UTF8 = FALSE; + + +void InitI18N(void) +{ + CPINFO CPInfo; + + + bHasCP_UTF8 = GetCPInfo(CP_UTF8, &CPInfo); +} + + + +// Returns true if the buffer only contains 7-bit characters. +BOOL __stdcall IsUSASCII(const char *pBuffer, int nSize) +{ + for (int nIndex = 0; nIndex < nSize; nIndex++) + if (BYTE(pBuffer[nIndex]) > 0x7F) + return FALSE; + + return TRUE; +} + +// Returns true if the unicode buffer only contains 7-bit characters. +BOOL __stdcall IsUnicodeAscii(const WCHAR *pBuffer, int nSize) +{ + for (int nIndex = 0; nIndex < nSize; nIndex++) + if (WORD(pBuffer[nIndex]) > 0x7F) + return FALSE; + + return TRUE; +} + + +// Scans a string encoded in UTF-8 to verify that it contains +// only valid sequences. It will return 1 if the string contains +// only legitimate encoding sequences; otherwise it will return 0; +// From 'Secure Programming Cookbook', John Viega & Matt Messier, 2003 +int __stdcall UTF8_IsValid(const char *pszInput) +{ + int nb; + if (!pszInput) + return 0; + + for ( BYTE* c = ( BYTE*)pszInput; *c; c += (nb + 1)) + { + if (!(*c & 0x80)) + nb = 0; + else if ((*c & 0xc0) == 0x80) return 0; + else if ((*c & 0xe0) == 0xc0) nb = 1; + else if ((*c & 0xf0) == 0xe0) nb = 2; + else if ((*c & 0xf8) == 0xf0) nb = 3; + else if ((*c & 0xfc) == 0xf8) nb = 4; + else if ((*c & 0xfe) == 0xfc) nb = 5; + else nb = 0; + + for (int i = 1; i<=nb; i++) // we this forward, do not cross end of string + if ((*(c + i) & 0xc0) != 0x80) + return 0; + } + + return 1; +} + + +int __stdcall get_utf8_size(const WCHAR *unicode) +{ + int size = 0; + int index = 0; + /* calculate the size of the utf-8 string */ + WCHAR c = unicode[index++]; + while (c) + { + if (c < 0x0080) + size += 1; + else if (c < 0x0800) + size += 2; + else + size += 3; + c = unicode[index++]; + } + return size; +} + + +// returns ansi string in all cases +char* __stdcall detect_decode_utf8(const char *from) +{ + char *temp = NULL; + + if (IsUSASCII(from, strlennull(from)) || !UTF8_IsValid(from) || !utf8_decode(from, &temp)) return (char*)from; + SAFE_FREE((void**)&from); + + return temp; +} + + +/* +* The following UTF8 routines are +* +* Copyright (C) 2001 Peter Harris +* Copyright (C) 2001 Edmund Grimley Evans +* +* under a GPL license +* +* -------------------------------------------------------------- +* Convert a string between UTF-8 and the locale's charset. +* Invalid bytes are replaced by '#', and characters that are +* not available in the target encoding are replaced by '?'. +* +* If the locale's charset is not set explicitly then it is +* obtained using nl_langinfo(CODESET), where available, the +* environment variable CHARSET, or assumed to be US-ASCII. +* +* Return value of conversion functions: +* +* -1 : memory allocation failed +* 0 : data was converted exactly +* 1 : valid data was converted approximately (using '?') +* 2 : input was invalid (but still converted, using '#') +* 3 : unknown encoding (but still converted, using '?') +*/ + + + +/* +* Convert a string between UTF-8 and the locale's charset. +*/ +char* __stdcall make_utf8_string_static(const WCHAR *unicode, char *utf8, size_t utf_size) +{ + int index = 0; + unsigned int out_index = 0; + unsigned short c; + + c = unicode[index++]; + while (c) + { + if (c < 0x080) + { + if (out_index + 1 >= utf_size) break; + utf8[out_index++] = (unsigned char)c; + } + else if (c < 0x800) + { + if (out_index + 2 >= utf_size) break; + utf8[out_index++] = 0xc0 | (c >> 6); + utf8[out_index++] = 0x80 | (c & 0x3f); + } + else + { + if (out_index + 3 >= utf_size) break; + utf8[out_index++] = 0xe0 | (c >> 12); + utf8[out_index++] = 0x80 | ((c >> 6) & 0x3f); + utf8[out_index++] = 0x80 | (c & 0x3f); + } + c = unicode[index++]; + } + utf8[out_index] = 0x00; + + return utf8; +} + + +char* __stdcall make_utf8_string(const WCHAR *unicode) +{ + if (!unicode) return NULL; + + /* first calculate the size of the target string */ + size_t size = get_utf8_size(unicode); + + char *out = (char*)SAFE_MALLOC(size + 1); + if (!out) + return NULL; + + return make_utf8_string_static(unicode, out, size + 1); +} + + +WCHAR* __stdcall make_unicode_string_static(const char *utf8, WCHAR *unicode, size_t unicode_size) +{ + unsigned int out_index = 0; + + if (utf8) + { + unsigned int index = 0; + unsigned char c = utf8[index++]; + + while (c) + { + if (out_index + 1 >= unicode_size) break; + if ((c & 0x80) == 0) + { + unicode[out_index++] = c; + } + else if ((c & 0xe0) == 0xe0) + { + unicode[out_index] = (c & 0x1F) << 12; + c = utf8[index++]; + unicode[out_index] |= (c & 0x3F) << 6; + c = utf8[index++]; + unicode[out_index++] |= (c & 0x3F); + } + else + { + unicode[out_index] = (c & 0x3F) << 6; + c = utf8[index++]; + unicode[out_index++] |= (c & 0x3F); + } + c = utf8[index++]; + } + } + unicode[out_index] = 0; + + return unicode; +} + + +WCHAR* __stdcall make_unicode_string(const char *utf8) +{ + int size = 0, index = 0; + + if (!utf8) return NULL; + + /* first calculate the size of the target string */ + unsigned char c = utf8[index++]; + while (c) + { + if ((c & 0x80) == 0) + { + index += 0; + } + else if ((c & 0xe0) == 0xe0) + { + index += 2; + } + else + { + index += 1; + } + size += 1; + c = utf8[index++]; + } + + WCHAR *out = (WCHAR*)SAFE_MALLOC((size + 1) * sizeof(WCHAR)); + if (!out) + return NULL; + else + return make_unicode_string_static(utf8, out, size + 1); +} + + +int __stdcall utf8_encode(const char *from, char **to) +{ + int wchars = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, from, strlennull(from), NULL, 0); + + if (wchars == 0) + { +#ifdef _DEBUG + fprintf(stderr, "Unicode translation error %d\n", GetLastError()); +#endif + return -1; + } + + WCHAR *unicode = (WCHAR*)_alloca((wchars + 1) * sizeof(WCHAR)); + ZeroMemory(unicode, (wchars + 1) * sizeof(WCHAR)); + + int err = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, from, strlennull(from), unicode, wchars); + if (err != wchars) + { +#ifdef _DEBUG + fprintf(stderr, "Unicode translation error %d\n", GetLastError()); +#endif + return -1; + } + + /* On NT-based windows systems, we could use WideCharToMultiByte(), but + * MS doesn't actually have a consistent API across win32. + */ + *to = make_utf8_string(unicode); + return 0; +} + + +char* __stdcall ansi_to_utf8(const char *ansi) +{ + char *szUtf = NULL; + + if (strlennull(ansi)) + { + utf8_encode(ansi, &szUtf); + return szUtf; + } + + return null_strdup(""); +} + + +char* __stdcall ansi_to_utf8_codepage(const char *ansi, WORD wCp) +{ + int wchars = strlennull(ansi); + WCHAR *unicode = (WCHAR*)_alloca((wchars + 1) * sizeof(WCHAR)); + ZeroMemory(unicode, (wchars + 1) * sizeof(WCHAR)); + + MultiByteToWideChar(wCp, MB_PRECOMPOSED, ansi, wchars, unicode, wchars); + + return make_utf8_string(unicode); +} + + +// Returns 0 on error, 1 on success +int __stdcall utf8_decode_codepage(const char *from, char **to, WORD wCp) +{ + int nResult = 0; + + _ASSERTE(!(*to)); // You passed a non-zero pointer, make sure it doesnt point to unfreed memory + + // Validate the string + if (!UTF8_IsValid(from)) + return 0; + + // Use the native conversion routines when available + if (bHasCP_UTF8) + { + int inlen = strlennull(from) + 1; + WCHAR *wszTemp = (WCHAR *)_alloca(inlen * sizeof(WCHAR)); + ZeroMemory(wszTemp, inlen * sizeof(WCHAR)); + + // Convert the UTF-8 string to UCS + if (MultiByteToWideChar(CP_UTF8, 0, from, -1, wszTemp, inlen)) + { + // Convert the UCS string to local ANSI codepage + *to = (char*)SAFE_MALLOC(inlen); + if (WideCharToMultiByte(wCp, 0, wszTemp, -1, *to, inlen, NULL, NULL)) + { + nResult = 1; + } + else + { + SAFE_FREE(to); + } + } + } + else + { + int chars = strlennull(from) + 1; + WCHAR *unicode = (WCHAR*)_alloca(chars * sizeof(WCHAR)); + make_unicode_string_static(from, unicode, chars); + + chars = WideCharToMultiByte(wCp, WC_COMPOSITECHECK, unicode, -1, NULL, 0, NULL, NULL); + + if (chars == 0) + { +#ifdef _DEBUG + fprintf(stderr, "Unicode translation error %d\n", GetLastError()); +#endif + return 0; + } + + *to = (char*)SAFE_MALLOC((chars + 1)*sizeof(char)); + if (*to == NULL) + { +#ifdef _DEBUG + fprintf(stderr, "Out of memory processing string to local charset\n"); +#endif + return 0; + } + + int err = WideCharToMultiByte(wCp, WC_COMPOSITECHECK, unicode, -1, *to, chars, NULL, NULL); + if (err != chars) + { +#ifdef _DEBUG + fprintf(stderr, "Unicode translation error %d\n", GetLastError()); +#endif + SAFE_FREE(to); + return 0; + } + + nResult = 1; + } + + return nResult; +} + + +// Standard version with current codepage +int __stdcall utf8_decode(const char *from, char **to) +{ + return utf8_decode_codepage(from, to, CP_ACP); +} + + +// Returns 0 on error, 1 on success +int __stdcall utf8_decode_static(const char *from, char *to, int to_size) +{ + int nResult = 0; + + _ASSERTE(to); // You passed a zero pointer + + // Validate the string + if (!UTF8_IsValid(from)) + return 0; + + // Clear target + ZeroMemory(to, to_size); + + // Use the native conversion routines when available + if (bHasCP_UTF8) + { + int inlen = strlennull(from) + 1; + WCHAR *wszTemp = (WCHAR*)_alloca(inlen * sizeof(WCHAR)); + ZeroMemory(wszTemp, inlen * sizeof(WCHAR)); + + // Convert the UTF-8 string to UCS + if (MultiByteToWideChar(CP_UTF8, 0, from, -1, wszTemp, inlen)) + { + // Convert the UCS string to local ANSI codepage + if (WideCharToMultiByte(CP_ACP, 0, wszTemp, -1, to, to_size, NULL, NULL)) + { + nResult = 1; + } + } + } + else + { + size_t chars = strlennull(from) + 1; + WCHAR *unicode = (WCHAR*)_alloca(chars * sizeof(WCHAR)); + + make_unicode_string_static(from, unicode, chars); + + WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, unicode, -1, to, to_size, NULL, NULL); + + nResult = 1; + } + + return nResult; +} + + +WCHAR* __stdcall ansi_to_unicode(const char *ansi) +{ + int wchars = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, ansi, strlennull(ansi), NULL, 0); + + if (wchars == 0) + { +#ifdef _DEBUG + fprintf(stderr, "Unicode translation error %d\n", GetLastError()); +#endif + return NULL; + } + + WCHAR *unicode = (WCHAR*)SAFE_MALLOC((wchars + 1) * sizeof(WCHAR)); + + int err = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, ansi, strlennull(ansi), unicode, wchars); + if (err != wchars) + { +#ifdef _DEBUG + fprintf(stderr, "Unicode translation error %d\n", GetLastError()); +#endif + SAFE_FREE(&unicode); + return NULL; + } + return unicode; +} + + +char* __stdcall unicode_to_ansi_static(const WCHAR *unicode, char *ansi, int ansi_size) +{ + ZeroMemory(ansi, ansi_size); + + if (WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, unicode, strlennull(unicode), ansi, ansi_size, NULL, NULL) > 1) + return ansi; + + return NULL; +} + + +char* __stdcall unicode_to_ansi(const WCHAR *unicode) +{ + int chars = WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, unicode, strlennull(unicode), NULL, 0, NULL, NULL); + + if (chars == 0) + { +#ifdef _DEBUG + fprintf(stderr, "Unicode translation error %d\n", GetLastError()); +#endif + return NULL; + } + + char* ansi = (char*)SAFE_MALLOC((chars + 1)*sizeof(char)); + if (ansi == NULL) + { +#ifdef _DEBUG + fprintf(stderr, "Out of memory processing string to local charset\n"); +#endif + return NULL; + } + + int err = WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, unicode, strlennull(unicode), ansi, chars, NULL, NULL); + if (err != chars) + { +#ifdef _DEBUG + fprintf(stderr, "Unicode translation error %d\n", GetLastError()); +#endif + return NULL; + } + + return ansi; +} diff --git a/protocols/IcqOscarJ/src/i18n.h b/protocols/IcqOscarJ/src/i18n.h new file mode 100644 index 0000000000..eaeaad54f3 --- /dev/null +++ b/protocols/IcqOscarJ/src/i18n.h @@ -0,0 +1,70 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Helper functions to convert text messages between different character sets. +// +// ----------------------------------------------------------------------------- + +#ifndef __I18N_H +#define __I18N_H + + +BOOL __stdcall IsUSASCII(const char *pBuffer, int nSize); +BOOL __stdcall IsUnicodeAscii(const WCHAR *pBuffer, int nSize); +int __stdcall UTF8_IsValid(const char *pszInput); + +int __stdcall get_utf8_size(const WCHAR *unicode); + +char* __stdcall detect_decode_utf8(const char *from); + +WCHAR* __stdcall make_unicode_string(const char *utf8); +WCHAR* __stdcall make_unicode_string_static(const char *utf8, WCHAR *unicode, size_t unicode_size); + +char* __stdcall make_utf8_string(const WCHAR *unicode); +char* __stdcall make_utf8_string_static(const WCHAR *unicode, char *utf8, size_t utf_size); + +char* __stdcall ansi_to_utf8(const char *ansi); +char* __stdcall ansi_to_utf8_codepage(const char *ansi, WORD wCp); + +WCHAR* __stdcall ansi_to_unicode(const char *ansi); +char* __stdcall unicode_to_ansi(const WCHAR *unicode); +char* __stdcall unicode_to_ansi_static(const WCHAR *unicode, char *ansi, int ansi_size); + +int __stdcall utf8_encode(const char *from, char **to); +int __stdcall utf8_decode(const char *from, char **to); +int __stdcall utf8_decode_codepage(const char *from, char **to, WORD wCp); +int __stdcall utf8_decode_static(const char *from, char *to, int to_size); + +#define tchar_to_utf8 make_utf8_string +#define utf8_to_tchar_static make_unicode_string_static +#define utf8_to_tchar make_unicode_string +#define ansi_to_tchar ansi_to_unicode +#define tchar_to_ansi unicode_to_ansi + +void InitI18N(void); + + +#endif /* __I18N_H */ diff --git a/protocols/IcqOscarJ/src/iconlib.cpp b/protocols/IcqOscarJ/src/iconlib.cpp new file mode 100644 index 0000000000..7c93bae17a --- /dev/null +++ b/protocols/IcqOscarJ/src/iconlib.cpp @@ -0,0 +1,92 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Support for IcoLib plug-in +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" +#include "m_icolib.h" + +IcqIconHandle IconLibDefine(const char *desc, const char *section, const char *module, const char *ident, const TCHAR *def_file, int def_idx) +{ + SKINICONDESC sid = {0}; + sid.cbSize = sizeof(sid); + sid.pwszSection = make_unicode_string(section); + sid.pwszDescription = make_unicode_string(desc); + sid.flags = SIDF_ALL_TCHAR; + + char szName[MAX_PATH + 128]; + null_snprintf(szName, sizeof(szName), "%s_%s", module ? module : ICQ_PROTOCOL_NAME, ident); + sid.pszName = szName; + sid.ptszDefaultFile = (TCHAR*)def_file; + sid.iDefaultIndex = def_idx; + + IcqIconHandle hIcon = (IcqIconHandle)SAFE_MALLOC(sizeof(IcqIconHandle_s)); + hIcon->szName = null_strdup(sid.pszName); + hIcon->hIcoLib = Skin_AddIcon(&sid); + + SAFE_FREE(&sid.pwszSection); + SAFE_FREE(&sid.pwszDescription); + + return hIcon; +} + + +void IconLibRemove(IcqIconHandle *phIcon) +{ + if (phIcon && *phIcon) + { + IcqIconHandle hIcon = *phIcon; + + CallService(MS_SKIN2_REMOVEICON, 0, (LPARAM)hIcon->szName); + SAFE_FREE(&hIcon->szName); + SAFE_FREE((void**)phIcon); + } +} + + +HANDLE IcqIconHandle_s::Handle() +{ + if (this) + return hIcoLib; + + return NULL; +} + + +HICON IcqIconHandle_s::GetIcon(bool big) +{ + if (this) + return (HICON)CallService(MS_SKIN2_GETICONBYHANDLE, big, (LPARAM)hIcoLib); + + return NULL; +} + +void IcqIconHandle_s::ReleaseIcon(bool big) +{ + CallService(big ? MS_SKIN2_RELEASEICONBIG : MS_SKIN2_RELEASEICON, 0, (LPARAM)szName); +} + diff --git a/protocols/IcqOscarJ/src/iconlib.h b/protocols/IcqOscarJ/src/iconlib.h new file mode 100644 index 0000000000..3a7482f872 --- /dev/null +++ b/protocols/IcqOscarJ/src/iconlib.h @@ -0,0 +1,52 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2009 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Headers for IconLib Plugin / module support +// +// ----------------------------------------------------------------------------- +#ifndef __ICONLIB_H +#define __ICONLIB_H + + +struct IcqIconHandle_s +{ + char *szName; + HANDLE hIcoLib; + + HANDLE Handle(); + HICON GetIcon(bool big = false); + void ReleaseIcon(bool big = false); +}; + +typedef IcqIconHandle_s *IcqIconHandle; + + +IcqIconHandle IconLibDefine(const char *desc, const char *section, const char *module, const char *ident, const TCHAR *def_file, int def_idx); +void IconLibRemove(IcqIconHandle *phIcon); + + + +#endif /* __ICONLIB_H */ diff --git a/protocols/IcqOscarJ/src/icq_advsearch.cpp b/protocols/IcqOscarJ/src/icq_advsearch.cpp new file mode 100644 index 0000000000..6c3a3ffa89 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_advsearch.cpp @@ -0,0 +1,185 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2008 Joe Kucera, Bio +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +static void InitComboBox(HWND hwndCombo, const FieldNamesItem *names) +{ + int iItem; + int i; + + iItem = ComboBoxAddStringUtf(hwndCombo, NULL, 0); + SendMessage(hwndCombo, CB_SETCURSEL, iItem, 0); + + if (names){ + for (i = 0; names[i].text; i++) { + iItem = ComboBoxAddStringUtf(hwndCombo, names[i].text, names[i].code); + } + } + else { + int ctryCount; + struct CountryListEntry *countries; + CallService( MS_UTILS_GETCOUNTRYLIST, ( WPARAM )&ctryCount, ( LPARAM )&countries ); + for (i = 0; i < ctryCount; i++) { + if (countries[i].id != 0xFFFF && countries[i].id != 0) + iItem = ComboBoxAddStringUtf(hwndCombo, LPGEN(countries[i].szName), countries[i].id); + } + } +} + +INT_PTR CALLBACK AdvancedSearchDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam) +{ + switch(message) + { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + InitComboBox(GetDlgItem(hwndDlg, IDC_GENDER), genderField); + InitComboBox(GetDlgItem(hwndDlg, IDC_AGERANGE), agesField); + InitComboBox(GetDlgItem(hwndDlg, IDC_MARITALSTATUS), maritalField); + InitComboBox(GetDlgItem(hwndDlg, IDC_WORKFIELD), occupationField); + InitComboBox(GetDlgItem(hwndDlg, IDC_ORGANISATION), affiliationField); + InitComboBox(GetDlgItem(hwndDlg, IDC_LANGUAGE), languageField); + InitComboBox(GetDlgItem(hwndDlg, IDC_COUNTRY), countryField); + InitComboBox(GetDlgItem(hwndDlg, IDC_INTERESTSCAT), interestsField); + InitComboBox(GetDlgItem(hwndDlg, IDC_PASTCAT), pastField); + + 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; + + case IDCANCEL: + // CheckDlgButton(GetParent(hwndDlg),IDC_ADVANCED,BST_UNCHECKED); + // SendMessage(GetParent(hwndDlg),WM_COMMAND,MAKEWPARAM(IDC_ADVANCED,BN_CLICKED),(LPARAM)GetDlgItem(GetParent(hwndDlg),IDC_ADVANCED)); + break; + + default: + break; + + } + break; + } + + default: + break; + } + + return FALSE; +} + +static DWORD getCurItemData(HWND hwndDlg, UINT iCtrl) +{ + return SendDlgItemMessage(hwndDlg, iCtrl, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, iCtrl, CB_GETCURSEL, 0, 0), 0); +} + +static void searchPackTLVLNTS(PBYTE *buf, int *buflen, HWND hwndDlg, UINT idControl, WORD wType) +{ + char str[512]; + + GetDlgItemTextA(hwndDlg, idControl, str, sizeof(str)); + + ppackLETLVLNTS(buf, buflen, str, wType, 0); +} + +static void searchPackTLVWordLNTS(PBYTE *buf, int *buflen, HWND hwndDlg, UINT idControl, WORD w, WORD wType) +{ + char str[512]; + + GetDlgItemTextA(hwndDlg, idControl, str, sizeof(str)); + + ppackLETLVWordLNTS(buf, buflen, w, str, wType, 0); +} + +static PBYTE createAdvancedSearchStructureTLV(HWND hwndDlg, int *length) +{ + PBYTE buf = NULL; + int buflen = 0; + + ppackLEWord(&buf, &buflen, META_SEARCH_GENERIC); /* subtype: full search */ + + searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_FIRSTNAME, TLV_FIRSTNAME); + searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_LASTNAME, TLV_LASTNAME); + searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_NICK, TLV_NICKNAME); + searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_EMAIL, TLV_EMAIL); + searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_CITY, TLV_CITY); + searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_STATE, TLV_STATE); + searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_COMPANY, TLV_COMPANY); + searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_DEPARTMENT, TLV_DEPARTMENT); + searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_POSITION, TLV_POSITION); + searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_KEYWORDS, TLV_KEYWORDS); + + ppackLETLVDWord(&buf, &buflen, (DWORD)getCurItemData(hwndDlg, IDC_AGERANGE), TLV_AGERANGE, 0); + + BYTE b = (BYTE)getCurItemData(hwndDlg, IDC_GENDER); + switch (b) { + case 'F': b = 1; break; + case 'M': b = 2; break; + default: b = 0; + }; + ppackLETLVByte(&buf, &buflen, b, TLV_GENDER, 0); + ppackLETLVByte(&buf, &buflen, (BYTE)getCurItemData(hwndDlg, IDC_MARITALSTATUS), TLV_MARITAL, 0); + ppackLETLVWord(&buf, &buflen, (WORD)getCurItemData(hwndDlg, IDC_LANGUAGE), TLV_LANGUAGE, 0); + ppackLETLVWord(&buf, &buflen, (WORD)getCurItemData(hwndDlg, IDC_COUNTRY), TLV_COUNTRY, 0); + ppackLETLVWord(&buf, &buflen, (WORD)getCurItemData(hwndDlg, IDC_WORKFIELD), TLV_OCUPATION, 0); + + WORD w = (WORD)getCurItemData(hwndDlg, IDC_PASTCAT); + searchPackTLVWordLNTS(&buf, &buflen, hwndDlg, IDC_PASTKEY, w, TLV_PASTINFO); + + w = (WORD)getCurItemData(hwndDlg, IDC_INTERESTSCAT); + searchPackTLVWordLNTS(&buf, &buflen, hwndDlg, IDC_INTERESTSKEY, w, TLV_INTERESTS); + + w = (WORD)getCurItemData(hwndDlg, IDC_ORGANISATION); + searchPackTLVWordLNTS(&buf, &buflen, hwndDlg, IDC_ORGKEYWORDS, w, TLV_AFFILATIONS); + + w = (WORD)getCurItemData(hwndDlg, IDC_HOMEPAGECAT); + if (w != 0xFFFF) + searchPackTLVWordLNTS(&buf, &buflen, hwndDlg, IDC_HOMEPAGEKEY, w, TLV_HOMEPAGE); + + if (IsDlgButtonChecked(hwndDlg, IDC_ONLINEONLY)) + ppackLETLVByte(&buf, &buflen, 1, TLV_ONLINEONLY, 1); + + if (length) + *length = buflen; + + return buf; +} + +PBYTE createAdvancedSearchStructure(HWND hwndDlg, int *length) +{ + if (!hwndDlg) + return NULL; + + return createAdvancedSearchStructureTLV(hwndDlg, length); +} diff --git a/protocols/IcqOscarJ/src/icq_advsearch.h b/protocols/IcqOscarJ/src/icq_advsearch.h new file mode 100644 index 0000000000..83bf35af93 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_advsearch.h @@ -0,0 +1,32 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000,2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001,2002 Jon Keating, Richard Hughes +// Copyright © 2002,2003,2004 Martin Öberg, Sam Kothari, 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- + + +INT_PTR CALLBACK AdvancedSearchDlgProc(HWND hwndDlg,UINT message,WPARAM wParam,LPARAM lParam); +PBYTE createAdvancedSearchStructure(HWND hwndDlg,int *length); diff --git a/protocols/IcqOscarJ/src/icq_avatar.cpp b/protocols/IcqOscarJ/src/icq_avatar.cpp new file mode 100644 index 0000000000..6e61f2bc0d --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_avatar.cpp @@ -0,0 +1,1814 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Manages Avatar connection, provides internal service for handling avatars +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" +#include "m_folders.h" + + +BYTE hashEmptyAvatar[9] = {0x00, 0x01, 0x00, 0x05, 0x02, 0x01, 0xD2, 0x04, 0x72}; + + +avatars_request::avatars_request(int type) +{ + this->type = type; +} + + +avatars_request::~avatars_request() +{ + if (this) + { + switch (type) + { + case ART_UPLOAD: + SAFE_FREE((void**)&pData); + break; + case ART_GET: + SAFE_FREE((void**)&hash); + SAFE_FREE(&szFile); + break; + case ART_BLOCK: + break; + } + } +} + + +avatars_request* CIcqProto::ReleaseAvatarRequestInQueue(avatars_request *request) +{ + avatars_request *pNext = request->pNext; + + avatars_request **par = &m_avatarsQueue; + avatars_request *ar = m_avatarsQueue; + + while (ar) + { + if (ar == request) + { // found it, remove + *par = ar->pNext; + break; + } + par = &ar->pNext; + ar = ar->pNext; + } + + delete request; + return pNext; +} + + +void CIcqProto::InitAvatars() +{ + if (!bAvatarsFolderInited) + { // do it only once + bAvatarsFolderInited = TRUE; + + if (ServiceExists(MS_FOLDERS_REGISTER_PATH)) + { // check if it does make sense + TCHAR tszPath[MAX_PATH * 2]; + null_snprintf(tszPath, MAX_PATH * 2, _T("%%miranda_avatarcache%%\\") _T(TCHAR_STR_PARAM) _T("\\"), m_szModuleName); + + hAvatarsFolder = FoldersRegisterCustomPathT(m_szModuleName, "Avatars Cache", tszPath); + } + } +} + + +TCHAR* CIcqProto::GetOwnAvatarFileName() +{ + DBVARIANT dbvFile = {DBVT_DELETED}; + + if (!getSettingStringT(NULL, "AvatarFile", &dbvFile)) + { + TCHAR tmp[MAX_PATH * 2]; + CallService(MS_UTILS_PATHTOABSOLUTET, (WPARAM)dbvFile.ptszVal, (LPARAM)tmp); + ICQFreeVariant(&dbvFile); + + return null_strdup(tmp); + } + return NULL; +} + + +void CIcqProto::GetFullAvatarFileName(int dwUin, const char *szUid, int dwFormat, TCHAR *pszDest, int cbLen) +{ + GetAvatarFileName(dwUin, szUid, pszDest, cbLen); + AddAvatarExt(dwFormat, pszDest); +} + + +void CIcqProto::GetAvatarFileName(int dwUin, const char *szUid, TCHAR *pszDest, int cbLen) +{ + TCHAR szPath[MAX_PATH * 2]; + FOLDERSGETDATA fgd = {0}; + + InitAvatars(); + + fgd.cbSize = sizeof(FOLDERSGETDATA); + fgd.nMaxPathSize = MAX_PATH * 2; + fgd.szPathT = szPath; + fgd.flags = FF_TCHAR; + if (CallService(MS_FOLDERS_GET_PATH, (WPARAM)hAvatarsFolder, (LPARAM)&fgd)) + { + TCHAR *tmpPath = Utils_ReplaceVarsT(_T("%miranda_avatarcache%")); + null_snprintf(szPath, MAX_PATH * 2, _T("%s\\") _T(TCHAR_STR_PARAM) _T("\\"), tmpPath, m_szModuleName); + mir_free(tmpPath); + } + else + _tcscat(szPath, _T("\\")); + + // fill the destination + lstrcpyn(pszDest, szPath, cbLen - 1); + int tPathLen = strlennull(pszDest); + + // make sure the avatar cache directory exists + CallService(MS_UTILS_CREATEDIRTREET, 0, (LPARAM)szPath); + + if (dwUin != 0) + { + _ltot(dwUin, pszDest + tPathLen, 10); + } + else if (szUid) + { + TCHAR* p = mir_a2t(szUid); + _tcscpy(pszDest + tPathLen, p); + mir_free( p ); + } + else + { + TCHAR szBuf[MAX_PATH]; + + if (CallService(MS_DB_GETPROFILENAMET, MAX_PATH, (LPARAM)szBuf)) + _tcscpy(pszDest + tPathLen, _T("avatar")); + else + { + TCHAR *szLastDot = _tcsrchr(szBuf, '.'); + if (szLastDot) szLastDot[0] = '\0'; + + _tcscpy(pszDest + tPathLen, szBuf); + _tcscat(pszDest + tPathLen, _T("_avt")); + } + } +} + + +void AddAvatarExt(int dwFormat, TCHAR *pszDest) +{ + if (dwFormat == PA_FORMAT_JPEG) + _tcscat(pszDest, _T(".jpg")); + else if (dwFormat == PA_FORMAT_GIF) + _tcscat(pszDest, _T(".gif")); + else if (dwFormat == PA_FORMAT_PNG) + _tcscat(pszDest, _T(".png")); + else if (dwFormat == PA_FORMAT_BMP) + _tcscat(pszDest, _T(".bmp")); + else if (dwFormat == PA_FORMAT_XML) + _tcscat(pszDest, _T(".xml")); + else if (dwFormat == PA_FORMAT_SWF) + _tcscat(pszDest, _T(".swf")); + else + _tcscat(pszDest, _T(".dat")); +} + + +int DetectAvatarFormatBuffer(const char *pBuffer) +{ + if (!strncmp(pBuffer, "%PNG", 4)) + return PA_FORMAT_PNG; + + if (!strncmp(pBuffer, "GIF8", 4)) + return PA_FORMAT_GIF; + + if (!_strnicmp(pBuffer, "isPending()) + { + NetLog_Server("Avatar, Multiple start thread attempt, ignored."); + SAFE_FREE((void**)&cookie); + return; + } + NetLog_Server("Avatars: Connection failed"); + + m_avatarsConnectionPending = FALSE; + + { // check if any upload request are waiting in the queue + avatars_request *ar = m_avatarsQueue; + int bYet = 0; + + while (ar) + { + if (ar->type == ART_UPLOAD) + { // we found it, return error + if (!bYet) + { + icq_LogMessage(LOG_WARNING, LPGEN("Error uploading avatar to server, server temporarily unavailable.")); + bYet = 1; + } + // remove upload request from queue + ar = ReleaseAvatarRequestInQueue(ar); + continue; + } + ar = ar->pNext; + } + } + SAFE_FREE((void**)&cookie); + + return; + } + + icq_lock l(m_avatarsMutex); + + if (m_avatarsConnection && m_avatarsConnection->isPending()) + { + NetLog_Server("Avatar, Multiple start thread attempt, ignored."); + + NetLib_CloseConnection(&hConn, FALSE); + + SAFE_FREE((void**)&cookie); + + return; + } + else if (m_avatarsConnection) + m_avatarsConnection->closeConnection(); + + m_avatarsConnection = new avatars_server_connection(this, hConn, cookie, cookieLen); // the old connection should not be used anymore + + // connection object created, remove the connection request pending flag + m_avatarsConnectionPending = FALSE; +} + + +void CIcqProto::StopAvatarThread() +{ + icq_lock l(m_avatarsMutex); // the connection is about to close + + if (m_avatarsConnection) + { + m_avatarsConnection->shutdownConnection(); // make the thread stop + } + + return; +} + + +#ifdef _DEBUG +static void NetLog_Hash(CIcqProto *ppro, const char *pszIdent, const BYTE *pHash, int nHashLen) +{ + if (nHashLen == 0x14) + ppro->NetLog_Server("Avatars: %s hash: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", pszIdent, pHash[0], pHash[1], pHash[2], pHash[3], pHash[4], pHash[5], pHash[6], pHash[7], pHash[8], pHash[9], pHash[10], pHash[11], pHash[12], pHash[13], pHash[14], pHash[15], pHash[16], pHash[17], pHash[18], pHash[19]); + else if (nHashLen == 0x09) + ppro->NetLog_Server("Avatars: %s hash: %02X%02X%02X%02X%02X%02X%02X%02X%02X", pszIdent, pHash[0], pHash[1], pHash[2], pHash[3], pHash[4], pHash[5], pHash[6], pHash[7], pHash[8]); + else + ppro->NetLog_Server("Avatars: %s hash: Unknown hash format.", pszIdent); +} +#endif + + +// handle Owner's avatar hash changes +void CIcqProto::handleAvatarOwnerHash(WORD wItemID, BYTE bFlags, BYTE *pData, BYTE nDataLen) +{ + if ((nDataLen >= 0x14) && m_bAvatarsEnabled) + { + switch (bFlags) + { + case 1: // our avatar is on the server + { + setSettingBlob(NULL, "AvatarHash", pData, 0x14); /// TODO: properly handle multiple avatar items (more formats) + + setUserInfo(); + // here we need to find a file, check its hash, if invalid get avatar from server + TCHAR *file = GetOwnAvatarFileName(); + if (!file) + { // we have no avatar file, download from server + TCHAR szFile[MAX_PATH * 2 + 4]; +#ifdef _DEBUG + NetLog_Server("We have no avatar, requesting from server."); +#endif + GetAvatarFileName(0, NULL, szFile, MAX_PATH * 2); + GetAvatarData(NULL, m_dwLocalUIN, NULL, pData, 0x14, szFile); + } + else + { // we know avatar filename + BYTE *hash = calcMD5HashOfFile(file); + + if (!hash) + { // hash could not be calculated - probably missing file, get avatar from server + TCHAR szFile[MAX_PATH * 2 + 4]; +#ifdef _DEBUG + NetLog_Server("We have no avatar, requesting from server."); +#endif + GetAvatarFileName(0, NULL, szFile, MAX_PATH * 2); + GetAvatarData(NULL, m_dwLocalUIN, NULL, pData, 0x14, szFile); + } // check if we had set any avatar if yes set our, if not download from server + else if (memcmp(hash, pData + 4, 0x10)) + { // we have different avatar, sync that + if (m_bSsiEnabled && getSettingByte(NULL, "ForceOurAvatar", 1)) + { // we want our avatar, update hash + DWORD dwPaFormat = DetectAvatarFormat(file); + BYTE *pHash = (BYTE*)_alloca(0x14); + + NetLog_Server("Our avatar is different, setting our new hash."); + + pHash[0] = 0; + pHash[1] = dwPaFormat == PA_FORMAT_XML ? AVATAR_HASH_FLASH : AVATAR_HASH_STATIC; + pHash[2] = 1; // state of the hash + pHash[3] = 0x10; // len of the hash + memcpy(pHash + 4, hash, 0x10); + updateServAvatarHash(pHash, 0x14); + } + else + { // get avatar from server + TCHAR tszFile[MAX_PATH * 2 + 4]; +#ifdef _DEBUG + NetLog_Server("We have different avatar, requesting new from server."); +#endif + GetAvatarFileName(0, NULL, tszFile, MAX_PATH * 2); + GetAvatarData(NULL, m_dwLocalUIN, NULL, pData, 0x14, tszFile); + } + } + SAFE_FREE((void**)&hash); + SAFE_FREE(&file); + } + break; + } + case 0x41: // request to upload avatar data + case 0x81: + { // request to re-upload avatar data + if (!m_bSsiEnabled) break; // we could not change serv-list if it is disabled... + + TCHAR *file = GetOwnAvatarFileName(); + if (!file) + { // we have no file to upload, remove hash from server + NetLog_Server("We do not have avatar, removing hash."); + SetMyAvatar(0, 0); + break; + } + DWORD dwPaFormat = DetectAvatarFormat(file); + BYTE *hash = calcMD5HashOfFile(file); + + if (!hash) + { // the hash could not be calculated, remove from server + NetLog_Server("We could not obtain hash, removing hash."); + SetMyAvatar(0, 0); + } + else if (!memcmp(hash, pData + 4, 0x10)) + { // we have the right file + HANDLE hFile = NULL, hMap = NULL; + BYTE *ppMap = NULL; + long cbFileSize = 0; + + NetLog_Server("Uploading our avatar data."); + + if ((hFile = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL )) != INVALID_HANDLE_VALUE) + if ((hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL)) != NULL) + if ((ppMap = (BYTE*)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0)) != NULL) + cbFileSize = GetFileSize(hFile, NULL); + + if (cbFileSize != 0) + { + SetAvatarData(NULL, (WORD)(dwPaFormat == PA_FORMAT_XML ? AVATAR_HASH_FLASH : AVATAR_HASH_STATIC), ppMap, cbFileSize); + } + + if (ppMap != NULL) UnmapViewOfFile(ppMap); + if (hMap != NULL) CloseHandle(hMap); + if (hFile != NULL) CloseHandle(hFile); + SAFE_FREE((void**)&hash); + } + else + { + BYTE *pHash = (BYTE*)_alloca(0x14); + + NetLog_Server("Our file is different, set our new hash."); + + pHash[0] = 0; + pHash[1] = dwPaFormat == PA_FORMAT_XML ? AVATAR_HASH_FLASH : AVATAR_HASH_STATIC; + pHash[2] = 1; // state of the hash + pHash[3] = 0x10; // len of the hash + memcpy(pHash + 4, hash, 0x10); + updateServAvatarHash(pHash, 0x14); + + SAFE_FREE((void**)&hash); + } + + SAFE_FREE(&file); + break; + } + default: + NetLog_Server("Received UNKNOWN Avatar Status."); + } + } +} + + +// handle Contact's avatar hash +void CIcqProto::handleAvatarContactHash(DWORD dwUIN, char *szUID, HANDLE hContact, BYTE *pHash, int nHashLen, WORD wOldStatus) +{ + int bJob = FALSE; + BOOL avatarInfoPresent = FALSE; + int avatarType = -1; + BYTE *pAvatarHash = NULL; + int cbAvatarHash; + BYTE emptyItem[0x10] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + + if (!m_bAvatarsEnabled) return; // only if enabled + + if (nHashLen < 4) return; // nothing to work with + + while (nHashLen >= 4) + { // parse online message items one by one + WORD itemType = pHash[0] << 8 | pHash[1]; + BYTE itemLen = pHash[3]; + BYTE itemFlags = pHash[2]; + + // just some validity check + if (itemLen + 4 > nHashLen) + itemLen = nHashLen - 4; + + if (itemLen && memcmp(pHash + 4, emptyItem, itemLen > 0x10 ? 0x10 : itemLen)) + { // Item types + // 0000: AIM mini avatar + // 0001: AIM/ICQ avatar ID/hash (len 5 or 16 bytes) + // 0002: iChat online message + // 0008: ICQ Flash avatar hash (16 bytes) + // 0009: iTunes music store link + // 000C: ICQ contact photo (16 bytes) + // 000D: Last update time of online message + // 000E: Status mood + if (itemType == AVATAR_HASH_MINI && itemLen == 0x05 && avatarType == -1) + { // mini avatar + pAvatarHash = pHash; + cbAvatarHash = itemLen + 4; + avatarType = itemType; + } + else if (itemType == AVATAR_HASH_STATIC && (itemLen == 0x05 || itemLen == 0x10) && (avatarType == -1 || avatarType == AVATAR_HASH_MINI)) + { // normal avatar + pAvatarHash = pHash; + cbAvatarHash = itemLen + 4; + avatarType = itemType; + } + else if (itemType == AVATAR_HASH_FLASH && itemLen == 0x10 && (avatarType == -1 || avatarType == AVATAR_HASH_MINI || avatarType == AVATAR_HASH_STATIC)) + { // flash avatar + pAvatarHash = pHash; + cbAvatarHash = itemLen + 4; + avatarType = itemType; + } + else if (itemType == AVATAR_HASH_PHOTO && itemLen == 0x10) + { // big avatar (ICQ 6+) + pAvatarHash = pHash; + cbAvatarHash = itemLen + 4; + avatarType = itemType; + } + } + else if ((itemLen == 0) && (itemType == AVATAR_HASH_MINI || itemType == AVATAR_HASH_STATIC || itemType == AVATAR_HASH_FLASH || itemType == AVATAR_HASH_PHOTO)) + { // empty item - indicating that avatar of that type was removed + avatarInfoPresent = TRUE; + } + + pHash += itemLen + 4; + nHashLen -= itemLen + 4; + } + + if (avatarType != -1) + { // check settings, should we request avatar immediatelly? + DBVARIANT dbv = {DBVT_DELETED}; + TCHAR tszAvatar[MAX_PATH * 2 +4]; + BYTE bAutoLoad = getSettingByte(NULL, "AvatarsAutoLoad", DEFAULT_LOAD_AVATARS); + + if ((avatarType == AVATAR_HASH_STATIC || avatarType == AVATAR_HASH_MINI) && cbAvatarHash == 0x09 && !memcmp(pAvatarHash + 4, hashEmptyAvatar + 4, 0x05)) + { // empty avatar - unlink image, clear hash + if (!getSetting(hContact, "AvatarHash", &dbv)) + { // contact had avatar, clear hash, notify UI +#ifdef _DEBUG + NetLog_Hash(this, "old", dbv.pbVal, dbv.cpbVal); +#endif + ICQFreeVariant(&dbv); + NetLog_Server("%s has removed Avatar.", strUID(dwUIN, szUID)); + + deleteSetting(hContact, "AvatarHash"); + BroadcastAck(hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, NULL, (LPARAM)NULL); + } +#ifdef _DEBUG + else + NetLog_Server("%s has empty Avatar.", strUID(dwUIN, szUID)); +#endif + return; + } + + if (getSetting(hContact, "AvatarHash", &dbv)) + { // we did not find old avatar hash, i.e. get new avatar + int avatarState = IsAvatarChanged(hContact, pAvatarHash, cbAvatarHash); + + // check saved hash and file, if equal only store hash + if (!avatarState) + { // hashes are the same + int dwPaFormat = getSettingByte(hContact, "AvatarType", PA_FORMAT_UNKNOWN); + + GetFullAvatarFileName(dwUIN, szUID, dwPaFormat, tszAvatar, MAX_PATH * 2); + if (_taccess(tszAvatar, 0) == 0) + { // the file is there, link to contactphoto, save hash + NetLog_Server("%s has published Avatar. Image was found in the cache.", strUID(dwUIN, szUID)); +#ifdef _DEBUG + NetLog_Hash(this, "new", pAvatarHash, cbAvatarHash); +#endif + setSettingBlob(hContact, "AvatarHash", pAvatarHash, cbAvatarHash); + BroadcastAck(hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, NULL, (LPARAM)NULL); + } + else + { // the file was lost, request avatar again + NetLog_Server("%s has published Avatar.", strUID(dwUIN, szUID)); +#ifdef _DEBUG + NetLog_Hash(this, "new", pAvatarHash, cbAvatarHash); +#endif + bJob = TRUE; + } + } + else + { // the hash is not the one we want, request avatar + NetLog_Server("%s has published a new Avatar.", strUID(dwUIN, szUID)); +#ifdef _DEBUG + NetLog_Hash(this, "new", pAvatarHash, cbAvatarHash); +#endif + bJob = TRUE; + } + } + else + { // we found hash check if it changed or not + if ((dbv.cpbVal != cbAvatarHash) || memcmp(dbv.pbVal, pAvatarHash, cbAvatarHash)) + { // the hash is different, request new avatar +#ifdef _DEBUG + NetLog_Hash(this, "old", dbv.pbVal, dbv.cpbVal); +#endif + NetLog_Server("%s has changed Avatar.", strUID(dwUIN, szUID)); +#ifdef _DEBUG + NetLog_Hash(this, "new", pAvatarHash, cbAvatarHash); +#endif + bJob = TRUE; + } + else + { // the hash was not changed, check if we have the correct file + int avatarState = IsAvatarChanged(hContact, pAvatarHash, cbAvatarHash); + + // we should have file, check if the file really exists + if (!avatarState) + { + int dwPaFormat = getSettingByte(hContact, "AvatarType", PA_FORMAT_UNKNOWN); + if (dwPaFormat == PA_FORMAT_UNKNOWN) + { // we do not know the format, get avatar again +#ifdef _DEBUG + NetLog_Hash(this, "current", dbv.pbVal, dbv.cpbVal); +#endif + NetLog_Server("%s has Avatar. Image is missing.", strUID(dwUIN, szUID)); + bJob = 2; + } + else + { + GetFullAvatarFileName(dwUIN, szUID, dwPaFormat, tszAvatar, MAX_PATH * 2); + if (_taccess(tszAvatar, 0) != 0) + { // the file was lost, get it again +#ifdef _DEBUG + NetLog_Hash(this, "current", dbv.pbVal, dbv.cpbVal); +#endif + NetLog_Server("%s has Avatar. Image is missing.", strUID(dwUIN, szUID)); + bJob = 2; + } +#ifdef _DEBUG + else + { + NetLog_Hash(this, "current", dbv.pbVal, dbv.cpbVal); + + NetLog_Server("%s has Avatar. Image was found in the cache.", strUID(dwUIN, szUID)); + } +#endif + } + } + else + { // the hash is not the one we want, request avatar +#ifdef _DEBUG + NetLog_Hash(this, "current", dbv.pbVal, dbv.cpbVal); +#endif + NetLog_Server("%s has Avatar. Image was not retrieved yet.", strUID(dwUIN, szUID)); + bJob = 2; + } + } + ICQFreeVariant(&dbv); + } + + if (bJob) + { + if (bJob == TRUE) + { // Remove possible block - hash changed, try again. + icq_lock l(m_avatarsMutex); + + avatars_request *ar = m_avatarsQueue; + + while (ar) + { + if (ar->hContact == hContact && ar->type == ART_BLOCK) + { // found one, remove + ReleaseAvatarRequestInQueue(ar); + break; + } + ar = ar->pNext; + } + } + + setSettingBlob(hContact, "AvatarHash", pAvatarHash, cbAvatarHash); + + BroadcastAck(hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, NULL, (LPARAM)NULL); + + if (bAutoLoad) + { // auto-load is on, so request the avatar now, otherwise we are done + GetAvatarFileName(dwUIN, szUID, tszAvatar, MAX_PATH * 2); + GetAvatarData(hContact, dwUIN, szUID, pAvatarHash, cbAvatarHash, tszAvatar); + } // avatar request sent or added to queue + } + } + else if (avatarInfoPresent) + { // hash was not found, clear the hash + DBVARIANT dbv = {DBVT_DELETED}; + + if (!getSetting(hContact, "AvatarHash", &dbv)) + { // contact had avatar, clear hash, notify UI +#ifdef _DEBUG + NetLog_Hash(this, "old", dbv.pbVal, dbv.cpbVal); +#endif + ICQFreeVariant(&dbv); + NetLog_Server("%s has removed Avatar.", strUID(dwUIN, szUID)); + + deleteSetting(hContact, "AvatarHash"); + BroadcastAck(hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, NULL, (LPARAM)NULL); + } +#ifdef _DEBUG + else + NetLog_Server("%s has no Avatar.", strUID(dwUIN, szUID)); +#endif + } +} + + +// request avatar data from server +int CIcqProto::GetAvatarData(HANDLE hContact, DWORD dwUin, const char *szUid, const BYTE *hash, unsigned int hashlen, const TCHAR *file) +{ + uid_str szUidData; + char *pszUid = NULL; + if (!dwUin && szUid) + { // create a copy in local writable buffer + strcpy(szUidData, szUid); + pszUid = szUidData; + } + + m_avatarsMutex->Enter(); + + if (m_avatarsConnection && m_avatarsConnection->isReady()) // check if we are ready + { // check if requests for this user are not blocked + DWORD dwNow = GetTickCount(); + avatars_request *ar = m_avatarsQueue; + + while (ar) + { + if (ar->hContact == hContact && ar->type == ART_BLOCK) + { // found a block item + if (GetTickCount() > ar->timeOut) + { // remove timeouted block + ar = ReleaseAvatarRequestInQueue(ar); + continue; + } + m_avatarsMutex->Leave(); + NetLog_Server("Avatars: Requests for %s avatar are blocked.", strUID(dwUin, pszUid)); + return 0; + } + ar = ar->pNext; + } + + avatars_server_connection *pConnection = m_avatarsConnection; + + pConnection->_Lock(); + m_avatarsMutex->Leave(); + + DWORD dwCookie = pConnection->sendGetAvatarRequest(hContact, dwUin, pszUid, hash, hashlen, file); + + m_avatarsMutex->Enter(); + pConnection->_Release(); + + if (dwCookie) + { // return now if the request was sent successfully + m_avatarsMutex->Leave(); + return dwCookie; + } + } + // we failed to send request, or avatar thread not ready + + // check if any request for this user is not already in the queue + avatars_request *ar = m_avatarsQueue; + + while (ar) + { + if (ar->hContact == hContact) + { // we found it, return error + if (ar->type == ART_BLOCK && GetTickCount() > ar->timeOut) + { // remove timeouted block + ar = ReleaseAvatarRequestInQueue(ar); + continue; + } + m_avatarsMutex->Leave(); + NetLog_Server("Avatars: Ignoring duplicate get %s avatar request.", strUID(dwUin, pszUid)); + + // make sure avatar connection is in progress + requestAvatarConnection(); + return 0; + } + ar = ar->pNext; + } + // add request to queue, processed after successful login + ar = new avatars_request(ART_GET); // get avatar + if (!ar) + { // out of memory, go away + m_avatarsMutex->Leave(); + return 0; + } + ar->hContact = hContact; + ar->dwUin = dwUin; + if (!dwUin) + strcpy(ar->szUid, szUid); + ar->hash = (BYTE*)SAFE_MALLOC(hashlen); + if (!ar->hash) + { // alloc failed + m_avatarsMutex->Leave(); + delete ar; + return 0; + } + memcpy(ar->hash, hash, hashlen); // copy the data + ar->hashlen = hashlen; + ar->szFile = null_strdup(file); // duplicate the string + ar->pNext = m_avatarsQueue; + m_avatarsQueue = ar; + m_avatarsMutex->Leave(); + + NetLog_Server("Avatars: Request to get %s image added to queue.", strUID(dwUin, pszUid)); + + // make sure avatar connection is in progress + requestAvatarConnection(); + + return -1; // we added to queue +} + + +// upload avatar data to server +int CIcqProto::SetAvatarData(HANDLE hContact, WORD wRef, const BYTE *data, unsigned int datalen) +{ + m_avatarsMutex->Enter(); + + if (m_avatarsConnection && m_avatarsConnection->isReady()) // check if we are ready + { + avatars_server_connection *pConnection = m_avatarsConnection; + + pConnection->_Lock(); + m_avatarsMutex->Leave(); + + DWORD dwCookie = pConnection->sendUploadAvatarRequest(hContact, wRef, data, datalen); + + m_avatarsMutex->Enter(); + pConnection->_Release(); + + if (dwCookie) + { // return now if the request was sent successfully + m_avatarsMutex->Leave(); + return dwCookie; + } + } + // we failed to send request, or avatar thread not ready + + // check if any request for this user is not already in the queue + avatars_request *ar = m_avatarsQueue; + int bYet = 0; + + while (ar) + { + if (ar->hContact == hContact && ar->type == ART_UPLOAD) + { // we found it, return error + m_avatarsMutex->Leave(); + NetLog_Server("Avatars: Ignoring duplicate upload avatar request."); + + // make sure avatar connection is in progress + requestAvatarConnection(); + return 0; + } + ar = ar->pNext; + } + // add request to queue, processed after successful login + ar = new avatars_request(ART_UPLOAD); // upload avatar + if (!ar) + { // out of memory, go away + m_avatarsMutex->Leave(); + return 0; + } + ar->hContact = hContact; + ar->pData = (BYTE*)SAFE_MALLOC(datalen); + if (!ar->pData) + { // alloc failed + m_avatarsMutex->Leave(); + delete ar; + return 0; + } + memcpy(ar->pData, data, datalen); // copy the data + ar->cbData = datalen; + ar->wRef = wRef; + ar->pNext = m_avatarsQueue; + m_avatarsQueue = ar; + m_avatarsMutex->Leave(); + + NetLog_Server("Avatars: Request to upload image added to queue."); + + // make sure avatar connection is in progress + requestAvatarConnection(); + + return -1; // we added to queue +} + + +void CIcqProto::requestAvatarConnection() +{ + m_avatarsMutex->Enter(); + if (!m_avatarsConnectionPending && (!m_avatarsConnection || (!m_avatarsConnection->isPending() && !m_avatarsConnection->isReady()))) + { // avatar connection is not pending, request new one + m_avatarsConnectionPending = TRUE; + m_avatarsMutex->Leave(); + + icq_requestnewfamily(ICQ_AVATAR_FAMILY, &CIcqProto::StartAvatarThread); + } + else + m_avatarsMutex->Leave(); +} + + +void __cdecl CIcqProto::AvatarThread(avatars_server_connection *pInfo) +{ + NetLog_Server("%s thread started.", "Avatar"); + + // Execute connection handler + pInfo->connectionThread(); + + { // Remove connection reference + icq_lock l(m_avatarsMutex); + if (m_avatarsConnection == pInfo) + m_avatarsConnection = NULL; + } + + { // Release connection handler + icq_lock l(m_avatarsMutex); + delete pInfo; + } + + NetLog_Server("%s thread ended.", "Avatar"); +} + + +avatars_server_connection::avatars_server_connection(CIcqProto *ppro, HANDLE hConnection, char *pCookie, WORD wCookieLen): +isLoggedIn(FALSE), stopThread(FALSE), isActive(FALSE) +{ + this->ppro = ppro; + this->hConnection = hConnection; + this->pCookie = pCookie; + this->wCookieLen = wCookieLen; + + // Initialize packet sequence + localSeqMutex = new icq_critical_section(); + wLocalSequence = generate_flap_sequence(); + + // Initialize rates + m_ratesMutex = new icq_critical_section(); + + // Create connection thread + ppro->ForkThread(( IcqThreadFunc )&CIcqProto::AvatarThread, this); +} + + +avatars_server_connection::~avatars_server_connection() +{ + delete m_ratesMutex; + delete localSeqMutex; +} + + +int avatars_server_connection::NetLog_Server(const char *fmt,...) +{ + va_list va; + char szText[1024 + 9]; + + strcpy(szText, "Avatars: "); + va_start(va, fmt); + mir_vsnprintf(szText + 9, sizeof(szText) - 9, fmt, va); + va_end(va); + return CallService(MS_NETLIB_LOG,(WPARAM)ppro->m_hServerNetlibUser,(LPARAM)szText); +} + + +void avatars_server_connection::closeConnection() +{ + stopThread = TRUE; + + icq_lock l(localSeqMutex); + if (hConnection) + NetLib_SafeCloseHandle(&hConnection); +} + + +void avatars_server_connection::shutdownConnection() +{ + stopThread = TRUE; + + icq_lock l(localSeqMutex); + if (hConnection) + Netlib_Shutdown(hConnection); +} + +DWORD avatars_server_connection::sendGetAvatarRequest(HANDLE hContact, DWORD dwUin, char *szUid, const BYTE *hash, unsigned int hashlen, const TCHAR *file) +{ + int i; + DWORD dwNow = GetTickCount(); + + ppro->m_avatarsMutex->Enter(); + + for(i = 0; i < runCount;) + { // look for timeouted requests + if (runTime[i] < dwNow) + { // found outdated, remove + runContact[i] = runContact[runCount - 1]; + runTime[i] = runTime[runCount - 1]; + runCount--; + } + else + i++; + } + + for(i = 0; i < runCount; i++) + { + if (runContact[i] == hContact) + { + ppro->m_avatarsMutex->Leave(); + NetLog_Server("Ignoring duplicate get %s image request.", strUID(dwUin, szUid)); + + return -1; // Success: request ignored + } + } + + if (runCount < 4) + { // 4 concurent requests at most + int bSendNow = TRUE; + + { // rate management + icq_lock l(m_ratesMutex); + WORD wGroup = m_rates->getGroupFromSNAC(ICQ_AVATAR_FAMILY, ICQ_AVATAR_GET_REQUEST); + + if (m_rates->getNextRateLevel(wGroup) < m_rates->getLimitLevel(wGroup, RML_ALERT)) + { // we will be over quota if we send the request now, add to queue instead + bSendNow = FALSE; +#ifdef _DEBUG + NetLog_Server("Rates: Delay avatar request."); +#endif + } + } + + if (bSendNow) + { + runContact[runCount] = hContact; + runTime[runCount] = GetTickCount() + 30000; // 30sec to complete request + runCount++; + + ppro->m_avatarsMutex->Leave(); + + int nUinLen = getUIDLen(dwUin, szUid); + + cookie_avatar *ack = (cookie_avatar*)SAFE_MALLOC(sizeof(cookie_avatar)); + if (!ack) return 0; // Failure: out of memory + + ack->dwUin = 1; //dwUin; // I should be damned for this - only to identify get request + ack->hContact = hContact; + ack->hash = (BYTE*)SAFE_MALLOC(hashlen); + memcpy(ack->hash, hash, hashlen); // copy the data + ack->hashlen = hashlen; + ack->szFile = null_strdup(file); // duplicate the string + + DWORD dwCookie = ppro->AllocateCookie(CKT_AVATAR, ICQ_AVATAR_GET_REQUEST, hContact, ack); + icq_packet packet; + + serverPacketInit(&packet, (WORD)(12 + nUinLen + hashlen)); + packFNACHeader(&packet, ICQ_AVATAR_FAMILY, ICQ_AVATAR_GET_REQUEST, 0, dwCookie); + packUID(&packet, dwUin, szUid); + packByte(&packet, 1); // unknown, probably type of request: 1 = get icon :) + packBuffer(&packet, hash, (WORD)hashlen); + + if (sendServerPacket(&packet)) + { + NetLog_Server("Request to get %s image sent.", strUID(dwUin, szUid)); + + return dwCookie; + } + ppro->FreeCookie(dwCookie); // sending failed, free resources + SAFE_FREE(&ack->szFile); + SAFE_FREE((void**)&ack->hash); + SAFE_FREE((void**)&ack); + } + else + ppro->m_avatarsMutex->Leave(); + } + else + ppro->m_avatarsMutex->Leave(); + + return 0; // Failure +} + + +DWORD avatars_server_connection::sendUploadAvatarRequest(HANDLE hContact, WORD wRef, const BYTE *data, unsigned int datalen) +{ + cookie_avatar *ack = (cookie_avatar*)SAFE_MALLOC(sizeof(cookie_avatar)); + if (!ack) return 0; // Failure: out of memory + + ack->hContact = hContact; + + DWORD dwCookie = ppro->AllocateCookie(CKT_AVATAR, ICQ_AVATAR_UPLOAD_REQUEST, 0, ack); + icq_packet packet; + + serverPacketInit(&packet, (WORD)(14 + datalen)); + packFNACHeader(&packet, ICQ_AVATAR_FAMILY, ICQ_AVATAR_UPLOAD_REQUEST, 0, dwCookie); + packWord(&packet, wRef); // unknown, probably reference + packWord(&packet, (WORD)datalen); + packBuffer(&packet, data, (WORD)datalen); + + if (sendServerPacket(&packet)) + { + NetLog_Server("Upload image packet sent."); + + return dwCookie; + } + ppro->ReleaseCookie(dwCookie); // failed to send, free resources + + return 0; +} + + +void avatars_server_connection::checkRequestQueue() +{ +#ifdef _DEBUG + NetLog_Server("Checking request queue..."); +#endif + + ppro->m_avatarsMutex->Enter(); + + while (ppro->m_avatarsQueue && runCount < 3) // pick up an request and send it - happens immediatelly after login + { // do not fill queue to top, leave one place free + avatars_request *pRequest = ppro->m_avatarsQueue; + + { // rate management + icq_lock l(m_ratesMutex); + WORD wGroup = m_rates->getGroupFromSNAC(ICQ_AVATAR_FAMILY, (WORD)(pRequest->type == ART_UPLOAD ? ICQ_AVATAR_GET_REQUEST : ICQ_AVATAR_UPLOAD_REQUEST)); + + if (m_rates->getNextRateLevel(wGroup) < m_rates->getLimitLevel(wGroup, RML_ALERT)) + { // we are over rate, leave queue and wait +#ifdef _DEBUG + NetLog_Server("Rates: Leaving avatar queue processing"); +#endif + break; + } + } + + if (pRequest->type == ART_BLOCK) + { // block contact processing + avatars_request **ppRequest = &ppro->m_avatarsQueue; + + while (pRequest) + { + if (GetTickCount() > pRequest->timeOut) + { // expired contact block, remove + *ppRequest = pRequest->pNext; + delete pRequest; + } + else // it is not time, move to next request + ppRequest = &pRequest->pNext; + + pRequest = *ppRequest; + } + // end queue processing (only block requests follows) + break; + } + else + ppro->m_avatarsQueue = pRequest->pNext; + + ppro->m_avatarsMutex->Leave(); + +#ifdef _DEBUG + NetLog_Server("Picked up the %s request from queue.", strUID(pRequest->dwUin, pRequest->szUid)); +#endif + switch (pRequest->type) + { + case ART_GET: // get avatar + sendGetAvatarRequest(pRequest->hContact, pRequest->dwUin, pRequest->szUid, pRequest->hash, pRequest->hashlen, pRequest->szFile); + break; + + case ART_UPLOAD: // set avatar + sendUploadAvatarRequest(pRequest->hContact, pRequest->wRef, pRequest->pData, pRequest->cbData); + break; + } + delete pRequest; + + ppro->m_avatarsMutex->Enter(); + } + + ppro->m_avatarsMutex->Leave(); +} + + +void avatars_server_connection::connectionThread() +{ + // This is the "infinite" loop that receives the packets from the ICQ avatar server + NETLIBPACKETRECVER packetRecv = {0}; + DWORD wLastKeepAlive = 0; // we send keep-alive at most one per 30secs + DWORD dwKeepAliveInterval = ppro->getSettingDword(NULL, "KeepAliveInterval", KEEPALIVE_INTERVAL); + + hPacketRecver = (HANDLE)CallService(MS_NETLIB_CREATEPACKETRECVER, (WPARAM)hConnection, 65536); + packetRecv.cbSize = sizeof(packetRecv); + packetRecv.dwTimeout = dwKeepAliveInterval < KEEPALIVE_INTERVAL ? dwKeepAliveInterval: KEEPALIVE_INTERVAL; // timeout - for stopThread to work + while (!stopThread) + { + int recvResult = CallService(MS_NETLIB_GETMOREPACKETS, (WPARAM)hPacketRecver, (LPARAM)&packetRecv); + + if (recvResult == 0) + { + NetLog_Server("Clean closure of server socket"); + break; + } + + if (recvResult == SOCKET_ERROR) + { + if (GetLastError() == ERROR_TIMEOUT) + { // timeout, check if we should be still running + if (Miranda_Terminated()) + { // we must stop here, cause due to a hack in netlib, we always get timeout, even if the connection is already dead + stopThread = 1; + continue; + } +#ifdef _DEBUG + else + NetLog_Server("Thread is Idle."); +#endif + if (GetTickCount() > wLastKeepAlive) + { // limit frequency (HACK: on some systems select() does not work well) + if (!ppro->m_bGatewayMode && ppro->getSettingByte(NULL, "KeepAlive", DEFAULT_KEEPALIVE_ENABLED)) + { // send keep-alive packet + icq_packet packet; + + packet.wLen = 0; + write_flap(&packet, ICQ_PING_CHAN); + sendServerPacket(&packet); + } + wLastKeepAlive = GetTickCount() + dwKeepAliveInterval; + } + else + { // this is bad, the system does not handle select() properly +#ifdef _DEBUG + NetLog_Server("Thread is Forcing Idle."); +#endif + SleepEx(500, TRUE); // wait some time, can we do anything else ?? + if (Miranda_Terminated()) + { + stopThread = 1; + continue; + } + } + // check if we got something to request + checkRequestQueue(); + continue; + } + if (!stopThread) + NetLog_Server("Abortive closure of server socket, error: %d", GetLastError()); + else + NetLog_Server("Connection closed."); + break; + } + + // Deal with the packet + packetRecv.bytesUsed = handleServerPackets(packetRecv.buffer, packetRecv.bytesAvailable); + + if (isActive && (packetRecv.bytesAvailable == packetRecv.bytesUsed)) // no packets pending + { // process request queue + checkRequestQueue(); + } + } + { // release connection + icq_lock l(localSeqMutex); + NetLib_SafeCloseHandle(&hPacketRecver); // Close the packet receiver + NetLib_CloseConnection(&hConnection, FALSE); // Close the connection + } + + { // release rates + icq_lock l(m_ratesMutex); + SAFE_DELETE((MZeroedObject**)&m_rates); + } + + SAFE_FREE((void**)&pCookie); +} + + +int avatars_server_connection::sendServerPacket(icq_packet *pPacket) +{ + int lResult = 0; + + // This critsec makes sure that the sequence order doesn't get screwed up + localSeqMutex->Enter(); + + if (hConnection) + { + int nRetries; + int nSendResult; + + // :IMPORTANT: + // The FLAP sequence must be a WORD. When it reaches 0xFFFF it should wrap to + // 0x0000, otherwise we'll get kicked by server. + wLocalSequence++; + + // Pack sequence number + pPacket->pData[2] = ((wLocalSequence & 0xff00) >> 8); + pPacket->pData[3] = (wLocalSequence & 0x00ff); + + for (nRetries = 3; nRetries >= 0; nRetries--) + { + nSendResult = Netlib_Send(hConnection, (const char *)pPacket->pData, pPacket->wLen, 0); + + if (nSendResult != SOCKET_ERROR) + break; + + Sleep(1000); + } + + // Send error + if (nSendResult == SOCKET_ERROR) + { // thread stops automatically + NetLog_Server("Your connection with the ICQ avatar server was abortively closed"); + } + else + { + lResult = 1; // packet sent successfully + + icq_lock l(m_ratesMutex); + if (m_rates) + m_rates->packetSent(pPacket); + } + } + else + { + NetLog_Server("Error: Failed to send packet (no connection)"); + } + + localSeqMutex->Leave(); + + SAFE_FREE((void**)&pPacket->pData); + + return lResult; +} + + +int avatars_server_connection::handleServerPackets(BYTE *buf, int buflen) +{ + BYTE channel; + WORD sequence; + WORD datalen; + int bytesUsed = 0; + + while (buflen > 0) + { + // All FLAPS begin with 0x2a + if (*buf++ != FLAP_MARKER) + break; + + if (buflen < 6) + break; + + unpackByte(&buf, &channel); + unpackWord(&buf, &sequence); + unpackWord(&buf, &datalen); + + if (buflen < 6 + datalen) + break; + +#ifdef _DEBUG + NetLog_Server("Server FLAP: Channel %u, Seq %u, Length %u bytes", channel, sequence, datalen); +#endif + + switch (channel) + { + case ICQ_LOGIN_CHAN: + handleLoginChannel(buf, datalen); + break; + + case ICQ_DATA_CHAN: + handleDataChannel(buf, datalen); + break; + + default: + NetLog_Server("Warning: Unhandled Server FLAP Channel: Channel %u, Seq %u, Length %u bytes", channel, sequence, datalen); + break; + } + + /* Increase pointers so we can check for more FLAPs */ + buf += datalen; + buflen -= (datalen + 6); + bytesUsed += (datalen + 6); + } + + return bytesUsed; +} + + +void avatars_server_connection::handleLoginChannel(BYTE *buf, WORD datalen) +{ + icq_packet packet; + + if (*(DWORD*)buf == 0x1000000) + { // here check if we received SRV_HELLO + wLocalSequence = generate_flap_sequence(); + + serverCookieInit(&packet, (LPBYTE)pCookie, wCookieLen); + sendServerPacket(&packet); + +#ifdef _DEBUG + NetLog_Server("Sent CLI_IDENT to %s", "avatar server"); +#endif + + SAFE_FREE((void**)&pCookie); + wCookieLen = 0; + } + else + { + NetLog_Server("Invalid Server response, Channel 1."); + } +} + + +void avatars_server_connection::handleDataChannel(BYTE *buf, WORD datalen) +{ + snac_header snacHeader = {0}; + + if (!unpackSnacHeader(&snacHeader, &buf, &datalen) || !snacHeader.bValid) + { + NetLog_Server("Error: Failed to parse SNAC header"); + } + else + { +#ifdef _DEBUG + if (snacHeader.wFlags & 0x8000) + NetLog_Server(" Received SNAC(x%02X,x%02X), version %u", snacHeader.wFamily, snacHeader.wSubtype, snacHeader.wVersion); + else + NetLog_Server(" Received SNAC(x%02X,x%02X)", snacHeader.wFamily, snacHeader.wSubtype); +#endif + + switch (snacHeader.wFamily) + { + + case ICQ_SERVICE_FAMILY: + handleServiceFam(buf, datalen, &snacHeader); + break; + + case ICQ_AVATAR_FAMILY: + handleAvatarFam(buf, datalen, &snacHeader); + break; + + default: + NetLog_Server("Ignoring SNAC(x%02X,x%02X) - FAMILYx%02X not implemented", snacHeader.wFamily, snacHeader.wSubtype, snacHeader.wFamily); + break; + } + } +} + + +void avatars_server_connection::handleServiceFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader) +{ + icq_packet packet; + + switch (pSnacHeader->wSubtype) + { + + case ICQ_SERVER_READY: +#ifdef _DEBUG + NetLog_Server("Server is ready and is requesting my Family versions"); + NetLog_Server("Sending my Families"); +#endif + + // Miranda mimics the behaviour of Icq5 + serverPacketInit(&packet, 18); + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_FAMILIES); + packDWord(&packet, 0x00010004); + packDWord(&packet, 0x00100001); + sendServerPacket(&packet); + break; + + case ICQ_SERVER_FAMILIES2: + /* This is a reply to CLI_FAMILIES and it tells the client which families and their versions that this server understands. + * We send a rate request packet */ +#ifdef _DEBUG + NetLog_Server("Server told me his Family versions"); + NetLog_Server("Requesting Rate Information"); +#endif + serverPacketInit(&packet, 10); + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_REQ_RATE_INFO); + sendServerPacket(&packet); + break; + + case ICQ_SERVER_RATE_INFO: +#ifdef _DEBUG + NetLog_Server("Server sent Rate Info"); +#endif + /* init rates management */ + m_rates = new rates(ppro, pBuffer, wBufferLength); + /* ack rate levels */ +#ifdef _DEBUG + NetLog_Server("Sending Rate Info Ack"); +#endif + m_rates->initAckPacket(&packet); + sendServerPacket(&packet); + + // send cli_ready + serverPacketInit(&packet, 26); + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_READY); + packDWord(&packet, 0x00010004); // mimic ICQ 6 + packDWord(&packet, 0x0010164f); + packDWord(&packet, 0x00100001); + packDWord(&packet, 0x0010164f); + sendServerPacket(&packet); + + isActive = TRUE; // we are ready to process requests + isLoggedIn = TRUE; + + NetLog_Server(" *** Yeehah, login sequence complete"); + break; + + default: + NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_SERVICE_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + } +} + + +void avatars_server_connection::handleAvatarFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader) +{ + switch (pSnacHeader->wSubtype) { + + case ICQ_AVATAR_GET_REPLY: // received avatar data, store to file + { // handle new avatar, notify + cookie_avatar *pCookieData; + + if (ppro->FindCookie(pSnacHeader->dwRef, NULL, (void**)&pCookieData)) + { + PROTO_AVATAR_INFORMATIONT ai = {0}; + BYTE bResult; + + { // remove from active request list + icq_lock l(ppro->m_avatarsMutex); + for(int i = 0; i < runCount; i++) + { // look for our record + if (runContact[i] == pCookieData->hContact) + { // found, remove + runContact[i] = runContact[runCount - 1]; + runTime[i] = runTime[runCount - 1]; + runCount--; + break; + } + } + } + + ai.cbSize = sizeof(PROTO_AVATAR_INFORMATIONT); + ai.format = PA_FORMAT_JPEG; // this is for error only + ai.hContact = pCookieData->hContact; + lstrcpyn(ai.filename, pCookieData->szFile, SIZEOF(ai.filename)); + AddAvatarExt(PA_FORMAT_JPEG, ai.filename); + + ppro->FreeCookie(pSnacHeader->dwRef); + + BYTE len; + WORD datalen; + + unpackByte(&pBuffer, &len); + if (wBufferLength < ((pCookieData->hashlen)<<1)+4+len) + { + NetLog_Server("Received invalid avatar reply."); + + ppro->BroadcastAck(pCookieData->hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, (HANDLE)&ai, 0); + + SAFE_FREE(&pCookieData->szFile); + SAFE_FREE((void**)&pCookieData->hash); + SAFE_FREE((void**)&pCookieData); + + break; + } + + pBuffer += len; + pBuffer += pCookieData->hashlen; + unpackByte(&pBuffer, &bResult); + pBuffer += pCookieData->hashlen; + unpackWord(&pBuffer, &datalen); + + wBufferLength -= 4 + len + (pCookieData->hashlen<<1); + if (datalen > wBufferLength) + { + datalen = wBufferLength; + NetLog_Server("Avatar reply broken, trying to do my best."); + } + + if (datalen > 4) + { // store to file... + int aValid = 1; + + if (pCookieData->hashlen == 0x14 && pCookieData->hash[3] == 0x10 && ppro->getSettingByte(NULL, "StrictAvatarCheck", DEFAULT_AVATARS_CHECK)) + { // check only standard hashes + mir_md5_state_t state; + mir_md5_byte_t digest[16]; + + mir_md5_init(&state); + mir_md5_append(&state, (const mir_md5_byte_t *)pBuffer, datalen); + mir_md5_finish(&state, digest); + // check if received data corresponds to specified hash + if (memcmp(pCookieData->hash+4, digest, 0x10)) aValid = 0; + } + + if (aValid) + { + NetLog_Server("Received user avatar, storing (%d bytes).", datalen); + + int dwPaFormat = DetectAvatarFormatBuffer((char*)pBuffer); + TCHAR *tszImageFile = (TCHAR*)_alloca(sizeof(TCHAR)*(strlennull(pCookieData->szFile) + 6)); + + _tcscpy(tszImageFile, pCookieData->szFile); + AddAvatarExt(dwPaFormat, tszImageFile); + + ppro->setSettingByte(pCookieData->hContact, "AvatarType", (BYTE)dwPaFormat); + ai.format = dwPaFormat; // set the format + lstrcpyn(ai.filename, tszImageFile, SIZEOF(ai.filename)); + + int out = _topen(tszImageFile, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY, _S_IREAD | _S_IWRITE); + if (out != -1) + { + DBVARIANT dbv = {DBVT_DELETED}; + + _write(out, pBuffer, datalen); + _close(out); + + if (!pCookieData->hContact) // our avatar, set filename + { + TCHAR tmp[MAX_PATH * 2]; + CallService(MS_UTILS_PATHTORELATIVET, (WPARAM)tszImageFile, (LPARAM)tmp); + ppro->setSettingStringT(NULL, "AvatarFile", tmp); + } + else + { // contact's avatar set hash + if (!ppro->getSetting(pCookieData->hContact, "AvatarHash", &dbv)) + { + if (ppro->setSettingBlob(pCookieData->hContact, "AvatarSaved", dbv.pbVal, dbv.cpbVal)) + NetLog_Server("Failed to set file hash."); + + ICQFreeVariant(&dbv); + } + else + { + NetLog_Server("Warning: DB error (no hash in DB)."); + // the hash was lost, try to fix that + if (ppro->setSettingBlob(pCookieData->hContact, "AvatarSaved", pCookieData->hash, pCookieData->hashlen) || + ppro->setSettingBlob(pCookieData->hContact, "AvatarHash", pCookieData->hash, pCookieData->hashlen)) + { + NetLog_Server("Failed to save avatar hash to DB"); + } + } + } + + ppro->BroadcastAck(pCookieData->hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, (HANDLE)&ai, 0); + } + } + else + { // avatar is broken + NetLog_Server("Error: Avatar data does not match avatar hash, ignoring."); + + if (pCookieData->hContact) + { + avatars_request *ar = new avatars_request(ART_BLOCK); + + icq_lock l(ppro->m_avatarsMutex); + + if (ar) + { + avatars_request *last = ppro->m_avatarsQueue; + + ar->hContact = pCookieData->hContact; + ar->timeOut = GetTickCount() + 14400000; // do not allow re-request four hours + + // add it to the end of queue, i.e. do not block other requests + while (last && last->pNext) last = last->pNext; + if (last) + last->pNext = ar; + else + ppro->m_avatarsQueue = ar; + } + } + ppro->BroadcastAck(pCookieData->hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, (HANDLE)&ai, 0); + } + } + else + { // the avatar is empty + NetLog_Server("Received empty avatar, nothing written (error 0x%x).", bResult); + + ppro->BroadcastAck(pCookieData->hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, (HANDLE)&ai, 0); + } + SAFE_FREE(&pCookieData->szFile); + SAFE_FREE((void**)&pCookieData->hash); + SAFE_FREE((void**)&pCookieData); + } + else + { + NetLog_Server("Warning: Received unexpected Avatar Reply SNAC(x10,x07)."); + } + + break; + } + case ICQ_AVATAR_UPLOAD_ACK: + { + // upload completed, notify + BYTE res; + unpackByte(&pBuffer, &res); + if (!res && (wBufferLength == 0x15)) + { + cookie_avatar *pCookieData; + if (ppro->FindCookie(pSnacHeader->dwRef, NULL, (void**)&pCookieData)) + { + // here we store the local hash + ppro->ReleaseCookie(pSnacHeader->dwRef); + } + else + { + NetLog_Server("Warning: Received unexpected Upload Avatar Reply SNAC(x10,x03)."); + } + } + else if (res) + { + NetLog_Server("Error uploading avatar to server, #%d", res); + + ppro->icq_LogMessage(LOG_WARNING, LPGEN("Error uploading avatar to server, server refused to accept the image.")); + } + else + NetLog_Server("Received invalid upload avatar ack."); + + break; + } + case ICQ_ERROR: + { + WORD wError; + cookie_avatar *pCookieData; + + if (ppro->FindCookie(pSnacHeader->dwRef, NULL, (void**)&pCookieData)) + { + if (pCookieData->dwUin) + { + NetLog_Server("Error: Avatar request failed"); + SAFE_FREE(&pCookieData->szFile); + SAFE_FREE((void**)&pCookieData->hash); + } + else + { + NetLog_Server("Error: Avatar upload failed"); + } + ppro->ReleaseCookie(pSnacHeader->dwRef); + } + + if (wBufferLength >= 2) + unpackWord(&pBuffer, &wError); + else + wError = 0; + + ppro->LogFamilyError(ICQ_AVATAR_FAMILY, wError); + break; + } + default: + NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_AVATAR_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef); + break; + + } +} diff --git a/protocols/IcqOscarJ/src/icq_avatar.h b/protocols/IcqOscarJ/src/icq_avatar.h new file mode 100644 index 0000000000..6bfcb65c45 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_avatar.h @@ -0,0 +1,127 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Avatars connection support declarations +// +// ----------------------------------------------------------------------------- +#ifndef __ICQ_AVATAR_H +#define __ICQ_AVATAR_H + + +extern BYTE hashEmptyAvatar[9]; + +#define AVATAR_HASH_MINI 0x00 +#define AVATAR_HASH_STATIC 0x01 +#define AVATAR_HASH_FLASH 0x08 +#define AVATAR_HASH_PHOTO 0x0C + +struct CIcqProto; + +struct avatars_server_connection : public lockable_struct +{ +protected: + CIcqProto *ppro; + HANDLE hConnection; // handle to the connection + HANDLE hPacketRecver; + WORD wLocalSequence; + icq_critical_section *localSeqMutex; + + BOOL isLoggedIn; + BOOL isActive; + BOOL stopThread; // horrible, but simple - signal for thread to stop + + char *pCookie; // auth to server + WORD wCookieLen; + + int sendServerPacket(icq_packet *pPacket); + + int handleServerPackets(BYTE *buf, int buflen); + + void handleLoginChannel(BYTE *buf, WORD datalen); + void handleDataChannel(BYTE *buf, WORD datalen); + + void handleServiceFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader); + void handleAvatarFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader); + + rates *m_rates; + icq_critical_section *m_ratesMutex; + + int NetLog_Server(const char *fmt,...); + + HANDLE runContact[4]; + DWORD runTime[4]; + int runCount; + void checkRequestQueue(); +public: + avatars_server_connection(CIcqProto *ppro, HANDLE hConnection, char *pCookie, WORD wCookieLen); + virtual ~avatars_server_connection(); + + void connectionThread(); + void closeConnection(); + void shutdownConnection(); + + __inline BOOL isPending() { return !isLoggedIn; }; + __inline BOOL isReady() { return isLoggedIn && isActive && !stopThread; }; + + DWORD sendGetAvatarRequest(HANDLE hContact, DWORD dwUin, char *szUid, const BYTE *hash, unsigned int hashlen, const TCHAR *file); + DWORD sendUploadAvatarRequest(HANDLE hContact, WORD wRef, const BYTE *data, unsigned int datalen); +}; + +__inline static void SAFE_DELETE(avatars_server_connection **p) { SAFE_DELETE((lockable_struct**)p); }; + + +struct avatars_request : public MZeroedObject +{ + int type; + HANDLE hContact; + DWORD dwUin; + uid_str szUid; + BYTE *hash; + unsigned int hashlen; + TCHAR *szFile; + BYTE *pData; + unsigned int cbData; + WORD wRef; + DWORD timeOut; + avatars_request *pNext; +public: + avatars_request(int type); + virtual ~avatars_request(); +}; + +__inline static void SAFE_DELETE(avatars_request **p) { SAFE_DELETE((MZeroedObject**)p); }; + +#define ART_GET 1 +#define ART_UPLOAD 2 +#define ART_BLOCK 4 + + +int DetectAvatarFormat(const TCHAR *szFile); +void AddAvatarExt(int dwFormat, TCHAR *pszDest); + +BYTE* calcMD5HashOfFile(const TCHAR *szFile); + +#endif /* __ICQ_AVATAR_H */ diff --git a/protocols/IcqOscarJ/src/icq_clients.cpp b/protocols/IcqOscarJ/src/icq_clients.cpp new file mode 100644 index 0000000000..bb1ebb5621 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_clients.cpp @@ -0,0 +1,1155 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Provides capability & signature based client detection +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +const capstr capShortCaps = {0x09, 0x46, 0x00, 0x00, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}; // CAP_AIM_BUDDYICON + +static const char* makeClientVersion(char *szBuf, const char *szClient, unsigned v1, unsigned v2, unsigned v3, unsigned v4) +{ + if (v4) + null_snprintf(szBuf, 64, "%s%u.%u.%u.%u", szClient, v1, v2, v3, v4); + else if (v3) + null_snprintf(szBuf, 64, "%s%u.%u.%u", szClient, v1, v2, v3); + else + null_snprintf(szBuf, 64, "%s%u.%u", szClient, v1, v2); + + return szBuf; +} + +static void verToStr(char *szStr, int v) +{ + char szVer[64]; + + makeClientVersion(szVer, "", (v>>24)&0x7F, (v>>16)&0xFF, (v>>8)&0xFF, v&0xFF); + strcat(szStr, szVer); + if (v&0x80000000) strcat(szStr, " alpha"); +} + +static char* MirandaVersionToStringEx(char* szStr, int bUnicode, const char* szPlug, int v, int m) +{ + if (!v) // this is not Miranda + return NULL; + + strcpy(szStr, "Miranda IM "); + + if (!m && v == 1) + verToStr(szStr, 0x80010200); + else if (!m && (v&0x7FFFFFFF) <= 0x030301) + verToStr(szStr, v); + else { + if (m) { + verToStr(szStr, m); + strcat(szStr, " "); + } + if (bUnicode) + strcat(szStr, "Unicode "); + + strcat(szStr, "("); + strcat(szStr, szPlug); + strcat(szStr, " v"); + verToStr(szStr, v); + strcat(szStr, ")"); + } + + return szStr; +} + +char* MirandaVersionToString(char* szStr, int bUnicode, int v, int m) +{ + return MirandaVersionToStringEx(szStr, bUnicode, "ICQ", v, m); +} + +char* MirandaModToString(char* szStr, capstr* capId, int bUnicode, const char* szModName) +{ // decode icqj mod version + char* szClient; + DWORD mver = (*capId)[0x4] << 0x18 | (*capId)[0x5] << 0x10 | (*capId)[0x6] << 8 | (*capId)[0x7]; + DWORD iver = (*capId)[0x8] << 0x18 | (*capId)[0x9] << 0x10 | (*capId)[0xA] << 8 | (*capId)[0xB]; + DWORD scode = (*capId)[0xC] << 0x18 | (*capId)[0xD] << 0x10 | (*capId)[0xE] << 8 | (*capId)[0xF]; + + szClient = MirandaVersionToStringEx(szStr, bUnicode, szModName, iver, mver); + if (scode == 0x5AFEC0DE) + { + strcat(szClient, " + SecureIM"); + } + return szClient; +} + +const capstr capMirandaIm = {'M', 'i', 'r', 'a', 'n', 'd', 'a', 'M', 0, 0, 0, 0, 0, 0, 0, 0}; +const capstr capMirandaNg = {'M', 'i', 'r', 'a', 'n', 'd', 'a', 'N', 0, 0, 0, 0, 0, 0, 0, 0}; +const capstr capIcqJs7 = {'i', 'c', 'q', 'j', ' ', 'S', 'e', 'c', 'u', 'r', 'e', ' ', 'I', 'M', 0, 0}; +const capstr capIcqJSin = {'s', 'i', 'n', 'j', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Miranda ICQJ S!N +const capstr capIcqJp = {'i', 'c', 'q', 'p', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const capstr capAimOscar = {'M', 'i', 'r', 'a', 'n', 'd', 'a', 'A', 0, 0, 0, 0, 0, 0, 0, 0}; +const capstr capMimMobile = {'M', 'i', 'r', 'a', 'n', 'd', 'a', 'M', 'o', 'b', 'i', 'l', 'e', 0, 0, 0}; +const capstr capMimPack = {'M', 'I', 'M', '/', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Custom Miranda Pack +const capstr capTrillian = {0x97, 0xb1, 0x27, 0x51, 0x24, 0x3c, 0x43, 0x34, 0xad, 0x22, 0xd6, 0xab, 0xf7, 0x3f, 0x14, 0x09}; +const capstr capTrilCrypt = {0xf2, 0xe7, 0xc7, 0xf4, 0xfe, 0xad, 0x4d, 0xfb, 0xb2, 0x35, 0x36, 0x79, 0x8b, 0xdf, 0x00, 0x00}; +const capstr capSim = {'S', 'I', 'M', ' ', 'c', 'l', 'i', 'e', 'n', 't', ' ', ' ', 0, 0, 0, 0}; +const capstr capSimOld = {0x97, 0xb1, 0x27, 0x51, 0x24, 0x3c, 0x43, 0x34, 0xad, 0x22, 0xd6, 0xab, 0xf7, 0x3f, 0x14, 0x00}; +const capstr capLicq = {'L', 'i', 'c', 'q', ' ', 'c', 'l', 'i', 'e', 'n', 't', ' ', 0, 0, 0, 0}; +const capstr capKopete = {'K', 'o', 'p', 'e', 't', 'e', ' ', 'I', 'C', 'Q', ' ', ' ', 0, 0, 0, 0}; +const capstr capmIcq = {'m', 'I', 'C', 'Q', ' ', 0xA9, ' ', 'R', '.', 'K', '.', ' ', 0, 0, 0, 0}; +const capstr capClimm = {'c', 'l', 'i', 'm', 'm', 0xA9, ' ', 'R', '.', 'K', '.', ' ', 0, 0, 0, 0}; +const capstr capAndRQ = {'&', 'R', 'Q', 'i', 'n', 's', 'i', 'd', 'e', 0, 0, 0, 0, 0, 0, 0}; +const capstr capRAndQ = {'R', '&', 'Q', 'i', 'n', 's', 'i', 'd', 'e', 0, 0, 0, 0, 0, 0, 0}; +const capstr capIMadering = {'I', 'M', 'a', 'd', 'e', 'r', 'i', 'n', 'g', ' ', 'C', 'l', 'i', 'e', 'n', 't'}; +const capstr capmChat = {'m', 'C', 'h', 'a', 't', ' ', 'i', 'c', 'q', ' ', 0, 0, 0, 0, 0, 0}; +const capstr capJimm = {'J', 'i', 'm', 'm', ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const capstr capCorePager = {'C', 'O', 'R', 'E', ' ', 'P', 'a', 'g', 'e', 'r', 0, 0, 0, 0, 0, 0}; +const capstr capDiChat = {'D', '[', 'i', ']', 'C', 'h', 'a', 't', ' ', 0, 0, 0, 0, 0, 0, 0}; +const capstr capVmIcq = {'V', 'm', 'I', 'C', 'Q', ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const capstr capSmapeR = {'S', 'm', 'a', 'p', 'e', 'r', ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const capstr capAnastasia = {0x44, 0xE5, 0xBF, 0xCE, 0xB0, 0x96, 0xE5, 0x47, 0xBD, 0x65, 0xEF, 0xD6, 0xA3, 0x7E, 0x36, 0x02}; +const capstr capPalmJicq = {'J', 'I', 'C', 'Q', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const capstr capInluxMsgr = {0xA7, 0xE4, 0x0A, 0x96, 0xB3, 0xA0, 0x47, 0x9A, 0xB8, 0x45, 0xC9, 0xE4, 0x67, 0xC5, 0x6B, 0x1F}; +const capstr capYapp = {'Y', 'a', 'p', 'p', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const capstr capMipClient = {0x4d, 0x49, 0x50, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x76, 0x00, 0x00, 0x00, 0x00}; +const capstr capPigeon = {'P', 'I', 'G', 'E', 'O', 'N', '!', 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const capstr capDigsbyBeta= {0x09, 0x46, 0x01, 0x05, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x45, 0x53, 0x54, 0x00}; +const capstr capDigsby = {'d', 'i', 'g', 's', 'b', 'y', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const capstr capJapp = {0x6a, 0x61, 0x70, 0x70, 0xa9, 0x20, 0x62, 0x79, 0x20, 0x53, 0x65, 0x72, 0x67, 0x6f, 0x00, 0x00}; +const capstr capNaim = {0xFF, 0xFF, 0xFF, 0xFF, 'n', 'a', 'i', 'm', 0, 0, 0, 0, 0, 0, 0, 0}; +const capstr capCitron = {0x09, 0x19, 0x19, 0x82, 0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}; +const capstr capQip = {0x56, 0x3F, 0xC8, 0x09, 0x0B, 0x6F, 0x41, 'Q', 'I', 'P', ' ', '2', '0', '0', '5', 'a'}; +const capstr capQipPDA = {0x56, 0x3F, 0xC8, 0x09, 0x0B, 0x6F, 0x41, 'Q', 'I', 'P', ' ', ' ', ' ', ' ', ' ', '!'}; +const capstr capQipSymbian= {0x51, 0xAD, 0xD1, 0x90, 0x72, 0x04, 0x47, 0x3D, 0xA1, 0xA1, 0x49, 0xF4, 0xA3, 0x97, 0xA4, 0x1F}; +const capstr capQipIphone = {0x60, 0xDE, 0x5C, 0x8A, 0xDF, 0x8C, 0x4E, 0x1D, 0xA4, 0xC8, 0xBC, 0x3B, 0xD9, 0x79, 0x4D, 0xD8}; +const capstr capQipMobile = {0xB0, 0x82, 0x62, 0xF6, 0x7F, 0x7C, 0x45, 0x61, 0xAD, 0xC1, 0x1C, 0x6D, 0x75, 0x70, 0x5E, 0xC5}; +const capstr capQipInfium = {0x7C, 0x73, 0x75, 0x02, 0xC3, 0xBE, 0x4F, 0x3E, 0xA6, 0x9F, 0x01, 0x53, 0x13, 0x43, 0x1E, 0x1A}; +const capstr capQip2010 = {0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x0A, 0x03, 0x0B, 0x04, 0x01, 0x53, 0x00, 0x00, 0x00, 0x00}; +const capstr capQip2012 = {0x7F, 0x7F, 0x7C, 0x7D, 0x7E, 0x7F, 0x0A, 0x03, 0x0B, 0x04, 0x01, 0x53, 0x13, 0x43, 0x1E, 0x1A}; +const capstr capIm2 = {0x74, 0xED, 0xC3, 0x36, 0x44, 0xDF, 0x48, 0x5B, 0x8B, 0x1C, 0x67, 0x1A, 0x1F, 0x86, 0x09, 0x9F}; // IM2 Ext Msg +const capstr capQutIm = {'q', 'u', 't', 'i', 'm', 0x30, 0x2e, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const capstr capBayan = {'b', 'a', 'y', 'a', 'n', 'I', 'C', 'Q', 0, 0, 0, 0, 0, 0, 0, 0}; +const capstr capJabberJIT = {'J', 'I', 'T', ' ', 0x76, 0x2E, 0x31, 0x2E, 0x78, 0x2E, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00}; +const capstr capIcqKid2 = {'I', 'c', 'q', 'K', 'i', 'd', '2', 0x00, 0x05, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const capstr capWebIcqPro = {'W', 'e', 'b', 'I', 'c', 'q', 'P', 'r', 'o', ' ', 0, 0, 0, 0, 0, 0}; +const capstr capMraJava = {0x4a, 0x32, 0x4d, 0x45, 0x20, 0x6d, 0x40, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x00, 0x00, 0x00, 0x00}; +const capstr capMacIcq = {0xdd, 0x16, 0xf2, 0x02, 0x84, 0xe6, 0x11, 0xd4, 0x90, 0xdb, 0x00, 0x10, 0x4b, 0x9b, 0x4b, 0x7d}; +const capstr capIs2001 = {0x2e, 0x7a, 0x64, 0x75, 0xfa, 0xdf, 0x4d, 0xc8, 0x88, 0x6f, 0xea, 0x35, 0x95, 0xfd, 0xb6, 0xdf}; +const capstr capIs2002 = {0x10, 0xcf, 0x40, 0xd1, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}; +const capstr capComm20012 = {0xa0, 0xe9, 0x3f, 0x37, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}; +const capstr capStrIcq = {0xa0, 0xe9, 0x3f, 0x37, 0x4f, 0xe9, 0xd3, 0x11, 0xbc, 0xd2, 0x00, 0x04, 0xac, 0x96, 0xdd, 0x96}; +const shortcapstr capAimIcon = {0x13, 0x46}; // CAP_AIM_BUDDYICON +const shortcapstr capAimDirect = {0x13, 0x45}; // CAP_AIM_DIRECTIM +const shortcapstr capAimFileShare = {0x13, 0x48}; // CAP_AIM_FILE_SHARE +const shortcapstr capIcqDevils = {0x13, 0x4C}; // CAP_DEVILS +const shortcapstr capAimSmartCaps = {0x01, 0xFF}; +const shortcapstr capAimLiveVideo = {0x01, 0x01}; // CAP_AIM_LIVE_VIDEO +const shortcapstr capAimLiveAudio = {0x01, 0x04}; // CAP_AIM_LIVE_AUDIO +const shortcapstr capStatusTextAware = {0x01, 0x0A}; // CAP_HOST_STATUS_TEXT_AWARE +const capstr capIcqLiteNew= {0xc8, 0x95, 0x3a, 0x9f, 0x21, 0xf1, 0x4f, 0xaa, 0xb0, 0xb2, 0x6d, 0xe6, 0x63, 0xab, 0xf5, 0xb7}; +const capstr capXtrazVideo= {0x17, 0x8C, 0x2D, 0x9B, 0xDA, 0xA5, 0x45, 0xBB, 0x8D, 0xDB, 0xF3, 0xBD, 0xBD, 0x53, 0xA1, 0x0A}; +const capstr capOscarChat = {0x74, 0x8F, 0x24, 0x20, 0x62, 0x87, 0x11, 0xD1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}; +const capstr capUim = {0xA7, 0xE4, 0x0A, 0x96, 0xB3, 0xA0, 0x47, 0x9A, 0xB8, 0x45, 0xC9, 0xE4, 0x67, 0xC5, 0x6B, 0x1F}; +const capstr capRambler = {0x7E, 0x11, 0xB7, 0x78, 0xA3, 0x53, 0x49, 0x26, 0xA8, 0x02, 0x44, 0x73, 0x52, 0x08, 0xC4, 0x2A}; +const capstr capAbv = {0x00, 0xE7, 0xE0, 0xDF, 0xA9, 0xD0, 0x4F, 0xe1, 0x91, 0x62, 0xC8, 0x90, 0x9A, 0x13, 0x2A, 0x1B}; +const capstr capNetvigator= {0x4C, 0x6B, 0x90, 0xA3, 0x3D, 0x2D, 0x48, 0x0E, 0x89, 0xD6, 0x2E, 0x4B, 0x2C, 0x10, 0xD9, 0x9F}; +const capstr captZers = {0xb2, 0xec, 0x8f, 0x16, 0x7c, 0x6f, 0x45, 0x1b, 0xbd, 0x79, 0xdc, 0x58, 0x49, 0x78, 0x88, 0xb9}; // CAP_TZERS +const capstr capSimpLite = {0x53, 0x49, 0x4D, 0x50, 0x53, 0x49, 0x4D, 0x50, 0x53, 0x49, 0x4D, 0x50, 0x53, 0x49, 0x4D, 0x50}; +const capstr capSimpPro = {0x53, 0x49, 0x4D, 0x50, 0x5F, 0x50, 0x52, 0x4F, 0x53, 0x49, 0x4D, 0x50, 0x5F, 0x50, 0x52, 0x4F}; +const capstr capIMsecure = {'I', 'M', 's', 'e', 'c', 'u', 'r', 'e', 'C', 'p', 'h', 'r', 0x00, 0x00, 0x06, 0x01}; // ZoneLabs +const capstr capIMSecKey1 = {1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // ZoneLabs +const capstr capIMSecKey2 = {2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // ZoneLabs +const capstr capFakeHtml = {0x01, 0x38, 0xca, 0x7b, 0x76, 0x9a, 0x49, 0x15, 0x88, 0xf2, 0x13, 0xfc, 0x00, 0x97, 0x9e, 0xa8}; + +const char* cliLibicq2k = "libicq2000"; +const char* cliLicqVer = "Licq "; +const char* cliCentericq = "Centericq"; +const char* cliLibicqUTF = "libicq2000 (Unicode)"; +const char* cliTrillian = "Trillian"; +const char* cliTrillian4 = "Trillian Astra"; +const char* cliQip = "QIP %s"; +const char* cliIM2 = "IM2"; +const char* cliSpamBot = "Spam Bot"; + +const char* CIcqProto::detectUserClient(HANDLE hContact, int nIsICQ, WORD wUserClass, DWORD dwOnlineSince, const char *szCurrentClient, + WORD wVersion, DWORD dwFT1, DWORD dwFT2, DWORD dwFT3, BYTE bDirectFlag, DWORD dwDirectCookie, DWORD dwWebPort, /* ICQ specific */ + BYTE *caps, WORD wLen, /* Client capabilities */ + BYTE *bClientId, /* Output: detected client-type */ + char *szClientBuf) +{ + LPCSTR szClient = NULL; + int bMirandaIM = FALSE; + + *bClientId = CLID_ALTERNATIVE; // Most clients does not tick as MsgIDs + + // Is this a Miranda IM client? + if (dwFT1 == 0xffffffff) + { + if (dwFT2 == 0xffffffff) + { // This is Gaim not Miranda + szClient = "Gaim"; + } + else if (!dwFT2 && wVersion == 7) + { // This is WebICQ not Miranda + szClient = "WebICQ"; + } + else if (!dwFT2 && dwFT3 == 0x3B7248ED) + { // And this is most probably Spam Bot + szClient = cliSpamBot; + } + else + { // Yes this is most probably Miranda, get the version info + szClient = MirandaVersionToString(szClientBuf, 0, dwFT2, 0); + *bClientId = CLID_MIRANDA; + bMirandaIM = TRUE; + } + } + else if (dwFT1 == 0x7fffffff) + { // This is Miranda with unicode core + szClient = MirandaVersionToString(szClientBuf, 1, dwFT2, 0); + *bClientId = CLID_MIRANDA; + bMirandaIM = TRUE; + } + else if ((dwFT1 & 0xFF7F0000) == 0x7D000000) + { // This is probably an Licq client + DWORD ver = dwFT1 & 0xFFFF; + + szClient = makeClientVersion(szClientBuf, cliLicqVer, ver / 1000, (ver / 10) % 100, ver % 10, 0); + if (dwFT1 & 0x00800000) + strcat(szClientBuf, "/SSL"); + } + else if (dwFT1 == 0xffffff8f) + { + szClient = "StrICQ"; + } + else if (dwFT1 == 0xffffff42) + { + szClient = "mICQ"; + } + else if (dwFT1 == 0xffffffbe) + { + unsigned ver1 = (dwFT2>>24)&0xFF; + unsigned ver2 = (dwFT2>>16)&0xFF; + unsigned ver3 = (dwFT2>>8)&0xFF; + + szClient = makeClientVersion(szClientBuf, "Alicq ", ver1, ver2, ver3, 0); + } + else if (dwFT1 == 0xFFFFFF7F) + { + szClient = "&RQ"; + } + else if (dwFT1 == 0xFFFFFFAB) + { + szClient = "YSM"; + } + else if (dwFT1 == 0x04031980) + { + szClient = "vICQ"; + } + else if ((dwFT1 == 0x3AA773EE) && (dwFT2 == 0x3AA66380)) + { + szClient = cliLibicq2k; + } + else if (dwFT1 == 0x3B75AC09) + { + szClient = cliTrillian; + } + else if (dwFT1 == 0x3BA8DBAF) // FT2: 0x3BEB5373; FT3: 0x3BEB5262; + { + if (wVersion == 2) + szClient = "stICQ"; + } + else if (dwFT1 == 0xFFFFFFFE && dwFT3 == 0xFFFFFFFE) + { + szClient = "Jimm"; + } + else if (dwFT1 == 0x3FF19BEB && dwFT3 == 0x3FF19BEB) + { + szClient = cliIM2; + } + else if (dwFT1 == 0xDDDDEEFF && !dwFT2 && !dwFT3) + { + szClient = "SmartICQ"; + } + else if ((dwFT1 & 0xFFFFFFF0) == 0x494D2B00 && !dwFT2 && !dwFT3) + { // last byte of FT1: (5 = Win32, 3 = SmartPhone, Pocket PC) + szClient = "IM+"; + } + else if (dwFT1 == 0x3B4C4C0C && !dwFT2 && dwFT3 == 0x3B7248ed) + { + szClient = "KXicq2"; + } + else if (dwFT1 == 0xFFFFF666 && !dwFT3) + { // this is R&Q (Rapid Edition) + null_snprintf(szClientBuf, 64, "R&Q %u", (unsigned)dwFT2); + szClient = szClientBuf; + } + else if (dwFT1 == 0x66666666 && dwFT3 == 0x66666666) + { // http://darkjimm.ucoz.ru/ + if (dwFT2 == 0x10000) + { + strcpy(szClientBuf, "D[i]Chat v."); + strcat(szClientBuf, "0.1a"); + } + else + { + makeClientVersion(szClientBuf, "D[i]Chat v.", (dwFT2 >> 8) & 0x0F, (dwFT2 >> 4) & 0x0F, 0, 0); + if ((dwFT2 & 0x0F) == 1) + strcat(szClientBuf, " alpha"); + else if ((dwFT2 & 0x0F) == 2) + strcat(szClientBuf, " beta"); + else if ((dwFT2 & 0x0F) == 3) + strcat(szClientBuf, " final"); + } + szClient = szClientBuf; + } + else if (dwFT1 == dwFT2 && dwFT2 == dwFT3 && wVersion == 8) + { + if ((dwFT1 < dwOnlineSince + 3600) && (dwFT1 > (dwOnlineSince - 3600))) + { + szClient = cliSpamBot; + } + } + else if (!dwFT1 && !dwFT2 && !dwFT3 && !wVersion && !wLen && dwWebPort == 0x75BB) + { + szClient = cliSpamBot; + } + else if (dwFT1 == 0x44F523B0 && dwFT2 == 0x44F523A6 && dwFT3 == 0x44F523A6 && wVersion == 8) + { + szClient = "Virus"; + } + + { // capabilities based detection + capstr* capId; + + if (nIsICQ && caps) + { + // check capabilities for client identification + if (capId = MatchCapability(caps, wLen, &capMirandaIm, 8)) { + // new Miranda Signature + DWORD iver = (*capId)[0xC] << 0x18 | (*capId)[0xD] << 0x10 | (*capId)[0xE] << 8 | (*capId)[0xF]; + DWORD mver = (*capId)[0x8] << 0x18 | (*capId)[0x9] << 0x10 | (*capId)[0xA] << 8 | (*capId)[0xB]; + + szClient = MirandaVersionToString(szClientBuf, dwFT1 == 0x7fffffff, iver, mver); + + if (MatchCapability(caps, wLen, &capIcqJs7, 0x4)) { + // detect mod + strcat(szClientBuf, " (s7 & sss)"); + if (MatchCapability(caps, wLen, &capIcqJs7, 0xE)) + strcat(szClientBuf, " + SecureIM"); + } + else if ((dwFT1 & 0x7FFFFFFF) == 0x7FFFFFFF) + { + if (MatchCapability(caps, wLen, &capMimMobile)) + strcat(szClientBuf, " (Mobile)"); + + if (dwFT3 == 0x5AFEC0DE) + strcat(szClientBuf, " + SecureIM"); + } + *bClientId = CLID_MIRANDA; + bMirandaIM = TRUE; + } + else if (capId = MatchCapability(caps, wLen, &capMirandaNg, 8)) { + WORD v[4]; + BYTE *buf = *capId + 8; + unpackWord(&buf, &v[0]); unpackWord(&buf, &v[1]); unpackWord(&buf, &v[2]); unpackWord(&buf, &v[3]); + mir_snprintf(szClientBuf, MAX_PATH, "Miranda NG ICQ %d.%d.%d.%d", v[0], v[1], v[2], v[3]); + + szClient = szClientBuf; + if ((dwFT1 & 0x7FFFFFFF) == 0x7FFFFFFF && dwFT3 == 0x5AFEC0DE) + strcat(szClientBuf, " + SecureIM"); + + *bClientId = CLID_MIRANDA; + bMirandaIM = TRUE; + } + else if (capId = MatchCapability(caps, wLen, &capIcqJs7, 4)) + { // detect newer icqj mod + szClient = MirandaModToString(szClientBuf, capId, dwFT3 == 0x80000000, "ICQ S7 & SSS"); + bMirandaIM = TRUE; + } + else if (capId = MatchCapability(caps, wLen, &capIcqJSin, 4)) + { // detect newer icqj mod + szClient = MirandaModToString(szClientBuf, capId, dwFT3 == 0x80000000, "ICQ S!N"); + bMirandaIM = TRUE; + } + else if (capId = MatchCapability(caps, wLen, &capIcqJp, 4)) + { // detect icqj plus mod + szClient = MirandaModToString(szClientBuf, capId, dwFT3 == 0x80000000, "ICQ Plus"); + bMirandaIM = TRUE; + } + else if (capId = MatchCapability(caps, wLen, &capMraJava, 12)) + { + unsigned ver1 = (*capId)[13]; + unsigned ver2 = (*capId)[14]; + + szClient = makeClientVersion(szClientBuf, "Mail.ru Agent (Java) v", ver1, ver2, 0, 0); + } + else if (MatchCapability(caps, wLen, &capTrillian) || MatchCapability(caps, wLen, &capTrilCrypt)) + { // this is Trillian, check for new versions + if (CheckContactCapabilities(hContact, CAPF_RTF)) + { + if (CheckContactCapabilities(hContact, CAPF_OSCAR_FILE)) + szClient = cliTrillian4; + else + { // workaroud for a bug in Trillian - make it receive msgs, other features will not work! + ClearContactCapabilities(hContact, CAPF_SRV_RELAY); + szClient = "Trillian v3"; + } + } + else if (MatchCapability(caps, wLen, &capFakeHtml) || CheckContactCapabilities(hContact, CAPF_OSCAR_FILE)) + szClient = cliTrillian4; + else + szClient = cliTrillian; + } + else if ((capId = MatchCapability(caps, wLen, &capSimOld, 0xF)) && ((*capId)[0xF] != 0x92 && (*capId)[0xF] >= 0x20 || (*capId)[0xF] == 0)) + { + int hiVer = (((*capId)[0xF]) >> 6) - 1; + unsigned loVer = (*capId)[0xF] & 0x1F; + + if ((hiVer < 0) || ((hiVer == 0) && (loVer == 0))) + szClient = "Kopete"; + else + szClient = makeClientVersion(szClientBuf, "SIM ", (unsigned)hiVer, loVer, 0, 0); + } + else if (capId = MatchCapability(caps, wLen, &capSim, 0xC)) + { + unsigned ver1 = (*capId)[0xC]; + unsigned ver2 = (*capId)[0xD]; + unsigned ver3 = (*capId)[0xE]; + unsigned ver4 = (*capId)[0xF]; + + szClient = makeClientVersion(szClientBuf, "SIM ", ver1, ver2, ver3, ver4 & 0x0F); + if (ver4 & 0x80) + strcat(szClientBuf,"/Win32"); + else if (ver4 & 0x40) + strcat(szClientBuf,"/MacOS X"); + } + else if (capId = MatchCapability(caps, wLen, &capLicq, 0xC)) + { + unsigned ver1 = (*capId)[0xC]; + unsigned ver2 = (*capId)[0xD] % 100; + unsigned ver3 = (*capId)[0xE]; + + szClient = makeClientVersion(szClientBuf, cliLicqVer, ver1, ver2, ver3, 0); + if ((*capId)[0xF]) + strcat(szClientBuf,"/SSL"); + } + else if (capId = MatchCapability(caps, wLen, &capKopete, 0xC)) + { + unsigned ver1 = (*capId)[0xC]; + unsigned ver2 = (*capId)[0xD]; + unsigned ver3 = (*capId)[0xE]; + unsigned ver4 = (*capId)[0xF]; + + szClient = makeClientVersion(szClientBuf, "Kopete ", ver1, ver2, ver3, ver4); + } + else if (capId = MatchCapability(caps, wLen, &capClimm, 0xC)) + { + unsigned ver1 = (*capId)[0xC]; + unsigned ver2 = (*capId)[0xD]; + unsigned ver3 = (*capId)[0xE]; + unsigned ver4 = (*capId)[0xF]; + + szClient = makeClientVersion(szClientBuf, "climm ", ver1, ver2, ver3, ver4); + if ((ver1 & 0x80) == 0x80) + strcat(szClientBuf, " alpha"); + if (dwFT3 == 0x02000020) + strcat(szClientBuf, "/Win32"); + else if (dwFT3 == 0x03000800) + strcat(szClientBuf, "/MacOS X"); + } + else if (capId = MatchCapability(caps, wLen, &capmIcq, 0xC)) + { + unsigned ver1 = (*capId)[0xC]; + unsigned ver2 = (*capId)[0xD]; + unsigned ver3 = (*capId)[0xE]; + unsigned ver4 = (*capId)[0xF]; + + szClient = makeClientVersion(szClientBuf, "mICQ ", ver1, ver2, ver3, ver4); + if ((ver1 & 0x80) == 0x80) + strcat(szClientBuf, " alpha"); + } + else if (MatchCapability(caps, wLen, &capIm2)) + { // IM2 v2 provides also Aim Icon cap + szClient = cliIM2; + } + else if (capId = MatchCapability(caps, wLen, &capAndRQ, 9)) + { + unsigned ver1 = (*capId)[0xC]; + unsigned ver2 = (*capId)[0xB]; + unsigned ver3 = (*capId)[0xA]; + unsigned ver4 = (*capId)[9]; + + szClient = makeClientVersion(szClientBuf, "&RQ ", ver1, ver2, ver3, ver4); + } + else if (capId = MatchCapability(caps, wLen, &capRAndQ, 9)) + { + unsigned ver1 = (*capId)[0xC]; + unsigned ver2 = (*capId)[0xB]; + unsigned ver3 = (*capId)[0xA]; + unsigned ver4 = (*capId)[9]; + + szClient = makeClientVersion(szClientBuf, "R&Q ", ver1, ver2, ver3, ver4); + } + else if (MatchCapability(caps, wLen, &capIMadering)) + { // http://imadering.com + szClient = "IMadering"; + } + else if (MatchCapability(caps, wLen, &capQipPDA)) + { + szClient = "QIP PDA (Windows)"; + } + else if (MatchCapability(caps, wLen, &capQipSymbian)) + { + szClient = "QIP PDA (Symbian)"; + } + else if (MatchCapability(caps, wLen, &capQipIphone)) + { + szClient = "QIP Mobile (IPhone)"; + } + else if (MatchCapability(caps, wLen, &capQipMobile)) + { + szClient = "QIP Mobile (Java)"; + } + else if (MatchCapability(caps, wLen, &capQipInfium)) + { + char ver[10]; + + strcpy(szClientBuf, "QIP Infium"); + if (dwFT1) + { // add build + null_snprintf(ver, 10, " (%d)", dwFT1); + strcat(szClientBuf, ver); + } + if (dwFT2 == 0x0B) + strcat(szClientBuf, " Beta"); + + szClient = szClientBuf; + } + else if (MatchCapability(caps, wLen, &capQip2010, 12)) + { + char ver[10]; + + strcpy(szClientBuf, "QIP 2010"); + if (dwFT1) + { // add build + null_snprintf(ver, 10, " (%d)", dwFT1); + strcat(szClientBuf, ver); + } + + szClient = szClientBuf; + } + else if (MatchCapability(caps, wLen, &capQip2012, 12)) + { + char ver[10]; + + strcpy(szClientBuf, "QIP 2012"); + if (dwFT1) + { // add build + null_snprintf(ver, 10, " (%d)", dwFT1); + strcat(szClientBuf, ver); + } + + szClient = szClientBuf; + } + else if (capId = MatchCapability(caps, wLen, &capQip, 0xE)) + { + char ver[10]; + + if (dwFT3 == 0x0F) + strcpy(ver, "2005"); + else + null_strcpy(ver, (char*)(*capId) + 11, 5); + + null_snprintf(szClientBuf, 64, cliQip, ver); + if (dwFT1 && dwFT2 == 0x0E) + { // add QIP build + null_snprintf(ver, 10, " (%d%d%d%d)", dwFT1 >> 0x18, (dwFT1 >> 0x10) & 0xFF, (dwFT1 >> 0x08) & 0xFF, dwFT1 & 0xFF); + strcat(szClientBuf, ver); + } + szClient = szClientBuf; + } + else if (capId = MatchCapability(caps, wLen, &capmChat, 0xA)) + { + strcpy(szClientBuf, "mChat "); + strncat(szClientBuf, (char*)(*capId) + 0xA, 6); + szClient = szClientBuf; + } + else if (capId = MatchCapability(caps, wLen, &capJimm, 5)) + { + strcpy(szClientBuf, "Jimm "); + strncat(szClientBuf, (char*)(*capId) + 5, 11); + szClient = szClientBuf; + } + else if (capId = MatchCapability(caps, wLen, &capCorePager, 0xA)) + { // http://corepager.net.ru/index/0-2 + strcpy(szClientBuf, "CORE Pager"); + if (dwFT2 == 0x0FFFF0011 && dwFT3 == 0x1100FFFF && (dwFT1 >> 0x18)) + { + char ver[16]; + + null_snprintf(ver, 10, " %d.%d", dwFT1 >> 0x18, (dwFT1 >> 0x10) & 0xFF); + if ((dwFT1 & 0xFF) == 0x0B) + strcat(ver, " Beta"); + strcat(szClientBuf, ver); + } + szClient = szClientBuf; + } + else if (capId = MatchCapability(caps, wLen, &capDiChat, 9)) + { // http://darkjimm.ucoz.ru/ + strcpy(szClientBuf, "D[i]Chat"); + strncat(szClientBuf, (char*)(*capId) + 8, 8); + szClient = szClientBuf; + } + else if (MatchCapability(caps, wLen, &capMacIcq)) + { + szClient = "ICQ for Mac"; + } + else if (MatchCapability(caps, wLen, &capUim)) + { + szClient = "uIM"; + } + else if (MatchCapability(caps, wLen, &capAnastasia)) + { // http://chis.nnov.ru/anastasia + szClient = "Anastasia"; + } + else if (capId = MatchCapability(caps, wLen, &capPalmJicq, 0xC)) + { // http://www.jsoft.ru + unsigned ver1 = (*capId)[0xC]; + unsigned ver2 = (*capId)[0xD]; + unsigned ver3 = (*capId)[0xE]; + unsigned ver4 = (*capId)[0xF]; + + szClient = makeClientVersion(szClientBuf, "JICQ ", ver1, ver2, ver3, ver4); + } + else if (MatchCapability(caps, wLen, &capInluxMsgr)) + { // http://www.inlusoft.com + szClient = "Inlux Messenger"; + } + else if (capId = MatchCapability(caps, wLen, &capMipClient, 0xC)) + { // http://mip.rufon.net + unsigned ver1 = (*capId)[0xC]; + unsigned ver2 = (*capId)[0xD]; + unsigned ver3 = (*capId)[0xE]; + unsigned ver4 = (*capId)[0xF]; + + if (ver1 < 30) + { + makeClientVersion(szClientBuf, "MIP ", ver1, ver2, ver3, ver4); + } + else + { + strcpy(szClientBuf, "MIP "); + strncat(szClientBuf, (char*)(*capId) + 11, 5); + } + szClient = szClientBuf; + } + else if (capId = MatchCapability(caps, wLen, &capMipClient, 0x04)) + { //http://mip.rufon.net - new signature + strcpy(szClientBuf, "MIP "); + strncat(szClientBuf, (char*)(*capId) + 4, 12); + szClient = szClientBuf; + } + else if (capId = MatchCapability(caps, wLen, &capVmIcq, 0x06)) + { + strcpy(szClientBuf, "VmICQ"); + strncat(szClientBuf, (char*)(*capId) + 5, 11); + szClient = szClientBuf; + } + else if (capId = MatchCapability(caps, wLen, &capSmapeR, 0x07)) + { // http://www.smape.com/smaper + strcpy(szClientBuf, "SmapeR"); + strncat(szClientBuf, (char*)(*capId) + 6, 10); + szClient = szClientBuf; + } + else if (capId = MatchCapability(caps, wLen, &capYapp, 0x04)) + { // http://yapp.ru + strcpy(szClientBuf, "Yapp! v"); + strncat(szClientBuf, (char*)(*capId) + 8, 5); + szClient = szClientBuf; + } + else if (MatchCapability(caps, wLen, &capDigsby, 0x06)) + { // http://www.dibsby.com (newer builds) + szClient = "Digsby"; + } + else if (MatchCapability(caps, wLen, &capDigsbyBeta)) + { // http://www.digsby.com - probably by mistake (feature detection as well) + szClient = "Digsby"; + } + else if (MatchCapability(caps, wLen, &capJapp)) + { // http://www.japp.org.ua + szClient = "japp"; + } + else if (MatchCapability(caps, wLen, &capPigeon, 0x07)) + { // http://pigeon.vpro.ru + szClient = "PIGEON!"; + } + else if (capId = MatchCapability(caps, wLen, &capQutIm, 0x05)) + { // http://www.qutim.org + if ((*capId)[0x6] == 0x2E) + { // old qutim id + unsigned ver1 = (*capId)[0x5] - 0x30; + unsigned ver2 = (*capId)[0x7] - 0x30; + + makeClientVersion(szClientBuf, "qutIM ", ver1, ver2, 0, 0); + } + else + { // new qutim id + unsigned ver1 = (*capId)[0x6]; + unsigned ver2 = (*capId)[0x7]; + unsigned ver3 = (*capId)[0x8]; + unsigned ver4 = ((*capId)[0x9] << 8) || (*capId)[0xA]; + + makeClientVersion(szClientBuf, "qutIM ", ver1, ver2, ver3, ver4); + + switch ((*capId)[0x5]) + { + case 'l': + strcat(szClientBuf, "/Linux"); + break; + case 'w': + strcat(szClientBuf, "/Win32"); + break; + case 'm': + strcat(szClientBuf, "/MacOS X"); + break; + } + } + szClient = szClientBuf; + } + else if (capId = MatchCapability(caps, wLen, &capBayan, 8)) + { // http://www.barobin.com/bayanICQ.html + strcpy(szClientBuf, "bayanICQ "); + strncat(szClientBuf, (char*)(*capId) + 8, 5); + szClient = szClientBuf; + } + else if (capId = MatchCapability(caps, wLen, &capJabberJIT, 0x04)) + { + szClient = "Jabber ICQ Transport"; + } + else if (capId = MatchCapability(caps, wLen, &capIcqKid2, 0x07)) + { // http://sourceforge.net/projects/icqkid2 + unsigned ver1 = (*capId)[0x7]; + unsigned ver2 = (*capId)[0x8]; + unsigned ver3 = (*capId)[0x9]; + unsigned ver4 = (*capId)[0xA]; + + szClient = makeClientVersion(szClientBuf, "IcqKid2 v", ver1, ver2, ver3, ver4); + } + else if (capId = MatchCapability(caps, wLen, &capWebIcqPro, 0x0A)) + { // http://intrigue.ru/workshop/webicqpro/webicqpro.html + szClient = "WebIcqPro"; + } + else if (capId = MatchCapability(caps, wLen, &capCitron)) + { // http://www.citron-im.com + szClient = "Citron IM"; + } + else if (szClient == cliLibicq2k) + { // try to determine which client is behind libicq2000 + if (CheckContactCapabilities(hContact, CAPF_RTF)) + szClient = cliCentericq; // centericq added rtf capability to libicq2000 + else if (CheckContactCapabilities(hContact, CAPF_UTF)) + szClient = cliLibicqUTF; // IcyJuice added unicode capability to libicq2000 + // others - like jabber transport uses unmodified library, thus cannot be detected + } + else if (szClient == NULL) // HERE ENDS THE SIGNATURE DETECTION, after this only feature default will be detected + { + if (wVersion == 8 && CheckContactCapabilities(hContact, CAPF_XTRAZ) && (MatchCapability(caps, wLen, &capIMSecKey1, 6) || MatchCapability(caps, wLen, &capIMSecKey2, 6))) + { // ZA mangled the version, OMG! + wVersion = 9; + } + if (wVersion == 8 && (MatchCapability(caps, wLen, &capComm20012) || CheckContactCapabilities(hContact, CAPF_SRV_RELAY))) + { // try to determine 2001-2003 versions + if (MatchCapability(caps, wLen, &capIs2001)) + { + if (!dwFT1 && !dwFT2 && !dwFT3) + if (CheckContactCapabilities(hContact, CAPF_RTF)) + szClient = "TICQClient"; // possibly also older GnomeICU + else + szClient = "ICQ for Pocket PC"; + else + { + *bClientId = CLID_GENERIC; + szClient = "ICQ 2001"; + } + } + else if (MatchCapability(caps, wLen, &capIs2002)) + { + *bClientId = CLID_GENERIC; + szClient = "ICQ 2002"; + } + else if (CheckContactCapabilities(hContact, CAPF_SRV_RELAY | CAPF_UTF | CAPF_RTF)) + { + if (!dwFT1 && !dwFT2 && !dwFT3) + { + if (!dwWebPort) + szClient = "GnomeICU 0.99.5+"; // no other way + else + szClient = "IC@"; + } + else + { + *bClientId = CLID_GENERIC; + szClient = "ICQ 2002/2003a"; + } + } + else if (CheckContactCapabilities(hContact, CAPF_SRV_RELAY | CAPF_UTF | CAPF_TYPING | CAPF_XTRAZ) && + MatchCapability(caps, wLen, &capOscarChat) && MatchShortCapability(caps, wLen, &capAimIcon) && + MatchCapability(caps, wLen, &capFakeHtml)) + { // libpurple (e.g. Pidgin 2.7.x) + if (MatchShortCapability(caps, wLen, &capAimDirect)) + szClient = "libpurple"; + else + szClient = "Meebo"; + } + else if (CheckContactCapabilities(hContact, CAPF_SRV_RELAY | CAPF_UTF | CAPF_TYPING)) + { + if (!dwFT1 && !dwFT2 && !dwFT3) + { + szClient = "PreludeICQ"; + } + } + } + else if (wVersion == 8) + { + if (CheckContactCapabilities(hContact, CAPF_UTF | CAPF_TYPING) && MatchShortCapability(caps, wLen, &capAimIcon) && MatchShortCapability(caps, wLen, &capAimDirect)) + szClient = "imo.im"; //https://imo.im/ - Web IM + } + else if (wVersion == 9) + { // try to determine lite versions + if (CheckContactCapabilities(hContact, CAPF_XTRAZ)) + { + *bClientId = CLID_GENERIC; + if (CheckContactCapabilities(hContact, CAPF_OSCAR_FILE)) + { + if (MatchCapability(caps, wLen, &captZers)) + { // capable of tZers ? + if (MatchCapability(caps, wLen, &capIcqLiteNew) && MatchShortCapability(caps, wLen, &capStatusTextAware) && + MatchShortCapability(caps, wLen, &capAimLiveVideo) && MatchShortCapability(caps, wLen, &capAimLiveAudio)) + { + strcpy(szClientBuf, "ICQ 7"); + } + else if (MatchCapability(caps, wLen, &capFakeHtml)) + { + if (MatchShortCapability(caps, wLen, &capAimLiveVideo) && MatchShortCapability(caps, wLen, &capAimLiveAudio)) + { + strcpy(szClientBuf, "ICQ 6"); + *bClientId = CLID_ICQ6; + } + else if (CheckContactCapabilities(hContact, CAPF_RTF) && !CheckContactCapabilities(hContact, CAPF_CONTACTS) && MatchShortCapability(caps, wLen, &capIcqDevils)) + { + strcpy(szClientBuf, "Qnext v4"); // finally handles SRV_RELAY correctly + *bClientId = CLID_ALTERNATIVE; + } + } + else + { + strcpy(szClientBuf, "icq5.1"); + } + } + else + { + strcpy(szClientBuf, "icq5"); + } + + if (MatchCapability(caps, wLen, &capRambler)) + { + strcat(szClientBuf, " (Rambler)"); + } + else if (MatchCapability(caps, wLen, &capAbv)) + { + strcat(szClientBuf, " (Abv)"); + } + else if (MatchCapability(caps, wLen, &capNetvigator)) + { + strcat(szClientBuf, " (Netvigator)"); + } + szClient = szClientBuf; + } + else if (!CheckContactCapabilities(hContact, CAPF_ICQDIRECT)) + { + *bClientId = CLID_ALTERNATIVE; + if (CheckContactCapabilities(hContact, CAPF_RTF)) + { + // most probably Qnext - try to make that shit at least receiving our msgs + ClearContactCapabilities(hContact, CAPF_SRV_RELAY); + NetLog_Server("Forcing simple messages (QNext client)."); + szClient = "Qnext"; + } + else if (CheckContactCapabilities(hContact, CAPF_TYPING) && MatchCapability(caps, wLen, &captZers) && MatchCapability(caps, wLen, &capFakeHtml)) + { + if (CheckContactCapabilities(hContact, CAPF_SRV_RELAY | CAPF_UTF) && MatchShortCapability(caps, wLen, &capAimLiveAudio)) + szClient = "Mail.ru Agent (PC)"; + else + szClient = "Fring"; + } + else + szClient = "pyICQ"; + } + else + szClient = "ICQ Lite v4"; + } + else if (MatchCapability(caps, wLen, &capIcqLiteNew)) + szClient = "ICQ Lite"; // the new ICQ Lite based on ICQ6 + else if (!CheckContactCapabilities(hContact, CAPF_ICQDIRECT)) + { + if (MatchCapability(caps, wLen, &capFakeHtml) && MatchCapability(caps, wLen, &capOscarChat) && MatchShortCapability(caps, wLen, &capAimSmartCaps)) + szClient = cliTrillian4; + else if (CheckContactCapabilities(hContact, CAPF_UTF) && !CheckContactCapabilities(hContact, CAPF_RTF)) + szClient = "pyICQ"; + } + } + else if (wVersion == 7) + { + if (CheckContactCapabilities(hContact, CAPF_RTF)) + szClient = "GnomeICU"; // this is an exception + else if (CheckContactCapabilities(hContact, CAPF_SRV_RELAY)) + { + if (!dwFT1 && !dwFT2 && !dwFT3) + szClient = "&RQ"; + else + { + *bClientId = CLID_GENERIC; + szClient = "ICQ 2000"; + } + } + else if (CheckContactCapabilities(hContact, CAPF_UTF)) + { + if (CheckContactCapabilities(hContact, CAPF_TYPING)) + szClient = "Icq2Go! (Java)"; + else if (wUserClass & CLASS_WIRELESS) + szClient = "Pocket Web 1&1"; + else + szClient = "Icq2Go!"; + } + } + else if (wVersion == 0xA) + { + if (!CheckContactCapabilities(hContact, CAPF_RTF) && !CheckContactCapabilities(hContact, CAPF_UTF)) + { // this is bad, but we must do it - try to detect QNext + ClearContactCapabilities(hContact, CAPF_SRV_RELAY); + NetLog_Server("Forcing simple messages (QNext client)."); + szClient = "Qnext"; + } + else if (!CheckContactCapabilities(hContact, CAPF_RTF) && CheckContactCapabilities(hContact, CAPF_UTF) && !dwFT1 && !dwFT2 && !dwFT3) + { // not really good, but no other option + szClient = "NanoICQ"; + } + } + else if (wVersion == 0xB) + { + if (CheckContactCapabilities(hContact, CAPF_XTRAZ | CAPF_SRV_RELAY | CAPF_TYPING | CAPF_UTF) && MatchShortCapability(caps, wLen, &capIcqDevils)) + { + szClient = "Mail.ru Agent (Symbian)"; + } + } + else if (wVersion == 0) + { // capability footprint based detection - not really reliable + if (!dwFT1 && !dwFT2 && !dwFT3 && !dwWebPort && !dwDirectCookie) + { // DC info is empty + if (CheckContactCapabilities(hContact, CAPF_TYPING) && MatchCapability(caps, wLen, &capIs2001) && + MatchCapability(caps, wLen, &capIs2002) && MatchCapability(caps, wLen, &capComm20012)) + szClient = cliSpamBot; + else if (MatchShortCapability(caps, wLen, &capAimIcon) && MatchShortCapability(caps, wLen, &capAimDirect) && + CheckContactCapabilities(hContact, CAPF_OSCAR_FILE | CAPF_UTF)) + { // detect libgaim/libpurple versions + if (CheckContactCapabilities(hContact, CAPF_SRV_RELAY)) + szClient = "Adium X"; // yeah, AFAIK only Adium has this fixed + else if (CheckContactCapabilities(hContact, CAPF_TYPING)) + szClient = "libpurple"; + else + szClient = "libgaim"; + } + else if (MatchShortCapability(caps, wLen, &capAimIcon) && MatchShortCapability(caps, wLen, &capAimDirect) && + MatchCapability(caps, wLen, &capOscarChat) && CheckContactCapabilities(hContact, CAPF_OSCAR_FILE) && wLen == 0x40) + szClient = "libgaim"; // Gaim 1.5.1 most probably + else if (CheckContactCapabilities(hContact, CAPF_OSCAR_FILE) && MatchCapability(caps, wLen, &capOscarChat) && wLen == 0x20) + szClient = "Easy Message"; + else if (CheckContactCapabilities(hContact, CAPF_UTF | CAPF_TYPING) && MatchShortCapability(caps, wLen, &capAimIcon) && MatchCapability(caps, wLen, &capOscarChat) && wLen == 0x40) + szClient = "Meebo"; + else if (CheckContactCapabilities(hContact, CAPF_UTF) && MatchShortCapability(caps, wLen, &capAimIcon) && wLen == 0x20) + szClient = "PyICQ-t Jabber Transport"; + else if (CheckContactCapabilities(hContact, CAPF_UTF | CAPF_XTRAZ) && MatchShortCapability(caps, wLen, &capAimIcon) && MatchCapability(caps, wLen, &capXtrazVideo)) + szClient = "PyICQ-t Jabber Transport"; + else if (CheckContactCapabilities(hContact, CAPF_UTF | CAPF_SRV_RELAY | CAPF_ICQDIRECT | CAPF_TYPING) && wLen == 0x40) + szClient = "Agile Messenger"; // Smartphone 2002 + else if (CheckContactCapabilities(hContact, CAPF_UTF | CAPF_SRV_RELAY | CAPF_ICQDIRECT | CAPF_OSCAR_FILE) && MatchShortCapability(caps, wLen, &capAimFileShare)) + szClient = "Slick"; // http://lonelycatgames.com/?app=slick + else if (CheckContactCapabilities(hContact, CAPF_UTF | CAPF_SRV_RELAY | CAPF_OSCAR_FILE | CAPF_CONTACTS) && MatchShortCapability(caps, wLen, &capAimFileShare) && MatchShortCapability(caps, wLen, &capAimIcon)) + szClient = "Digsby"; // http://www.digsby.com + else if (CheckContactCapabilities(hContact, CAPF_UTF | CAPF_SRV_RELAY | CAPF_CONTACTS) && MatchShortCapability(caps, wLen, &capAimIcon) && MatchCapability(caps, wLen, &capFakeHtml)) + szClient = "mundu IM"; // http://messenger.mundu.com + else if (CheckContactCapabilities(hContact, CAPF_UTF | CAPF_OSCAR_FILE) && MatchCapability(caps, wLen, &capOscarChat)) + szClient = "eBuddy"; //http://www.ebuddy.com + else if (CheckContactCapabilities(hContact, CAPF_CONTACTS | CAPF_OSCAR_FILE) && MatchShortCapability(caps, wLen, &capAimIcon) && MatchShortCapability(caps, wLen, &capAimDirect) && MatchCapability(caps, wLen, &capOscarChat)) + szClient = "IloveIM"; //http://www.iloveim.com/ + + } + } + } + } + else if (!nIsICQ) + { // detect AIM clients + if (caps) + { + if (capId = MatchCapability(caps, wLen, &capAimOscar, 8)) + { // AimOscar Signature + DWORD aver = (*capId)[0xC] << 0x18 | (*capId)[0xD] << 0x10 | (*capId)[0xE] << 8 | (*capId)[0xF]; + DWORD mver = (*capId)[0x8] << 0x18 | (*capId)[0x9] << 0x10 | (*capId)[0xA] << 8 | (*capId)[0xB]; + + szClient = MirandaVersionToStringEx(szClientBuf, 0, "AimOscar", aver, mver); + bMirandaIM = TRUE; + } + else if (capId = MatchCapability(caps, wLen, &capSim, 0xC)) + { // Sim is universal + unsigned ver1 = (*capId)[0xC]; + unsigned ver2 = (*capId)[0xD]; + unsigned ver3 = (*capId)[0xE]; + + szClient = makeClientVersion(szClientBuf, "SIM ", ver1, ver2, ver3, 0); + if ((*capId)[0xF] & 0x80) + strcat(szClientBuf,"/Win32"); + else if ((*capId)[0xF] & 0x40) + strcat(szClientBuf,"/MacOS X"); + } + else if (capId = MatchCapability(caps, wLen, &capKopete, 0xC)) + { + unsigned ver1 = (*capId)[0xC]; + unsigned ver2 = (*capId)[0xD]; + unsigned ver3 = (*capId)[0xE]; + unsigned ver4 = (*capId)[0xF]; + + szClient = makeClientVersion(szClientBuf, "Kopete ", ver1, ver2, ver3, ver4); + } + else if (MatchCapability(caps, wLen, &capIm2)) + { // IM2 extensions + szClient = cliIM2; + } + else if (MatchCapability(caps, wLen, &capNaim, 0x8)) + { + szClient = "naim"; + } + else if (MatchCapability(caps, wLen, &capDigsby, 0x06) || MatchCapability(caps, wLen, &capDigsbyBeta)) + { // http://www.dibsby.com + szClient = "Digsby"; + } + else if (MatchShortCapability(caps, wLen, &capAimIcon) && MatchCapability(caps, wLen, &capOscarChat) && + CheckContactCapabilities(hContact, CAPF_UTF | CAPF_TYPING) && wLen == 0x40) + szClient = "Meebo"; + else if (wLen == 0x90 && CheckContactCapabilities(hContact, CAPF_SRV_RELAY | CAPF_UTF | CAPF_TYPING | CAPF_XTRAZ) && + MatchCapability(caps, wLen, &capOscarChat) && MatchShortCapability(caps, wLen, &capAimIcon) && + MatchShortCapability(caps, wLen, &capAimDirect) && MatchCapability(caps, wLen, &capFakeHtml)) + { // libpurple (e.g. Pidgin 2.7.x) + szClient = "libpurple"; + } + else if (wLen == 0x70 && CheckContactCapabilities(hContact, CAPF_SRV_RELAY | CAPF_UTF | CAPF_TYPING | CAPF_XTRAZ) && + MatchCapability(caps, wLen, &capOscarChat) && MatchShortCapability(caps, wLen, &capAimIcon) && + MatchCapability(caps, wLen, &capFakeHtml)) + { // libpurple - Meebo (without DirectIM and OFT) + szClient = "Meebo"; + } + else + szClient = "AIM"; + } + else if(wUserClass & CLASS_WIRELESS) + szClient = "AIM (Mobile)"; + else + szClient = "AIM"; + } + } + if (caps && bMirandaIM) + { // custom miranda packs + capstr* capId; + + if (capId = MatchCapability(caps, wLen, &capMimPack, 4)) + { + char szPack[16]; + + null_snprintf(szPack, 16, " [%.12s]", (*capId)+4); + + if (szClient != szClientBuf) + { // make sure client string is not constant + strcpy(szClientBuf, szClient); + szClient = szClientBuf; + } + + strcat(szClientBuf, szPack); + } + } + + BOOL bClientDetected = (szClient != NULL); + + if (!szClient) + { + *bClientId = CLID_GENERIC; + + switch (wVersion) + { // client detection failed, provide default clients + case 6: + szClient = "ICQ99"; + break; + case 7: + szClient = "ICQ 2000/Icq2Go"; + break; + case 8: + szClient = "ICQ 2001-2003a"; + break; + case 9: + szClient = "ICQ Lite"; + break; + case 0xA: + szClient = "ICQ 2003b"; + } + } + + if (szClient) + { + char *szExtra = NULL; + + if (MatchCapability(caps, wLen, &capSimpLite)) + szExtra = " + SimpLite"; + else if (MatchCapability(caps, wLen, &capSimpPro)) + szExtra = " + SimpPro"; + else if (MatchCapability(caps, wLen, &capIMsecure) || MatchCapability(caps, wLen, &capIMSecKey1, 6) || MatchCapability(caps, wLen, &capIMSecKey2, 6)) + szExtra = " + IMsecure"; + + if (szExtra) + { + if (szClient != szClientBuf) + { + strcpy(szClientBuf, szClient); + szClient = szClientBuf; + } + strcat(szClientBuf, szExtra); + } + } + + if (!szCurrentClient || strcmpnull(szCurrentClient, szClient)) + { // Log the detection result if it has changed or contact just logged on... + if (bClientDetected) + NetLog_Server("Client identified as %s", szClient); + else + NetLog_Server("No client identification, put default ICQ client for protocol."); + } + + return szClient; +} diff --git a/protocols/IcqOscarJ/src/icq_constants.h b/protocols/IcqOscarJ/src/icq_constants.h new file mode 100644 index 0000000000..4da90f1bc0 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_constants.h @@ -0,0 +1,652 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Global constants and default settings are defined here +// +// ----------------------------------------------------------------------------- +// Most of the protocol constants follow the naming conventions of the +// Oscar documentation at http://iserverd.khstu.ru/oscar/index.html +// BIG THANKS to Alexandr for maintaining this site and to everyone +// in the ICQ devel community who have helped to collect the data. + +#ifndef __ICQ_CONSTANTS_H +#define __ICQ_CONSTANTS_H + + +/* Static icon indexes */ +#define ISI_AUTH_REQUEST 0 +#define ISI_AUTH_GRANT 1 +#define ISI_AUTH_REVOKE 2 +#define ISI_ADD_TO_SERVLIST 3 + +/* Contact menu item indexes */ +#define ICMI_AUTH_REQUEST 0 +#define ICMI_AUTH_GRANT 1 +#define ICMI_AUTH_REVOKE 2 +#define ICMI_ADD_TO_SERVLIST 3 +#define ICMI_XSTATUS_DETAILS 4 +#define ICMI_OPEN_PROFILE 5 + +/* Some default settings */ +#define DEFAULT_SERVER_PORT 5190 +#define DEFAULT_SERVER_PORT_SSL 443 +#define DEFAULT_SERVER_HOST "login.icq.com" +#define DEFAULT_SERVER_HOST_SSL "slogin.icq.com" +#define DEFAULT_SS_ENABLED 1 +#define DEFAULT_SS_ADDSERVER 1 +#define DEFAULT_SS_LOAD 0 +#define DEFAULT_SS_STORE 1 +#define DEFAULT_SS_GROUP "General" + +#define DEFAULT_SECURE_LOGIN 1 +#define DEFAULT_SECURE_CONNECTION 1 +#define DEFAULT_KEEPALIVE_ENABLED 1 +#define DEFAULT_AIM_ENABLED 1 +#define DEFAULT_UTF_ENABLED 2 // everything unicode is default +#define DEFAULT_ANSI_CODEPAGE CP_ACP +#define DEFAULT_DCMSG_ENABLED 1 // passive dc messaging is default +#define DEFAULT_TEMPVIS_ENABLED 1 // temporary visible is enabled by default +#define DEFAULT_MTN_ENABLED 1 +#define DEFAULT_AVATARS_ENABLED 1 +#define DEFAULT_LOAD_AVATARS 1 +#define DEFAULT_BIGGER_AVATARS 0 +#define DEFAULT_AVATARS_CHECK 1 +#define DEFAULT_XSTATUS_ENABLED 0 +#define DEFAULT_XSTATUS_AUTO 1 +#define DEFAULT_XSTATUS_RESET 0 +#define DEFAULT_MOODS_ENABLED 1 +#define DEFAULT_KILLSPAM_ENABLED 1 + +#define DEFAULT_SLOWSEND 1 +#define DEFAULT_ONLYSERVERACKS 1 + +#define DEFAULT_POPUPS_ENABLED 1 +#define DEFAULT_SPAM_POPUPS_ENABLED 1 +#define DEFAULT_LOG_POPUPS_ENABLED 1 +#define DEFAULT_POPUPS_SYS_ICONS 1 +#define DEFAULT_LOG0_TEXT_COLORS RGB(0,0,0) // LOG_NOTE +#define DEFAULT_LOG0_BACK_COLORS RGB(255,255,255) +#define DEFAULT_LOG0_TIMEOUT 0 +#define DEFAULT_LOG1_TEXT_COLORS RGB(0,0,0) // LOG_WARNING +#define DEFAULT_LOG1_BACK_COLORS RGB(255,255,255) +#define DEFAULT_LOG1_TIMEOUT 0 +#define DEFAULT_LOG2_TEXT_COLORS RGB(0,0,0) // LOG_ERROR +#define DEFAULT_LOG2_BACK_COLORS RGB(255,255,255) +#define DEFAULT_LOG2_TIMEOUT 0 +#define DEFAULT_LOG3_TEXT_COLORS RGB(0,0,0) // LOG_FATAL +#define DEFAULT_LOG3_BACK_COLORS RGB(255,255,255) +#define DEFAULT_LOG3_TIMEOUT 0 +#define DEFAULT_SPAM_TEXT_COLORS RGB(193,0,38) +#define DEFAULT_SPAM_BACK_COLORS RGB(213,209,208) +#define DEFAULT_SPAM_TIMEOUT 0 +#define DEFAULT_POPUPS_WIN_COLORS 0 +#define DEFAULT_POPUPS_DEF_COLORS (BYTE)!DEFAULT_POPUPS_WIN_COLORS + +/* Database setting names */ +#define DBSETTING_CAPABILITIES "caps" +// Contact's server-list items +#define DBSETTING_SERVLIST_ID "ServerId" +#define DBSETTING_SERVLIST_GROUP "SrvGroupId" +#define DBSETTING_SERVLIST_PERMIT "SrvPermitId" +#define DBSETTING_SERVLIST_DENY "SrvDenyId" +#define DBSETTING_SERVLIST_IGNORE "SrvIgnoreId" +// Owner's server-list items +#define DBSETTING_SERVLIST_PRIVACY "SrvVisibilityID" +#define DBSETTING_SERVLIST_PHOTO "SrvPhotoID" +#define DBSETTING_SERVLIST_AVATAR "SrvAvatarID" +#define DBSETTING_SERVLIST_METAINFO "SrvMetaInfoID" +#define DBSETTING_SERVLIST_UNHANDLED "SrvUnhandledIDList" +// Contact's data from server-list +#define DBSETTING_SERVLIST_DATA "ServerData" +// User Details +#define DBSETTING_METAINFO_TOKEN "MetaInfoToken" +#define DBSETTING_METAINFO_TIME "MetaInfoTime" +#define DBSETTING_METAINFO_SAVED "InfoTS" +// Status Note & Mood +#define DBSETTING_STATUS_NOTE "StatusNote" +#define DBSETTING_STATUS_NOTE_TIME "StatusNoteTS" +#define DBSETTING_STATUS_MOOD "StatusMood" +// Custom Status +#define DBSETTING_XSTATUS_ID "XStatusId" +#define DBSETTING_XSTATUS_NAME "XStatusName" +#define DBSETTING_XSTATUS_MSG "XStatusMsg" + + +// Status FLAGS (used to determine status of other users) +#define ICQ_STATUSF_ONLINE 0x0000 +#define ICQ_STATUSF_AWAY 0x0001 +#define ICQ_STATUSF_DND 0x0002 +#define ICQ_STATUSF_NA 0x0004 +#define ICQ_STATUSF_OCCUPIED 0x0010 +#define ICQ_STATUSF_FFC 0x0020 +#define ICQ_STATUSF_INVISIBLE 0x0100 + +// Status values (used to set own status) +#define ICQ_STATUS_ONLINE 0x0000 +#define ICQ_STATUS_AWAY 0x0001 +#define ICQ_STATUS_NA 0x0005 +#define ICQ_STATUS_OCCUPIED 0x0011 +#define ICQ_STATUS_DND 0x0013 +#define ICQ_STATUS_FFC 0x0020 +#define ICQ_STATUS_INVISIBLE 0x0100 + +#define STATUS_WEBAWARE 0x0001 // Status webaware flag +#define STATUS_SHOWIP 0x0002 // Status show ip flag +#define STATUS_BIRTHDAY 0x0008 // User birthday flag +#define STATUS_WEBFRONT 0x0020 // User active webfront flag +#define STATUS_DCDISABLED 0x0100 // Direct connection not supported +#define STATUS_DCAUTH 0x1000 // Direct connection upon authorization +#define STATUS_DCCONT 0x2000 // DC only with contact users + + + +// Typing notification statuses +#define MTN_FINISHED 0x0000 +#define MTN_TYPED 0x0001 +#define MTN_BEGUN 0x0002 +#define MTN_WINDOW_CLOSED 0x000F + + + +// Ascii Capability IDs +#define CAP_RTFMSGS "{97B12751-243C-4334-AD22-D6ABF73F1492}" +#define CAP_UTF8MSGS "{0946134E-4C7F-11D1-8222-444553540000}" + +// Binary Capability Sizes +#define BINARY_CAP_SIZE 16 +#define BINARY_SHORT_CAP_SIZE 2 + +// Binary Capability IDs +#define CAP_SRV_RELAY 0x09, 0x46, 0x13, 0x49, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00 +#define CAP_UTF 0x09, 0x46, 0x13, 0x4e, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00 +#define CAP_RTF 0x97, 0xb1, 0x27, 0x51, 0x24, 0x3c, 0x43, 0x34, 0xad, 0x22, 0xd6, 0xab, 0xf7, 0x3f, 0x14, 0x92 +#define CAP_CONTACTS 0x09, 0x46, 0x13, 0x4b, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00 +#define CAP_TYPING 0x56, 0x3f, 0xc8, 0x09, 0x0b, 0x6f, 0x41, 0xbd, 0x9f, 0x79, 0x42, 0x26, 0x09, 0xdf, 0xa2, 0xf3 +#define CAP_ICQDIRECT 0x09, 0x46, 0x13, 0x44, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00 +#define CAP_XTRAZ 0x1A, 0x09, 0x3C, 0x6C, 0xD7, 0xFD, 0x4E, 0xC5, 0x9D, 0x51, 0xA6, 0x47, 0x4E, 0x34, 0xF5, 0xA0 +#define CAP_OSCAR_FILE 0x09, 0x46, 0x13, 0x43, 0x4C, 0x7F, 0x11, 0xD1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00 + +// Miranda IM Capability bitmask +#define CAPF_SRV_RELAY 0x00000001 +#define CAPF_UTF 0x00000002 +#define CAPF_RTF 0x00000004 +#define CAPF_CONTACTS 0x00000010 +#define CAPF_TYPING 0x00000020 +#define CAPF_ICQDIRECT 0x00000080 +#define CAPF_XTRAZ 0x00000100 +#define CAPF_OSCAR_FILE 0x00000400 +#define CAPF_STATUS_MESSAGES 0x10000000 +#define CAPF_STATUS_MOOD 0x40000000 +#define CAPF_XSTATUS 0x80000000 + + +// Message Capability IDs +#define MCAP_SRV_RELAY_FMT_s 0x09461349, 0x4c7f11d1, 0x82224445, 0x53540000 +#define MCAP_REVERSE_DC_REQ_s 0x09461344, 0x4c7f11d1, 0x82224445, 0x53540000 +#define MCAP_FILE_TRANSFER_s 0x09461343, 0x4c7f11d1, 0x82224445, 0x53540000 +#define MCAP_CONTACTS_s 0x0946134b, 0x4c7f11d1, 0x82224445, 0x53540000 + +// Plugin Type GUIDs +#define PSIG_MESSAGE_s 0x00000000, 0x00000000, 0x00000000, 0x00000000 +#define PSIG_INFO_PLUGIN_s 0xa0e93f37, 0x4fe9d311, 0xbcd20004, 0xac96dd96 +#define PSIG_STATUS_PLUGIN_s 0x10cf40d1, 0x4fe9d311, 0xbcd20004, 0xac96dd96 + +// Plugin Message GUIDs +#define PMSG_QUERY_INFO_s 0xF002BF71, 0x4371D311, 0x8DD20010, 0x4B06462E +#define PMSG_QUERY_STATUS_s 0x10180670, 0x5471D311, 0x8DD20010, 0x4B06462E + + + +// Message types +#define MTYPE_PLAIN 0x01 // Plain text (simple) message +#define MTYPE_CHAT 0x02 // Chat request message +#define MTYPE_FILEREQ 0x03 // File request / file ok message +#define MTYPE_URL 0x04 // URL message (0xFE formatted) +#define MTYPE_AUTHREQ 0x06 // Authorization request message (0xFE formatted) +#define MTYPE_AUTHDENY 0x07 // Authorization denied message (0xFE formatted) +#define MTYPE_AUTHOK 0x08 // Authorization given message (empty) +#define MTYPE_SERVER 0x09 // Message from OSCAR server (0xFE formatted) +#define MTYPE_ADDED 0x0C // "You-were-added" message (0xFE formatted) +#define MTYPE_WWP 0x0D // Web pager message (0xFE formatted) +#define MTYPE_EEXPRESS 0x0E // Email express message (0xFE formatted) +#define MTYPE_CONTACTS 0x13 // Contact list message +#define MTYPE_PLUGIN 0x1A // Plugin message described by text string +#define MTYPE_AUTOONLINE 0xE7 // Auto online message (internal only) +#define MTYPE_AUTOAWAY 0xE8 // Auto away message +#define MTYPE_AUTOBUSY 0xE9 // Auto occupied message +#define MTYPE_AUTONA 0xEA // Auto not available message +#define MTYPE_AUTODND 0xEB // Auto do not disturb message +#define MTYPE_AUTOFFC 0xEC // Auto free for chat message +// Internal Message types +#define MTYPE_UNKNOWN 0x00 // Unknown message + +#define MTYPE_GREETINGCARD 0x101 // Greeting Card +#define MTYPE_REQUESTCONTACTS 0x102 // Request for Contacts +#define MTYPE_MESSAGE 0x103 // Message+ +#define MTYPE_STATUSMSGEXT 0x104 // StatusMsgExt (2003b) +#define MTYPE_SMS_MESSAGE 0x110 // SMS message from Mobile +#define MTYPE_SCRIPT_INVITATION 0x201 // Xtraz Invitation +#define MTYPE_SCRIPT_DATA 0x202 // Xtraz Message +#define MTYPE_SCRIPT_NOTIFY 0x208 // Xtraz Response +#define MTYPE_REVERSE_REQUEST 0x401 // Reverse DC request + +// Message Plugin Type GUIDs +#define MGTYPE_MESSAGE_s 0xBE6B7305, 0x0FC2104F, 0xA6DE4DB1, 0xE3564B0E +#define MGTYPE_STATUSMSGEXT_s 0x811a18bc, 0x0e6c1847, 0xa5916f18, 0xdcc76f1a +#define MGTYPE_FILE_s 0xF02D12D9, 0x3091D311, 0x8DD70010, 0x4B06462E +#define MGTYPE_WEBURL_s 0x371C5872, 0xE987D411, 0xA4C100D0, 0xB759B1D9 +#define MGTYPE_CONTACTS_s 0x2A0E7D46, 0x7676D411, 0xBCE60004, 0xAC961EA6 +#define MGTYPE_GREETING_CARD_s 0x01E53B48, 0x2AE4D111, 0xB6790060, 0x97E1E294 +#define MGTYPE_CHAT_s 0xBFF720B2, 0x378ED411, 0xBD280004, 0xAC96D905 +#define MGTYPE_SMS_MESSAGE_s 0x0e28f600, 0x11e7d311, 0xbcf30004, 0xac969dc2 +#define MGTYPE_XTRAZ_SCRIPT_s 0x3b60b3ef, 0xd82a6c45, 0xa4e09c5a, 0x5e67e865 + +// Message Plugin Sub-Type IDs +#define MGTYPE_STANDARD_SEND 0x0000 +#define MGTYPE_CONTACTS_REQUEST 0x0002 +#define MGTYPE_SCRIPT_INVITATION 0x0001 +#define MGTYPE_SCRIPT_DATA 0x0002 +#define MGTYPE_SCRIPT_USER_REMOVE 0x0004 +#define MGTYPE_SCRIPT_NOTIFY 0x0008 +#define MGTYPE_UNDEFINED 0xFFFF + + + +/* Channels */ +#define ICQ_LOGIN_CHAN 0x01 +#define ICQ_DATA_CHAN 0x02 +#define ICQ_ERROR_CHAN 0x03 +#define ICQ_CLOSE_CHAN 0x04 +#define ICQ_PING_CHAN 0x05 + +/* Families */ +#define ICQ_SERVICE_FAMILY 0x0001 +#define ICQ_LOCATION_FAMILY 0x0002 +#define ICQ_BUDDY_FAMILY 0x0003 +#define ICQ_MSG_FAMILY 0x0004 +#define ICQ_BOS_FAMILY 0x0009 +#define ICQ_LOOKUP_FAMILY 0x000a +#define ICQ_STATS_FAMILY 0x000b +#define ICQ_CHAT_NAVIGATION_FAMILY 0x000d +#define ICQ_CHAT_FAMILY 0x000e +#define ICQ_AVATAR_FAMILY 0x0010 +#define ICQ_LISTS_FAMILY 0x0013 +#define ICQ_EXTENSIONS_FAMILY 0x0015 +#define ICQ_AUTHORIZATION_FAMILY 0x0017 +#define ICQ_DIRECTORY_FAMILY 0x0025 + +/* Subtypes for Service Family 0x0001 */ +#define ICQ_ERROR 0x0001 +#define ICQ_CLIENT_READY 0x0002 +#define ICQ_SERVER_READY 0x0003 +#define ICQ_CLIENT_NEW_SERVICE 0x0004 +#define ICQ_SERVER_REDIRECT_SERVICE 0x0005 +#define ICQ_CLIENT_REQ_RATE_INFO 0x0006 +#define ICQ_SERVER_RATE_INFO 0x0007 +#define ICQ_CLIENT_RATE_ACK 0x0008 +#define ICQ_SERVER_RATE_CHANGE 0x000a +#define ICQ_SERVER_PAUSE 0x000b +#define ICQ_CLIENT_PAUSE_ACK 0x000c +#define ICQ_SERVER_RESUME 0x000d +#define ICQ_CLIENT_REQINFO 0x000e +#define ICQ_SERVER_NAME_INFO 0x000f +#define ICQ_SERVER_EVIL_NOTICE 0x0010 +#define ICQ_CLIENT_SET_IDLE 0x0011 +#define ICQ_SERVER_MIGRATIONREQ 0x0012 +#define ICQ_SERVER_MOTD 0x0013 +#define ICQ_CLIENT_FAMILIES 0x0017 +#define ICQ_SERVER_FAMILIES2 0x0018 +#define ICQ_CLIENT_SET_STATUS 0x001e +#define ICQ_SERVER_EXTSTATUS 0x0021 + +/* Subtypes for Location Family 0x0002 */ +#define ICQ_LOCATION_CLI_REQ_RIGHTS 0x0002 +#define ICQ_LOCATION_RIGHTS_REPLY 0x0003 +#define ICQ_LOCATION_SET_USER_INFO 0x0004 +#define ICQ_LOCATION_REQ_USER_INFO 0x0005 +#define ICQ_LOCATION_USR_INFO_REPLY 0x0006 +#define ICQ_LOCATION_QRY_USER_INFO 0x0015 + +/* Subtypes for Buddy Family 0x0003 */ +#define ICQ_USER_CLI_REQBUDDY 0x0002 +#define ICQ_USER_SRV_REPLYBUDDY 0x0003 +#define ICQ_USER_ADDTOLIST 0x0004 /* deprecated */ +#define ICQ_USER_REMOVEFROMLIST 0x0005 /* deprecated */ +#define ICQ_USER_NOTIFY_REJECTED 0x000a +#define ICQ_USER_ONLINE 0x000b +#define ICQ_USER_OFFLINE 0x000c +#define ICQ_USER_ADDTOTEMPLIST 0x000f +#define ICQ_USER_REMOVEFROMTEMPLIST 0x0010 + +/* Subtypes for Message Family 0x0004 */ +#define ICQ_MSG_SRV_ERROR 0x0001 +#define ICQ_MSG_CLI_SETPARAMS 0x0002 +#define ICQ_MSG_CLI_RESETPARAMS 0x0003 +#define ICQ_MSG_CLI_REQICBM 0x0004 +#define ICQ_MSG_SRV_REPLYICBM 0x0005 +#define ICQ_MSG_SRV_SEND 0x0006 +#define ICQ_MSG_SRV_RECV 0x0007 +#define ICQ_MSG_SRV_MISSED_MESSAGE 0x000A +#define ICQ_MSG_RESPONSE 0x000B +#define ICQ_MSG_SRV_ACK 0x000C +#define ICQ_MSG_CLI_REQ_OFFLINE 0x0010 +#define ICQ_MSG_MTN 0x0014 +#define ICQ_MSG_SRV_OFFLINE_REPLY 0x0017 + +/* Subtypes for Privacy Family 0x0009 */ +#define ICQ_PRIVACY_REQ_RIGHTS 0x0002 +#define ICQ_PRIVACY_RIGHTS_REPLY 0x0003 +#define ICQ_CLI_ADDVISIBLE 0x0005 +#define ICQ_CLI_REMOVEVISIBLE 0x0006 +#define ICQ_CLI_ADDINVISIBLE 0x0007 +#define ICQ_CLI_REMOVEINVISIBLE 0x0008 +#define ICQ_PRIVACY_SERVICE_ERROR 0x0009 +#define ICQ_CLI_ADDTEMPVISIBLE 0x000A +#define ICQ_CLI_REMOVETEMPVISIBLE 0x000B + +/* Subtypes for Lookup Family 0x000a */ +#define ICQ_LOOKUP_REQUEST 0x0002 +#define ICQ_LOOKUP_EMAIL_REPLY 0x0003 + +/* Subtypes for Stats Family 0x000b */ +#define ICQ_STATS_MINREPORTINTERVAL 0x0002 + +/* Subtypes for Avatar Family 0x0010 */ +#define ICQ_AVATAR_ERROR 0x0001 +#define ICQ_AVATAR_UPLOAD_REQUEST 0x0002 +#define ICQ_AVATAR_UPLOAD_ACK 0x0003 +#define ICQ_AVATAR_GET_REQUEST 0x0006 +#define ICQ_AVATAR_GET_REPLY 0x0007 + +/* Subtypes for Server Lists Family 0x0013 */ +#define ICQ_LISTS_ERROR 0x0001 +#define ICQ_LISTS_CLI_REQLISTS 0x0002 +#define ICQ_LISTS_SRV_REPLYLISTS 0x0003 +#define ICQ_LISTS_CLI_REQUEST 0x0004 +#define ICQ_LISTS_CLI_CHECK 0x0005 +#define ICQ_LISTS_LIST 0x0006 +#define ICQ_LISTS_GOTLIST 0x0007 +#define ICQ_LISTS_ADDTOLIST 0x0008 +#define ICQ_LISTS_UPDATEGROUP 0x0009 +#define ICQ_LISTS_REMOVEFROMLIST 0x000A +#define ICQ_LISTS_ACK 0x000E +#define ICQ_LISTS_UPTODATE 0x000F +#define ICQ_LISTS_CLI_MODIFYSTART 0x0011 +#define ICQ_LISTS_CLI_MODIFYEND 0x0012 +#define ICQ_LISTS_GRANTAUTH 0x0014 +#define ICQ_LISTS_AUTHGRANTED 0x0015 +#define ICQ_LISTS_REVOKEAUTH 0x0016 +#define ICQ_LISTS_REQUESTAUTH 0x0018 +#define ICQ_LISTS_AUTHREQUEST 0x0019 +#define ICQ_LISTS_CLI_AUTHRESPONSE 0x001A +#define ICQ_LISTS_SRV_AUTHRESPONSE 0x001B +#define ICQ_LISTS_YOUWEREADDED 0x001C + +/* Subtypes for ICQ Extensions Family 0x0015 */ +#define ICQ_META_ERROR 0x0001 +#define ICQ_META_CLI_REQUEST 0x0002 +#define ICQ_META_SRV_REPLY 0x0003 +#define ICQ_META_SRV_UPDATE 0x0004 + +/* Subtypes for Authorization Family 0x0017 */ +#define ICQ_SIGNON_ERROR 0x0001 +#define ICQ_SIGNON_LOGIN_REQUEST 0x0002 +#define ICQ_SIGNON_LOGIN_REPLY 0x0003 +#define ICQ_SIGNON_REGISTRATION_REQ 0x0004 +#define ICQ_SIGNON_NEW_UIN 0x0005 +#define ICQ_SIGNON_AUTH_REQUEST 0x0006 +#define ICQ_SIGNON_AUTH_KEY 0x0007 +#define ICQ_SIGNON_REQUEST_IMAGE 0x000C +#define ICQ_SIGNON_REG_AUTH_IMAGE 0x000D + +// Class constants +#define CLASS_UNCONFIRMED 0x0001 +#define CLASS_ADMINISTRATOR 0x0002 +#define CLASS_AOL 0x0004 +#define CLASS_COMMERCIAL 0x0008 +#define CLASS_FREE 0x0010 +#define CLASS_AWAY 0x0020 +#define CLASS_ICQ 0x0040 +#define CLASS_WIRELESS 0x0080 +#define CLASS_FORWARDING 0x0200 +#define CLASS_BOT 0x0400 + +// Reply types for SNAC 15/02 & 15/03 +#define CLI_DELETE_OFFLINE_MSGS_REQ 0x003E +#define CLI_META_INFO_REQ 0x07D0 +#define SRV_META_INFO_REPLY 0x07DA + +// Reply subtypes for SNAC 15/02 & 15/03 +#define META_PROCESSING_ERROR 0x0001 // Meta processing error server reply +#define META_SMS_DELIVERY_RECEIPT 0x0096 // Server SMS response (delivery receipt) +#define META_SET_PASSWORD_ACK 0x00AA // Set user password server ack +#define META_UNREGISTER_ACK 0x00B4 // Unregister account server ack +#define META_BASIC_USERINFO 0x00C8 // User basic info reply +#define META_WORK_USERINFO 0x00D2 // User work info reply +#define META_MORE_USERINFO 0x00DC // User more info reply +#define META_NOTES_USERINFO 0x00E6 // User notes (about) info reply +#define META_EMAIL_USERINFO 0x00EB // User extended email info reply +#define META_INTERESTS_USERINFO 0x00F0 // User interests info reply +#define META_AFFILATIONS_USERINFO 0x00FA // User past/affilations info reply +#define META_SHORT_USERINFO 0x0104 // Short user information reply +#define META_HPAGECAT_USERINFO 0x010E // User homepage category information reply +#define SRV_USER_FOUND 0x01A4 // Search: user found reply +#define SRV_LAST_USER_FOUND 0x01AE // Search: last user found reply +#define META_REGISTRATION_STATS_ACK 0x0302 // Registration stats ack +#define SRV_RANDOM_FOUND 0x0366 // Random search server reply +#define META_SET_PASSWORD_REQ 0x042E // Set user password request +#define META_REQUEST_FULL_INFO 0x04B2 // Request full user info +#define META_REQUEST_SHORT_INFO 0x04BA // Request short user info +#define META_REQUEST_SELF_INFO 0x04D0 // Request full self user info +#define META_SEARCH_GENERIC 0x055F // Search user by details (TLV) +#define META_SEARCH_UIN 0x0569 // Search user by UIN (TLV) +#define META_SEARCH_EMAIL 0x0573 // Search user by E-mail (TLV) +#define META_DIRECTORY_QUERY 0x0FA0 +#define META_DIRECTORY_DATA 0x0FAA +#define META_DIRECTORY_RESPONSE 0x0FB4 +#define META_DIRECTORY_UPDATE 0x0FD2 +#define META_DIRECTORY_UPDATE_ACK 0x0FDC + +#define META_XML_INFO 0x08A2 // Server variable requested via xml +#define META_SET_FULLINFO_REQ 0x0C3A // Set full user info request +#define META_SET_FULLINFO_ACK 0x0C3F // Server ack for set fullinfo command +#define META_SPAM_REPORT_ACK 0x2012 // Server ack for user spam report + +// Subtypes for Directory meta requests (family 0x5b9) +#define DIRECTORY_QUERY_INFO 0x0002 +#define DIRECTORY_SET_INFO 0x0003 +#define DIRECTORY_QUERY_MULTI_INFO 0x0006 +#define DIRECTORY_QUERY_INFO_ACK 0x0009 +#define DIRECTORY_SET_INFO_ACK 0x000A + +// TLV types + +// SECURITY flags +#define TLV_AUTH 0x02F8 // uint8 User authorization permissions +#define TLV_WEBAWARE 0x030C // uint8 User 'show web status' permissions + + +// SEARCH only TLVs +#define TLV_AGERANGE 0x0168 // acombo Age range to search +#define TLV_KEYWORDS 0x0226 // sstring Whitepages search keywords string +#define TLV_ONLINEONLY 0x0230 // uint8 Search only online users flag +#define TLV_UIN 0x0136 // uint32 User uin + +// common +#define TLV_FIRSTNAME 0x0140 // sstring User firstname +#define TLV_LASTNAME 0x014A // sstring User lastname +#define TLV_NICKNAME 0x0154 // sstring User nickname +#define TLV_EMAIL 0x015E // ecombo User email +#define TLV_GENDER 0x017C // uint8 User gender +#define TLV_MARITAL 0x033E // uint8 User marital status +#define TLV_LANGUAGE 0x0186 // uint16 User spoken language +#define TLV_CITY 0x0190 // sstring User home city name +#define TLV_STATE 0x019A // sstring User home state abbr +#define TLV_COUNTRY 0x01A4 // uint16 User home country code +#define TLV_COMPANY 0x01AE // sstring User work company name +#define TLV_DEPARTMENT 0x01B8 // sstring User work department name +#define TLV_POSITION 0x01C2 // sstring User work position (title) +#define TLV_OCUPATION 0x01CC // uint16 User work ocupation code +#define TLV_PASTINFO 0x01D6 // icombo User affilations node +#define TLV_AFFILATIONS 0x01FE // icombo User past info node +#define TLV_INTERESTS 0x01EA // icombo User interests node +#define TLV_HOMEPAGE 0x0212 // sstring User homepage category/keywords + +// changeinfo +#define TLV_AGE 0x0172 // uint16 User age +#define TLV_URL 0x0213 // sstring User homepage url +#define TLV_BIRTH 0x023A // bcombo User birthday info (year, month, day) +#define TLV_ABOUT 0x0258 // sstring User notes (about) text +#define TLV_STREET 0x0262 // sstring User home street address +#define TLV_ZIPCODE 0x026D // sstring User home zip code +#define TLV_PHONE 0x0276 // sstring User home phone number +#define TLV_FAX 0x0280 // sstring User home fax number +#define TLV_MOBILE 0x028A // sstring User home cellular phone number +#define TLV_WORKSTREET 0x0294 // sstring User work street address +#define TLV_WORKCITY 0x029E // sstring User work city name +#define TLV_WORKSTATE 0x02A8 // sstring User work state name +#define TLV_WORKCOUNTRY 0x02B2 // uint16 User work country code +#define TLV_WORKZIPCODE 0x02BD // sstring User work zip code +#define TLV_WORKPHONE 0x02C6 // sstring User work phone number +#define TLV_WORKFAX 0x02D0 // sstring User work fax number +#define TLV_WORKURL 0x02DA // sstring User work webpage url +#define TLV_TIMEZONE 0x0316 // uint8 User GMT offset +#define TLV_ORGCITY 0x0320 // sstring User originally from city +#define TLV_ORGSTATE 0x032A // sstring User originally from state +#define TLV_ORGCOUNTRY 0x0334 // uint16 User originally from country (code) +#define TLV_ALLOWSPAM 0x0348 // uint8 +#define TLV_CODEPAGE 0x0352 // uint16 Codepage used for details + + +/* Direct packet types */ +#define PEER_INIT 0xFF +#define PEER_INIT_ACK 0x01 +#define PEER_MSG_INIT 0x03 +#define PEER_MSG 0x02 +#define PEER_FILE_INIT 0x00 +#define PEER_FILE_INIT_ACK 0x01 +#define PEER_FILE_NEXTFILE 0x02 +#define PEER_FILE_RESUME 0x03 +#define PEER_FILE_STOP 0x04 +#define PEER_FILE_SPEED 0x05 +#define PEER_FILE_DATA 0x06 + +/* Direct command types */ +#define DIRECT_CANCEL 0x07D0 /* 2000 TCP cancel previous file/chat request */ +#define DIRECT_ACK 0x07DA /* 2010 TCP acknowledge message packet */ +#define DIRECT_MESSAGE 0x07EE /* 2030 TCP message */ + +// DC types +#define DC_DISABLED 0x0000 // Direct connection disabled / auth required +#define DC_HTTPS 0x0001 // Direct connection thru firewall or https proxy +#define DC_SOCKS 0x0002 // Direct connection thru socks4/5 proxy server +#define DC_NORMAL 0x0004 // Normal direct connection (without proxy/firewall) +#define DC_WEB 0x0006 // Web client - no direct connection + +// Message flags +#define MFLAG_NORMAL 0x01 // Normal message +#define MFLAG_AUTO 0x03 // Auto-message flag +#define MFLAG_MULTI 0x80 // This is multiple recipients message + +// Some SSI constants +#define SSI_ITEM_BUDDY 0x0000 // Buddy record (name: uin for ICQ and screenname for AIM) +#define SSI_ITEM_GROUP 0x0001 // Group record +#define SSI_ITEM_PERMIT 0x0002 // Permit record ("Allow" list in AIM, and "Visible" list in ICQ) +#define SSI_ITEM_DENY 0x0003 // Deny record ("Block" list in AIM, and "Invisible" list in ICQ) +#define SSI_ITEM_VISIBILITY 0x0004 // Permit/deny settings or/and bitmask of the AIM classes +#define SSI_ITEM_PRESENCE 0x0005 // Presence info (if others can see your idle status, etc) +#define SSI_ITEM_CLIENTDATA 0x0009 // Client specific, e.g. ICQ2k shortcut bar items +#define SSI_ITEM_IGNORE 0x000e // Ignore list record. +#define SSI_ITEM_LASTUPDATE 0x000f // Item that contain roster update time (name: "LastUpdateDate") +#define SSI_ITEM_NONICQ 0x0010 // Non-ICQ contact (to send SMS). Name: 1#EXT, 2#EXT, etc +#define SSI_ITEM_UNKNOWN2 0x0011 // Unknown. +#define SSI_ITEM_IMPORTTIME 0x0013 // Item that contain roster import time (name: "Import time") +#define SSI_ITEM_BUDDYICON 0x0014 // Buddy icon info. (names: "1", "8", etc. according ot the icon type) +#define SSI_ITEM_SAVED 0x0019 +#define SSI_ITEM_PREAUTH 0x001B +#define SSI_ITEM_METAINFO 0x0020 // Owner Details' token & last update time + +#define SSI_TLV_AWAITING_AUTH 0x0066 // Contact not authorized in list +#define SSI_TLV_NOT_IN_LIST 0x006A // Always empty +#define SSI_TLV_UNKNOWN 0x006D // WTF ? +#define SSI_TLV_SUBITEMS 0x00C8 // List of sub-items IDs +#define SSI_TLV_VISIBILITY 0x00CA +#define SSI_TLV_SHORTCUT 0x00CD +#define SSI_TLV_TIMESTAMP 0x00D4 // Import Timestamp +#define SSI_TLV_AVATARHASH 0x00D5 +#define SSI_TLV_NAME 0x0131 // Custom contact nickname +#define SSI_TLV_GROUP_OPENNED 0x0134 +#define SSI_TLV_EMAIL 0x0137 // Custom contact email +#define SSI_TLV_PHONE 0x0138 // Custom contact phone number +#define SSI_TLV_PHONE_CELLULAR 0x0139 // Custom contact cellphone number +#define SSI_TLV_PHONE_SMS 0x013A // Custom contact SMS number +#define SSI_TLV_COMMENT 0x013C // User comment +#define SSI_TLV_METAINFO_TOKEN 0x015C // Privacy token for Contact's details +#define SSI_TLV_METAINFO_TIME 0x015D // Contact's details last update time + +#define MAX_SSI_TLV_NAME_SIZE 0x40 +#define MAX_SSI_TLV_COMMENT_SIZE 0x50 + +// Client ID constants (internal) +#define CLID_GENERIC 0x00 // Generic clients (eg. older official clients) +#define CLID_ALTERNATIVE 0x01 // Clients not using tick for MsgID (most third-party clients) +#define CLID_MIRANDA 0x02 // Hey, that's mate! +#define CLID_ICQ6 0x10 // Mark ICQ6 as it has some non obvious limitations! + + +// Internal Constants +#define ICQ_PROTOCOL_NAME LPGEN("ICQ") +#define ICQ_PLUG_VERSION __VERSION_DWORD +#define ICQ_VERSION 8 // Protocol version +#define DC_TYPE DC_NORMAL // Used for DC settings +#define MAX_CONTACTSSEND 15 +#define MAX_MESSAGESNACSIZE 8000 +#define CLIENTRATELIMIT 0 +#define COOKIE_TIMEOUT 3600 // One hour +#define KEEPALIVE_INTERVAL 57000 // One minute +#define WEBFRONTPORT 0x50 +#define CLIENTFEATURES 0x3 +#define URL_FORGOT_PASSWORD "https://www.icq.com/password/" +#define URL_REGISTER "https://www.icq.com/register/" +#define FLAP_MARKER 0x2a +#define CLIENT_MD5_STRING "AOL Instant Messenger (SM)" +#define UNIQUEIDSETTING "UIN" +#define UINMAXLEN 11 // DWORD string max len + 1 +#define PASSWORDMAXLEN 128 +#define OSCAR_PROXY_HOST "ars.icq.com" +#define OSCAR_PROXY_VERSION 0x044A + +#define CLIENT_ID_STRING "ICQ Client" // Client identification, mimic ICQ 6.5 +#define CLIENT_ID_CODE 0x010a +#define CLIENT_VERSION_MAJOR 0x0014 +#define CLIENT_VERSION_MINOR 0x0034 +#define CLIENT_VERSION_LESSER 0x0000 +#define CLIENT_VERSION_BUILD 0x0c18 +#define CLIENT_DISTRIBUTION 0x00000611 +#define CLIENT_LANGUAGE "en" +#define CLIENT_COUNTRY "us" + +#endif /* __ICQ_CONSTANTS_H */ diff --git a/protocols/IcqOscarJ/src/icq_db.cpp b/protocols/IcqOscarJ/src/icq_db.cpp new file mode 100644 index 0000000000..f199860df8 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_db.cpp @@ -0,0 +1,399 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Internal Database API +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +void CIcqProto::CreateResidentSetting(const char *szSetting) +{ + char pszSetting[2*MAX_PATH]; + + strcpy(pszSetting, m_szModuleName); + strcat(pszSetting, "/"); + strcat(pszSetting, szSetting); + CallService(MS_DB_SETSETTINGRESIDENT, 1, (WPARAM)pszSetting); +} + + +int CIcqProto::getSetting(HANDLE hContact, const char *szSetting, DBVARIANT *dbv) +{ + return DBGetContactSettingW(hContact, m_szModuleName, szSetting, dbv); +} + + +BYTE CIcqProto::getSettingByte(HANDLE hContact, const char *szSetting, BYTE byDef) +{ + return DBGetContactSettingByte(hContact, m_szModuleName, szSetting, byDef); +} + + +WORD CIcqProto::getSettingWord(HANDLE hContact, const char *szSetting, WORD wDef) +{ + return DBGetContactSettingWord(hContact, m_szModuleName, szSetting, wDef); +} + + +DWORD CIcqProto::getSettingDword(HANDLE hContact, const char *szSetting, DWORD dwDef) +{ + DBVARIANT dbv = {DBVT_DELETED}; + DWORD dwRes; + + if (getSetting(hContact, szSetting, &dbv)) + return dwDef; // not found, give default + + if (dbv.type != DBVT_DWORD) + dwRes = dwDef; // invalid type, give default + else // found and valid, give result + dwRes = dbv.dVal; + + ICQFreeVariant(&dbv); + return dwRes; +} + + +double CIcqProto::getSettingDouble(HANDLE hContact, const char *szSetting, double dDef) +{ + DBVARIANT dbv = {DBVT_DELETED}; + double dRes; + + if (getSetting(hContact, szSetting, &dbv)) + return dDef; // not found, give default + + if (dbv.type != DBVT_BLOB || dbv.cpbVal != sizeof(double)) + dRes = dDef; + else + dRes = *(double*)dbv.pbVal; + + ICQFreeVariant(&dbv); + return dRes; +} + + +DWORD CIcqProto::getContactUin(HANDLE hContact) +{ + return getSettingDword(hContact, UNIQUEIDSETTING, 0); +} + + +int CIcqProto::getContactUid(HANDLE hContact, DWORD *pdwUin, uid_str *ppszUid) +{ + DBVARIANT dbv = {DBVT_DELETED}; + int iRes = 1; + + *pdwUin = 0; + if (ppszUid) *ppszUid[0] = '\0'; + + if (!getSetting(hContact, UNIQUEIDSETTING, &dbv)) + { + if (dbv.type == DBVT_DWORD) + { + *pdwUin = dbv.dVal; + iRes = 0; + } + else if (dbv.type == DBVT_ASCIIZ) + { + if (ppszUid && m_bAimEnabled) + { + strcpy(*ppszUid, dbv.pszVal); + iRes = 0; + } + else + NetLog_Server("AOL screennames not accepted"); + } + ICQFreeVariant(&dbv); + } + return iRes; +} + + +int CIcqProto::getSettingString(HANDLE hContact, const char *szSetting, DBVARIANT *dbv) +{ + int res = DBGetContactSettingString(hContact, m_szModuleName, szSetting, dbv); + + if (res) + ICQFreeVariant(dbv); + + return res; +} + + +int CIcqProto::getSettingStringW(HANDLE hContact, const char *szSetting, DBVARIANT *dbv) +{ + int res = DBGetContactSettingWString(hContact, m_szModuleName, szSetting, dbv); + + if (res) + ICQFreeVariant(dbv); + + return res; +} + + +char* CIcqProto::getSettingStringUtf(HANDLE hContact, const char *szModule, const char *szSetting, char *szDef) +{ + DBVARIANT dbv = {DBVT_DELETED}; + + if (DBGetContactSettingUTF8String(hContact, szModule, szSetting, &dbv)) + { + ICQFreeVariant(&dbv); // for a setting with invalid contents/type + return null_strdup(szDef); + } + + char *szRes = null_strdup(dbv.pszVal); + ICQFreeVariant(&dbv); + return szRes; +} + + +char* CIcqProto::getSettingStringUtf(HANDLE hContact, const char *szSetting, char *szDef) +{ + return getSettingStringUtf(hContact, m_szModuleName, szSetting, szDef); +} + + +WORD CIcqProto::getContactStatus(HANDLE hContact) +{ + return getSettingWord(hContact, "Status", ID_STATUS_OFFLINE); +} + + +int CIcqProto::getSettingStringStatic(HANDLE hContact, const char *szSetting, char *dest, int dest_len) +{ + DBVARIANT dbv = {DBVT_DELETED}; + DBCONTACTGETSETTING sVal = {0}; + + dbv.pszVal = dest; + dbv.cchVal = dest_len; + dbv.type = DBVT_ASCIIZ; + + sVal.pValue = &dbv; + sVal.szModule = m_szModuleName; + sVal.szSetting = szSetting; + + if (CallService(MS_DB_CONTACT_GETSETTINGSTATIC, (WPARAM)hContact, (LPARAM)&sVal) != 0) + { // due to MS_DB_CONTACT_GETSETTINGSTATIC setting type check, we need to request UTF8 as well + dbv.pszVal = dest; + dbv.cchVal = dest_len; + dbv.type = DBVT_UTF8; + + if (CallService(MS_DB_CONTACT_GETSETTINGSTATIC, (WPARAM)hContact, (LPARAM)&sVal) != 0) + return 1; // nothing found + } + + return (dbv.type != DBVT_ASCIIZ); +} + + +int CIcqProto::deleteSetting(HANDLE hContact, const char *szSetting) +{ + return DBDeleteContactSetting(hContact, m_szModuleName, szSetting); +} + + +int CIcqProto::setSettingByte(HANDLE hContact, const char *szSetting, BYTE byValue) +{ + return DBWriteContactSettingByte(hContact, m_szModuleName, szSetting, byValue); +} + + +int CIcqProto::setSettingWord(HANDLE hContact, const char *szSetting, WORD wValue) +{ + return DBWriteContactSettingWord(hContact, m_szModuleName, szSetting, wValue); +} + + +int CIcqProto::setSettingDword(HANDLE hContact, const char *szSetting, DWORD dwValue) +{ + return DBWriteContactSettingDword(hContact, m_szModuleName, szSetting, dwValue); +} + + +int CIcqProto::setSettingDouble(HANDLE hContact, const char *szSetting, double dValue) +{ + return setSettingBlob(hContact, szSetting, (BYTE*)&dValue, sizeof(double)); +} + + +int CIcqProto::setSettingString(HANDLE hContact, const char *szSetting, const char *szValue) +{ + return DBWriteContactSettingString(hContact, m_szModuleName, szSetting, szValue); +} + + +int CIcqProto::setSettingStringW(HANDLE hContact, const char *szSetting, const WCHAR *wszValue) +{ + return DBWriteContactSettingWString(hContact, m_szModuleName, szSetting, wszValue); +} + + +int CIcqProto::setSettingStringUtf(HANDLE hContact, const char *szModule, const char *szSetting, const char *szValue) +{ + return DBWriteContactSettingUTF8String(hContact, szModule, szSetting, (char*)szValue); +} + + +int CIcqProto::setSettingStringUtf(HANDLE hContact, const char *szSetting, const char *szValue) +{ + return setSettingStringUtf(hContact, m_szModuleName, szSetting, szValue); +} + + +int CIcqProto::setSettingBlob(HANDLE hContact, const char *szSetting, const BYTE *pValue, const int cbValue) +{ + return DBWriteContactSettingBlob(hContact, m_szModuleName, szSetting, (void*)pValue, cbValue); +} + + +int CIcqProto::setContactHidden(HANDLE hContact, BYTE bHidden) +{ + int nResult = DBWriteContactSettingByte(hContact, "CList", "Hidden", bHidden); + + if (!bHidden) // clear zero setting + DBDeleteContactSetting(hContact, "CList", "Hidden"); + + return nResult; +} + +void CIcqProto::setStatusMsgVar(HANDLE hContact, char* szStatusMsg, bool isAnsi) +{ + if (szStatusMsg && szStatusMsg[0]) + { + if (isAnsi) + { + char* szStatusNote = getSettingStringUtf(hContact, DBSETTING_STATUS_NOTE, ""); + wchar_t* szStatusNoteW = make_unicode_string(szStatusNote); + int len = (int)wcslen(szStatusNoteW) * 3 + 1; + char* szStatusNoteAnsi = (char*)alloca(len); + WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, szStatusNoteW, -1, szStatusNoteAnsi, len, NULL, NULL); + bool notmatch = false; + for (int i=0; ;++i) + { + if (szStatusNoteAnsi[i] != szStatusMsg[i] && szStatusNoteAnsi[i] != '?' && szStatusMsg[i] != '?') + { + notmatch = true; + break; + } + if (!szStatusNoteAnsi[i] || !szStatusMsg[i]) + break; + } + szStatusMsg = notmatch ? ansi_to_utf8(szStatusMsg) : szStatusNote; + SAFE_FREE(&szStatusNoteW); + if (notmatch) SAFE_FREE(&szStatusNote); + } + + char* oldStatusMsg = NULL; + DBVARIANT dbv; + if (!DBGetContactSetting(hContact, "CList", "StatusMsg", &dbv)) + { + switch (dbv.type) + { + case DBVT_UTF8: + oldStatusMsg = null_strdup(dbv.pszVal); + break; + + case DBVT_WCHAR: + oldStatusMsg = make_utf8_string(dbv.pwszVal); + break; + } + ICQFreeVariant(&dbv); + } + + if (!oldStatusMsg || strcmp(oldStatusMsg, szStatusMsg)) + setSettingStringUtf(hContact, "CList", "StatusMsg", szStatusMsg); + SAFE_FREE(&oldStatusMsg); + if (isAnsi) SAFE_FREE(&szStatusMsg); + } + else + DBDeleteContactSetting(hContact, "CList", "StatusMsg"); +} + +int __fastcall ICQFreeVariant(DBVARIANT *dbv) +{ + return DBFreeVariant(dbv); +} + + +int CIcqProto::IsICQContact(HANDLE hContact) +{ + char* szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0); + + return !strcmpnull(szProto, m_szModuleName); +} + + +HANDLE CIcqProto::AddEvent(HANDLE hContact, WORD wType, DWORD dwTime, DWORD flags, DWORD cbBlob, PBYTE pBlob) +{ + DBEVENTINFO dbei = {0}; + + dbei.cbSize = sizeof(dbei); + dbei.szModule = m_szModuleName; + dbei.timestamp = dwTime; + dbei.flags = flags; + dbei.eventType = wType; + dbei.cbBlob = cbBlob; + dbei.pBlob = pBlob; + + return (HANDLE)CallService(MS_DB_EVENT_ADD, (WPARAM)hContact, (LPARAM)&dbei); +} + + +HANDLE CIcqProto::FindFirstContact() +{ + HANDLE hContact = db_find_first(m_szModuleName); + + if (IsICQContact(hContact)) + return hContact; + + return FindNextContact(hContact); +} + + +HANDLE CIcqProto::FindNextContact(HANDLE hContact) +{ + hContact = db_find_next(hContact, m_szModuleName); + while (hContact != NULL) + { + if (IsICQContact(hContact)) + return hContact; + hContact = db_find_next(hContact, m_szModuleName); + } + return hContact; +} + + +char* CIcqProto::getContactCListGroup(HANDLE hContact) +{ + return getSettingStringUtf(hContact, "CList", "Group", NULL); +} + + +int __stdcall ICQSetContactCListGroup(HANDLE hContact, const char *szGroup) +{ + /// TODO + return 0; +} diff --git a/protocols/IcqOscarJ/src/icq_db.h b/protocols/IcqOscarJ/src/icq_db.h new file mode 100644 index 0000000000..5bfc22d58b --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_db.h @@ -0,0 +1,44 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#ifndef __ICQ_DB_H +#define __ICQ_DB_H + + +#ifdef _UNICODE + #define getSettingStringT getSettingStringW + #define setSettingStringT setSettingStringW +#else + #define getSettingStringT getSettingString + #define setSettingStringT setSettingString +#endif + +int __fastcall ICQFreeVariant(DBVARIANT* dbv); + +#endif /* __ICQ_DB_H */ diff --git a/protocols/IcqOscarJ/src/icq_direct.cpp b/protocols/IcqOscarJ/src/icq_direct.cpp new file mode 100644 index 0000000000..7779f1bad7 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_direct.cpp @@ -0,0 +1,1171 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +struct directthreadstartinfo +{ + int type; // Only valid for outgoing connections + int incoming; // 1=incoming, 0=outgoing + HANDLE hConnection; // only valid for incoming connections, handle to the connection + HANDLE hContact; // Only valid for outgoing connections + void* pvExtra; // Only valid for outgoing connections +}; + +static char client_check_data[] = { + "As part of this software beta version Mirabilis is " + "granting a limited access to the ICQ network, " + "servers, directories, listings, information and databases (\"" + "ICQ Services and Information\"). The " + "ICQ Service and Information may databases (\"" + "ICQ Services and Information\"). The " + "ICQ Service and Information may\0" +}; + +void CIcqProto::CloseContactDirectConns(HANDLE hContact) +{ + icq_lock l(directConnListMutex); + + for ( int i = 0; i < directConns.getCount(); i++) + { + if (!hContact || directConns[i]->hContact == hContact) + { + HANDLE hConnection = directConns[i]->hConnection; + + directConns[i]->hConnection = NULL; // do not allow reuse + NetLib_CloseConnection(&hConnection, FALSE); + } + } +} + + +directconnect* CIcqProto::FindFileTransferDC(filetransfer* ft) +{ + directconnect* dc = NULL; + icq_lock l(directConnListMutex); + + for (int i = 0; i < directConns.getCount(); i++) + { + if ( directConns[i]->ft == ft ) + { + dc = directConns[i]; + break; + } + } + + return dc; +} + + +filetransfer* CIcqProto::FindExpectedFileRecv(DWORD dwUin, DWORD dwTotalSize) +{ + filetransfer* pFt = NULL; + icq_lock l(expectedFileRecvMutex); + + for (int i = 0; i < expectedFileRecvs.getCount(); i++) + { + if (expectedFileRecvs[i]->dwUin == dwUin && expectedFileRecvs[i]->dwTotalSize == dwTotalSize) + { + pFt = expectedFileRecvs[i]; + expectedFileRecvs.remove( i ); + break; + } + } + + return pFt; +} + + +int CIcqProto::sendDirectPacket(directconnect* dc, icq_packet* pkt) +{ + int nResult = Netlib_Send(dc->hConnection, (const char*)pkt->pData, pkt->wLen + 2, 0); + if (nResult == SOCKET_ERROR) + { + NetLog_Direct("Direct %p socket error: %d, closing", dc->hConnection, GetLastError()); + CloseDirectConnection(dc); + } + + SAFE_FREE((void**)&pkt->pData); + + return nResult; +} + +directthreadstartinfo* CreateDTSI(HANDLE hContact, HANDLE hConnection, int type) +{ + directthreadstartinfo* dtsi = (directthreadstartinfo*)SAFE_MALLOC(sizeof(directthreadstartinfo)); + dtsi->hContact = hContact; + dtsi->hConnection = hConnection; + if (type == -1) + dtsi->incoming = 1; + else + dtsi->type = type; + + return dtsi; +} + +// Check if we have an open and initialized DC with type +// 'type' to the specified contact +BOOL CIcqProto::IsDirectConnectionOpen(HANDLE hContact, int type, int bPassive) +{ + BOOL bIsOpen = FALSE, bIsCreated = FALSE; + + { + icq_lock l(directConnListMutex); + + for (int i = 0; i < directConns.getCount(); i++) + { + if (directConns[i] && (directConns[i]->type == type)) + { + if (directConns[i]->hContact == hContact) + if (directConns[i]->initialised) + { + // Connection is OK + bIsOpen = TRUE; + // we are going to use the conn, so prevent timeout + directConns[i]->packetPending = 1; + break; + } + else + bIsCreated = TRUE; // we found pending connection + } + } + } + + if (!bPassive && !bIsCreated && !bIsOpen && type == DIRECTCONN_STANDARD && m_bDCMsgEnabled == 2) + { // do not try to open DC to offline contact + if (getContactStatus(hContact) == ID_STATUS_OFFLINE) return FALSE; + // do not try to open DC if previous attempt was not successfull + if (getSettingByte(hContact, "DCStatus", 0)) return FALSE; + + // Set DC status as tried + setSettingByte(hContact, "DCStatus", 1); + // Create a new connection + OpenDirectConnection(hContact, DIRECTCONN_STANDARD, NULL); + } + + return bIsOpen; +} + +// This function is called from the Netlib when someone is connecting to +// one of our incomming DC ports +void icq_newConnectionReceived(HANDLE hNewConnection, DWORD dwRemoteIP, void *pExtra) +{ + // Start a new thread for the incomming connection + CIcqProto* ppro = (CIcqProto*)pExtra; + ppro->ForkThread(( IcqThreadFunc )&CIcqProto::icq_directThread, CreateDTSI(NULL, hNewConnection, -1)); +} + +// Opens direct connection of specified type to specified contact +void CIcqProto::OpenDirectConnection(HANDLE hContact, int type, void* pvExtra) +{ + // Create a new connection + directthreadstartinfo* dtsi = CreateDTSI(hContact, NULL, type); + dtsi->pvExtra = pvExtra; + ForkThread(( IcqThreadFunc )&CIcqProto::icq_directThread, dtsi); +} + +// Safely close NetLib connection - do not corrupt direct connection list +void CIcqProto::CloseDirectConnection(directconnect *dc) +{ + icq_lock l(directConnListMutex); + + NetLib_CloseConnection(&dc->hConnection, FALSE); +#ifdef _DEBUG + if (dc->hConnection) + NetLog_Direct("Direct conn closed (%p)", dc->hConnection); +#endif +} + +// Called from icq_newConnectionReceived when a new incomming dc is done +// Called from OpenDirectConnection when a new outgoing dc is done +// Called from SendDirectMessage when a new outgoing dc is done + +void __cdecl CIcqProto::icq_directThread( directthreadstartinfo *dtsi ) +{ + directconnect dc = {0}; + NETLIBPACKETRECVER packetRecv={0}; + HANDLE hPacketRecver; + BOOL bFirstPacket = TRUE; + int nSkipPacketBytes = 0; + DWORD dwReqMsgID1; + DWORD dwReqMsgID2; + + srand(time(NULL)); + + { // add to DC connection list + icq_lock l(directConnListMutex); + directConns.insert( &dc ); + } + + // Initialize DC struct + dc.hContact = dtsi->hContact; + dc.dwThreadId = GetCurrentThreadId(); + dc.incoming = dtsi->incoming; + dc.hConnection = dtsi->hConnection; + dc.ft = NULL; + + if (!dc.incoming) + { + dc.type = dtsi->type; + dc.dwRemoteExternalIP = getSettingDword(dtsi->hContact, "IP", 0); + dc.dwRemoteInternalIP = getSettingDword(dtsi->hContact, "RealIP", 0); + dc.dwRemotePort = getSettingWord(dtsi->hContact, "UserPort", 0); + dc.dwRemoteUin = getContactUin(dtsi->hContact); + dc.dwConnectionCookie = getSettingDword(dtsi->hContact, "DirectCookie", 0); + dc.wVersion = getSettingWord(dtsi->hContact, "Version", 0); + + if (!dc.dwRemoteExternalIP && !dc.dwRemoteInternalIP) + { // we do not have any ip, do not try to connect + SAFE_FREE((void**)&dtsi); + goto LBL_Exit; + } + if (!dc.dwRemotePort) + { // we do not have port, do not try to connect + SAFE_FREE((void**)&dtsi); + goto LBL_Exit; + } + + if (dc.type == DIRECTCONN_STANDARD) + { + // do nothing - some specific init for msg sessions + } + else if (dc.type == DIRECTCONN_FILE) + { + dc.ft = (filetransfer*)dtsi->pvExtra; + dc.dwRemotePort = dc.ft->dwRemotePort; + } + else if (dc.type == DIRECTCONN_REVERSE) + { + cookie_reverse_connect *pCookie = (cookie_reverse_connect*)dtsi->pvExtra; + + dwReqMsgID1 = pCookie->dwMsgID1; + dwReqMsgID2 = pCookie->dwMsgID2; + dc.dwReqId = (DWORD)pCookie->ft; + SAFE_FREE((void**)&pCookie); + } + } + else + { + dc.type = DIRECTCONN_STANDARD; + } + + SAFE_FREE((void**)&dtsi); + + // Load local IP information + dc.dwLocalExternalIP = getSettingDword(NULL, "IP", 0); + dc.dwLocalInternalIP = getSettingDword(NULL, "RealIP", 0); + + // Create outgoing DC + if (!dc.incoming) + { + NETLIBOPENCONNECTION nloc = {0}; + IN_ADDR addr = {0}, addr2 = {0}; + + if (dc.dwRemoteExternalIP == dc.dwLocalExternalIP && dc.dwRemoteInternalIP) + addr.S_un.S_addr = htonl(dc.dwRemoteInternalIP); + else + { + addr.S_un.S_addr = htonl(dc.dwRemoteExternalIP); + // for different internal, try it also (for LANs with multiple external IP, VPNs, etc.) + if (dc.dwRemoteInternalIP != dc.dwRemoteExternalIP) + addr2.S_un.S_addr = htonl(dc.dwRemoteInternalIP); + } + + // IP to connect to is empty, go away + if (!addr.S_un.S_addr) + goto LBL_Exit; + + nloc.szHost = inet_ntoa(addr); + nloc.wPort = (WORD)dc.dwRemotePort; + nloc.timeout = 8; // 8 secs to connect + dc.hConnection = NetLib_OpenConnection(m_hDirectNetlibUser, dc.type==DIRECTCONN_REVERSE?"Reverse ":NULL, &nloc); + if (!dc.hConnection && addr2.S_un.S_addr) + { // first address failed, try second one if available + nloc.szHost = inet_ntoa(addr2); + dc.hConnection = NetLib_OpenConnection(m_hDirectNetlibUser, dc.type==DIRECTCONN_REVERSE?"Reverse ":NULL, &nloc); + } + if (!dc.hConnection) + { + if (CheckContactCapabilities(dc.hContact, CAPF_ICQDIRECT)) + { // only if the contact support ICQ DC connections + if (dc.type != DIRECTCONN_REVERSE) + { // try reverse connect + cookie_reverse_connect *pCookie = (cookie_reverse_connect*)SAFE_MALLOC(sizeof(cookie_reverse_connect)); + DWORD dwCookie; + + NetLog_Direct("connect() failed (%d), trying reverse.", GetLastError()); + + if (pCookie) + { // init cookie + InitMessageCookie(pCookie); + pCookie->bMessageType = MTYPE_REVERSE_REQUEST; + pCookie->hContact = dc.hContact; + pCookie->dwUin = dc.dwRemoteUin; + pCookie->type = dc.type; + pCookie->ft = dc.ft; + dwCookie = AllocateCookie(CKT_REVERSEDIRECT, 0, dc.hContact, pCookie); + icq_sendReverseReq(&dc, dwCookie, (cookie_message_data*)pCookie); + goto LBL_Exit; + } + + NetLog_Direct("Reverse failed (%s)", "malloc failed"); + } + } + else // Set DC status to failed + setSettingByte(dc.hContact, "DCStatus", 2); + + if (dc.type == DIRECTCONN_REVERSE) // failed reverse connection + { // announce we failed + icq_sendReverseFailed(&dc, dwReqMsgID1, dwReqMsgID2, dc.dwReqId); + } + NetLog_Direct("connect() failed (%d)", GetLastError()); + if (dc.type == DIRECTCONN_FILE) + { + BroadcastAck(dc.ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, dc.ft, 0); + // Release transfer + SafeReleaseFileTransfer((void**)&dc.ft); + } + goto LBL_Exit; + } + + if (dc.type == DIRECTCONN_FILE) + dc.ft->hConnection = dc.hConnection; + + if (dc.wVersion > 6) + { + sendPeerInit_v78(&dc); + } + else + { + NetLog_Direct("Error: Unsupported direct protocol: %d, closing.", dc.wVersion); + CloseDirectConnection(&dc); + goto LBL_Exit; + } + } + + hPacketRecver = (HANDLE)CallService(MS_NETLIB_CREATEPACKETRECVER, (WPARAM)dc.hConnection, 8192); + packetRecv.cbSize = sizeof(packetRecv); + packetRecv.bytesUsed = 0; + + // Packet receiving loop + + while (dc.hConnection) + { + int recvResult; + + packetRecv.dwTimeout = dc.wantIdleTime ? 0 : 600000; + + recvResult = CallService(MS_NETLIB_GETMOREPACKETS, (WPARAM)hPacketRecver, (LPARAM)&packetRecv); + if (recvResult == 0) + { + NetLog_Direct("Clean closure of direct socket (%p)", dc.hConnection); + break; + } + + if (recvResult == SOCKET_ERROR) + { + if (GetLastError() == ERROR_TIMEOUT) + { // TODO: this will not work on some systems + if (dc.wantIdleTime) + { + switch (dc.type) + { + case DIRECTCONN_FILE: + handleFileTransferIdle(&dc); + break; + } + } + else if (dc.packetPending) + { // do we expect packet soon? + NetLog_Direct("Keeping connection, packet pending."); + } + else + { + NetLog_Direct("Connection inactive for 10 minutes, closing."); + break; + } + } + else + { + NetLog_Direct("Abortive closure of direct socket (%p) (%d)", dc.hConnection, GetLastError()); + break; + } + } + + if (dc.type == DIRECTCONN_CLOSING) + packetRecv.bytesUsed = packetRecv.bytesAvailable; + else if (packetRecv.bytesAvailable < nSkipPacketBytes) + { // the whole buffer needs to be skipped + nSkipPacketBytes -= packetRecv.bytesAvailable; + packetRecv.bytesUsed = packetRecv.bytesAvailable; + } + else + { + int i; + + for (i = nSkipPacketBytes, nSkipPacketBytes = 0; i + 2 <= packetRecv.bytesAvailable;) + { + WORD wLen = *(WORD*)(packetRecv.buffer + i); + + if (bFirstPacket) + { + if (wLen > 64) + { // roughly check first packet size + NetLog_Direct("Error: Overflowed packet, closing connection."); + CloseDirectConnection(&dc); + break; + } + bFirstPacket = FALSE; + } + else + { + if (packetRecv.bytesAvailable >= i + 2 && wLen > 8190) + { // check for too big packages + NetLog_Direct("Error: Package too big: %d bytes, skipping."); + nSkipPacketBytes = wLen; + packetRecv.bytesUsed = i + 2; + break; + } + } + + if (wLen + 2 + i > packetRecv.bytesAvailable) + break; + + if (dc.type == DIRECTCONN_STANDARD && wLen && packetRecv.buffer[i + 2] == 2) + { + if (!DecryptDirectPacket(&dc, packetRecv.buffer + i + 3, (WORD)(wLen - 1))) + { + NetLog_Direct("Error: Corrupted packet encryption, ignoring packet"); + i += wLen + 2; + continue; + } + } +#ifdef _DEBUG + NetLog_Direct("New direct package"); +#endif + if (dc.type == DIRECTCONN_FILE && dc.initialised) + handleFileTransferPacket(&dc, packetRecv.buffer + i + 2, wLen); + else + handleDirectPacket(&dc, packetRecv.buffer + i + 2, wLen); + + i += wLen + 2; + } + packetRecv.bytesUsed = i; + } + } + + // End of packet receiving loop + + NetLib_SafeCloseHandle(&hPacketRecver); + CloseDirectConnection(&dc); + + if (dc.ft) + { + if (dc.ft->fileId != -1) + { + _close(dc.ft->fileId); + BroadcastAck(dc.ft->hContact, ACKTYPE_FILE, dc.ft->dwBytesDone==dc.ft->dwTotalSize ? ACKRESULT_SUCCESS : ACKRESULT_FAILED, dc.ft, 0); + } + else if (dc.ft->hConnection) + BroadcastAck(dc.ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, dc.ft, 0); + + SafeReleaseFileTransfer((void**)&dc.ft); + _chdir("\\"); /* so we don't leave a subdir handle open so it can't be deleted */ + } + +LBL_Exit: + { // remove from DC connection list + icq_lock l(directConnListMutex); + directConns.remove( &dc ); + } +} + + +void CIcqProto::handleDirectPacket(directconnect* dc, PBYTE buf, WORD wLen) +{ + if (wLen < 1) + return; + + switch (buf[0]) + { + case PEER_FILE_INIT: // first packet of a file transfer +#ifdef _DEBUG + NetLog_Direct("Received PEER_FILE_INIT from %u",dc->dwRemoteUin); +#endif + if (dc->handshake) + handleFileTransferPacket(dc, buf, wLen); + else + NetLog_Direct("Received %s on uninitialised DC, ignoring.", "PEER_FILE_INIT"); + + break; + + case PEER_INIT_ACK: // This is sent as a response to our PEER_INIT packet + if (wLen != 4) + { + NetLog_Direct("Error: Received malformed PEER_INITACK from %u", dc->dwRemoteUin); + break; + } +#ifdef _DEBUG + NetLog_Direct("Received PEER_INITACK from %u on %s DC", dc->dwRemoteUin, dc->incoming?"incoming":"outgoing"); +#endif + if (dc->incoming) dc->handshake = 1; + + if (dc->incoming && dc->type == DIRECTCONN_REVERSE) + { + cookie_reverse_connect *pCookie; + + dc->incoming = 0; + + if (FindCookie(dc->dwReqId, NULL, (void**)&pCookie) && pCookie) + { // valid reverse DC, check and init session + FreeCookie(dc->dwReqId); + if (pCookie->dwUin == dc->dwRemoteUin) + { // valid connection + dc->type = pCookie->type; + dc->ft = (filetransfer*)pCookie->ft; + dc->hContact = pCookie->hContact; + if (dc->type == DIRECTCONN_STANDARD) + { // init message session + sendPeerMsgInit(dc, 0); + } + else if (dc->type == DIRECTCONN_FILE) + { // init file session + sendPeerFileInit(dc); + dc->initialised = 1; + } + SAFE_FREE((void**)&pCookie); + break; + } + else + { + SAFE_FREE((void**)&pCookie); + NetLog_Direct("Error: Invalid connection (UINs does not match)."); + CloseDirectConnection(dc); + return; + } + } + else + { + NetLog_Direct("Error: Received unexpected reverse DC, closing."); + CloseDirectConnection(dc); + return; + } + } + break; + + case PEER_INIT: /* connect packet */ +#ifdef _DEBUG + NetLog_Direct("Received PEER_INIT"); +#endif + buf++; + + if (wLen < 3) + return; + + unpackLEWord(&buf, &dc->wVersion); + + if (dc->wVersion > 6) + { // we support only versions 7 and up + WORD wSecondLen; + DWORD dwUin; + DWORD dwPort; + DWORD dwCookie; + HANDLE hContact; + + if (wLen != 0x30) + { + NetLog_Direct("Error: Received malformed PEER_INIT"); + return; + } + + unpackLEWord(&buf, &wSecondLen); + if (wSecondLen && wSecondLen != 0x2b) + { // OMG? GnomeICU sets this to zero + NetLog_Direct("Error: Received malformed PEER_INIT"); + return; + } + + unpackLEDWord(&buf, &dwUin); + if (dwUin != m_dwLocalUIN) + { + NetLog_Direct("Error: Received PEER_INIT targeted to %u", dwUin); + CloseDirectConnection(dc); + return; + } + + buf += 2; /* 00 00 */ + unpackLEDWord(&buf, &dc->dwRemotePort); + unpackLEDWord(&buf, &dc->dwRemoteUin); + unpackDWord(&buf, &dc->dwRemoteExternalIP); + unpackDWord(&buf, &dc->dwRemoteInternalIP); + buf ++; /* 04: accept direct connections */ + unpackLEDWord(&buf, &dwPort); + if (dwPort != dc->dwRemotePort) + { + NetLog_Direct("Error: Received malformed PEER_INIT (invalid port)"); + return; + } + unpackLEDWord(&buf, &dwCookie); + + buf += 8; // Unknown stuff + unpackLEDWord(&buf, &dc->dwReqId); + + if (dc->dwRemoteUin || !dc->dwReqId) + { // OMG! Licq sends on reverse connection empty uin + hContact = HContactFromUIN(dc->dwRemoteUin, NULL); + if (hContact == INVALID_HANDLE_VALUE) + { + NetLog_Direct("Error: Received PEER_INIT from %u not on my list", dwUin); + CloseDirectConnection(dc); + return; /* don't allow direct connection with people not on my clist */ + } + + if (dc->incoming) + { // this is the first PEER_INIT with our cookie + if (dwCookie != getSettingDword(hContact, "DirectCookie", 0)) + { + NetLog_Direct("Error: Received PEER_INIT with broken cookie"); + CloseDirectConnection(dc); + return; + } + } + else + { // this is the second PEER_INIT with peer cookie + if (dwCookie != dc->dwConnectionCookie) + { + NetLog_Direct("Error: Received PEER_INIT with broken cookie"); + CloseDirectConnection(dc); + return; + } + } + } + + if (dc->incoming && dc->dwReqId) + { // this is reverse connection + dc->type = DIRECTCONN_REVERSE; + if (!dc->dwRemoteUin) + { // we need to load cookie (licq) + cookie_reverse_connect *pCookie; + + if (FindCookie(dc->dwReqId, NULL, (void**)&pCookie) && pCookie) + { // valid reverse DC, check and init session + dc->dwRemoteUin = pCookie->dwUin; + dc->hContact = pCookie->hContact; + } + else + { + NetLog_Direct("Error: Received unexpected reverse DC, closing."); + CloseDirectConnection(dc); + return; + } + } + } + + sendPeerInitAck(dc); // ack good PEER_INIT packet + + if (dc->incoming) + { // store good IP info + dc->hContact = hContact; + dc->dwConnectionCookie = dwCookie; + setSettingDword(dc->hContact, "IP", dc->dwRemoteExternalIP); + setSettingDword(dc->hContact, "RealIP", dc->dwRemoteInternalIP); + sendPeerInit_v78(dc); // reply with our PEER_INIT + } + else // outgoing + { + dc->handshake = 1; + + if (dc->type == DIRECTCONN_REVERSE) + { + dc->incoming = 1; // this is incoming reverse connection + dc->type = DIRECTCONN_STANDARD; // we still do not know type + } + else if (dc->type == DIRECTCONN_STANDARD) + { // send PEER_MSGINIT + sendPeerMsgInit(dc, 0); + } + else if (dc->type == DIRECTCONN_FILE) + { + sendPeerFileInit(dc); + dc->initialised = 1; + } + } + // Set DC Status to successful + setSettingByte(dc->hContact, "DCStatus", 0); + } + else + { + NetLog_Direct("Unsupported direct protocol: %d, closing connection", dc->wVersion); + CloseDirectConnection(dc); + } + break; + + case PEER_MSG: /* messaging packets */ +#ifdef _DEBUG + NetLog_Direct("Received PEER_MSG from %u", dc->dwRemoteUin); +#endif + if (dc->initialised) + handleDirectMessage(dc, buf + 1, (WORD)(wLen - 1)); + else + NetLog_Direct("Received %s on uninitialised DC, ignoring.", "PEER_MSG"); + + break; + + case PEER_MSG_INIT: /* init message connection */ + { // it is sent by both contains GUID of message channel + DWORD q1,q2,q3,q4; + + if (!m_bDCMsgEnabled) + { // DC messaging disabled, close connection + NetLog_Direct("Messaging DC requested, denied"); + CloseDirectConnection(dc); + break; + } + +#ifdef _DEBUG + NetLog_Direct("Received PEER_MSG_INIT from %u",dc->dwRemoteUin); +#endif + buf++; + if (wLen != 0x21) + break; + + if (!dc->handshake) + { + NetLog_Direct("Received %s on unitialised DC, ignoring.", "PEER_MSG_INIT"); + break; + } + + buf += 4; /* always 10 */ + buf += 4; /* some id */ + buf += 4; /* sequence - always 0 on incoming */ + unpackDWord(&buf, &q1); // session type GUID + unpackDWord(&buf, &q2); + if (!dc->incoming) + { // skip marker on sequence 1 + buf += 4; + } + unpackDWord(&buf, &q3); + unpackDWord(&buf, &q4); + if (!CompareGUIDs(q1,q2,q3,q4,PSIG_MESSAGE)) + { // This is not for normal messages, useless so kill. + if (CompareGUIDs(q1,q2,q3,q4,PSIG_STATUS_PLUGIN)) + { + NetLog_Direct("Status Manager Plugin connections not supported, closing."); + } + else if (CompareGUIDs(q1,q2,q3,q4,PSIG_INFO_PLUGIN)) + { + NetLog_Direct("Info Manager Plugin connection not supported, closing."); + } + else + { + NetLog_Direct("Unknown connection type init, closing."); + } + CloseDirectConnection(dc); + break; + } + + if (dc->incoming) + { // reply with our PEER_MSG_INIT + sendPeerMsgInit(dc, 1); + } + else + { // connection initialized, ready to send message packet + } + NetLog_Direct("Direct message session ready."); + dc->initialised = 1; + } + break; + + default: + NetLog_Direct("Unknown direct packet ignored."); + break; + } +} + +void EncryptDirectPacket(directconnect* dc, icq_packet* p) +{ + unsigned long B1; + unsigned long M1; + unsigned long check; + unsigned int i; + unsigned char X1; + unsigned char X2; + unsigned char X3; + unsigned char* buf = (unsigned char*)(p->pData + 3); + unsigned char bak[6]; + unsigned long offset; + unsigned long key; + unsigned long hex; + unsigned long size = p->wLen - 1; + + + if (dc->wVersion < 4) + return; // no encryption necessary. + + + switch (dc->wVersion) + { + case 4: + case 5: + offset = 6; + break; + + case 6: + case 7: + case 8: + case 9: + case 10: + default: + offset = 0; + } + + // calculate verification data + M1 = (rand() % ((size < 255 ? size : 255)-10))+10; + X1 = buf[M1] ^ 0xFF; + X2 = rand() % 220; + X3 = client_check_data[X2] ^ 0xFF; + if (offset) + { + memcpy(bak, buf, sizeof(bak)); + B1 = (buf[offset+4]<<24) | (buf[offset+6]<<16) | (buf[2]<<8) | buf[0]; + } + else + { + B1 = (buf[4]<<24) | (buf[6]<<16) | (buf[4]<<8) | (buf[6]); + } + + // calculate checkcode + check = (M1<<24) | (X1<<16) | (X2<<8) | X3; + check ^= B1; + + // main XOR key + key = 0x67657268 * size + check; + + // XORing the actual data + for (i = 0; i<(size+3)/4; i+=4) + { + hex = key + client_check_data[i&0xFF]; + *(PDWORD)(buf + i) ^= hex; + } + + // in TCPv4 are the first 6 bytes unencrypted + // so restore them + if (offset) + memcpy(buf, bak, sizeof(bak)); + + // storing the checkcode + *(PDWORD)(buf + offset) = check; +} + +int DecryptDirectPacket(directconnect* dc, PBYTE buf, WORD wLen) +{ + unsigned long hex; + unsigned long key; + unsigned long B1; + unsigned long M1; + unsigned long check; + unsigned int i; + unsigned char X1; + unsigned char X2; + unsigned char X3; + unsigned char bak[6]; + unsigned long size = wLen; + unsigned long offset; + + + if (dc->wVersion < 4) + return 1; // no decryption necessary. + + if (size < 4) + return 1; + + if (dc->wVersion < 4) + return 1; + + if (dc->wVersion == 4 || dc->wVersion == 5) + { + offset = 6; + } + else + { + offset = 0; + } + + // backup the first 6 bytes + if (offset) + memcpy(bak, buf, sizeof(bak)); + + // retrieve checkcode + check = *(PDWORD)(buf+offset); + + // main XOR key + key = 0x67657268 * size + check; + + for (i=4; i<(size+3)/4; i+=4) + { + hex = key + client_check_data[i&0xFF]; + *(PDWORD)(buf + i) ^= hex; + } + + // retrive validate data + if (offset) + { + // in TCPv4 are the first 6 bytes unencrypted + // so restore them + memcpy(buf, bak, sizeof(bak)); + B1 = (buf[offset+4]<<24) | (buf[offset+6]<<16) | (buf[2]<<8) | buf[0]; + } + else + { + B1 = (buf[4]<<24) | (buf[6]<<16) | (buf[4]<<8) | (buf[6]<<0); + } + + // special decryption + B1 ^= check; + + // validate packet + M1 = (B1>>24) & 0xFF; + if (M1 < 10 || M1 >= size) + { + return 0; + } + + X1 = buf[M1] ^ 0xFF; + if (((B1 >> 16) & 0xFF) != X1) + { + return 0; + } + + X2 = (BYTE)((B1 >> 8) & 0xFF); + if (X2 < 220) + { + X3 = client_check_data[X2] ^ 0xFF; + if ((B1 & 0xFF) != X3) + { + return 0; + } + } +#ifdef _DEBUG + { // log decrypted data + char szTitleLine[128]; + char* szBuf; + int titleLineLen; + int line; + int col; + int colsInLine; + char* pszBuf; + + + titleLineLen = null_snprintf(szTitleLine, 128, "DECRYPTED\n"); + szBuf = (char*)_alloca(titleLineLen + ((wLen+15)>>4) * 76 + 1); + CopyMemory(szBuf, szTitleLine, titleLineLen); + pszBuf = szBuf + titleLineLen; + + for (line = 0; ; line += 16) + { + colsInLine = min(16, wLen - line); + pszBuf += wsprintfA(pszBuf, "%08X: ", line); + + for (col = 0; colhContact == hContact) + { + if (directConns[i]->initialised) + { + // This connection can be reused, send packet and exit + NetLog_Direct("Sending direct message"); + + if (pkt->pData[2] == 2) + EncryptDirectPacket(directConns[i], pkt); + + sendDirectPacket(directConns[i], pkt); + directConns[i]->packetPending = 0; // packet done + + return TRUE; // Success + } + break; // connection not ready, use server instead + } + } + + return FALSE; // connection pending, we failed, use server instead +} + +// Sends a PEER_INIT packet through a DC +// ----------------------------------------------------------------------- +// This packet is sent during direct connection initialization between two +// ICQ clients. It is sent by the originator of the connection to start +// the handshake and by the receiver directly after it has sent the +// PEER_ACK packet as a reply to the originator's PEER_INIT. The values +// after the COOKIE field have been added for v7. + +void CIcqProto::sendPeerInit_v78(directconnect* dc) +{ + icq_packet packet; + + directPacketInit(&packet, 48); // Full packet length + packByte(&packet, PEER_INIT); // Command + packLEWord(&packet, dc->wVersion); // Version + packLEWord(&packet, 43); // Data length + packLEDWord(&packet, dc->dwRemoteUin); // UIN of remote user + packWord(&packet, 0); // Unknown + packLEDWord(&packet, wListenPort); // Our port + packLEDWord(&packet, m_dwLocalUIN); // Our UIN + packDWord(&packet, dc->dwLocalExternalIP); // Our external IP + packDWord(&packet, dc->dwLocalInternalIP); // Our internal IP + packByte(&packet, DC_TYPE); // TCP connection flags + packLEDWord(&packet, wListenPort); // Our port + packLEDWord(&packet, dc->dwConnectionCookie); // DC cookie + packLEDWord(&packet, WEBFRONTPORT); // Unknown + packLEDWord(&packet, CLIENTFEATURES); // Unknown + if (dc->type == DIRECTCONN_REVERSE) + packLEDWord(&packet, dc->dwReqId); // Reverse Request Cookie + else + packDWord(&packet, 0); // Unknown + + sendDirectPacket(dc, &packet); +#ifdef _DEBUG + NetLog_Direct("Sent PEER_INIT to %u on %s DC", dc->dwRemoteUin, dc->incoming?"incoming":"outgoing"); +#endif +} + +// Sends a PEER_INIT packet through a DC +// ----------------------------------------------------------------------- +// This is sent to acknowledge a PEER_INIT packet. + +void CIcqProto::sendPeerInitAck(directconnect* dc) +{ + icq_packet packet; + + directPacketInit(&packet, 4); // Packet length + packLEDWord(&packet, PEER_INIT_ACK); // + + sendDirectPacket(dc, &packet); +#ifdef _DEBUG + NetLog_Direct("Sent PEER_INIT_ACK to %u on %s DC", dc->dwRemoteUin, dc->incoming?"incoming":"outgoing"); +#endif +} + +// Sends a PEER_MSG_INIT packet through a DC +// ----------------------------------------------------------------------- +// This packet starts message session. + +void CIcqProto::sendPeerMsgInit(directconnect* dc, DWORD dwSeq) +{ + icq_packet packet; + + directPacketInit(&packet, 33); + packByte(&packet, PEER_MSG_INIT); + packLEDWord(&packet, 10); // unknown + packLEDWord(&packet, 1); // message connection + packLEDWord(&packet, dwSeq); // sequence is 0,1 + if (!dwSeq) + { + packGUID(&packet, PSIG_MESSAGE); // message type GUID + packLEWord(&packet, 1); // delimiter + packLEWord(&packet, 4); + } + else + { + packDWord(&packet, 0); // first part of Message GUID + packDWord(&packet, 0); + packLEWord(&packet, 1); // delimiter + packLEWord(&packet, 4); + packDWord(&packet, 0); // second part of Message GUID + packDWord(&packet, 0); + } + sendDirectPacket(dc, &packet); +#ifdef _DEBUG + NetLog_Direct("Sent PEER_MSG_INIT to %u on %s DC", dc->dwRemoteUin, dc->incoming?"incoming":"outgoing"); +#endif +} + +// Sends a PEER_FILE_INIT packet through a DC +// ----------------------------------------------------------------------- +// This packet configures file-transfer session. + +void CIcqProto::sendPeerFileInit(directconnect* dc) +{ + icq_packet packet; + DBVARIANT dbv; + char* szNick; + int nNickLen; + + dbv.type = DBVT_DELETED; + if (getSettingString(NULL, "Nick", &dbv)) + szNick = ""; + else + szNick = dbv.pszVal; + nNickLen = strlennull(szNick); + + directPacketInit(&packet, (WORD)(20 + nNickLen)); + packByte(&packet, PEER_FILE_INIT); /* packet type */ + packLEDWord(&packet, 0); /* unknown */ + packLEDWord(&packet, dc->ft->dwFileCount); + packLEDWord(&packet, dc->ft->dwTotalSize); + packLEDWord(&packet, dc->ft->dwTransferSpeed); + packLEWord(&packet, (WORD)(nNickLen + 1)); + packBuffer(&packet, (LPBYTE)szNick, (WORD)(nNickLen + 1)); + sendDirectPacket(dc, &packet); +#ifdef _DEBUG + NetLog_Direct("Sent PEER_FILE_INIT to %u on %s DC", dc->dwRemoteUin, dc->incoming?"incoming":"outgoing"); +#endif + ICQFreeVariant(&dbv); +} diff --git a/protocols/IcqOscarJ/src/icq_direct.h b/protocols/IcqOscarJ/src/icq_direct.h new file mode 100644 index 0000000000..3cf91cb493 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_direct.h @@ -0,0 +1,94 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2009 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#ifndef __ICQ_DIRECT_H +#define __ICQ_DIRECT_H + +struct filetransfer: public basic_filetransfer +{ + int status; + int sending; + int iCurrentFile; + int currentIsDir; + DWORD dwCookie; + DWORD dwUin; + DWORD dwRemotePort; + HANDLE hContact; + char *szFilename; + char *szDescription; + char *szSavePath; + char *szThisFile; + char *szThisSubdir; + char **pszFiles; + DWORD dwThisFileSize; + DWORD dwThisFileDate; + DWORD dwTotalSize; + DWORD dwFileCount; + DWORD dwTransferSpeed; + DWORD dwBytesDone, dwFileBytesDone; + int fileId; + HANDLE hConnection; + DWORD dwLastNotify; + int nVersion; // Was this sent with a v7 or a v8 packet? + BOOL bDC; // Was this received over a DC or through server? + BOOL bEmptyDesc; // Was the description empty ? +}; + +#define DIRECTCONN_STANDARD 0 +#define DIRECTCONN_FILE 1 +#define DIRECTCONN_CHAT 2 +#define DIRECTCONN_REVERSE 10 +#define DIRECTCONN_CLOSING 15 + +struct directconnect +{ + HANDLE hContact; + HANDLE hConnection; + DWORD dwConnectionCookie; + int type; + WORD wVersion; + int incoming; + int wantIdleTime; + int packetPending; + DWORD dwRemotePort; + DWORD dwRemoteUin; + DWORD dwRemoteExternalIP; + DWORD dwRemoteInternalIP; + DWORD dwLocalExternalIP; + DWORD dwLocalInternalIP; + int initialised; + int handshake; + DWORD dwThreadId; + filetransfer *ft; + DWORD dwReqId; // Reverse Connect request cookie +}; + +int DecryptDirectPacket(directconnect* dc, PBYTE buf, WORD wLen); + +#endif /* __ICQ_DIRECT_H */ diff --git a/protocols/IcqOscarJ/src/icq_directmsg.cpp b/protocols/IcqOscarJ/src/icq_directmsg.cpp new file mode 100644 index 0000000000..89fbb7cea5 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_directmsg.cpp @@ -0,0 +1,361 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +void CIcqProto::handleDirectMessage(directconnect* dc, PBYTE buf, WORD wLen) +{ + WORD wCommand; + WORD wCookie; + BYTE bMsgType,bMsgFlags; + WORD wStatus; + WORD wFlags; + WORD wTextLen; + char* pszText = NULL; + + + // The first part of the packet should always be at least 31 bytes + if (wLen < 31) + { + NetLog_Direct("Error during parsing of DC packet 2 PEER_MSG (too short)"); + return; + } + + // Skip packet checksum + buf += 4; + wLen -= 4; + + // Command: + // 0x07d0 = 2000 - cancel given message. + // 0x07da = 2010 - acknowledge message. + // 0x07ee = 2030 - normal message/request. + unpackLEWord(&buf, &wCommand); + wLen -= 2; + + // Unknown, always 0xe (14) + buf += 2; + wLen -= 2; + + // Sequence number + unpackLEWord(&buf, &wCookie); + wLen -=2; + + // Unknown, always zeroes + buf += 12; + wLen -= 12; + + // Peer message type + unpackByte(&buf, &bMsgType); + // Peer message flags + unpackByte(&buf, &bMsgFlags); + wLen -= 2; + + // The current status of the user, or whether the message was accepted or not. + // 0x00 - user is online, or message was receipt, or file transfer accepted + // 0x01 - refused + // 0x04 - auto-refused, because of away + // 0x09 - auto-refused, because of occupied + // 0x0a - auto-refused, because of dnd + // 0x0e - auto-refused, because of na + unpackLEWord(&buf, &wStatus); + wLen -= 2; + + // Flags, or priority + // Seen: 1 - Chat request + // 0 - File auto accept (type 3) + // 33 - priority ? + unpackLEWord(&buf, &wFlags); + wLen -= 2; + + // Messagetext. This is either the status message or the actual message + // when this is a PEER_MSG_MSG packet + unpackLEWord(&buf, &wTextLen); + if (wTextLen > 0) + { + pszText = (char*)_alloca(wTextLen+1); + unpackString(&buf, pszText, wTextLen); + pszText[wTextLen] = '\0'; + } + wLen = (wLen - 2) - wTextLen; + +#ifdef _DEBUG + NetLog_Direct("Handling PEER_MSG '%s', command %u, cookie %u, messagetype %u, messageflags %u, status %u, flags %u", pszText, wCommand, wCookie, bMsgType, bMsgFlags, wStatus, wFlags); +#else + NetLog_Direct("Message through direct - UID: %u", dc->dwRemoteUin); +#endif + + // The remaining actual message is handled either as a status message request, + // a greeting message, a acknowledge or a normal (text, url, file) message + if (wCommand == DIRECT_MESSAGE) + switch (bMsgType) + { + case MTYPE_FILEREQ: // File inits + handleFileRequest(buf, wLen, dc->dwRemoteUin, wCookie, 0, 0, pszText, 7, TRUE); + break; + + case MTYPE_AUTOAWAY: + case MTYPE_AUTOBUSY: + case MTYPE_AUTONA: + case MTYPE_AUTODND: + case MTYPE_AUTOFFC: + { + char **szMsg = MirandaStatusToAwayMsg(AwayMsgTypeToStatus(bMsgType)); + if (szMsg) + icq_sendAwayMsgReplyDirect(dc, wCookie, bMsgType, ( const char** )szMsg); + } + break; + + case MTYPE_PLUGIN: // Greeting + handleDirectGreetingMessage(dc, buf, wLen, wCommand, wCookie, bMsgType, bMsgFlags, wStatus, wFlags, pszText); + break; + + default: + { + message_ack_params pMsgAck = {0}; + uid_str szUID; + + buf -= wTextLen; + wLen += wTextLen; + + pMsgAck.bType = MAT_DIRECT; + pMsgAck.pDC = dc; + pMsgAck.wCookie = wCookie; + pMsgAck.msgType = bMsgType; + pMsgAck.bFlags = bMsgFlags; + handleMessageTypes(dc->dwRemoteUin, szUID, time(NULL), 0, 0, wCookie, dc->wVersion, (int)bMsgType, (int)bMsgFlags, 0, (DWORD)wLen, wTextLen, (char*)buf, MTF_DIRECT, &pMsgAck); + break; + } + } + else if (wCommand == DIRECT_ACK) + { + if (bMsgFlags == 3) + { // this is status reply + uid_str szUID; + + buf -= wTextLen; + wLen += wTextLen; + + handleMessageTypes(dc->dwRemoteUin, szUID, time(NULL), 0, 0, wCookie, dc->wVersion, (int)bMsgType, (int)bMsgFlags, 2, (DWORD)wLen, wTextLen, (char*)buf, MTF_DIRECT, NULL); + } + else + { + HANDLE hCookieContact; + cookie_message_data *pCookieData = NULL; + + if (!FindCookie(wCookie, &hCookieContact, (void**)&pCookieData)) + { + NetLog_Direct("Received an unexpected direct ack"); + } + else if (hCookieContact != dc->hContact) + { + NetLog_Direct("Direct Contact does not match Cookie Contact(0x%x != 0x%x)", dc->hContact, hCookieContact); + ReleaseCookie(wCookie); // This could be a bad idea, but I think it is safe + } + else + { // the ack is correct + int ackType = -1; + + switch (bMsgType) + { + case MTYPE_PLAIN: + ackType = ACKTYPE_MESSAGE; + break; + case MTYPE_URL: + ackType = ACKTYPE_URL; + break; + case MTYPE_CONTACTS: + ackType = ACKTYPE_CONTACTS; + break; + + case MTYPE_FILEREQ: // File acks + handleFileAck(buf, wLen, dc->dwRemoteUin, wCookie, wStatus, pszText); + break; + + case MTYPE_PLUGIN: // Greeting + handleDirectGreetingMessage(dc, buf, wLen, wCommand, wCookie, bMsgType, bMsgFlags, wStatus, wFlags, pszText); + break; + + default: + NetLog_Direct("Skipped packet from direct connection"); + break; + } + if (ackType != -1) + { // was a good ack to broadcast ? + BroadcastAck(dc->hContact, ackType, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); + ReleaseCookie(wCookie); + } + } + } + } + else if (wCommand == DIRECT_CANCEL) + { + NetLog_Direct("Cannot handle abort messages yet... :("); + } + else + NetLog_Direct("Unknown wCommand, packet skipped"); +} + +void CIcqProto::handleDirectGreetingMessage(directconnect* dc, PBYTE buf, WORD wLen, WORD wCommand, WORD wCookie, BYTE bMsgType, BYTE bMsgFlags, WORD wStatus, WORD wFlags, char* pszText) +{ + DWORD dwLengthToEnd; + DWORD dwDataLength; + char* pszFileName = NULL; + int typeId; + WORD qt; + +#ifdef _DEBUG + NetLog_Direct("Handling PEER_MSG_GREETING, command %u, cookie %u, messagetype %u, messageflags %u, status %u, flags %u", wCommand, wCookie, bMsgType, bMsgFlags, wStatus, wFlags); +#endif + + NetLog_Direct("Parsing Greeting message through direct"); + + if (!unpackPluginTypeId(&buf, &wLen, &typeId, &qt, TRUE)) return; + + // Length of remaining data + unpackLEDWord(&buf, &dwLengthToEnd); + if (dwLengthToEnd < 4 || dwLengthToEnd > wLen) + { + NetLog_Direct("Error: Sanity checking failed (%d) in handleDirectGreetingMessage, datalen %u wLen %u", 2, dwLengthToEnd, wLen); + return; + } + + // Length of message/reason + unpackLEDWord(&buf, &dwDataLength); + wLen -= 4; + if (dwDataLength > wLen) + { + NetLog_Direct("Error: Sanity checking failed (%d) in handleDirectGreetingMessage, datalen %u wLen %u", 3, dwDataLength, wLen); + return; + } + + if (typeId == MTYPE_FILEREQ && wCommand == DIRECT_MESSAGE) + { + char* szMsg; + + NetLog_Direct("This is file request"); + szMsg = (char*)_alloca(dwDataLength+1); + unpackString(&buf, szMsg, (WORD)dwDataLength); + szMsg[dwDataLength] = '\0'; + wLen = wLen - (WORD)dwDataLength; + + handleFileRequest(buf, wLen, dc->dwRemoteUin, wCookie, 0, 0, szMsg, 8, TRUE); + } + else if (typeId == MTYPE_FILEREQ && wCommand == DIRECT_ACK) + { + char* szMsg; + + NetLog_Direct("This is file ack"); + szMsg = (char*)_alloca(dwDataLength+1); + unpackString(&buf, szMsg, (WORD)dwDataLength); + szMsg[dwDataLength] = '\0'; + wLen = wLen - (WORD)dwDataLength; + + // 50 - file request granted/refused + handleFileAck(buf, wLen, dc->dwRemoteUin, wCookie, wStatus, szMsg); + } + else if (typeId && wCommand == DIRECT_MESSAGE) + { + uid_str szUID; + message_ack_params pMsgAck = {0}; + + pMsgAck.bType = MAT_DIRECT; + pMsgAck.pDC = dc; + pMsgAck.wCookie = wCookie; + pMsgAck.msgType = typeId; + handleMessageTypes(dc->dwRemoteUin, szUID, time(NULL), 0, 0, wCookie, dc->wVersion, typeId, 0, 0, dwLengthToEnd, (WORD)dwDataLength, (char*)buf, MTF_PLUGIN | MTF_DIRECT, &pMsgAck); + } + else if (typeId == MTYPE_STATUSMSGEXT && wCommand == DIRECT_ACK) + { // especially for icq2003b + NetLog_Direct("This is extended status reply"); + + char *szMsg = (char*)_alloca(dwDataLength+1); + uid_str szUID; + unpackString(&buf, szMsg, (WORD)dwDataLength); + szMsg[dwDataLength] = '\0'; + + handleMessageTypes(dc->dwRemoteUin, szUID, time(NULL), 0, 0, wCookie, dc->wVersion, (int)(qt + 0xE7), 3, 2, (DWORD)wLen, (WORD)dwDataLength, szMsg, MTF_PLUGIN | MTF_DIRECT, NULL); + } + else if (typeId && wCommand == DIRECT_ACK) + { + HANDLE hCookieContact; + cookie_message_data *pCookieData = NULL; + + if (!FindCookie(wCookie, &hCookieContact, (void**)&pCookieData)) + { + NetLog_Direct("Received an unexpected direct ack"); + } + else if (hCookieContact != dc->hContact) + { + NetLog_Direct("Direct Contact does not match Cookie Contact(0x%x != 0x%x)", dc->hContact, hCookieContact); + ReleaseCookie(wCookie); // This could be a bad idea, but I think it is safe + } + else + { + int ackType = -1; + + switch (typeId) + { + case MTYPE_MESSAGE: + ackType = ACKTYPE_MESSAGE; + break; + case MTYPE_URL: + ackType = ACKTYPE_URL; + break; + case MTYPE_CONTACTS: + ackType = ACKTYPE_CONTACTS; + break; + case MTYPE_SCRIPT_NOTIFY: + { + char *szMsg; + + szMsg = (char*)_alloca(dwDataLength + 1); + if (dwDataLength > 0) + memcpy(szMsg, buf, dwDataLength); + szMsg[dwDataLength] = '\0'; + + handleXtrazNotifyResponse(dc->dwRemoteUin, dc->hContact, wCookie, szMsg, dwDataLength); + } + break; + + default: + NetLog_Direct("Skipped packet from direct connection"); + break; + } + + if (ackType != -1) + { // was a good ack to broadcast ? + BroadcastAck(dc->hContact, ackType, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); + } + // Release cookie + ReleaseCookie(wCookie); + } + } + else + NetLog_Direct("Unsupported plugin message type %s", typeId); +} diff --git a/protocols/IcqOscarJ/src/icq_fieldnames.cpp b/protocols/IcqOscarJ/src/icq_fieldnames.cpp new file mode 100644 index 0000000000..d953a081cf --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_fieldnames.cpp @@ -0,0 +1,595 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2009 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +const FieldNamesItem countryField[]={ + {9999, LPGEN("Other")}, + {93, LPGEN("Afghanistan")}, + {355, LPGEN("Albania")}, + {213, LPGEN("Algeria")}, + {376, LPGEN("Andorra")}, + {244, LPGEN("Angola")}, + {1264, LPGEN("Anguilla")}, + {1268, LPGEN("Antigua and Barbuda")}, +//{5902, LPGEN("Antilles")}, /* removed: it is not a country, it's a group of islands from diffrent countries (all are included in the list)*/ + {54, LPGEN("Argentina")}, + {374, LPGEN("Armenia")}, + {297, LPGEN("Aruba")}, + {247, LPGEN("Ascension Island")}, + {61, LPGEN("Australia")}, + {6720, LPGEN("Australia, Antarctic Territory")}, /* added country code 672(0)*/ + {614, LPGEN("Australia, Christmas Island")}, /* rename (from Christmas Island) and change to official county code 61(4) (from 672) */ + {61891, LPGEN("Australia, Cocos (Keeling) Islands")}, /* rename and change to official county code 61(891) (from 6102) */ + {6723 , LPGEN("Australia, Norfolk Island")}, /* rename (from Norfolk Island) and change to official county code 672(3) (from 6722) */ + {43, LPGEN("Austria")}, + {994, LPGEN("Azerbaijan")}, + {1242, LPGEN("Bahamas")}, + {973, LPGEN("Bahrain")}, + {880, LPGEN("Bangladesh")}, + {1246, LPGEN("Barbados")}, +//{120, LPGEN("Barbuda")}, /* removed: it is not a country and no special island, see Antigua and Barbuda*/ + {375, LPGEN("Belarus")}, + {32, LPGEN("Belgium")}, + {501, LPGEN("Belize")}, + {229, LPGEN("Benin")}, + {1441, LPGEN("Bermuda")}, + {975, LPGEN("Bhutan")}, + {591, LPGEN("Bolivia")}, + {387, LPGEN("Bosnia and Herzegovina")}, + {267, LPGEN("Botswana")}, + {55, LPGEN("Brazil")}, + {106, LPGEN("British Virgin Islands")}, + {673, LPGEN("Brunei")}, + {359, LPGEN("Bulgaria")}, + {226, LPGEN("Burkina Faso")}, + {257, LPGEN("Burundi")}, + {855, LPGEN("Cambodia")}, + {237, LPGEN("Cameroon")}, + {1002, LPGEN("Canada")}, + {178, LPGEN("Canary Islands")}, + {238, LPGEN("Cape Verde Islands")}, + {1345, LPGEN("Cayman Islands")}, + {236, LPGEN("Central African Republic")}, + {235, LPGEN("Chad")}, + {56, LPGEN("Chile, Republic of")}, + {86, LPGEN("China")}, +//{6101, LPGEN("Cocos (Keeling) Islands")}, /* removed (double): see Australia, Cocos (Keeling) Islands */ + {57, LPGEN("Colombia")}, + {269, LPGEN("Comoros")}, + {243, LPGEN("Congo, Democratic Republic of (Zaire)")}, + {242, LPGEN("Congo, Republic of the")}, + {682, LPGEN("Cook Islands")}, + {506, LPGEN("Costa Rica")}, + {225, LPGEN("Cote d'Ivoire (Ivory Coast)")}, + {385, LPGEN("Croatia")}, + {53, LPGEN("Cuba")}, + {357, LPGEN("Greek, Republic of South Cyprus")}, /* rename coz Turkey, Republic of Northern Cyprus */ + {420, LPGEN("Czech Republic")}, + {45, LPGEN("Denmark")}, + {246, LPGEN("Diego Garcia")}, + {253, LPGEN("Djibouti")}, + {1767, LPGEN("Dominica")}, + {1809, LPGEN("Dominican Republic")}, + {593, LPGEN("Ecuador")}, + {20, LPGEN("Egypt")}, + {503, LPGEN("El Salvador")}, + {240, LPGEN("Equatorial Guinea")}, + {291, LPGEN("Eritrea")}, + {372, LPGEN("Estonia")}, + {251, LPGEN("Ethiopia")}, + {3883,LPGEN("Europe")}, /* add county code +388 3 official European Telephony Numbering Space*/ + {298, LPGEN("Faeroe Islands")}, + {500, LPGEN("Falkland Islands")}, + {679, LPGEN("Fiji")}, + {358, LPGEN("Finland")}, + {33, LPGEN("France")}, + {5901, LPGEN("French Antilles")}, + {594, LPGEN("French Guiana")}, + {689, LPGEN("French Polynesia")}, + {241, LPGEN("Gabon")}, + {220, LPGEN("Gambia")}, + {995, LPGEN("Georgia")}, + {49, LPGEN("Germany")}, + {233, LPGEN("Ghana")}, + {350, LPGEN("Gibraltar")}, + {30, LPGEN("Greece")}, + {299, LPGEN("Greenland")}, + {1473, LPGEN("Grenada")}, + {590, LPGEN("Guadeloupe")}, + {1671, LPGEN("Guam, US Territory of")}, + {502, LPGEN("Guatemala")}, + {224, LPGEN("Guinea")}, + {245, LPGEN("Guinea-Bissau")}, + {592, LPGEN("Guyana")}, + {509, LPGEN("Haiti")}, + {504, LPGEN("Honduras")}, + {852, LPGEN("Hong Kong")}, + {36, LPGEN("Hungary")}, + {354, LPGEN("Iceland")}, + {91, LPGEN("India")}, + {62, LPGEN("Indonesia")}, + {98, LPGEN("Iran (Islamic Republic of)")}, + {964, LPGEN("Iraq")}, + {353, LPGEN("Ireland")}, + {972, LPGEN("Israel")}, + {39, LPGEN("Italy")}, + {1876, LPGEN("Jamaica")}, + {81, LPGEN("Japan")}, + {962, LPGEN("Jordan")}, + {705, LPGEN("Kazakhstan")}, + {254, LPGEN("Kenya")}, + {686, LPGEN("Kiribati")}, + {850, LPGEN("Korea, North")}, + {82, LPGEN("Korea, South")}, + {965, LPGEN("Kuwait")}, + {996, LPGEN("Kyrgyzstan")}, + {856, LPGEN("Laos")}, + {371, LPGEN("Latvia")}, + {961, LPGEN("Lebanon")}, + {266, LPGEN("Lesotho")}, + {231, LPGEN("Liberia")}, + {218, LPGEN("Libyan Arab Jamahiriya")}, + {423, LPGEN("Liechtenstein")}, + {370, LPGEN("Lithuania")}, + {352, LPGEN("Luxembourg")}, + {853, LPGEN("Macau")}, + {389, LPGEN("Macedonia, Republic of")}, + {261, LPGEN("Madagascar")}, + {265, LPGEN("Malawi")}, + {60, LPGEN("Malaysia")}, + {960, LPGEN("Maldives")}, + {223, LPGEN("Mali")}, + {356, LPGEN("Malta")}, + {692, LPGEN("Marshall Islands")}, + {596, LPGEN("Martinique")}, + {222, LPGEN("Mauritania")}, + {230, LPGEN("Mauritius")}, + {262, LPGEN("Mayotte Island")}, + {52, LPGEN("Mexico")}, + {691, LPGEN("Micronesia, Federated States of")}, + {373, LPGEN("Moldova, Republic of")}, + {377, LPGEN("Monaco")}, + {976, LPGEN("Mongolia")}, + {1664, LPGEN("Montserrat")}, + {212, LPGEN("Morocco")}, + {258, LPGEN("Mozambique")}, + {95, LPGEN("Myanmar")}, + {264, LPGEN("Namibia")}, + {674, LPGEN("Nauru")}, + {977, LPGEN("Nepal")}, + {31, LPGEN("Netherlands")}, + {599, LPGEN("Netherlands Antilles")}, /* dissolved 2010 */ + {5995, LPGEN("St. Maarten")}, /* add new country in 2010 (from Netherlands Antilles) */ + {5999, LPGEN("Curacao")}, /* add new country in 2010 (from Netherlands Antilles) */ + {5997, LPGEN("Netherlands (Bonaire Island)")}, /* add new Part of Netherlands in 2010 (from Netherlands Antilles) */ + {59946, LPGEN("Netherlands (Saba Island)")}, /* add new Part of Netherlands in 2010 (from Netherlands Antilles) */ + {59938, LPGEN("Netherlands (St. Eustatius Island)")}, /* add new Part of Netherlands in 2010 (from Netherlands Antilles) */ +//{114, LPGEN("Nevis")}, /* removed: it is not a country, it's part of Saint Kitts and Nevis*/ + {687, LPGEN("New Caledonia")}, + {64, LPGEN("New Zealand")}, + {505, LPGEN("Nicaragua")}, + {227, LPGEN("Niger")}, + {234, LPGEN("Nigeria")}, + {683, LPGEN("Niue")}, + {1670, LPGEN("Northern Mariana Islands, US Territory of")}, /* added NANP */ + {47, LPGEN("Norway")}, + {968, LPGEN("Oman")}, + {92, LPGEN("Pakistan")}, + {680, LPGEN("Palau")}, + {507, LPGEN("Panama")}, + {675, LPGEN("Papua New Guinea")}, + {595, LPGEN("Paraguay")}, + {51, LPGEN("Peru")}, + {63, LPGEN("Philippines")}, + {48, LPGEN("Poland")}, + {351, LPGEN("Portugal")}, + {1939, LPGEN("Puerto Rico")}, + {974, LPGEN("Qatar")}, + {262, LPGEN("Reunion Island")}, + {40, LPGEN("Romania")}, +//{6701, LPGEN("Rota Island")}, /* removed: it is not a country it is part of Northern Mariana Islands, US Territory of */ + {7, LPGEN("Russia")}, + {250, LPGEN("Rwanda")}, + {1684, LPGEN("Samoa (USA)")}, /* rename (from American Samoa) change county code to NANP (from 684) */ + {685, LPGEN("Samoa, Western")}, /* rename (from Western Samoa) */ + {290, LPGEN("Saint Helena")}, +//{115, LPGEN("Saint Kitts")}, /* removed: it is not a country it is part of Saint Kitts and Nevis*/ + {1869, LPGEN("Saint Kitts and Nevis")}, + {1758, LPGEN("Saint Lucia")}, + {508, LPGEN("Saint Pierre and Miquelon")}, + {1784, LPGEN("Saint Vincent and the Grenadines")}, +//{670, LPGEN("Saipan Island")}, /* removed: it is not a country it is part of Northern Mariana Islands, US Territory of */ + {378, LPGEN("San Marino")}, + {239, LPGEN("Sao Tome and Principe")}, + {966, LPGEN("Saudi Arabia")}, + {442, LPGEN("Scotland")}, + {221, LPGEN("Senegal")}, + {248, LPGEN("Seychelles")}, + {232, LPGEN("Sierra Leone")}, + {65, LPGEN("Singapore")}, + {421, LPGEN("Slovakia")}, + {386, LPGEN("Slovenia")}, + {677, LPGEN("Solomon Islands")}, + {252, LPGEN("Somalia")}, + {27, LPGEN("South Africa")}, + {34, LPGEN("Spain")}, + {3492, LPGEN("Spain, Canary Islands")}, /*rename and change county code to 34(92) spain + canary code*/ + {94, LPGEN("Sri Lanka")}, + {249, LPGEN("Sudan")}, + {597, LPGEN("Suriname")}, + {268, LPGEN("Swaziland")}, + {46, LPGEN("Sweden")}, + {41, LPGEN("Switzerland")}, + {963, LPGEN("Syrian Arab Republic")}, + {886, LPGEN("Taiwan")}, + {992, LPGEN("Tajikistan")}, + {255, LPGEN("Tanzania")}, + {66, LPGEN("Thailand")}, +//{6702, LPGEN("Tinian Island")}, /* removed: it is not a country it is part of Northern Mariana Islands, US Territory of */ + {670 , LPGEN("Timor, East")}, /* added (is part off Northern Mariana Islands but not US Territory*/ + {228, LPGEN("Togo")}, + {690, LPGEN("Tokelau")}, + {676, LPGEN("Tonga")}, + {1868, LPGEN("Trinidad and Tobago")}, + {216, LPGEN("Tunisia")}, + {90, LPGEN("Turkey")}, + {90392, LPGEN("Turkey, Republic of Northern Cyprus")}, /* added (is diffrent from Greek part)*/ + {993, LPGEN("Turkmenistan")}, + {1649, LPGEN("Turks and Caicos Islands")}, + {688, LPGEN("Tuvalu")}, + {256, LPGEN("Uganda")}, + {380, LPGEN("Ukraine")}, + {971, LPGEN("United Arab Emirates")}, + {44, LPGEN("United Kingdom")}, + {598, LPGEN("Uruguay")}, + {1, LPGEN("USA")}, + {998, LPGEN("Uzbekistan")}, + {678, LPGEN("Vanuatu")}, + {379, LPGEN("Vatican City")}, + {58, LPGEN("Venezuela")}, + {84, LPGEN("Vietnam")}, + {1284, LPGEN("Virgin Islands (UK)")}, /* change county code to NANP (from 105) - rename coz Virgin Islands (USA) */ + {1340, LPGEN("Virgin Islands (USA)")}, /* change county code to NANP (from 123) */ + {441, LPGEN("Wales")}, + {681, LPGEN("Wallis and Futuna Islands")}, + {967, LPGEN("Yemen")}, + {38, LPGEN("Yugoslavia")}, + {381, LPGEN("Serbia, Republic of")}, /* rename need (from Yugoslavia)*/ + {383, LPGEN("Kosovo, Republic of")}, /*change country code (from 3811), rename need (from Yugoslavia - Serbia) */ + {382, LPGEN("Montenegro, Republic of")}, /* rename need (from Yugoslavia - Montenegro) */ + {260, LPGEN("Zambia")}, + {263, LPGEN("Zimbabwe")}, + {0, NULL} +}; + + +const FieldNamesItem interestsField[]={ + {137, LPGEN("50's")}, + {134, LPGEN("60's")}, + {135, LPGEN("70's")}, + {136, LPGEN("80's")}, + {100, LPGEN("Art")}, + {128, LPGEN("Astronomy")}, + {147, LPGEN("Audio and Visual")}, + {125, LPGEN("Business")}, + {146, LPGEN("Business Services")}, + {101, LPGEN("Cars")}, + {102, LPGEN("Celebrity Fans")}, + {130, LPGEN("Clothing")}, + {103, LPGEN("Collections")}, + {104, LPGEN("Computers")}, + {105, LPGEN("Culture")}, + {122, LPGEN("Ecology")}, + {139, LPGEN("Entertainment")}, + {138, LPGEN("Finance and Corporate")}, + {106, LPGEN("Fitness")}, + {142, LPGEN("Health and Beauty")}, + {108, LPGEN("Hobbies")}, + {150, LPGEN("Home Automation")}, + {144, LPGEN("Household Products")}, + {107, LPGEN("Games")}, + {124, LPGEN("Government")}, + {109, LPGEN("ICQ - Help")}, + {110, LPGEN("Internet")}, + {111, LPGEN("Lifestyle")}, + {145, LPGEN("Mail Order Catalog")}, + {143, LPGEN("Media")}, + {112, LPGEN("Movies and TV")}, + {113, LPGEN("Music")}, + {126, LPGEN("Mystics")}, + {123, LPGEN("News and Media")}, + {114, LPGEN("Outdoors")}, + {115, LPGEN("Parenting")}, + {131, LPGEN("Parties")}, + {116, LPGEN("Pets and Animals")}, + {149, LPGEN("Publishing")}, + {117, LPGEN("Religion")}, + {141, LPGEN("Retail Stores")}, + {118, LPGEN("Science")}, + {119, LPGEN("Skills")}, + {133, LPGEN("Social science")}, + {129, LPGEN("Space")}, + {148, LPGEN("Sporting and Athletic")}, + {120, LPGEN("Sports")}, + {127, LPGEN("Travel")}, + {121, LPGEN("Web Design")}, + {132, LPGEN("Women")}, + {-1, NULL} +}; + + +const FieldNamesItem languageField[]={ + {55, LPGEN("Afrikaans")}, + {58, LPGEN("Albanian")}, + {1, LPGEN("Arabic")}, + {59, LPGEN("Armenian")}, + {68, LPGEN("Azerbaijani")}, + {72, LPGEN("Belorussian")}, + {2, LPGEN("Bhojpuri")}, + {56, LPGEN("Bosnian")}, + {3, LPGEN("Bulgarian")}, + {4, LPGEN("Burmese")}, + {5, LPGEN("Cantonese")}, + {6, LPGEN("Catalan")}, + {61, LPGEN("Chamorro")}, + {7, LPGEN("Chinese")}, + {8, LPGEN("Croatian")}, + {9, LPGEN("Czech")}, + {10, LPGEN("Danish")}, + {11, LPGEN("Dutch")}, + {12, LPGEN("English")}, + {13, LPGEN("Esperanto")}, + {14, LPGEN("Estonian")}, + {15, LPGEN("Farsi")}, + {16, LPGEN("Finnish")}, + {17, LPGEN("French")}, + {18, LPGEN("Gaelic")}, + {19, LPGEN("German")}, + {20, LPGEN("Greek")}, + {70, LPGEN("Gujarati")}, + {21, LPGEN("Hebrew")}, + {22, LPGEN("Hindi")}, + {23, LPGEN("Hungarian")}, + {24, LPGEN("Icelandic")}, + {25, LPGEN("Indonesian")}, + {26, LPGEN("Italian")}, + {27, LPGEN("Japanese")}, + {28, LPGEN("Khmer")}, + {29, LPGEN("Korean")}, + {69, LPGEN("Kurdish")}, + {30, LPGEN("Lao")}, + {31, LPGEN("Latvian")}, + {32, LPGEN("Lithuanian")}, + {65, LPGEN("Macedonian")}, + {33, LPGEN("Malay")}, + {63, LPGEN("Mandarin")}, + {62, LPGEN("Mongolian")}, + {34, LPGEN("Norwegian")}, + {57, LPGEN("Persian")}, + {35, LPGEN("Polish")}, + {36, LPGEN("Portuguese")}, + {60, LPGEN("Punjabi")}, + {37, LPGEN("Romanian")}, + {38, LPGEN("Russian")}, + {39, LPGEN("Serbian")}, + {66, LPGEN("Sindhi")}, + {40, LPGEN("Slovak")}, + {41, LPGEN("Slovenian")}, + {42, LPGEN("Somali")}, + {43, LPGEN("Spanish")}, + {44, LPGEN("Swahili")}, + {45, LPGEN("Swedish")}, + {46, LPGEN("Tagalog")}, + {64, LPGEN("Taiwanese")}, + {71, LPGEN("Tamil")}, + {47, LPGEN("Tatar")}, + {48, LPGEN("Thai")}, + {49, LPGEN("Turkish")}, + {50, LPGEN("Ukrainian")}, + {51, LPGEN("Urdu")}, + {52, LPGEN("Vietnamese")}, + {67, LPGEN("Welsh")}, + {53, LPGEN("Yiddish")}, + {54, LPGEN("Yoruba")}, + {0, NULL} +}; + + +const FieldNamesItem pastField[]={ + {300, LPGEN("Elementary School")}, + {301, LPGEN("High School")}, + {302, LPGEN("College")}, + {303, LPGEN("University")}, + {304, LPGEN("Military")}, + {305, LPGEN("Past Work Place")}, + {306, LPGEN("Past Organization")}, + {399, LPGEN("Other")}, + {0, NULL} +}; + + +const FieldNamesItem genderField[]={ + {'F', LPGEN("Female")}, + {'M', LPGEN("Male")}, + {0, NULL} +}; + + +const FieldNamesItem studyLevelField[]={ + {4, LPGEN("Associated degree")}, + {5, LPGEN("Bachelor's degree")}, + {1, LPGEN("Elementary")}, + {2, LPGEN("High-school")}, + {6, LPGEN("Master's degree")}, + {7, LPGEN("PhD")}, + {8, LPGEN("Postdoctoral")}, + {3, LPGEN("University / College")}, + {0, NULL} +}; + + +const FieldNamesItem industryField[]={ + {2, LPGEN("Agriculture")}, + {3, LPGEN("Arts")}, + {4, LPGEN("Construction")}, + {5, LPGEN("Consumer Goods")}, + {6, LPGEN("Corporate Services")}, + {7, LPGEN("Education")}, + {8, LPGEN("Finance")}, + {9, LPGEN("Government")}, + {10, LPGEN("High Tech")}, + {11, LPGEN("Legal")}, + {12, LPGEN("Manufacturing")}, + {13, LPGEN("Media")}, + {14, LPGEN("Medical & Health Care")}, + {15, LPGEN("Non-Profit Organization Management")}, + {19, LPGEN("Other")}, + {16, LPGEN("Recreation, Travel & Entertainment")}, + {17, LPGEN("Service Industry")}, + {18, LPGEN("Transportation")}, + {0, NULL} +}; + + +const FieldNamesItem occupationField[]={ + {1, LPGEN("Academic")}, + {2, LPGEN("Administrative")}, + {3, LPGEN("Art/Entertainment")}, + {4, LPGEN("College Student")}, + {5, LPGEN("Computers")}, + {6, LPGEN("Community & Social")}, + {7, LPGEN("Education")}, + {8, LPGEN("Engineering")}, + {9, LPGEN("Financial Services")}, + {10, LPGEN("Government")}, + {11, LPGEN("High School Student")}, + {12, LPGEN("Home")}, + {13, LPGEN("ICQ - Providing Help")}, + {14, LPGEN("Law")}, + {15, LPGEN("Managerial")}, + {16, LPGEN("Manufacturing")}, + {17, LPGEN("Medical/Health")}, + {18, LPGEN("Military")}, + {19, LPGEN("Non-Government Organization")}, + {20, LPGEN("Professional")}, + {21, LPGEN("Retail")}, + {22, LPGEN("Retired")}, + {23, LPGEN("Science & Research")}, + {24, LPGEN("Sports")}, + {25, LPGEN("Technical")}, + {26, LPGEN("University Student")}, + {27, LPGEN("Web Building")}, + {99, LPGEN("Other Services")}, + {0, NULL} +}; + + +const FieldNamesItem affiliationField[]={ + {200, LPGEN("Alumni Org.")}, + {201, LPGEN("Charity Org.")}, + {202, LPGEN("Club/Social Org.")}, + {203, LPGEN("Community Org.")}, + {204, LPGEN("Cultural Org.")}, + {205, LPGEN("Fan Clubs")}, + {206, LPGEN("Fraternity/Sorority")}, + {207, LPGEN("Hobbyists Org.")}, + {208, LPGEN("International Org.")}, + {209, LPGEN("Nature and Environment Org.")}, + {210, LPGEN("Professional Org.")}, + {211, LPGEN("Scientific/Technical Org.")}, + {212, LPGEN("Self Improvement Group")}, + {213, LPGEN("Spiritual/Religious Org.")}, + {214, LPGEN("Sports Org.")}, + {215, LPGEN("Support Org.")}, + {216, LPGEN("Trade and Business Org.")}, + {217, LPGEN("Union")}, + {218, LPGEN("Volunteer Org.")}, + {299, LPGEN("Other")}, + {0, NULL} +}; + + +const FieldNamesItem agesField[]={ + {0x0011000D, LPGEN("13-17")}, + {0x00160012, LPGEN("18-22")}, + {0x001D0017, LPGEN("23-29")}, + {0x0027001E, LPGEN("30-39")}, + {0x00310028, LPGEN("40-49")}, + {0x003B0032, LPGEN("50-59")}, + {0x2710003C, LPGEN("60-above")}, + {-1, NULL} +}; + + +const FieldNamesItem maritalField[]={ + {10, LPGEN("Single")}, + {11, LPGEN("Close relationships")}, + {12, LPGEN("Engaged")}, + {20, LPGEN("Married")}, + {30, LPGEN("Divorced")}, + {31, LPGEN("Separated")}, + {40, LPGEN("Widowed")}, + {50, LPGEN("Open relationship")}, + {255, LPGEN("Other")}, + {0, NULL} +}; + + +char *LookupFieldName(const FieldNamesItem *table, int code) +{ + int i; + + if (code != 0) + { + for(i = 0; table[i].text; i++) + { + if (table[i].code == code) + return table[i].text; + } + + // Tried to get unexisting field name, you have an + // error in the data or in the table + _ASSERT(FALSE); + } + + return NULL; +} + + +char *LookupFieldNameUtf(const FieldNamesItem *table, int code, char *str, size_t strsize) +{ + char *szText = LookupFieldName(table, code); + + if (szText) + return ICQTranslateUtfStatic(szText, str, strsize); + + return NULL; +} diff --git a/protocols/IcqOscarJ/src/icq_fieldnames.h b/protocols/IcqOscarJ/src/icq_fieldnames.h new file mode 100644 index 0000000000..cf15475299 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_fieldnames.h @@ -0,0 +1,49 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2009 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +struct FieldNamesItem +{ + int code; + char *text; +}; + +extern const FieldNamesItem countryField[]; +extern const FieldNamesItem interestsField[]; +extern const FieldNamesItem languageField[]; +extern const FieldNamesItem pastField[]; +extern const FieldNamesItem genderField[]; +extern const FieldNamesItem agesField[]; +extern const FieldNamesItem studyLevelField[]; +extern const FieldNamesItem industryField[]; +extern const FieldNamesItem occupationField[]; +extern const FieldNamesItem affiliationField[]; +extern const FieldNamesItem maritalField[]; + +char *LookupFieldName(const FieldNamesItem *table, int code); +char *LookupFieldNameUtf(const FieldNamesItem *table, int code, char *str, size_t strsize); diff --git a/protocols/IcqOscarJ/src/icq_filerequests.cpp b/protocols/IcqOscarJ/src/icq_filerequests.cpp new file mode 100644 index 0000000000..d85de72580 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_filerequests.cpp @@ -0,0 +1,218 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2009 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +void CIcqProto::handleFileAck(PBYTE buf, WORD wLen, DWORD dwUin, DWORD dwCookie, WORD wStatus, char* pszText) +{ + char* pszFileName = NULL; + DWORD dwFileSize; + HANDLE hCookieContact; + WORD wPort; + WORD wFilenameLength; + filetransfer* ft; + + + // Find the filetransfer that belongs to this response + if (!FindCookie(dwCookie, &hCookieContact, (void**)&ft)) + { + NetLog_Direct("Error: Received unexpected file transfer request response"); + return; + } + + FreeCookie(dwCookie); + + if (hCookieContact != HContactFromUIN(dwUin, NULL)) + { + NetLog_Direct("Error: UINs do not match in file transfer request response"); + return; + } + + // If status != 0, a request has been denied + if (wStatus != 0) + { + NetLog_Direct("File transfer denied by %u.", dwUin); + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_DENIED, (HANDLE)ft, 0); + + FreeCookie(dwCookie); + + return; + } + + if (wLen < 6) + { // sanity check + NetLog_Direct("Ignoring malformed file transfer request response"); + return; + } + + // Port to connect to + unpackWord(&buf, &wPort); + ft->dwRemotePort = wPort; + wLen -= 2; + + // Unknown + buf += 2; + wLen -= 2; + + // Filename + unpackLEWord(&buf, &wFilenameLength); + if (wFilenameLength > 0) + { + if (wFilenameLength > wLen - 2) + wFilenameLength = wLen - 2; + pszFileName = (char*)_alloca(wFilenameLength+1); + unpackString(&buf, pszFileName, wFilenameLength); + pszFileName[wFilenameLength] = '\0'; + } + wLen = wLen - 2 - wFilenameLength; + + if (wLen >= 4) + { // Total filesize + unpackLEDWord(&buf, &dwFileSize); + wLen -= 4; + } + else + dwFileSize = 0; + + NetLog_Direct("File transfer ack from %u, port %u, name %s, size %u", dwUin, ft->dwRemotePort, pszFileName, dwFileSize); + + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTING, (HANDLE)ft, 0); + + OpenDirectConnection(ft->hContact, DIRECTCONN_FILE, ft); +} + +filetransfer* CIcqProto::CreateFileTransfer(HANDLE hContact, DWORD dwUin, int nVersion) +{ + filetransfer *ft = CreateIcqFileTransfer(); + + ft->dwUin = dwUin; + ft->hContact = hContact; + ft->nVersion = nVersion; + ft->pMessage.bMessageType = MTYPE_FILEREQ; + InitMessageCookie(&ft->pMessage); + + return ft; +} + +// pszDescription points to a string with the reason +// buf points to the first data after the string +void CIcqProto::handleFileRequest(PBYTE buf, WORD wLen, DWORD dwUin, DWORD dwCookie, DWORD dwID1, DWORD dwID2, char* pszDescription, int nVersion, BOOL bDC) +{ + BOOL bEmptyDesc = FALSE; + if (strlennull(pszDescription) == 0) { + pszDescription = Translate("No description given"); + bEmptyDesc = TRUE; + } + + // Empty port+pad + buf += 4; + wLen -= 4; + + // Filename + WORD wFilenameLength; + unpackLEWord(&buf, &wFilenameLength); + if (!wFilenameLength) { + NetLog_Direct("Ignoring malformed file send request"); + return; + } + + char *pszFileName = (char*)_alloca(wFilenameLength + 1); + unpackString(&buf, pszFileName, wFilenameLength); + pszFileName[wFilenameLength] = '\0'; + + wLen = wLen - 2 - wFilenameLength; + + // Total filesize + DWORD dwFileSize; + unpackLEDWord(&buf, &dwFileSize); + wLen -= 4; + + int bAdded; + HANDLE hContact = HContactFromUIN(dwUin, &bAdded); + + // Initialize a filetransfer struct + filetransfer *ft = CreateFileTransfer(hContact, dwUin, nVersion); + ft->dwCookie = dwCookie; + ft->szFilename = mir_strdup(pszFileName); + ft->szDescription = 0; + ft->fileId = -1; + ft->dwTotalSize = dwFileSize; + ft->pMessage.dwMsgID1 = dwID1; + ft->pMessage.dwMsgID2 = dwID2; + ft->bDC = bDC; + ft->bEmptyDesc = bEmptyDesc; + + // Send chain event + TCHAR* ptszFileName = mir_utf8decodeT(pszFileName); + + PROTORECVFILET pre = {0}; + pre.flags = PREF_TCHAR; + pre.fileCount = 1; + pre.timestamp = time(NULL); + pre.tszDescription = mir_utf8decodeT(pszDescription); + pre.ptszFiles = &ptszFileName; + pre.lParam = (LPARAM)ft; + + CCSDATA ccs; + ccs.szProtoService = PSR_FILE; + ccs.hContact = hContact; + ccs.wParam = 0; + ccs.lParam = (LPARAM)⪯ + CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs); + + mir_free(pre.tszDescription); + mir_free(ptszFileName); +} + +void CIcqProto::handleDirectCancel(directconnect *dc, PBYTE buf, WORD wLen, WORD wCommand, DWORD dwCookie, WORD wMessageType, WORD wStatus, WORD wFlags, char* pszText) +{ + NetLog_Direct("handleDirectCancel: Unhandled cancel"); +} + +// ******************************************************************************* + +void CIcqProto::icq_CancelFileTransfer(HANDLE hContact, filetransfer* ft) +{ + DWORD dwCookie; + + if (FindCookieByData(ft, &dwCookie, NULL)) + FreeCookie(dwCookie); /* this bit stops a send that's waiting for acceptance */ + + if (IsValidFileTransfer(ft)) + { // Transfer still out there, end it + NetLib_CloseConnection(&ft->hConnection, FALSE); + + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0); + + if (!FindFileTransferDC(ft)) + { // Release orphan structure only + SafeReleaseFileTransfer((void**)&ft); + } + } +} diff --git a/protocols/IcqOscarJ/src/icq_filetransfer.cpp b/protocols/IcqOscarJ/src/icq_filetransfer.cpp new file mode 100644 index 0000000000..61484c3987 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_filetransfer.cpp @@ -0,0 +1,515 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2009 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +static void file_buildProtoFileTransferStatus(filetransfer* ft, PROTOFILETRANSFERSTATUS* pfts) +{ + ZeroMemory(pfts, sizeof(PROTOFILETRANSFERSTATUS)); + pfts->cbSize = sizeof(PROTOFILETRANSFERSTATUS); + pfts->hContact = ft->hContact; + pfts->flags = PFTS_UTF | (ft->sending ? PFTS_SENDING : PFTS_RECEIVING); /* Standard FT is Ansi only */ + if (ft->sending) + pfts->pszFiles = ft->pszFiles; + else + pfts->pszFiles = NULL; /* FIXME */ + pfts->totalFiles = ft->dwFileCount; + pfts->currentFileNumber = ft->iCurrentFile; + pfts->totalBytes = ft->dwTotalSize; + pfts->totalProgress = ft->dwBytesDone; + pfts->szWorkingDir = ft->szSavePath; + pfts->szCurrentFile = ft->szThisFile; + pfts->currentFileSize = ft->dwThisFileSize; + pfts->currentFileTime = ft->dwThisFileDate; + pfts->currentFileProgress = ft->dwFileBytesDone; +} + + +static void file_sendTransferSpeed(CIcqProto* ppro, directconnect* dc) +{ + icq_packet packet; + + directPacketInit(&packet, 5); + packByte(&packet, PEER_FILE_SPEED); /* Ident */ + packLEDWord(&packet, dc->ft->dwTransferSpeed); + ppro->sendDirectPacket(dc, &packet); +} + + +static void file_sendNick(CIcqProto* ppro, directconnect* dc) +{ + icq_packet packet; + char* szNick; + WORD wNickLen; + DBVARIANT dbv = {DBVT_DELETED}; + + if (ppro->getSettingString(NULL, "Nick", &dbv)) + szNick = ""; + else + szNick = dbv.pszVal; + + wNickLen = strlennull(szNick); + + directPacketInit(&packet, (WORD)(8 + wNickLen)); + packByte(&packet, PEER_FILE_INIT_ACK); /* Ident */ + packLEDWord(&packet, dc->ft->dwTransferSpeed); + packLEWord(&packet, (WORD)(wNickLen + 1)); + packBuffer(&packet, (LPBYTE)szNick, (WORD)(wNickLen + 1)); + ppro->sendDirectPacket(dc, &packet); + ICQFreeVariant(&dbv); +} + + +static void file_sendNextFile(CIcqProto* ppro, directconnect* dc) +{ + icq_packet packet; + struct _stati64 statbuf; + char szThisSubDir[MAX_PATH]; + + if (dc->ft->iCurrentFile >= (int)dc->ft->dwFileCount) + { + ppro->BroadcastAck(dc->ft->hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, dc->ft, 0); + ppro->CloseDirectConnection(dc); + dc->ft->hConnection = NULL; + return; + } + + dc->ft->szThisFile = dc->ft->pszFiles[dc->ft->iCurrentFile]; + if (FileStatUtf(dc->ft->szThisFile, &statbuf)) + { + ppro->icq_LogMessage(LOG_ERROR, LPGEN("Your file transfer has been aborted because one of the files that you selected to send is no longer readable from the disk. You may have deleted or moved it.")); + ppro->CloseDirectConnection(dc); + dc->ft->hConnection = NULL; + return; + } + + char *pszThisFileName = FindFilePathContainer((LPCSTR*)dc->ft->pszFiles, dc->ft->iCurrentFile, szThisSubDir); + + if (statbuf.st_mode&_S_IFDIR) + { + dc->ft->currentIsDir = 1; + } + else + { + dc->ft->currentIsDir = 0; + dc->ft->fileId = OpenFileUtf(dc->ft->szThisFile, _O_BINARY | _O_RDONLY, _S_IREAD); + if (dc->ft->fileId == -1) + { + ppro->icq_LogMessage(LOG_ERROR, LPGEN("Your file transfer has been aborted because one of the files that you selected to send is no longer readable from the disk. You may have deleted or moved it.")); + ppro->CloseDirectConnection(dc); + dc->ft->hConnection = NULL; + return; + } + + } + dc->ft->dwThisFileSize = statbuf.st_size; + dc->ft->dwThisFileDate = statbuf.st_mtime; + dc->ft->dwFileBytesDone = 0; + + char *szThisFileNameAnsi = NULL, *szThisSubDirAnsi = NULL; + if (!utf8_decode(pszThisFileName, &szThisFileNameAnsi)) + szThisFileNameAnsi = NULL; + if (!utf8_decode(szThisSubDir, &szThisSubDirAnsi)) + szThisSubDirAnsi = NULL; + WORD wThisFileNameLen = strlennull(szThisFileNameAnsi); + WORD wThisSubDirLen = strlennull(szThisSubDirAnsi); + + directPacketInit(&packet, (WORD)(20 + wThisFileNameLen + wThisSubDirLen)); + packByte(&packet, PEER_FILE_NEXTFILE); /* Ident */ + packByte(&packet, (BYTE)((statbuf.st_mode & _S_IFDIR) != 0)); // Is subdir + packLEWord(&packet, (WORD)(wThisFileNameLen + 1)); + packBuffer(&packet, (LPBYTE)szThisFileNameAnsi, (WORD)(wThisFileNameLen + 1)); + packLEWord(&packet, (WORD)(wThisSubDirLen + 1)); + packBuffer(&packet, (LPBYTE)szThisSubDirAnsi, (WORD)(wThisSubDirLen + 1)); + packLEDWord(&packet, dc->ft->dwThisFileSize); + packLEDWord(&packet, statbuf.st_mtime); + packLEDWord(&packet, dc->ft->dwTransferSpeed); + SAFE_FREE(&szThisFileNameAnsi); + SAFE_FREE(&szThisSubDirAnsi); + ppro->sendDirectPacket(dc, &packet); + + ppro->BroadcastAck(dc->ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, dc->ft, 0); +} + + +static void file_sendResume(CIcqProto* ppro, directconnect* dc) +{ + icq_packet packet; + + directPacketInit(&packet, 17); + packByte(&packet, PEER_FILE_RESUME); /* Ident */ + packLEDWord(&packet, dc->ft->dwFileBytesDone); /* file resume */ + packLEDWord(&packet, 0); /* unknown */ + packLEDWord(&packet, dc->ft->dwTransferSpeed); + packLEDWord(&packet, dc->ft->iCurrentFile + 1); /* file number */ + ppro->sendDirectPacket(dc, &packet); +} + + +static void file_sendData(CIcqProto* ppro, directconnect* dc) +{ + BYTE buf[2048]; + int bytesRead = 0; + + if (!dc->ft->currentIsDir) + { + icq_packet packet; + + if (dc->ft->fileId == -1) + return; + bytesRead = _read(dc->ft->fileId, buf, sizeof(buf)); + if (bytesRead == -1) + return; + + directPacketInit(&packet, (WORD)(1 + bytesRead)); + packByte(&packet, PEER_FILE_DATA); /* Ident */ + packBuffer(&packet, buf, (WORD)bytesRead); + ppro->sendDirectPacket(dc, &packet); + } + + dc->ft->dwBytesDone += bytesRead; + dc->ft->dwFileBytesDone += bytesRead; + + if (GetTickCount() > dc->ft->dwLastNotify + 500 || bytesRead == 0) + { + PROTOFILETRANSFERSTATUS pfts; + + file_buildProtoFileTransferStatus(dc->ft, &pfts); + ppro->BroadcastAck(dc->ft->hContact, ACKTYPE_FILE, ACKRESULT_DATA, dc->ft, (LPARAM)&pfts); + + dc->ft->dwLastNotify = GetTickCount(); + } + + if (bytesRead == 0) + { + if (!dc->ft->currentIsDir) _close(dc->ft->fileId); + dc->ft->fileId = -1; + dc->wantIdleTime = 0; + dc->ft->iCurrentFile++; + file_sendNextFile(ppro, dc); /* this will close the socket if no more files */ + } +} + + +void CIcqProto::handleFileTransferIdle(directconnect* dc) +{ + file_sendData(this, dc); +} + + +void CIcqProto::icq_sendFileResume(filetransfer *ft, int action, const char *szFilename) +{ + if (ft->hConnection == NULL) + return; + + directconnect *dc = FindFileTransferDC(ft); + if (!dc) return; // something is broken... + + int openFlags; + + switch (action) + { + case FILERESUME_RESUME: + openFlags = _O_BINARY | _O_WRONLY; + break; + + case FILERESUME_OVERWRITE: + openFlags = _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY; + ft->dwFileBytesDone = 0; + break; + + case FILERESUME_SKIP: + openFlags = _O_BINARY | _O_WRONLY; + ft->dwFileBytesDone = ft->dwThisFileSize; + break; + + case FILERESUME_RENAME: + openFlags = _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY; + SAFE_FREE(&ft->szThisFile); + ft->szThisFile = null_strdup(szFilename); + ft->dwFileBytesDone = 0; + break; + } + + ft->fileId = OpenFileUtf(ft->szThisFile, openFlags, _S_IREAD | _S_IWRITE); + if (ft->fileId == -1) + { + icq_LogMessage(LOG_ERROR, LPGEN("Your file receive has been aborted because Miranda could not open the destination file in order to write to it. You may be trying to save to a read-only folder.")); + NetLib_CloseConnection(&ft->hConnection, FALSE); + return; + } + + if (action == FILERESUME_RESUME) + ft->dwFileBytesDone = _lseek(ft->fileId, 0, SEEK_END); + else + _lseek(ft->fileId, ft->dwFileBytesDone, SEEK_SET); + + ft->dwBytesDone += ft->dwFileBytesDone; + + file_sendResume(this, dc); + + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0); +} + + +// small utility function +void NormalizeBackslash(char* path) +{ + int len = strlennull(path); + + if (len && path[len-1] != '\\') strcat(path, "\\"); +} + +/* a file transfer looks like this: +S: 0 +R: 5 +R: 1 +S: 2 +R: 3 +S: 6 * many +(for more files, send 2, 3, 6*many) +*/ + +void CIcqProto::handleFileTransferPacket(directconnect* dc, PBYTE buf, WORD wLen) +{ + if (wLen < 1) + return; + + NetLog_Direct("Handling file packet"); + + switch (buf[0]) + { + case PEER_FILE_INIT: /* first packet of a file transfer */ + if (dc->initialised) + return; + if (wLen < 19) + return; + buf += 5; /* id, and unknown 0 */ + dc->type = DIRECTCONN_FILE; + { + DWORD dwFileCount; + DWORD dwTotalSize; + DWORD dwTransferSpeed; + WORD wNickLength; + int bAdded; + + unpackLEDWord(&buf, &dwFileCount); + unpackLEDWord(&buf, &dwTotalSize); + unpackLEDWord(&buf, &dwTransferSpeed); + unpackLEWord(&buf, &wNickLength); + + dc->ft = FindExpectedFileRecv(dc->dwRemoteUin, dwTotalSize); + if (dc->ft == NULL) + { + NetLog_Direct("Unexpected file receive"); + CloseDirectConnection(dc); + return; + } + dc->ft->dwFileCount = dwFileCount; + dc->ft->dwTransferSpeed = dwTransferSpeed; + dc->ft->hContact = HContactFromUIN(dc->ft->dwUin, &bAdded); + dc->ft->dwBytesDone = 0; + dc->ft->iCurrentFile = -1; + dc->ft->fileId = -1; + dc->ft->hConnection = dc->hConnection; + dc->ft->dwLastNotify = GetTickCount(); + + dc->initialised = 1; + + file_sendTransferSpeed(this, dc); + file_sendNick(this, dc); + } + BroadcastAck(dc->ft->hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, dc->ft, 0); + break; + + case PEER_FILE_INIT_ACK: + if (wLen < 8) + return; + buf++; + unpackLEDWord(&buf, &dc->ft->dwTransferSpeed); + /* followed by nick */ + file_sendNextFile(this, dc); + break; + + case PEER_FILE_NEXTFILE: + if (wLen < 20) + return; + buf++; /* id */ + { + char *szAnsi; + WORD wThisFilenameLen, wSubdirLen; + BYTE isDirectory; + + unpackByte(&buf, &isDirectory); + unpackLEWord(&buf, &wThisFilenameLen); + if (wLen < 19 + wThisFilenameLen) + return; + SAFE_FREE(&dc->ft->szThisFile); + szAnsi = (char *)_alloca(wThisFilenameLen + 1); + memcpy(szAnsi, buf, wThisFilenameLen); + szAnsi[wThisFilenameLen] = '\0'; + dc->ft->szThisFile = ansi_to_utf8(szAnsi); + buf += wThisFilenameLen; + + unpackLEWord(&buf, &wSubdirLen); + if (wLen < 18 + wThisFilenameLen + wSubdirLen) + return; + SAFE_FREE(&dc->ft->szThisSubdir); + szAnsi = (char *)_alloca(wSubdirLen + 1); + memcpy(szAnsi, buf, wSubdirLen); + szAnsi[wSubdirLen] = '\0'; + dc->ft->szThisSubdir = ansi_to_utf8(szAnsi); + buf += wSubdirLen; + + unpackLEDWord(&buf, &dc->ft->dwThisFileSize); + unpackLEDWord(&buf, &dc->ft->dwThisFileDate); + unpackLEDWord(&buf, &dc->ft->dwTransferSpeed); + + /* no cheating with paths */ + if (!IsValidRelativePath(dc->ft->szThisFile) || !IsValidRelativePath(dc->ft->szThisSubdir)) + { + NetLog_Direct("Invalid path information"); + break; + } + + char *szFullPath = (char*)SAFE_MALLOC(strlennull(dc->ft->szSavePath)+strlennull(dc->ft->szThisSubdir)+strlennull(dc->ft->szThisFile)+3); + strcpy(szFullPath, dc->ft->szSavePath); + NormalizeBackslash(szFullPath); + strcat(szFullPath, dc->ft->szThisSubdir); + NormalizeBackslash(szFullPath); +// _chdir(szFullPath); // set current dir - not very useful + strcat(szFullPath, dc->ft->szThisFile); + // we joined the full path to dest file + SAFE_FREE(&dc->ft->szThisFile); + dc->ft->szThisFile = szFullPath; + + dc->ft->dwFileBytesDone = 0; + dc->ft->iCurrentFile++; + + if (isDirectory) + { + MakeDirUtf(dc->ft->szThisFile); + dc->ft->fileId = -1; + } + else + { + /* file resume */ + PROTOFILETRANSFERSTATUS pfts = {0}; + + file_buildProtoFileTransferStatus(dc->ft, &pfts); + if (BroadcastAck(dc->ft->hContact, ACKTYPE_FILE, ACKRESULT_FILERESUME, dc->ft, (LPARAM)&pfts)) + break; /* UI supports resume: it will call PS_FILERESUME */ + + dc->ft->fileId = OpenFileUtf(dc->ft->szThisFile, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY, _S_IREAD | _S_IWRITE); + if (dc->ft->fileId == -1) + { + icq_LogMessage(LOG_ERROR, LPGEN("Your file receive has been aborted because Miranda could not open the destination file in order to write to it. You may be trying to save to a read-only folder.")); + CloseDirectConnection(dc); + dc->ft->hConnection = NULL; + break; + } + } + } + file_sendResume(this, dc); + BroadcastAck(dc->ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, dc->ft, 0); + break; + + case PEER_FILE_RESUME: + if (dc->ft->fileId == -1 && !dc->ft->currentIsDir) + return; + if (wLen < 13) + return; + if (wLen < 17) + NetLog_Direct("Warning: Received short PEER_FILE_RESUME"); + buf++; + { + DWORD dwRestartFrom; + + unpackLEDWord(&buf, &dwRestartFrom); + if (dwRestartFrom > dc->ft->dwThisFileSize) + return; + buf += 4; /* unknown. 0 */ + unpackLEDWord(&buf, &dc->ft->dwTransferSpeed); + buf += 4; /* unknown. 1 */ + if (!dc->ft->currentIsDir) + _lseek(dc->ft->fileId, dwRestartFrom, 0); + dc->wantIdleTime = 1; + dc->ft->dwBytesDone += dwRestartFrom; + dc->ft->dwFileBytesDone += dwRestartFrom; + } + break; + + case PEER_FILE_SPEED: + if (wLen < 5) + return; + buf++; + unpackLEDWord(&buf, &dc->ft->dwTransferSpeed); + dc->ft->dwLastNotify = GetTickCount(); + break; + + case PEER_FILE_DATA: + if (!dc->ft->currentIsDir) + { + if (dc->ft->fileId == -1) + break; + buf++; wLen--; + _write(dc->ft->fileId, buf, wLen); + } + else + wLen = 0; + dc->ft->dwBytesDone += wLen; + dc->ft->dwFileBytesDone += wLen; + if (GetTickCount() > dc->ft->dwLastNotify + 500 || wLen < 2048) + { + PROTOFILETRANSFERSTATUS pfts; + + file_buildProtoFileTransferStatus(dc->ft, &pfts); + BroadcastAck(dc->ft->hContact, ACKTYPE_FILE, ACKRESULT_DATA, dc->ft, (LPARAM)&pfts); + dc->ft->dwLastNotify = GetTickCount(); + } + if (wLen < 2048) + { + /* EOF */ + if (!dc->ft->currentIsDir) + _close(dc->ft->fileId); + dc->ft->fileId = -1; + if ((DWORD)dc->ft->iCurrentFile == dc->ft->dwFileCount - 1) + { + dc->type = DIRECTCONN_CLOSING; /* this guarantees that we won't accept any more data but that the sender is still free to closesocket() neatly */ + BroadcastAck(dc->ft->hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, dc->ft, 0); + } + } + break; + + default: + NetLog_Direct("Unknown file transfer packet ignored."); + break; + } +} diff --git a/protocols/IcqOscarJ/src/icq_firstrun.cpp b/protocols/IcqOscarJ/src/icq_firstrun.cpp new file mode 100644 index 0000000000..1208d6d0ec --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_firstrun.cpp @@ -0,0 +1,124 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2009 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +static void accountLoadDetails(CIcqProto *ppro, HWND hwndDlg) +{ + char pszUIN[20]; + DWORD dwUIN = ppro->getContactUin(NULL); + if (dwUIN) + { + null_snprintf(pszUIN, 20, "%u", dwUIN); + SetDlgItemTextA(hwndDlg, IDC_UIN, pszUIN); + } + + char pszPwd[PASSWORDMAXLEN]; + if (ppro->GetUserStoredPassword(pszPwd, PASSWORDMAXLEN)) + SetDlgItemTextA(hwndDlg, IDC_PW, pszPwd); +} + + +INT_PTR CALLBACK icq_FirstRunDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + CIcqProto* ppro = (CIcqProto*)GetWindowLongPtr( hwndDlg, GWLP_USERDATA ); + + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + + ppro = (CIcqProto*)lParam; + SetWindowLongPtr( hwndDlg, GWLP_USERDATA, lParam ); + { + SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)ppro->m_hIconProtocol->GetIcon(true)); + SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)ppro->m_hIconProtocol->GetIcon()); + + SendDlgItemMessage(hwndDlg, IDC_PW, EM_LIMITTEXT, PASSWORDMAXLEN - 1, 0); + + accountLoadDetails(ppro, hwndDlg); + } + return TRUE; + + case WM_DESTROY: + ppro->m_hIconProtocol->ReleaseIcon(true); + ppro->m_hIconProtocol->ReleaseIcon(); + break; + + case WM_CLOSE: + EndDialog(hwndDlg, 0); + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_REGISTER: + CallService(MS_UTILS_OPENURL, 1, (LPARAM)URL_REGISTER); + break; + + case IDC_UIN: + case IDC_PW: + if (HIWORD(wParam) == EN_CHANGE && (HWND)lParam == GetFocus()) + { + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + } + } + break; + + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->code) + { + case PSN_APPLY: + { + char str[128]; + GetDlgItemTextA(hwndDlg, IDC_UIN, str, sizeof(str)); + ppro->setSettingDword(NULL, UNIQUEIDSETTING, atoi(str)); + GetDlgItemTextA(hwndDlg, IDC_PW, str, sizeof(ppro->m_szPassword)); + strcpy(ppro->m_szPassword, str); + CallService(MS_DB_CRYPT_ENCODESTRING, sizeof(ppro->m_szPassword), (LPARAM) str); + ppro->setSettingString(NULL, "Password", str); + } + break; + + case PSN_RESET: + accountLoadDetails(ppro, hwndDlg); + break; + } + break; + } + + return FALSE; +} + + +INT_PTR CIcqProto::OnCreateAccMgrUI(WPARAM wParam, LPARAM lParam) +{ + return (INT_PTR)CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_ICQACCOUNT), (HWND)lParam, icq_FirstRunDlgProc, LPARAM(this)); +} diff --git a/protocols/IcqOscarJ/src/icq_http.cpp b/protocols/IcqOscarJ/src/icq_http.cpp new file mode 100644 index 0000000000..3e50db43ed --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_http.cpp @@ -0,0 +1,212 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000,2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001,2002 Jon Keating, Richard Hughes +// Copyright © 2002,2003,2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004,2005,2006 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// HTTP Gateway Handling routines +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +int icq_httpGatewayInit(HANDLE hConn, NETLIBOPENCONNECTION *nloc, NETLIBHTTPREQUEST *nlhr) +{ + // initial response from ICQ http gateway + WORD wLen, wVersion, wType; + WORD wIpLen; + DWORD dwSid1, dwSid2, dwSid3, dwSid4; + BYTE *buf; + char szSid[33], szHttpServer[256], szHttpGetUrl[300], szHttpPostUrl[300]; + NETLIBHTTPPROXYINFO nlhpi = {0}; + + if (nlhr->dataLength < 31) + { + SetLastError(ERROR_INVALID_DATA); + return 0; + } + + buf = (PBYTE)nlhr->pData; + unpackWord(&buf, &wLen); + unpackWord(&buf, &wVersion); /* always 0x0443 */ + unpackWord(&buf, &wType); /* hello reply */ + buf += 6; /* dunno */ + unpackDWord(&buf, &dwSid1); + unpackDWord(&buf, &dwSid2); + unpackDWord(&buf, &dwSid3); + unpackDWord(&buf, &dwSid4); + null_snprintf(szSid, 33, "%08x%08x%08x%08x", dwSid1, dwSid2, dwSid3, dwSid4); + unpackWord(&buf, &wIpLen); + + if(nlhr->dataLength < 30 + wIpLen || wIpLen == 0 || wIpLen > sizeof(szHttpServer) - 1) + { + SetLastError(ERROR_INVALID_DATA); + return 0; + } + + SetGatewayIndex(hConn, 1); // new master connection begins here + + memcpy(szHttpServer, buf, wIpLen); + szHttpServer[wIpLen] = '\0'; + + nlhpi.cbSize = sizeof(nlhpi); + nlhpi.flags = NLHPIF_USEPOSTSEQUENCE; + nlhpi.szHttpGetUrl = szHttpGetUrl; + nlhpi.szHttpPostUrl = szHttpPostUrl; + nlhpi.firstPostSequence = 1; + null_snprintf(szHttpGetUrl, 300, "http://%s/monitor?sid=%s", szHttpServer, szSid); + null_snprintf(szHttpPostUrl, 300, "http://%s/data?sid=%s&seq=", szHttpServer, szSid); + + return CallService(MS_NETLIB_SETHTTPPROXYINFO, (WPARAM)hConn, (LPARAM)&nlhpi); +} + + + +int icq_httpGatewayBegin(HANDLE hConn, NETLIBOPENCONNECTION* nloc) +{ // open our "virual data connection" + icq_packet packet; + size_t serverNameLen; + + serverNameLen = strlennull(nloc->szHost); + + packet.wLen = (WORD)(serverNameLen + 4); + write_httphdr(&packet, HTTP_PACKETTYPE_LOGIN, GetGatewayIndex(hConn)); + packWord(&packet, (WORD)serverNameLen); + packBuffer(&packet, (LPBYTE)nloc->szHost, (WORD)serverNameLen); + packWord(&packet, nloc->wPort); + INT_PTR res = Netlib_Send(hConn, (char*)packet.pData, packet.wLen, MSG_DUMPPROXY|MSG_NOHTTPGATEWAYWRAP); + SAFE_FREE((void**)&packet.pData); + + return res != SOCKET_ERROR; +} + + + +int icq_httpGatewayWrapSend(HANDLE hConn, PBYTE buf, int len, int flags, MIRANDASERVICE pfnNetlibSend) +{ + PBYTE sendBuf = buf; + int sendLen = len; + int sendResult = 0; + + while (sendLen > 0) + { // imitate polite behaviour of icq5.1 and split large packets + icq_packet packet; + WORD curLen; + int curResult; + + if (sendLen > 512) curLen = 512; else curLen = (WORD)sendLen; + // send wrapped data + packet.wLen = curLen; + write_httphdr(&packet, HTTP_PACKETTYPE_FLAP, GetGatewayIndex(hConn)); + packBuffer(&packet, sendBuf, (WORD)curLen); + + NETLIBBUFFER nlb={ (char*)packet.pData, packet.wLen, flags }; + curResult = pfnNetlibSend((WPARAM)hConn, (LPARAM)&nlb); + + SAFE_FREE((void**)&packet.pData); + + // sending failed, end loop + if (curResult <= 0) + return curResult; + // calculare real number of data bytes sent + if (curResult > 14) sendResult += curResult - 14; + // move on + sendLen -= curLen; + sendBuf += curLen; + } + + return sendResult; +} + + + +PBYTE icq_httpGatewayUnwrapRecv(NETLIBHTTPREQUEST* nlhr, PBYTE buf, int len, int* outBufLen, void *(*NetlibRealloc)(void *, size_t)) +{ + WORD wLen, wType; + DWORD dwPackSeq; + PBYTE tbuf; + int i, copyBytes; + + + tbuf = buf; + for(i = 0;;) + { + if (tbuf - buf + 2 > len) + break; + unpackWord(&tbuf, &wLen); + if (wLen < 12) + break; + if (tbuf - buf + wLen > len) + break; + tbuf += 2; /* version */ + unpackWord(&tbuf, &wType); + tbuf += 4; /* flags */ + unpackDWord(&tbuf, &dwPackSeq); + if (wType == HTTP_PACKETTYPE_FLAP) + { // it is normal data packet + copyBytes = wLen - 12; + if (copyBytes > len - i) + { + /* invalid data - do our best to get something out of it */ + copyBytes = len - i; + } + memcpy(buf + i, tbuf, copyBytes); + i += copyBytes; + } + else if (wType == HTTP_PACKETTYPE_LOGINREPLY) + { // our "virtual connection" was established, good + BYTE bRes; + + unpackByte(&tbuf, &bRes); + wLen -= 1; + if (!bRes) + Netlib_Logf( NULL, "Gateway Connection #%d Established.", dwPackSeq); + else + Netlib_Logf( NULL, "Gateway Connection #%d Failed, error: %d", dwPackSeq, bRes); + } + else if (wType == HTTP_PACKETTYPE_CLOSEREPLY) + { // "virtual connection" closed - only received if any other "virual connection" still active + Netlib_Logf( NULL, "Gateway Connection #%d Closed.", dwPackSeq); + } + tbuf += wLen - 12; + } + *outBufLen = i; + + return buf; +} + + + +int icq_httpGatewayWalkTo(HANDLE hConn, NETLIBOPENCONNECTION* nloc) +{ // this is bad simplification - for avatars to work we need to handle + // two "virtual connections" at the same time + icq_packet packet; + DWORD dwGatewaySeq = GetGatewayIndex(hConn); + + packet.wLen = 0; + write_httphdr(&packet, HTTP_PACKETTYPE_CLOSE, dwGatewaySeq); + Netlib_Send(hConn, (char*)packet.pData, packet.wLen, MSG_DUMPPROXY|MSG_NOHTTPGATEWAYWRAP); + // we closed virtual connection, open new one + dwGatewaySeq++; + SetGatewayIndex(hConn, dwGatewaySeq); + return icq_httpGatewayBegin(hConn, nloc); +} diff --git a/protocols/IcqOscarJ/src/icq_http.h b/protocols/IcqOscarJ/src/icq_http.h new file mode 100644 index 0000000000..017a5ea5fc --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_http.h @@ -0,0 +1,48 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000,2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001,2002 Jon Keating, Richard Hughes +// Copyright © 2002,2003,2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004,2005 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#ifndef __ICQ_HTTP_H +#define __ICQ_HTTP_H + +#define HTTP_PROXY_VERSION 0x0443 + +#define HTTP_PACKETTYPE_HELLOREPLY 2 +#define HTTP_PACKETTYPE_LOGIN 3 +#define HTTP_PACKETTYPE_LOGINREPLY 4 /* contains 1 byte: 0 */ +#define HTTP_PACKETTYPE_FLAP 5 +#define HTTP_PACKETTYPE_CLOSE 6 /* contains no data */ +#define HTTP_PACKETTYPE_CLOSEREPLY 7 /* contains 1 byte: 0 */ + +int icq_httpGatewayInit(HANDLE hConn, NETLIBOPENCONNECTION *nloc, NETLIBHTTPREQUEST *nlhr); +int icq_httpGatewayBegin(HANDLE hConn, NETLIBOPENCONNECTION *nloc); +int icq_httpGatewayWrapSend(HANDLE hConn, PBYTE buf, int len, int flags, MIRANDASERVICE pfnNetlibSend); +PBYTE icq_httpGatewayUnwrapRecv(NETLIBHTTPREQUEST *nlhr, PBYTE buf, int bufLen, int *outBufLen, void *(*NetlibRealloc)(void *, size_t)); +int icq_httpGatewayWalkTo(HANDLE hConn, NETLIBOPENCONNECTION* nloc); + +#endif /* __ICQ_HTTP_H */ diff --git a/protocols/IcqOscarJ/src/icq_infoupdate.cpp b/protocols/IcqOscarJ/src/icq_infoupdate.cpp new file mode 100644 index 0000000000..014df20e01 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_infoupdate.cpp @@ -0,0 +1,401 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Background thread for automatic update of user details +// +// ----------------------------------------------------------------------------- + +#include "icqoscar.h" + +// Retrieve users' info +void CIcqProto::icq_InitInfoUpdate(void) +{ + // Create wait objects + hInfoQueueEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (hInfoQueueEvent) { + // Init mutexes + infoUpdateMutex = new icq_critical_section(); + + // Init list + for (int i = 0; iEnter(); + + // Check if in list + for (i = 0; (iLeave(); + + return TRUE; + } + + return FALSE; +} + + +void CIcqProto::icq_DequeueUser(DWORD dwUin) +{ + if (nInfoUserCount > 0) + { + int nChecked = 0; + // Check if in list + infoUpdateMutex->Enter(); + for (int i = 0; (i < LISTSIZE && nChecked < nInfoUserCount); i++) + { + if (m_infoUpdateList[i].dwUin) + { + nChecked++; + // Remove from list + if (m_infoUpdateList[i].dwUin == dwUin) + { +#ifdef _DEBUG + NetLog_Server("Dequeued user %u", m_infoUpdateList[i].dwUin); +#endif + m_infoUpdateList[i].dwUin = 0; + m_infoUpdateList[i].hContact = NULL; + m_infoUpdateList[i].queued = 0; + nInfoUserCount--; + break; + } + } + } + infoUpdateMutex->Leave(); + } +} + + +void CIcqProto::icq_RescanInfoUpdate() +{ + bInfoPendingUsers = 0; + /* This is here, cause we do not want to emit large number of reuqest at once, + fill queue, and let thread deal with it */ + bInfoUpdateEnabled = 0; // freeze thread + + // Queue all outdated users + HANDLE hContact = FindFirstContact(); + while (hContact != NULL) + { + if (IsMetaInfoChanged(hContact)) + { // Queue user + if (!icq_QueueUser(hContact)) + { // The queue is full, pause queuing contacts + bInfoPendingUsers = 1; + break; + } + } + hContact = FindNextContact(hContact); + } + + bInfoUpdateEnabled = TRUE; +} + + +void CIcqProto::icq_EnableUserLookup(BOOL bEnable) +{ + bInfoUpdateEnabled = bEnable; + + // Notify worker thread + if (bInfoUpdateEnabled && hInfoQueueEvent) + SetEvent(hInfoQueueEvent); +} + + +void __cdecl CIcqProto::InfoUpdateThread( void* ) +{ + int i; + DWORD dwWait = WAIT_OBJECT_0; + + NetLog_Server("%s thread starting.", "Info-Update"); + + bInfoUpdateRunning = TRUE; + + while (bInfoUpdateRunning) + { + if (!nInfoUserCount && bInfoPendingUsers) // whole queue processed, check if more users needs updating + icq_RescanInfoUpdate(); + + if (!nInfoUserCount || !bInfoUpdateEnabled || !icqOnline()) + { + dwWait = WAIT_TIMEOUT; + while (bInfoUpdateRunning && dwWait == WAIT_TIMEOUT) + { // wait for new work or until we should end + dwWait = ICQWaitForSingleObject(hInfoQueueEvent, 10000); + } + } + if (!bInfoUpdateRunning) break; + + switch (dwWait) { + case WAIT_IO_COMPLETION: + // Possible shutdown in progress + break; + + case WAIT_OBJECT_0: + case WAIT_TIMEOUT: + // Time to check for new users + if (!bInfoUpdateEnabled) continue; // we can't send requests now + + if (nInfoUserCount && icqOnline()) + { + time_t now = time(NULL); + BOOL bNotReady = FALSE, bTimeOuted = FALSE; + + // Check the list, take only users that were there for at least 5sec + // wait if any user is there shorter than 5sec and not a single user is there longer than 20sec + infoUpdateMutex->Enter(); + for (i = 0; i= now) + bNotReady = TRUE; + } + } + infoUpdateMutex->Leave(); + + if (!bTimeOuted && bNotReady) + { + SleepEx(1000, TRUE); + if (!bInfoUpdateRunning) + { // need to end as fast as possible + NetLog_Server("%s thread ended.", "Info-Update"); + goto LBL_Exit; + } + continue; + } + + if (FindCookie(dwInfoActiveRequest, NULL, NULL)) + { // only send another request, when the previous is completed +#ifdef _DEBUG + NetLog_Server("Info-Update: Request 0x%x still in progress.", dwInfoActiveRequest); +#endif + SleepEx(1000, TRUE); + if (!bInfoUpdateRunning) + { // need to end as fast as possible + NetLog_Server("%s thread ended.", "Info-Update"); + goto LBL_Exit; + } + continue; + } + +#ifdef _DEBUG + NetLog_Server("Info-Update: Users %u in queue.", nInfoUserCount); +#endif + // Either some user is waiting long enough, or all users are ready (waited at least the minimum time) + m_ratesMutex->Enter(); + if (!m_rates) + { // we cannot send info request - icq is offline + m_ratesMutex->Leave(); + break; + } + WORD wGroup = m_rates->getGroupFromSNAC(ICQ_EXTENSIONS_FAMILY, ICQ_META_CLI_REQUEST); + while (m_rates->getNextRateLevel(wGroup) < m_rates->getLimitLevel(wGroup, RML_IDLE_30)) + { // we are over rate, need to wait before sending + int nDelay = m_rates->getDelayToLimitLevel(wGroup, RML_IDLE_50); + + m_ratesMutex->Leave(); +#ifdef _DEBUG + NetLog_Server("Rates: InfoUpdate delayed %dms", nDelay); +#endif + SleepEx(nDelay, TRUE); // do not keep things locked during sleep + if (!bInfoUpdateRunning) + { // need to end as fast as possible + NetLog_Server("%s thread ended.", "Info-Update"); + goto LBL_Exit; + } + m_ratesMutex->Enter(); + if (!m_rates) // we lost connection when we slept, go away + break; + } + if (!m_rates) + { // we cannot send info request - icq is offline + m_ratesMutex->Leave(); + break; + } + m_ratesMutex->Leave(); + + userinfo *hContactList[LISTSIZE]; + int nListIndex = 0; + BYTE *pRequestData = NULL; + int nRequestSize = 0; + + infoUpdateMutex->Enter(); + for (i = 0; iLeave(); + break; + } + if (!(dwInfoActiveRequest = sendUserInfoMultiRequest(pRequestData, nRequestSize, nListIndex))) + { // sending data packet failed + SAFE_FREE((void**)&pRequestData); + infoUpdateMutex->Leave(); + break; + } + SAFE_FREE((void**)&pRequestData); + + for (i = 0; idwUin = 0; + hContactList[i]->hContact = NULL; + hContactList[i]->queued = 0; + nInfoUserCount--; + } + infoUpdateMutex->Leave(); + } + break; + + default: + // Something strange happened. Exit + bInfoUpdateRunning = FALSE; + break; + } + } + + NetLog_Server("%s thread ended.", "Info-Update"); + +LBL_Exit: + SAFE_DELETE(&infoUpdateMutex); + CloseHandle(hInfoQueueEvent); +} + +// Clean up before exit +void CIcqProto::icq_InfoUpdateCleanup(void) +{ + NetLog_Server("%s must die.", "Info-Update"); + bInfoUpdateRunning = FALSE; + if (hInfoQueueEvent) + SetEvent(hInfoQueueEvent); // break queue loop +} diff --git a/protocols/IcqOscarJ/src/icq_menu.cpp b/protocols/IcqOscarJ/src/icq_menu.cpp new file mode 100644 index 0000000000..f3dd072cde --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_menu.cpp @@ -0,0 +1,263 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2008 Joe Kucera, Bio +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +#include + +static HANDLE hPrebuildMenuHook; + +HANDLE g_hContactMenuItems[6]; +HANDLE g_hContactMenuSvc[6]; + +static int sttCompareProtocols(const CIcqProto *p1, const CIcqProto *p2) +{ + return strcmp(p1->m_szModuleName, p2->m_szModuleName); +} + +LIST g_Instances(1, sttCompareProtocols); + +static CIcqProto* IcqGetInstanceByHContact(HANDLE hContact) +{ + char* szProto = ( char* )CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0); + if (szProto == NULL) + return NULL; + + for (int i = 0; i < g_Instances.getCount(); i++) + if (!strcmp(szProto, g_Instances[i]->m_szModuleName)) + return g_Instances[i]; + + return NULL; +} + +static INT_PTR IcqMenuHandleRequestAuth(WPARAM wParam, LPARAM lParam) +{ + CIcqProto* ppro = IcqGetInstanceByHContact((HANDLE)wParam); + return (ppro) ? ppro->RequestAuthorization(wParam, lParam) : 0; +} + +static INT_PTR IcqMenuHandleGrantAuth(WPARAM wParam, LPARAM lParam) +{ + CIcqProto* ppro = IcqGetInstanceByHContact((HANDLE)wParam); + return (ppro) ? ppro->GrantAuthorization(wParam, lParam) : 0; +} + +static INT_PTR IcqMenuHandleRevokeAuth(WPARAM wParam, LPARAM lParam) +{ + CIcqProto* ppro = IcqGetInstanceByHContact((HANDLE)wParam); + return (ppro) ? ppro->RevokeAuthorization(wParam, lParam) : 0; +} + +static INT_PTR IcqMenuHandleAddServContact(WPARAM wParam, LPARAM lParam) +{ + CIcqProto* ppro = IcqGetInstanceByHContact((HANDLE)wParam); + return (ppro) ? ppro->AddServerContact(wParam, lParam) : 0; +} + +static INT_PTR IcqMenuHandleXStatusDetails(WPARAM wParam, LPARAM lParam) +{ + CIcqProto* ppro = IcqGetInstanceByHContact((HANDLE)wParam); + return (ppro) ? ppro->ShowXStatusDetails(wParam, lParam) : 0; +} + +static INT_PTR IcqMenuHandleOpenProfile(WPARAM wParam, LPARAM lParam) +{ + CIcqProto* ppro = IcqGetInstanceByHContact((HANDLE)wParam); + return (ppro) ? ppro->OpenWebProfile(wParam, lParam) : 0; +} + +static void sttEnableMenuItem( HANDLE hMenuItem, bool bEnable ) +{ + CLISTMENUITEM clmi = {0}; + clmi.cbSize = sizeof(CLISTMENUITEM); + clmi.flags = CMIM_FLAGS; + if ( !bEnable ) + clmi.flags |= CMIF_HIDDEN; + + CallService( MS_CLIST_MODIFYMENUITEM, ( WPARAM )hMenuItem, ( LPARAM )&clmi ); +} + +static int IcqPrebuildContactMenu( WPARAM wParam, LPARAM lParam ) +{ + sttEnableMenuItem(g_hContactMenuItems[ICMI_AUTH_REQUEST], FALSE); + sttEnableMenuItem(g_hContactMenuItems[ICMI_AUTH_GRANT], FALSE); + sttEnableMenuItem(g_hContactMenuItems[ICMI_AUTH_REVOKE], FALSE); + sttEnableMenuItem(g_hContactMenuItems[ICMI_ADD_TO_SERVLIST], FALSE); + sttEnableMenuItem(g_hContactMenuItems[ICMI_XSTATUS_DETAILS], FALSE); + sttEnableMenuItem(g_hContactMenuItems[ICMI_OPEN_PROFILE], FALSE); + + CIcqProto* ppro = IcqGetInstanceByHContact((HANDLE)wParam); + return (ppro) ? ppro->OnPreBuildContactMenu(wParam, lParam) : 0; +} + +void g_MenuInit(void) +{ + /////////////// + // Contact menu + + hPrebuildMenuHook = HookEvent(ME_CLIST_PREBUILDCONTACTMENU, IcqPrebuildContactMenu); + + // Contact menu initialization + + char str[MAXMODULELABELLENGTH], *pszDest = str + 3; + strcpy( str, "ICQ" ); + + CLISTMENUITEM mi = { 0 }; + mi.cbSize = sizeof(CLISTMENUITEM); + mi.pszService = str; + mi.flags = CMIF_ICONFROMICOLIB; + + // "Request authorization" + mi.pszName = LPGEN("Request authorization"); + mi.position = 1000030000; + mi.icolibItem = hStaticIcons[ISI_AUTH_REQUEST]->Handle(); + strcpy(pszDest, MS_REQ_AUTH); + g_hContactMenuItems[ICMI_AUTH_REQUEST] = Menu_AddContactMenuItem(&mi); + g_hContactMenuSvc[ICMI_AUTH_REQUEST] = CreateServiceFunction( str, IcqMenuHandleRequestAuth ); + + // "Grant authorization" + mi.pszName = LPGEN("Grant authorization"); + mi.position = 1000029999; + mi.icolibItem = hStaticIcons[ISI_AUTH_GRANT]->Handle(); + strcpy(pszDest, MS_GRANT_AUTH); + g_hContactMenuItems[ICMI_AUTH_GRANT] = Menu_AddContactMenuItem(&mi); + g_hContactMenuSvc[ICMI_AUTH_GRANT] = CreateServiceFunction(mi.pszService, IcqMenuHandleGrantAuth); + + // "Revoke authorization" + mi.pszName = LPGEN("Revoke authorization"); + mi.position = 1000029998; + mi.icolibItem = hStaticIcons[ISI_AUTH_REVOKE]->Handle(); + strcpy(pszDest, MS_REVOKE_AUTH); + g_hContactMenuItems[ICMI_AUTH_REVOKE] = Menu_AddContactMenuItem(&mi); + g_hContactMenuSvc[ICMI_AUTH_REVOKE] = CreateServiceFunction(mi.pszService, IcqMenuHandleRevokeAuth); + + // "Add to server list" + mi.pszName = LPGEN("Add to server list"); + mi.position = -2049999999; + mi.icolibItem = hStaticIcons[ISI_ADD_TO_SERVLIST]->Handle(); + strcpy(pszDest, MS_ICQ_ADDSERVCONTACT); + g_hContactMenuItems[ICMI_ADD_TO_SERVLIST] = Menu_AddContactMenuItem(&mi); + g_hContactMenuSvc[ICMI_ADD_TO_SERVLIST] = CreateServiceFunction(mi.pszService, IcqMenuHandleAddServContact); + + // "Show custom status details" + mi.pszName = LPGEN("Show custom status details"); + mi.position = -2000004999; + mi.flags = 0; + strcpy(pszDest, MS_XSTATUS_SHOWDETAILS); + g_hContactMenuItems[ICMI_XSTATUS_DETAILS] = Menu_AddContactMenuItem(&mi); + g_hContactMenuSvc[ICMI_XSTATUS_DETAILS] = CreateServiceFunction(mi.pszService, IcqMenuHandleXStatusDetails); + + // "Open ICQ profile" + mi.pszName = LPGEN("Open ICQ profile"); + mi.position = 1000029997; + strcpy(pszDest, MS_OPEN_PROFILE); + g_hContactMenuItems[ICMI_OPEN_PROFILE] = Menu_AddContactMenuItem(&mi); + g_hContactMenuSvc[ICMI_OPEN_PROFILE] = CreateServiceFunction(mi.pszService, IcqMenuHandleOpenProfile); +} + +void g_MenuUninit(void) +{ + UnhookEvent(hPrebuildMenuHook); + + CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)g_hContactMenuItems[ICMI_AUTH_REQUEST], 0); + CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)g_hContactMenuItems[ICMI_AUTH_GRANT], 0); + CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)g_hContactMenuItems[ICMI_AUTH_REVOKE], 0); + CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)g_hContactMenuItems[ICMI_ADD_TO_SERVLIST], 0); + CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)g_hContactMenuItems[ICMI_XSTATUS_DETAILS], 0); + CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)g_hContactMenuItems[ICMI_OPEN_PROFILE], 0); + + DestroyServiceFunction(g_hContactMenuSvc[ICMI_AUTH_REQUEST]); + DestroyServiceFunction(g_hContactMenuSvc[ICMI_AUTH_GRANT]); + DestroyServiceFunction(g_hContactMenuSvc[ICMI_AUTH_REVOKE]); + DestroyServiceFunction(g_hContactMenuSvc[ICMI_ADD_TO_SERVLIST]); + DestroyServiceFunction(g_hContactMenuSvc[ICMI_XSTATUS_DETAILS]); + DestroyServiceFunction(g_hContactMenuSvc[ICMI_OPEN_PROFILE]); +} + + +INT_PTR CIcqProto::OpenWebProfile(WPARAM wParam, LPARAM lParam) +{ + HANDLE hContact = (HANDLE)wParam; + DWORD dwUin = getContactUin(hContact); + char url[256]; + mir_snprintf(url, sizeof(url), "http://www.icq.com/people/%d",dwUin); + return CallService(MS_UTILS_OPENURL, 1, (LPARAM)url); +} + + +int CIcqProto::OnPreBuildContactMenu(WPARAM wParam, LPARAM) +{ + HANDLE hContact = (HANDLE)wParam; + if (hContact == NULL) + return 0; + + if (icqOnline()) + { + BOOL bCtrlPressed = (GetKeyState(VK_CONTROL)&0x8000 ) != 0; + + DWORD dwUin = getContactUin(hContact); + + + sttEnableMenuItem(g_hContactMenuItems[ICMI_AUTH_REQUEST], + dwUin && (bCtrlPressed || (getSettingByte((HANDLE)wParam, "Auth", 0) && getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_ID, 0)))); + sttEnableMenuItem(g_hContactMenuItems[ICMI_AUTH_GRANT], dwUin && (bCtrlPressed || getSettingByte((HANDLE)wParam, "Grant", 0))); + sttEnableMenuItem(g_hContactMenuItems[ICMI_AUTH_REVOKE], + dwUin && (bCtrlPressed || (getSettingByte(NULL, "PrivacyItems", 0) && !getSettingByte((HANDLE)wParam, "Grant", 0)))); + sttEnableMenuItem(g_hContactMenuItems[ICMI_ADD_TO_SERVLIST], + m_bSsiEnabled && !getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_ID, 0) && + !getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_IGNORE, 0) && + !DBGetContactSettingByte(hContact, "CList", "NotOnList", 0)); + } + + sttEnableMenuItem(g_hContactMenuItems[ICMI_OPEN_PROFILE],getContactUin(hContact) != 0); + BYTE bXStatus = getContactXStatus((HANDLE)wParam); + + sttEnableMenuItem(g_hContactMenuItems[ICMI_XSTATUS_DETAILS], m_bHideXStatusUI ? 0 : bXStatus != 0); + if (bXStatus && !m_bHideXStatusUI) { + CLISTMENUITEM clmi = {0}; + + clmi.cbSize = sizeof(clmi); + clmi.flags = CMIM_ICON; + + if (bXStatus > 0 && bXStatus <= XSTATUS_COUNT) + clmi.hIcon = getXStatusIcon(bXStatus, LR_SHARED); + else + clmi.hIcon = LoadSkinnedIcon(SKINICON_OTHER_SMALLDOT); + CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)g_hContactMenuItems[ICMI_XSTATUS_DETAILS], (LPARAM)&clmi); + } + + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// OnPreBuildStatusMenu event + +int CIcqProto::OnPreBuildStatusMenu(WPARAM wParam, LPARAM lParam) +{ + InitXStatusItems(TRUE); + return 0; +} diff --git a/protocols/IcqOscarJ/src/icq_opts.cpp b/protocols/IcqOscarJ/src/icq_opts.cpp new file mode 100644 index 0000000000..44c1881d14 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_opts.cpp @@ -0,0 +1,617 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +#include + +extern BOOL bPopUpService; + +static const char* szLogLevelDescr[] = { + LPGEN("Display all problems"), + LPGEN("Display problems causing possible loss of data"), + LPGEN("Display explanations for disconnection"), + LPGEN("Display problems requiring user intervention"), + LPGEN("Do not display any problems (not recommended)") +}; + +static BOOL (WINAPI *pfnEnableThemeDialogTexture)(HANDLE, DWORD) = 0; + +static void LoadDBCheckState(CIcqProto* ppro, HWND hwndDlg, int idCtrl, const char* szSetting, BYTE bDef) +{ + CheckDlgButton(hwndDlg, idCtrl, ppro->getSettingByte(NULL, szSetting, bDef)); +} + +static void StoreDBCheckState(CIcqProto* ppro, HWND hwndDlg, int idCtrl, const char* szSetting) +{ + ppro->setSettingByte(NULL, szSetting, (BYTE)IsDlgButtonChecked(hwndDlg, idCtrl)); +} + +static void OptDlgChanged(HWND hwndDlg) +{ + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// standalone option pages + +static INT_PTR CALLBACK DlgProcIcqOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + CIcqProto* ppro = (CIcqProto*)GetWindowLongPtr( hwndDlg, GWLP_USERDATA ); + + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + + ppro = (CIcqProto*)lParam; + SetWindowLongPtr( hwndDlg, GWLP_USERDATA, lParam ); + { + DWORD dwUin = ppro->getContactUin(NULL); + if (dwUin) + SetDlgItemInt(hwndDlg, IDC_ICQNUM, dwUin, FALSE); + else // keep it empty when no UIN entered + SetDlgItemTextA(hwndDlg, IDC_ICQNUM, ""); + + SendDlgItemMessage(hwndDlg, IDC_PASSWORD, EM_LIMITTEXT, PASSWORDMAXLEN - 1, 0); + + char pszPwd[PASSWORDMAXLEN]; + if (ppro->GetUserStoredPassword(pszPwd, sizeof(pszPwd))) + { + //bit of a security hole here, since it's easy to extract a password from an edit box + SetDlgItemTextA(hwndDlg, IDC_PASSWORD, pszPwd); + } + + LoadDBCheckState(ppro, hwndDlg, IDC_SSL, "SecureConnection", DEFAULT_SECURE_CONNECTION); + LoadDBCheckState(ppro, hwndDlg, IDC_MD5LOGIN, "SecureLogin", DEFAULT_SECURE_LOGIN); + + char szServer[MAX_PATH]; + if (!ppro->getSettingStringStatic(NULL, "OscarServer", szServer, MAX_PATH)) + SetDlgItemTextA(hwndDlg, IDC_ICQSERVER, szServer); + else + SetDlgItemTextA(hwndDlg, IDC_ICQSERVER, IsDlgButtonChecked(hwndDlg, IDC_SSL) ? DEFAULT_SERVER_HOST_SSL : DEFAULT_SERVER_HOST); + + SetDlgItemInt(hwndDlg, IDC_ICQPORT, ppro->getSettingWord(NULL, "OscarPort", IsDlgButtonChecked(hwndDlg, IDC_SSL) ? DEFAULT_SERVER_PORT_SSL : DEFAULT_SERVER_PORT), FALSE); + LoadDBCheckState(ppro, hwndDlg, IDC_KEEPALIVE, "KeepAlive", DEFAULT_KEEPALIVE_ENABLED); + SendDlgItemMessage(hwndDlg, IDC_LOGLEVEL, TBM_SETRANGE, FALSE, MAKELONG(0, 4)); + SendDlgItemMessage(hwndDlg, IDC_LOGLEVEL, TBM_SETPOS, TRUE, 4-ppro->getSettingByte(NULL, "ShowLogLevel", LOG_WARNING)); + { + char buf[MAX_PATH]; + SetDlgItemTextUtf(hwndDlg, IDC_LEVELDESCR, ICQTranslateUtfStatic(szLogLevelDescr[4-SendDlgItemMessage(hwndDlg, IDC_LOGLEVEL, TBM_GETPOS, 0, 0)], buf, MAX_PATH)); + } + ShowDlgItem(hwndDlg, IDC_RECONNECTREQD, SW_HIDE); + LoadDBCheckState(ppro, hwndDlg, IDC_NOERRMULTI, "IgnoreMultiErrorBox", 0); + } + return TRUE; + + case WM_HSCROLL: + { + char str[MAX_PATH]; + + SetDlgItemTextUtf(hwndDlg, IDC_LEVELDESCR, ICQTranslateUtfStatic(szLogLevelDescr[4-SendDlgItemMessage(hwndDlg, IDC_LOGLEVEL,TBM_GETPOS, 0, 0)], str, MAX_PATH)); + OptDlgChanged(hwndDlg); + } + break; + + case WM_COMMAND: + { + switch (LOWORD(wParam)) { + case IDC_LOOKUPLINK: + CallService(MS_UTILS_OPENURL, 1, (LPARAM)URL_FORGOT_PASSWORD); + return TRUE; + + case IDC_NEWUINLINK: + CallService(MS_UTILS_OPENURL, 1, (LPARAM)URL_REGISTER); + return TRUE; + + case IDC_RESETSERVER: + SetDlgItemInt(hwndDlg, IDC_ICQPORT, IsDlgButtonChecked(hwndDlg, IDC_SSL) ? DEFAULT_SERVER_PORT_SSL : DEFAULT_SERVER_PORT, FALSE); + + case IDC_SSL: + SetDlgItemTextA(hwndDlg, IDC_ICQSERVER, IsDlgButtonChecked(hwndDlg, IDC_SSL) ? DEFAULT_SERVER_HOST_SSL : DEFAULT_SERVER_HOST); + SetDlgItemInt(hwndDlg, IDC_ICQPORT, IsDlgButtonChecked(hwndDlg, IDC_SSL) ? DEFAULT_SERVER_PORT_SSL : DEFAULT_SERVER_PORT, FALSE); + OptDlgChanged(hwndDlg); + return TRUE; + } + + if (ppro->icqOnline() && LOWORD(wParam) != IDC_NOERRMULTI) + { + char szClass[80]; + GetClassNameA((HWND)lParam, szClass, sizeof(szClass)); + + if (stricmpnull(szClass, "EDIT") || HIWORD(wParam) == EN_CHANGE) + ShowDlgItem(hwndDlg, IDC_RECONNECTREQD, SW_SHOW); + } + + if ((LOWORD(wParam)==IDC_ICQNUM || LOWORD(wParam)==IDC_PASSWORD || LOWORD(wParam)==IDC_ICQSERVER || LOWORD(wParam)==IDC_ICQPORT) && + (HIWORD(wParam)!=EN_CHANGE || (HWND)lParam!=GetFocus())) + { + return 0; + } + + OptDlgChanged(hwndDlg); + break; + } + + case WM_NOTIFY: + { + switch (((LPNMHDR)lParam)->code) + { + + case PSN_APPLY: + { + char str[128]; + + ppro->setSettingDword(NULL, UNIQUEIDSETTING, GetDlgItemInt(hwndDlg, IDC_ICQNUM, NULL, FALSE)); + GetDlgItemTextA(hwndDlg, IDC_PASSWORD, str, sizeof(ppro->m_szPassword)); + if (strlennull(str)) + { + strcpy(ppro->m_szPassword, str); + ppro->m_bRememberPwd = TRUE; + } + else + ppro->m_bRememberPwd = ppro->getSettingByte(NULL, "RememberPass", 0); + + CallService(MS_DB_CRYPT_ENCODESTRING, sizeof(ppro->m_szPassword), (LPARAM)str); + ppro->setSettingString(NULL, "Password", str); + GetDlgItemTextA(hwndDlg,IDC_ICQSERVER, str, sizeof(str)); + ppro->setSettingString(NULL, "OscarServer", str); + ppro->setSettingWord(NULL, "OscarPort", (WORD)GetDlgItemInt(hwndDlg, IDC_ICQPORT, NULL, FALSE)); + StoreDBCheckState(ppro, hwndDlg, IDC_KEEPALIVE, "KeepAlive"); + StoreDBCheckState(ppro, hwndDlg, IDC_SSL, "SecureConnection"); + StoreDBCheckState(ppro, hwndDlg, IDC_MD5LOGIN, "SecureLogin"); + StoreDBCheckState(ppro, hwndDlg, IDC_NOERRMULTI, "IgnoreMultiErrorBox"); + ppro->setSettingByte(NULL, "ShowLogLevel", (BYTE)(4-SendDlgItemMessage(hwndDlg, IDC_LOGLEVEL, TBM_GETPOS, 0, 0))); + + return TRUE; + } + } + } + break; + } + + return FALSE; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static const UINT icqPrivacyControls[] = { + IDC_DCALLOW_ANY, IDC_DCALLOW_CLIST, IDC_DCALLOW_AUTH, IDC_ADD_ANY, IDC_ADD_AUTH, + IDC_WEBAWARE, IDC_PUBLISHPRIMARY, IDC_STATIC_DC1, IDC_STATIC_DC2, IDC_STATIC_CLIST +}; + +static INT_PTR CALLBACK DlgProcIcqPrivacyOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + CIcqProto* ppro = (CIcqProto*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + + ppro = (CIcqProto*)lParam; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); + { + int nDcType = ppro->getSettingByte(NULL, "DCType", 0); + int nAddAuth = ppro->getSettingByte(NULL, "Auth", 1); + + if (!ppro->icqOnline()) + { + icq_EnableMultipleControls(hwndDlg, icqPrivacyControls, SIZEOF(icqPrivacyControls), FALSE); + ShowDlgItem(hwndDlg, IDC_STATIC_NOTONLINE, SW_SHOW); + } + else + ShowDlgItem(hwndDlg, IDC_STATIC_NOTONLINE, SW_HIDE); + + CheckDlgButton(hwndDlg, IDC_DCALLOW_ANY, (nDcType == 0)); + CheckDlgButton(hwndDlg, IDC_DCALLOW_CLIST, (nDcType == 1)); + CheckDlgButton(hwndDlg, IDC_DCALLOW_AUTH, (nDcType == 2)); + CheckDlgButton(hwndDlg, IDC_ADD_ANY, (nAddAuth == 0)); + CheckDlgButton(hwndDlg, IDC_ADD_AUTH, (nAddAuth == 1)); + LoadDBCheckState(ppro, hwndDlg, IDC_WEBAWARE, "WebAware", 0); + LoadDBCheckState(ppro, hwndDlg, IDC_PUBLISHPRIMARY, "PublishPrimaryEmail", 0); + LoadDBCheckState(ppro, hwndDlg, IDC_STATUSMSG_CLIST, "StatusMsgReplyCList", 0); + LoadDBCheckState(ppro, hwndDlg, IDC_STATUSMSG_VISIBLE, "StatusMsgReplyVisible", 0); + if (!ppro->getSettingByte(NULL, "StatusMsgReplyCList", 0)) + EnableDlgItem(hwndDlg, IDC_STATUSMSG_VISIBLE, FALSE); + } + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_DCALLOW_ANY: + case IDC_DCALLOW_CLIST: + case IDC_DCALLOW_AUTH: + case IDC_ADD_ANY: + case IDC_ADD_AUTH: + case IDC_WEBAWARE: + case IDC_PUBLISHPRIMARY: + case IDC_STATUSMSG_VISIBLE: + if ((HWND)lParam != GetFocus()) return 0; + break; + case IDC_STATUSMSG_CLIST: + if (IsDlgButtonChecked(hwndDlg, IDC_STATUSMSG_CLIST)) + { + EnableDlgItem(hwndDlg, IDC_STATUSMSG_VISIBLE, TRUE); + LoadDBCheckState(ppro, hwndDlg, IDC_STATUSMSG_VISIBLE, "StatusMsgReplyVisible", 0); + } + else + { + EnableDlgItem(hwndDlg, IDC_STATUSMSG_VISIBLE, FALSE); + CheckDlgButton(hwndDlg, IDC_STATUSMSG_VISIBLE, FALSE); + } + break; + default: + return 0; + } + OptDlgChanged(hwndDlg); + break; + + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->code) { + case PSN_APPLY: + StoreDBCheckState(ppro, hwndDlg, IDC_WEBAWARE, "WebAware"); + StoreDBCheckState(ppro, hwndDlg, IDC_PUBLISHPRIMARY, "PublishPrimaryEmail"); + StoreDBCheckState(ppro, hwndDlg, IDC_STATUSMSG_CLIST, "StatusMsgReplyCList"); + StoreDBCheckState(ppro, hwndDlg, IDC_STATUSMSG_VISIBLE, "StatusMsgReplyVisible"); + if (IsDlgButtonChecked(hwndDlg, IDC_DCALLOW_AUTH)) + ppro->setSettingByte(NULL, "DCType", 2); + else if (IsDlgButtonChecked(hwndDlg, IDC_DCALLOW_CLIST)) + ppro->setSettingByte(NULL, "DCType", 1); + else + ppro->setSettingByte(NULL, "DCType", 0); + StoreDBCheckState(ppro, hwndDlg, IDC_ADD_AUTH, "Auth"); + + if (ppro->icqOnline()) + { + PBYTE buf=NULL; + int buflen=0; + + ppackTLVWord(&buf, &buflen, 0x19A, !ppro->getSettingByte(NULL, "Auth", 1)); + ppackTLVByte(&buf, &buflen, 0x212, ppro->getSettingByte(NULL, "WebAware", 0)); + ppackTLVWord(&buf, &buflen, 0x1F9, ppro->getSettingByte(NULL, "PrivacyLevel", 1)); + + ppro->icq_changeUserDirectoryInfoServ(buf, (WORD)buflen, DIRECTORYREQUEST_UPDATEPRIVACY); + + SAFE_FREE((void**)&buf); + + // Send a status packet to notify the server about the webaware setting + { + WORD wStatus = MirandaStatusToIcq(ppro->m_iStatus); + + if (ppro->m_iStatus == ID_STATUS_INVISIBLE) + { + if (ppro->m_bSsiEnabled) + ppro->updateServVisibilityCode(3); + ppro->icq_setstatus(wStatus, NULL); + } + else + { + ppro->icq_setstatus(wStatus, NULL); + if (ppro->m_bSsiEnabled) + ppro->updateServVisibilityCode(4); + } + } + } + return TRUE; + } + break; + } + + return FALSE; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static HWND hCpCombo; + +struct CPTABLE { + WORD cpId; + char *cpName; +}; + +struct CPTABLE cpTable[] = { + { 874, LPGEN("Thai") }, + { 932, LPGEN("Japanese") }, + { 936, LPGEN("Simplified Chinese") }, + { 949, LPGEN("Korean") }, + { 950, LPGEN("Traditional Chinese") }, + { 1250, LPGEN("Central European") }, + { 1251, LPGEN("Cyrillic") }, + { 1252, LPGEN("Latin I") }, + { 1253, LPGEN("Greek") }, + { 1254, LPGEN("Turkish") }, + { 1255, LPGEN("Hebrew") }, + { 1256, LPGEN("Arabic") }, + { 1257, LPGEN("Baltic") }, + { 1258, LPGEN("Vietnamese") }, + { 1361, LPGEN("Korean (Johab)") }, + { -1, NULL} +}; + +static BOOL CALLBACK FillCpCombo(LPSTR str) +{ + int i; + UINT cp; + + cp = atoi(str); + for (i=0; cpTable[i].cpName != NULL && cpTable[i].cpId!=cp; i++); + if (cpTable[i].cpName) + ComboBoxAddStringUtf(hCpCombo, cpTable[i].cpName, cpTable[i].cpId); + + return TRUE; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static const UINT icqUnicodeControls[] = {IDC_UTFALL,IDC_UTFSTATIC,IDC_UTFCODEPAGE}; +static const UINT icqDCMsgControls[] = {IDC_DCPASSIVE}; +static const UINT icqXStatusControls[] = {IDC_XSTATUSAUTO}; +static const UINT icqCustomStatusControls[] = {IDC_XSTATUSRESET}; +static const UINT icqAimControls[] = {IDC_AIMENABLE}; + +static INT_PTR CALLBACK DlgProcIcqFeaturesOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + CIcqProto* ppro = (CIcqProto*)GetWindowLongPtr( hwndDlg, GWLP_USERDATA ); + + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + + ppro = (CIcqProto*)lParam; + SetWindowLongPtr( hwndDlg, GWLP_USERDATA, lParam ); + { + BYTE byData = ppro->getSettingByte(NULL, "UtfEnabled", DEFAULT_UTF_ENABLED); + CheckDlgButton(hwndDlg, IDC_UTFENABLE, byData?TRUE:FALSE); + CheckDlgButton(hwndDlg, IDC_UTFALL, byData==2?TRUE:FALSE); + icq_EnableMultipleControls(hwndDlg, icqUnicodeControls, SIZEOF(icqUnicodeControls), byData?TRUE:FALSE); + LoadDBCheckState(ppro, hwndDlg, IDC_TEMPVISIBLE, "TempVisListEnabled",DEFAULT_TEMPVIS_ENABLED); + LoadDBCheckState(ppro, hwndDlg, IDC_SLOWSEND, "SlowSend", DEFAULT_SLOWSEND); + LoadDBCheckState(ppro, hwndDlg, IDC_ONLYSERVERACKS, "OnlyServerAcks", DEFAULT_ONLYSERVERACKS); + byData = ppro->getSettingByte(NULL, "DirectMessaging", DEFAULT_DCMSG_ENABLED); + CheckDlgButton(hwndDlg, IDC_DCENABLE, byData?TRUE:FALSE); + CheckDlgButton(hwndDlg, IDC_DCPASSIVE, byData==1?TRUE:FALSE); + icq_EnableMultipleControls(hwndDlg, icqDCMsgControls, SIZEOF(icqDCMsgControls), byData?TRUE:FALSE); + BYTE byXStatusEnabled = ppro->getSettingByte(NULL, "XStatusEnabled", DEFAULT_XSTATUS_ENABLED); + CheckDlgButton(hwndDlg, IDC_XSTATUSENABLE, byXStatusEnabled); + BYTE byMoodsEnabled = ppro->getSettingByte(NULL, "MoodsEnabled", DEFAULT_MOODS_ENABLED); + CheckDlgButton(hwndDlg, IDC_MOODSENABLE, byMoodsEnabled); + icq_EnableMultipleControls(hwndDlg, icqXStatusControls, SIZEOF(icqXStatusControls), byXStatusEnabled); + icq_EnableMultipleControls(hwndDlg, icqCustomStatusControls, SIZEOF(icqCustomStatusControls), byXStatusEnabled || byMoodsEnabled); + LoadDBCheckState(ppro, hwndDlg, IDC_XSTATUSAUTO, "XStatusAuto", DEFAULT_XSTATUS_AUTO); + LoadDBCheckState(ppro, hwndDlg, IDC_XSTATUSRESET, "XStatusReset", DEFAULT_XSTATUS_RESET); + LoadDBCheckState(ppro, hwndDlg, IDC_KILLSPAMBOTS, "KillSpambots", DEFAULT_KILLSPAM_ENABLED); + LoadDBCheckState(ppro, hwndDlg, IDC_AIMENABLE, "AimEnabled", DEFAULT_AIM_ENABLED); + icq_EnableMultipleControls(hwndDlg, icqAimControls, SIZEOF(icqAimControls), ppro->icqOnline()?FALSE:TRUE); + + hCpCombo = GetDlgItem(hwndDlg, IDC_UTFCODEPAGE); + int sCodePage = ppro->getSettingWord(NULL, "AnsiCodePage", CP_ACP); + ComboBoxAddStringUtf(GetDlgItem(hwndDlg, IDC_UTFCODEPAGE), LPGEN("System default codepage"), 0); + EnumSystemCodePagesA(FillCpCombo, CP_INSTALLED); + if(sCodePage == 0) + SendDlgItemMessage(hwndDlg, IDC_UTFCODEPAGE, CB_SETCURSEL, (WPARAM)0, 0); + else + { + for (int i = 0; i < SendDlgItemMessage(hwndDlg, IDC_UTFCODEPAGE, CB_GETCOUNT, 0, 0); i++) + { + if (SendDlgItemMessage(hwndDlg, IDC_UTFCODEPAGE, CB_GETITEMDATA, (WPARAM)i, 0) == sCodePage) + { + SendDlgItemMessage(hwndDlg, IDC_UTFCODEPAGE, CB_SETCURSEL, (WPARAM)i, 0); + break; + } + } + } + } + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_UTFENABLE: + icq_EnableMultipleControls(hwndDlg, icqUnicodeControls, SIZEOF(icqUnicodeControls), IsDlgButtonChecked(hwndDlg, IDC_UTFENABLE)); + OptDlgChanged(hwndDlg); + break; + case IDC_UTFCODEPAGE: + if(HIWORD(wParam)==CBN_SELCHANGE) + OptDlgChanged(hwndDlg); + break; + case IDC_DCENABLE: + icq_EnableMultipleControls(hwndDlg, icqDCMsgControls, SIZEOF(icqDCMsgControls), IsDlgButtonChecked(hwndDlg, IDC_DCENABLE)); + OptDlgChanged(hwndDlg); + break; + case IDC_XSTATUSENABLE: + icq_EnableMultipleControls(hwndDlg, icqXStatusControls, SIZEOF(icqXStatusControls), IsDlgButtonChecked(hwndDlg, IDC_XSTATUSENABLE)); + case IDC_MOODSENABLE: + icq_EnableMultipleControls(hwndDlg, icqCustomStatusControls, SIZEOF(icqCustomStatusControls), IsDlgButtonChecked(hwndDlg, IDC_XSTATUSENABLE) || IsDlgButtonChecked(hwndDlg, IDC_MOODSENABLE)); + default: + OptDlgChanged(hwndDlg); + break; + } + break; + + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->code) { + case PSN_APPLY: + if (IsDlgButtonChecked(hwndDlg, IDC_UTFENABLE)) + ppro->m_bUtfEnabled = IsDlgButtonChecked(hwndDlg, IDC_UTFALL)?2:1; + else + ppro->m_bUtfEnabled = 0; + { + int i = SendDlgItemMessage(hwndDlg, IDC_UTFCODEPAGE, CB_GETCURSEL, 0, 0); + ppro->m_wAnsiCodepage = (WORD)SendDlgItemMessage(hwndDlg, IDC_UTFCODEPAGE, CB_GETITEMDATA, (WPARAM)i, 0); + ppro->setSettingWord(NULL, "AnsiCodePage", ppro->m_wAnsiCodepage); + } + ppro->setSettingByte(NULL, "UtfEnabled", ppro->m_bUtfEnabled); + ppro->m_bTempVisListEnabled = (BYTE)IsDlgButtonChecked(hwndDlg, IDC_TEMPVISIBLE); + ppro->setSettingByte(NULL, "TempVisListEnabled", ppro->m_bTempVisListEnabled); + StoreDBCheckState(ppro, hwndDlg, IDC_SLOWSEND, "SlowSend"); + StoreDBCheckState(ppro, hwndDlg, IDC_ONLYSERVERACKS, "OnlyServerAcks"); + if (IsDlgButtonChecked(hwndDlg, IDC_DCENABLE)) + ppro->m_bDCMsgEnabled = IsDlgButtonChecked(hwndDlg, IDC_DCPASSIVE)?1:2; + else + ppro->m_bDCMsgEnabled = 0; + ppro->setSettingByte(NULL, "DirectMessaging", ppro->m_bDCMsgEnabled); + ppro->m_bXStatusEnabled = (BYTE)IsDlgButtonChecked(hwndDlg, IDC_XSTATUSENABLE); + ppro->setSettingByte(NULL, "XStatusEnabled", ppro->m_bXStatusEnabled); + ppro->m_bMoodsEnabled = (BYTE)IsDlgButtonChecked(hwndDlg, IDC_MOODSENABLE); + ppro->setSettingByte(NULL, "MoodsEnabled", ppro->m_bMoodsEnabled); + StoreDBCheckState(ppro, hwndDlg, IDC_XSTATUSAUTO, "XStatusAuto"); + StoreDBCheckState(ppro, hwndDlg, IDC_XSTATUSRESET, "XStatusReset"); + StoreDBCheckState(ppro, hwndDlg, IDC_KILLSPAMBOTS , "KillSpambots"); + StoreDBCheckState(ppro, hwndDlg, IDC_AIMENABLE, "AimEnabled"); + return TRUE; + } + break; + } + return FALSE; +} + +static const UINT icqContactsControls[] = {IDC_ADDSERVER,IDC_LOADFROMSERVER,IDC_SAVETOSERVER,IDC_UPLOADNOW}; +static const UINT icqAvatarControls[] = {IDC_AUTOLOADAVATARS,IDC_STRICTAVATARCHECK}; + +static INT_PTR CALLBACK DlgProcIcqContactsOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + CIcqProto* ppro = (CIcqProto*)GetWindowLongPtr( hwndDlg, GWLP_USERDATA ); + + switch (msg) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + + ppro = (CIcqProto*)lParam; + SetWindowLongPtr( hwndDlg, GWLP_USERDATA, lParam ); + + LoadDBCheckState(ppro, hwndDlg, IDC_ENABLE, "UseServerCList", DEFAULT_SS_ENABLED); + LoadDBCheckState(ppro, hwndDlg, IDC_ADDSERVER, "ServerAddRemove", DEFAULT_SS_ADDSERVER); + LoadDBCheckState(ppro, hwndDlg, IDC_LOADFROMSERVER, "LoadServerDetails", DEFAULT_SS_LOAD); + LoadDBCheckState(ppro, hwndDlg, IDC_SAVETOSERVER, "StoreServerDetails", DEFAULT_SS_STORE); + LoadDBCheckState(ppro, hwndDlg, IDC_ENABLEAVATARS, "AvatarsEnabled", DEFAULT_AVATARS_ENABLED); + LoadDBCheckState(ppro, hwndDlg, IDC_AUTOLOADAVATARS, "AvatarsAutoLoad", DEFAULT_LOAD_AVATARS); + LoadDBCheckState(ppro, hwndDlg, IDC_STRICTAVATARCHECK, "StrictAvatarCheck", DEFAULT_AVATARS_CHECK); + + icq_EnableMultipleControls(hwndDlg, icqContactsControls, SIZEOF(icqContactsControls), + ppro->getSettingByte(NULL, "UseServerCList", DEFAULT_SS_ENABLED)?TRUE:FALSE); + icq_EnableMultipleControls(hwndDlg, icqAvatarControls, SIZEOF(icqAvatarControls), + ppro->getSettingByte(NULL, "AvatarsEnabled", DEFAULT_AVATARS_ENABLED)?TRUE:FALSE); + + if (ppro->icqOnline()) + { + ShowDlgItem(hwndDlg, IDC_OFFLINETOENABLE, SW_SHOW); + EnableDlgItem(hwndDlg, IDC_ENABLE, FALSE); + EnableDlgItem(hwndDlg, IDC_ENABLEAVATARS, FALSE); + } + else + EnableDlgItem(hwndDlg, IDC_UPLOADNOW, FALSE); + + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_UPLOADNOW: + ppro->ShowUploadContactsDialog(); + return TRUE; + case IDC_ENABLE: + icq_EnableMultipleControls(hwndDlg, icqContactsControls, SIZEOF(icqContactsControls), IsDlgButtonChecked(hwndDlg, IDC_ENABLE)); + if (ppro->icqOnline()) + ShowDlgItem(hwndDlg, IDC_RECONNECTREQD, SW_SHOW); + else + EnableDlgItem(hwndDlg, IDC_UPLOADNOW, FALSE); + break; + case IDC_ENABLEAVATARS: + icq_EnableMultipleControls(hwndDlg, icqAvatarControls, SIZEOF(icqAvatarControls), IsDlgButtonChecked(hwndDlg, IDC_ENABLEAVATARS)); + break; + } + OptDlgChanged(hwndDlg); + break; + + case WM_NOTIFY: + if (((LPNMHDR)lParam)->code == PSN_APPLY ) + { + StoreDBCheckState(ppro, hwndDlg, IDC_ENABLE, "UseServerCList"); + StoreDBCheckState(ppro, hwndDlg, IDC_ADDSERVER, "ServerAddRemove"); + StoreDBCheckState(ppro, hwndDlg, IDC_LOADFROMSERVER, "LoadServerDetails"); + StoreDBCheckState(ppro, hwndDlg, IDC_SAVETOSERVER, "StoreServerDetails"); + StoreDBCheckState(ppro, hwndDlg, IDC_ENABLEAVATARS, "AvatarsEnabled"); + StoreDBCheckState(ppro, hwndDlg, IDC_AUTOLOADAVATARS, "AvatarsAutoLoad"); + StoreDBCheckState(ppro, hwndDlg, IDC_STRICTAVATARCHECK, "StrictAvatarCheck"); + return TRUE; + } + break; + } + return FALSE; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CALLBACK DlgProcIcqPopupOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); + +int CIcqProto::OnOptionsInit(WPARAM wParam, LPARAM lParam) +{ + if (IsWinVerXPPlus()) { + HMODULE hUxTheme = GetModuleHandleA("uxtheme.dll"); + if (hUxTheme) + pfnEnableThemeDialogTexture = (BOOL (WINAPI *)(HANDLE, DWORD))GetProcAddress(hUxTheme, "EnableThemeDialogTexture"); + } + + OPTIONSDIALOGPAGE odp = {0}; + odp.cbSize = sizeof(odp); + odp.position = -800000000; + odp.hInstance = hInst; + odp.ptszGroup = LPGENT("Network"); + odp.dwInitParam = LPARAM(this); + odp.ptszTitle = m_tszUserName; + odp.flags = ODPF_BOLDGROUPS | ODPF_TCHAR | ODPF_DONTTRANSLATE; + + odp.ptszTab = LPGENT("Account"); + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_ICQ); + odp.pfnDlgProc = DlgProcIcqOpts; + Options_AddPage(wParam, &odp); + + odp.ptszTab = LPGENT("Contacts"); + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_ICQCONTACTS); + odp.pfnDlgProc = DlgProcIcqContactsOpts; + Options_AddPage(wParam, &odp); + + odp.ptszTab = LPGENT("Features"); + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_ICQFEATURES); + odp.pfnDlgProc = DlgProcIcqFeaturesOpts; + Options_AddPage(wParam, &odp); + + odp.ptszTab = LPGENT("Privacy"); + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_ICQPRIVACY); + odp.pfnDlgProc = DlgProcIcqPrivacyOpts; + Options_AddPage(wParam, &odp); + + if (bPopUpService) { + odp.position = 100000000; + odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_POPUPS); + odp.groupPosition = 900000000; + odp.pfnDlgProc = DlgProcIcqPopupOpts; + odp.ptszGroup = LPGENT("Popups"); + odp.ptszTab = NULL; + Options_AddPage(wParam, &odp); + } + return 0; +} diff --git a/protocols/IcqOscarJ/src/icq_packet.cpp b/protocols/IcqOscarJ/src/icq_packet.cpp new file mode 100644 index 0000000000..79241cf297 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_packet.cpp @@ -0,0 +1,898 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera, Bio +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +WORD generate_flap_sequence() +{ + DWORD n = rand(), s = 0; + + for (DWORD i = n; i >>= 3; s += i); + + return (((0 - s) ^ (BYTE)n) & 7 ^ n) + 2; +} + +void __fastcall init_generic_packet(icq_packet *pPacket, WORD wHeaderLen) +{ + pPacket->wPlace = 0; + pPacket->wLen += wHeaderLen; + pPacket->pData = (BYTE*)SAFE_MALLOC(pPacket->wLen); +} + +void write_httphdr(icq_packet *pPacket, WORD wType, DWORD dwSeq) +{ + init_generic_packet(pPacket, 14); + + packWord(pPacket, (WORD)(pPacket->wLen - 2)); + packWord(pPacket, HTTP_PROXY_VERSION); + packWord(pPacket, wType); + packDWord(pPacket, 0); // Flags? + packDWord(pPacket, dwSeq); // Connection sequence ? +} + +void __fastcall write_flap(icq_packet *pPacket, BYTE byFlapChannel) +{ + init_generic_packet(pPacket, 6); + + pPacket->nChannel = byFlapChannel; + + packByte(pPacket, FLAP_MARKER); + packByte(pPacket, byFlapChannel); + packWord(pPacket, 0); // This is the sequence ID, it is filled in during the actual sending + packWord(pPacket, (WORD)(pPacket->wLen - 6)); // This counter should not include the flap header (thus the -6) +} + +void __fastcall serverPacketInit(icq_packet *pPacket, WORD wSize) +{ + pPacket->wLen = wSize; + write_flap(pPacket, ICQ_DATA_CHAN); +} + +void __fastcall directPacketInit(icq_packet *pPacket, DWORD dwSize) +{ + pPacket->wPlace = 0; + pPacket->wLen = (WORD)dwSize; + pPacket->pData = (BYTE *)SAFE_MALLOC(dwSize + 2); + + packLEWord(pPacket, pPacket->wLen); +} + +void __fastcall serverCookieInit(icq_packet *pPacket, BYTE *pCookie, WORD wCookieSize) +{ + pPacket->wLen = (WORD)(wCookieSize + 8 + sizeof(CLIENT_ID_STRING) + 66); + + write_flap(pPacket, ICQ_LOGIN_CHAN); + packDWord(pPacket, 0x00000001); + packTLV(pPacket, 0x06, wCookieSize, pCookie); + + // Pack client identification details. + packTLV(pPacket, 0x0003, (WORD)sizeof(CLIENT_ID_STRING)-1, (LPBYTE)CLIENT_ID_STRING); + packTLVWord(pPacket, 0x0017, CLIENT_VERSION_MAJOR); + packTLVWord(pPacket, 0x0018, CLIENT_VERSION_MINOR); + packTLVWord(pPacket, 0x0019, CLIENT_VERSION_LESSER); + packTLVWord(pPacket, 0x001a, CLIENT_VERSION_BUILD); + packTLVWord(pPacket, 0x0016, CLIENT_ID_CODE); + packTLVDWord(pPacket, 0x0014, CLIENT_DISTRIBUTION); + packTLV(pPacket, 0x000f, 0x0002, (LPBYTE)CLIENT_LANGUAGE); + packTLV(pPacket, 0x000e, 0x0002, (LPBYTE)CLIENT_COUNTRY); + packDWord(pPacket, 0x00940001); // reconnect flag + packByte(pPacket, 0); + packTLVDWord(pPacket, 0x8003, 0x00100000); // Unknown +} + +void __fastcall packByte(icq_packet *pPacket, BYTE byValue) +{ + pPacket->pData[pPacket->wPlace++] = byValue; +} + +void __fastcall packWord(icq_packet *pPacket, WORD wValue) +{ + pPacket->pData[pPacket->wPlace++] = ((wValue & 0xff00) >> 8); + pPacket->pData[pPacket->wPlace++] = (wValue & 0x00ff); +} + +void __fastcall packDWord(icq_packet *pPacket, DWORD dwValue) +{ + pPacket->pData[pPacket->wPlace++] = (BYTE)((dwValue & 0xff000000) >> 24); + pPacket->pData[pPacket->wPlace++] = (BYTE)((dwValue & 0x00ff0000) >> 16); + pPacket->pData[pPacket->wPlace++] = (BYTE)((dwValue & 0x0000ff00) >> 8); + pPacket->pData[pPacket->wPlace++] = (BYTE) (dwValue & 0x000000ff); +} + +void __fastcall packQWord(icq_packet *pPacket, DWORD64 qwValue) +{ + packDWord(pPacket, (DWORD)(qwValue >> 32)); + packDWord(pPacket, (DWORD)(qwValue & 0xffffffff)); +} + +void packTLV(icq_packet *pPacket, WORD wType, WORD wLength, const BYTE *pbyValue) +{ + packWord(pPacket, wType); + packWord(pPacket, wLength); + packBuffer(pPacket, pbyValue, wLength); +} + +void packTLVWord(icq_packet *pPacket, WORD wType, WORD wValue) +{ + packWord(pPacket, wType); + packWord(pPacket, 0x02); + packWord(pPacket, wValue); +} + +void packTLVDWord(icq_packet *pPacket, WORD wType, DWORD dwValue) +{ + packWord(pPacket, wType); + packWord(pPacket, 0x04); + packDWord(pPacket, dwValue); +} + + +void packTLVUID(icq_packet *pPacket, WORD wType, DWORD dwUin, const char *szUid) +{ + if (dwUin) + { + char szUin[UINMAXLEN]; + + _ltoa(dwUin, szUin, 10); + + packTLV(pPacket, wType, getUINLen(dwUin), (BYTE*)szUin); + } + else if (szUid) + packTLV(pPacket, wType, strlennull(szUid), (BYTE*)szUid); +} + + +// Pack a preformatted buffer. +// This can be used to pack strings or any type of raw data. +void packBuffer(icq_packet *pPacket, const BYTE* pbyBuffer, WORD wLength) +{ + while (wLength) + { + pPacket->pData[pPacket->wPlace++] = *pbyBuffer++; + wLength--; + } +} + +// Pack a buffer and prepend it with the size as a LE WORD. +// Commented out since its not actually used anywhere right now. +//void packLEWordSizedBuffer(icq_packet* pPacket, const BYTE* pbyBuffer, WORD wLength) +//{ +// +// packLEWord(pPacket, wLength); +// packBuffer(pPacket, pbyBuffer, wLength); +// +//} + +int __fastcall getUINLen(DWORD dwUin) +{ + BYTE dwUinLen = 0; + + while(dwUin) { + dwUin /= 10; + dwUinLen += 1; + } + return dwUinLen; +} + +int __fastcall getUIDLen(DWORD dwUin, const char *szUid) +{ + if (dwUin) + return getUINLen(dwUin); + else + return strlennull(szUid); +} + +void __fastcall packUIN(icq_packet *pPacket, DWORD dwUin) +{ + char pszUin[UINMAXLEN]; + BYTE nUinLen = getUINLen(dwUin); + + _ltoa(dwUin, pszUin, 10); + + packByte(pPacket, nUinLen); // Length of user id + packBuffer(pPacket, (LPBYTE)pszUin, nUinLen); // Receiving user's id +} + +void __fastcall packUID(icq_packet *pPacket, DWORD dwUin, const char *szUid) +{ + if (dwUin) + packUIN(pPacket, dwUin); + else + { + BYTE nLen = strlennull(szUid); + packByte(pPacket, nLen); + packBuffer(pPacket, (LPBYTE)szUid, nLen); + } +} + + +void packFNACHeader(icq_packet *pPacket, WORD wFamily, WORD wSubtype) +{ + packFNACHeader(pPacket, wFamily, wSubtype, 0, wSubtype << 0x10); +} + + +void packFNACHeader(icq_packet *pPacket, WORD wFamily, WORD wSubtype, WORD wFlags, DWORD dwSequence) +{ + WORD wSequence = (WORD)dwSequence & 0x7FFF; // this is necessary, if that bit is there we get disconnected + + packWord(pPacket, wFamily); // Family type + packWord(pPacket, wSubtype); // Family subtype + packWord(pPacket, wFlags); // SNAC flags + packWord(pPacket, wSequence); // SNAC request id (sequence) + packWord(pPacket, (WORD)(dwSequence >> 0x10)); // SNAC request id (command) +} + + +void packFNACHeader(icq_packet *pPacket, WORD wFamily, WORD wSubtype, WORD wFlags, DWORD dwSequence, WORD wVersion) +{ + packFNACHeader(pPacket, wFamily, wSubtype, wFlags | 0x8000, dwSequence); + packWord(pPacket, 0x06); + packTLVWord(pPacket, 0x01, wVersion); +} + + +void __fastcall packLEWord(icq_packet *pPacket, WORD wValue) +{ + pPacket->pData[pPacket->wPlace++] = (wValue & 0x00ff); + pPacket->pData[pPacket->wPlace++] = ((wValue & 0xff00) >> 8); +} + +void __fastcall packLEDWord(icq_packet *pPacket, DWORD dwValue) +{ + pPacket->pData[pPacket->wPlace++] = (BYTE) (dwValue & 0x000000ff); + pPacket->pData[pPacket->wPlace++] = (BYTE)((dwValue & 0x0000ff00) >> 8); + pPacket->pData[pPacket->wPlace++] = (BYTE)((dwValue & 0x00ff0000) >> 16); + pPacket->pData[pPacket->wPlace++] = (BYTE)((dwValue & 0xff000000) >> 24); +} + + +/* helper function to place numerics to buffer */ +static void packWord(PBYTE buf, WORD wValue) +{ + *(buf) = ((wValue & 0xff00) >> 8); + *(buf + 1) = (wValue & 0x00ff); +} + + +static void packDWord(PBYTE buf, DWORD dwValue) +{ + *(buf) = (BYTE)((dwValue & 0xff000000) >> 24); + *(buf + 1) = (BYTE)((dwValue & 0x00ff0000) >> 16); + *(buf + 2) = (BYTE)((dwValue & 0x0000ff00) >> 8); + *(buf + 3) = (BYTE) (dwValue & 0x000000ff); +} + + +static void packQWord(PBYTE buf, DWORD64 qwValue) +{ + packDWord(buf, (DWORD)(qwValue >> 32)); + packDWord(buf + 4, (DWORD)(qwValue & 0xffffffff)); +} + + +void ppackByte(PBYTE *buf, int *buflen, BYTE byValue) +{ + *buf = (PBYTE)SAFE_REALLOC(*buf, 1 + *buflen); + *(*buf + *buflen) = byValue; + ++*buflen; +} + + +void ppackWord(PBYTE *buf, int *buflen, WORD wValue) +{ + *buf = (PBYTE)SAFE_REALLOC(*buf, 2 + *buflen); + packWord(*buf + *buflen, wValue); + *buflen += 2; +} + + +void ppackLEWord(PBYTE *buf, int *buflen, WORD wValue) +{ + *buf=(PBYTE)SAFE_REALLOC(*buf, 2 + *buflen); + *(PWORD)(*buf + *buflen) = wValue; + *buflen+=2; +} + + +void ppackLEDWord(PBYTE *buf, int *buflen, DWORD dwValue) +{ + *buf = (PBYTE)SAFE_REALLOC(*buf, 4 + *buflen); + *(PDWORD)(*buf + *buflen) = dwValue; + *buflen += 4; +} + + +void ppackLELNTS(PBYTE *buf, int *buflen, const char *str) +{ + WORD len = strlennull(str); + ppackLEWord(buf, buflen, len); + *buf = (PBYTE)SAFE_REALLOC(*buf, *buflen + len); + memcpy(*buf + *buflen, str, len); + *buflen += len; +} + + +void ppackBuffer(PBYTE *buf, int *buflen, WORD wLength, const BYTE *pbyValue) +{ + if (wLength) + { + *buf = (PBYTE)SAFE_REALLOC(*buf, wLength + *buflen); + memcpy(*buf + *buflen, pbyValue, wLength); + *buflen += wLength; + } +} + + +void ppackTLV(PBYTE *buf, int *buflen, WORD wType, WORD wLength, const BYTE *pbyValue) +{ + *buf = (PBYTE)SAFE_REALLOC(*buf, 4 + wLength + *buflen); + packWord(*buf + *buflen, wType); + packWord(*buf + *buflen + 2, wLength); + if (wLength) + memcpy(*buf + *buflen + 4, pbyValue, wLength); + *buflen += 4 + wLength; +} + + +void ppackTLVByte(PBYTE *buf, int *buflen, WORD wType, BYTE byValue) +{ + *buf = (PBYTE)SAFE_REALLOC(*buf, 5 + *buflen); + packWord(*buf + *buflen, wType); + packWord(*buf + *buflen + 2, 1); + *(*buf + *buflen + 4) = byValue; + *buflen += 5; +} + + +void ppackTLVWord(PBYTE *buf, int *buflen, WORD wType, WORD wValue) +{ + *buf = (PBYTE)SAFE_REALLOC(*buf, 6 + *buflen); + packWord(*buf + *buflen, wType); + packWord(*buf + *buflen + 2, 2); + packWord(*buf + *buflen + 4, wValue); + *buflen += 6; +} + + +void ppackTLVDWord(PBYTE *buf, int *buflen, WORD wType, DWORD dwValue) +{ + *buf = (PBYTE)SAFE_REALLOC(*buf, 8 + *buflen); + packWord(*buf + *buflen, wType); + packWord(*buf + *buflen + 2, 4); + packDWord(*buf + *buflen + 4, dwValue); + *buflen += 8; +} + + +void ppackTLVDouble(PBYTE *buf, int *buflen, WORD wType, double dValue) +{ + DWORD64 qwValue; + + memcpy(&qwValue, &dValue, 8); + + *buf = (PBYTE)SAFE_REALLOC(*buf, 12 + *buflen); + packWord(*buf + *buflen, wType); + packWord(*buf + *buflen + 2, 8); + packQWord(*buf + *buflen + 4, qwValue); + *buflen += 12; +} + + +void ppackTLVUID(PBYTE *buf, int *buflen, WORD wType, DWORD dwUin, const char *szUid) +{ + if (dwUin) + { + char szUin[UINMAXLEN]; + + _ltoa(dwUin, szUin, 10); + + ppackTLV(buf, buflen, wType, getUINLen(dwUin), (BYTE*)szUin); + } + else if (szUid) + ppackTLV(buf, buflen, wType, strlennull(szUid), (BYTE*)szUid); +} + + +// *** TLV based (!!! WORDs and DWORDs are LE !!!) +void ppackLETLVByte(PBYTE *buf, int *buflen, BYTE byValue, WORD wType, BYTE always) +{ + if (!always && !byValue) return; + + *buf = (PBYTE)SAFE_REALLOC(*buf, 5 + *buflen); + *(PWORD)(*buf + *buflen) = wType; + *(PWORD)(*buf + *buflen + 2) = 1; + *(*buf + *buflen + 4) = byValue; + *buflen += 5; +} + + +void ppackLETLVWord(PBYTE *buf, int *buflen, WORD wValue, WORD wType, BYTE always) +{ + if (!always && !wValue) return; + + *buf = (PBYTE)SAFE_REALLOC(*buf, 6 + *buflen); + *(PWORD)(*buf + *buflen) = wType; + *(PWORD)(*buf + *buflen + 2) = 2; + *(PWORD)(*buf + *buflen + 4) = wValue; + *buflen += 6; +} + + +void ppackLETLVDWord(PBYTE *buf, int *buflen, DWORD dwValue, WORD wType, BYTE always) +{ + if (!always && !dwValue) return; + + *buf = (PBYTE)SAFE_REALLOC(*buf, 8 + *buflen); + *(PWORD)(*buf + *buflen) = wType; + *(PWORD)(*buf + *buflen + 2) = 4; + *(PDWORD)(*buf + *buflen + 4) = dwValue; + *buflen += 8; +} + + +void packLETLVLNTS(PBYTE *buf, int *bufpos, const char *str, WORD wType) +{ + int len = strlennull(str) + 1; + + *(PWORD)(*buf + *bufpos) = wType; + *(PWORD)(*buf + *bufpos + 2) = len + 2; + *(PWORD)(*buf + *bufpos + 4) = len; + memcpy(*buf + *bufpos + 6, str, len); + *bufpos += len + 6; +} + + +void ppackLETLVLNTS(PBYTE *buf, int *buflen, const char *str, WORD wType, BYTE always) +{ + int len = strlennull(str) + 1; + + if (!always && len < 2) return; + + *buf = (PBYTE)SAFE_REALLOC(*buf, 6 + *buflen + len); + packLETLVLNTS(buf, buflen, str, wType); +} + + +void ppackLETLVWordLNTS(PBYTE *buf, int *buflen, WORD w, const char *str, WORD wType, BYTE always) +{ + int len = strlennull(str) + 1; + + if (!always && len < 2 && !w) return; + + *buf = (PBYTE)SAFE_REALLOC(*buf, 8 + *buflen + len); + *(PWORD)(*buf + *buflen) = wType; + *(PWORD)(*buf + *buflen + 2) = len + 4; + *(PWORD)(*buf + *buflen + 4) = w; + *(PWORD)(*buf + *buflen + 6) = len; + memcpy(*buf + *buflen + 8, str, len); + *buflen += len + 8; +} + + +void ppackLETLVLNTSByte(PBYTE *buf, int *buflen, const char *str, BYTE b, WORD wType) +{ + int len = strlennull(str) + 1; + + *buf = (PBYTE)SAFE_REALLOC(*buf, 7 + *buflen + len); + *(PWORD)(*buf + *buflen) = wType; + *(PWORD)(*buf + *buflen + 2) = len + 3; + *(PWORD)(*buf + *buflen + 4) = len; + memcpy(*buf + *buflen + 6, str, len); + *(*buf + *buflen + 6 + len) = b; + *buflen += len + 7; +} + + +void CIcqProto::ppackLETLVLNTSfromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wType) +{ + char szTmp[1024]; + char *str = ""; + + if (!getSettingStringStatic(NULL, szSetting, szTmp, sizeof(szTmp))) + str = szTmp; + + ppackLETLVLNTS(buf, buflen, str, wType, 1); +} + +void CIcqProto::ppackLETLVWordLNTSfromDB(PBYTE *buf, int *buflen, WORD w, const char *szSetting, WORD wType) +{ + char szTmp[1024]; + char *str = ""; + + if (!getSettingStringStatic(NULL, szSetting, szTmp, sizeof(szTmp))) + str = szTmp; + + ppackLETLVWordLNTS(buf, buflen, w, str, wType, 1); +} + +void CIcqProto::ppackLETLVLNTSBytefromDB(PBYTE *buf, int *buflen, const char *szSetting, BYTE b, WORD wType) +{ + char szTmp[1024]; + char *str = ""; + + if (!getSettingStringStatic(NULL, szSetting, szTmp, sizeof(szTmp))) + str = szTmp; + + ppackLETLVLNTSByte(buf, buflen, str, b, wType); +} + + +void CIcqProto::ppackTLVStringFromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wType) +{ + char szTmp[1024]; + char *str = ""; + + if (!getSettingStringStatic(NULL, szSetting, szTmp, sizeof(szTmp))) + str = szTmp; + + ppackTLV(buf, buflen, wType, strlennull(str), (PBYTE)str); +} + + +void CIcqProto::ppackTLVStringUtfFromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wType) +{ + char *str = getSettingStringUtf(NULL, szSetting, NULL); + + ppackTLV(buf, buflen, wType, strlennull(str), (PBYTE)str); + + SAFE_FREE((void**)&str); +} + + +void CIcqProto::ppackTLVDateFromDB(PBYTE *buf, int *buflen, const char *szSettingYear, const char *szSettingMonth, const char *szSettingDay, WORD wType) +{ + SYSTEMTIME sTime = {0}; + double time = 0; + + sTime.wYear = getSettingWord(NULL, szSettingYear, 0); + sTime.wMonth = getSettingByte(NULL, szSettingMonth, 0); + sTime.wDay = getSettingByte(NULL, szSettingDay, 0); + if (sTime.wYear || sTime.wMonth || sTime.wDay) + { + SystemTimeToVariantTime(&sTime, &time); + time -= 2; + } + + ppackTLVDouble(buf, buflen, wType, time); +} + + +int CIcqProto::ppackTLVWordStringItemFromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wTypeID, WORD wTypeData, WORD wID) +{ + char szTmp[1024]; + char *str = NULL; + + if (!getSettingStringStatic(NULL, szSetting, szTmp, sizeof(szTmp))) + str = szTmp; + + if (str) + { + WORD wLen = strlennull(str); + + ppackWord(buf, buflen, wLen + 0x0A); + ppackTLVWord(buf, buflen, wTypeID, wID); + ppackTLV(buf, buflen, wTypeData, wLen, (PBYTE)str); + + return 1; // Success + } + + return 0; // No data +} + + +int CIcqProto::ppackTLVWordStringUtfItemFromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wTypeID, WORD wTypeData, WORD wID) +{ + char *str = getSettingStringUtf(NULL, szSetting, NULL); + + if (str) + { + WORD wLen = strlennull(str); + + ppackWord(buf, buflen, wLen + 0x0A); + ppackTLVWord(buf, buflen, wTypeID, wID); + ppackTLV(buf, buflen, wTypeData, wLen, (PBYTE)str); + + SAFE_FREE(&str); + + return 1; // Success + } + + return 0; // No data +} + + +void ppackTLVBlockItems(PBYTE *buf, int *buflen, WORD wType, int *nItems, PBYTE *pBlock, WORD *wLength, BOOL bSingleItem) +{ + *buf = (PBYTE)SAFE_REALLOC(*buf, 8 + *buflen + *wLength); + packWord(*buf + *buflen, wType); + packWord(*buf + *buflen + 2, (bSingleItem ? 4 : 2) + *wLength); + packWord(*buf + *buflen + 4, *nItems); + if (bSingleItem) + packWord(*buf + *buflen + 6, *wLength); + if (*wLength) + memcpy(*buf + *buflen + (bSingleItem ? 8 : 6), *pBlock, *wLength); + *buflen += (bSingleItem ? 8 : 6) + *wLength; + + SAFE_FREE((void**)pBlock); + *wLength = 0; + *nItems = 0; +} + + +void ppackTLVBlockItem(PBYTE *buf, int *buflen, WORD wType, PBYTE *pItem, WORD *wLength) +{ + if (wLength) + { + *buf = (PBYTE)SAFE_REALLOC(*buf, 8 + *buflen + *wLength); + packWord(*buf + *buflen, wType); + packWord(*buf + *buflen + 2, 4 + *wLength); + packWord(*buf + *buflen + 4, 1); + packWord(*buf + *buflen + 6, *wLength); + memcpy(*buf + *buflen + 8, *pItem, *wLength); + *buflen += 8 + *wLength; + } + else + { + *buf = (PBYTE)SAFE_REALLOC(*buf, 6 + *buflen); + packWord(*buf + *buflen, wType); + packWord(*buf + *buflen + 2, 0x02); + packWord(*buf + *buflen + 4, 0); + *buflen += 6; + } + + SAFE_FREE((void**)pItem); + *wLength = 0; +} + + +void __fastcall unpackByte(BYTE **pSource, BYTE *byDestination) +{ + if (byDestination) + *byDestination = *(*pSource)++; + else + *pSource += 1; +} + +void __fastcall unpackWord(BYTE **pSource, WORD *wDestination) +{ + BYTE *tmp = *pSource; + + if (wDestination) + { + *wDestination = *tmp++ << 8; + *wDestination |= *tmp++; + + *pSource = tmp; + } + else + *pSource += 2; +} + +void __fastcall unpackDWord(BYTE **pSource, DWORD *dwDestination) +{ + BYTE *tmp = *pSource; + + if (dwDestination) + { + *dwDestination = *tmp++ << 24; + *dwDestination |= *tmp++ << 16; + *dwDestination |= *tmp++ << 8; + *dwDestination |= *tmp++; + + *pSource = tmp; + } + else + *pSource += 4; +} + +void __fastcall unpackQWord(BYTE **pSource, DWORD64 *qwDestination) +{ + DWORD dwData; + + if (qwDestination) + { + unpackDWord(pSource, &dwData); + *qwDestination = ((DWORD64)dwData) << 32; + unpackDWord(pSource, &dwData); + *qwDestination |= dwData; + } + else + *pSource += 8; +} + +void __fastcall unpackLEWord(BYTE **buf, WORD *w) +{ + BYTE *tmp = *buf; + + if (w) + { + *w = (*tmp++); + *w |= ((*tmp++) << 8); + } + else + tmp += 2; + + *buf = tmp; +} + +void __fastcall unpackLEDWord(BYTE **buf, DWORD *dw) +{ + BYTE *tmp = *buf; + + if (dw) + { + *dw = (*tmp++); + *dw |= ((*tmp++) << 8); + *dw |= ((*tmp++) << 16); + *dw |= ((*tmp++) << 24); + } + else + tmp += 4; + + *buf = tmp; +} + +void unpackString(BYTE **buf, char *string, WORD len) +{ + BYTE *tmp = *buf; + + if (string) + { + while (len) /* Can have 0x00 so go by len */ + { + *string++ = *tmp++; + len--; + } + } + else + tmp += len; + + *buf = tmp; +} + +void unpackWideString(BYTE **buf, WCHAR *string, WORD len) +{ + BYTE *tmp = *buf; + + while (len > 1) + { + *string = (*tmp++ << 8); + *string |= *tmp++; + + string++; + len -= 2; + } + + // We have a stray byte at the end, this means that the buffer had an odd length + // which indicates an error. + _ASSERTE(len == 0); + if (len != 0) + { + // We dont copy the last byte but we still need to increase the buffer pointer + // (we assume that 'len' was correct) since the calling function expects + // that it is increased 'len' bytes. + *tmp += len; + } + + *buf = tmp; +} + +void unpackTypedTLV(BYTE *buf, int buflen, WORD type, WORD *ttype, WORD *tlen, BYTE **ttlv) +{ + WORD wType, wLen; + +NextTLV: + // Unpack type and length + unpackWord(&buf, &wType); + unpackWord(&buf, &wLen); + buflen -= 4; + + if (wType != type && buflen >= wLen + 4) + { // Not the right TLV, try next + buflen -= wLen; + buf += wLen; + goto NextTLV; + } + // Check buffer size + if (wLen > buflen) wLen = buflen; + + // Make sure we have a good pointer + if (ttlv) + { + if (wLen) + { // Unpack and save value + *ttlv = (BYTE*)SAFE_MALLOC(wLen + 1); // Add 1 for \0 + unpackString(&buf, (char*)*ttlv, wLen); + *(*ttlv + wLen) = '\0'; + } + else + *ttlv = NULL; + } + + // Save type and length + if (ttype) + *ttype = wType; + if (tlen) + *tlen = wLen; +} + + +BOOL CIcqProto::unpackUID(BYTE **ppBuf, WORD *pwLen, DWORD *pdwUIN, uid_str *ppszUID) +{ + BYTE nUIDLen; + + // sanity check + if (!ppBuf || !pwLen || *pwLen < 1) + return FALSE; + + // Sender UIN + unpackByte(ppBuf, &nUIDLen); + *pwLen -= 1; + + if ((nUIDLen > *pwLen) || (nUIDLen == 0)) + return FALSE; + + if (nUIDLen <= UINMAXLEN) + { // it can be uin, check + char szUIN[UINMAXLEN+1]; + + unpackString(ppBuf, szUIN, nUIDLen); + szUIN[nUIDLen] = '\0'; + *pwLen -= nUIDLen; + + if (IsStringUIN(szUIN)) + { + *pdwUIN = atoi(szUIN); + return TRUE; + } + else + { // go back + *ppBuf -= nUIDLen; + *pwLen += nUIDLen; + } + } + if (!m_bAimEnabled || !ppszUID || !(*ppszUID)) + { // skip the UID data + *ppBuf += nUIDLen; + *pwLen -= nUIDLen; + + NetLog_Server("Malformed UIN in packet"); + return FALSE; + } + + unpackString(ppBuf, *ppszUID, nUIDLen); + *pwLen -= nUIDLen; + (*ppszUID)[nUIDLen] = '\0'; + + *pdwUIN = 0; // this is how we determine aim contacts internally + + return TRUE; +} diff --git a/protocols/IcqOscarJ/src/icq_packet.h b/protocols/IcqOscarJ/src/icq_packet.h new file mode 100644 index 0000000000..ccaeb91fcc --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_packet.h @@ -0,0 +1,120 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera, Bio +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#ifndef __ICQ_PACKET_H +#define __ICQ_PACKET_H + +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef unsigned long DWORD; + +/*---------* Structures *--------------*/ + +struct icq_packet +{ + WORD wPlace; + BYTE nChannel; + WORD wLen; + BYTE *pData; +}; + +/*---------* Functions *---------------*/ + +WORD generate_flap_sequence(); + +void __fastcall init_generic_packet(icq_packet *pPacket, WORD wHeaderLen); +void write_httphdr(icq_packet *pPacket, WORD wType, DWORD dwSeq); +void __fastcall write_flap(icq_packet *pPacket, BYTE byFlapChannel); +void __fastcall serverPacketInit(icq_packet *pPacket, WORD wSize); +void __fastcall directPacketInit(icq_packet *pPacket, DWORD dwSize); + +void __fastcall serverCookieInit(icq_packet *pPacket, BYTE *pCookie, WORD wCookieSize); + +void __fastcall packByte(icq_packet *pPacket, BYTE byValue); +void __fastcall packWord(icq_packet *pPacket, WORD wValue); +void __fastcall packDWord(icq_packet *pPacket, DWORD dwValue); +void __fastcall packQWord(icq_packet *pPacket, DWORD64 qwValue); +void packTLV(icq_packet *pPacket, WORD wType, WORD wLength, const BYTE *pbyValue); +void packTLVWord(icq_packet *pPacket, WORD wType, WORD wData); +void packTLVDWord(icq_packet *pPacket, WORD wType, DWORD dwData); +void packTLVUID(icq_packet *pPacket, WORD wType, DWORD dwUin, const char *szUid); + +void packBuffer(icq_packet *pPacket, const BYTE *pbyBuffer, WORD wLength); +//void packLEWordSizedBuffer(icq_packet* pPacket, const BYTE* pbyBuffer, WORD wLength); +int __fastcall getUINLen(DWORD dwUin); +int __fastcall getUIDLen(DWORD dwUin, const char *szUid); +void __fastcall packUIN(icq_packet *pPacket, DWORD dwUin); +void __fastcall packUID(icq_packet *pPacket, DWORD dwUin, const char *szUid); + +void packFNACHeader(icq_packet *pPacket, WORD wFamily, WORD wSubtype); +void packFNACHeader(icq_packet *pPacket, WORD wFamily, WORD wSubtype, WORD wFlags, DWORD dwSequence); +void packFNACHeader(icq_packet *pPacket, WORD wFamily, WORD wSubtype, WORD wFlags, DWORD dwSequence, WORD wVersion); + +void __fastcall packLEWord(icq_packet *pPacket, WORD wValue); +void __fastcall packLEDWord(icq_packet *pPacket, DWORD dwValue); + +void packLETLVLNTS(PBYTE *buf, int *bufpos, const char *str, WORD wType); + +void ppackByte(PBYTE *buf, int *buflen, BYTE byValue); +void ppackWord(PBYTE *buf, int *buflen, WORD wValue); +void ppackLEWord(PBYTE *buf, int *buflen, WORD wValue); +void ppackLEDWord(PBYTE *buf, int *buflen, DWORD dwValue); +void ppackLELNTS(PBYTE *buf, int *buflen, const char *str); +void ppackBuffer(PBYTE *buf, int *buflen, WORD wLength, const BYTE *pbyValue); + +void ppackTLV(PBYTE *buf, int *buflen, WORD wType, WORD wLength, const BYTE *pbyValue); +void ppackTLVByte(PBYTE *buf, int *buflen, WORD wType, BYTE byValue); +void ppackTLVWord(PBYTE *buf, int *buflen, WORD wType, WORD wValue); +void ppackTLVDWord(PBYTE *buf, int *buflen, WORD wType, DWORD dwValue); +void ppackTLVDouble(PBYTE *buf, int *buflen, WORD wType, double dValue); +void ppackTLVUID(PBYTE *buf, int *buflen, WORD wType, DWORD dwUin, const char *szUid); + +void ppackLETLVByte(PBYTE *buf, int *buflen, BYTE byValue, WORD wType, BYTE always); +void ppackLETLVWord(PBYTE *buf, int *buflen, WORD wValue, WORD wType, BYTE always); +void ppackLETLVDWord(PBYTE *buf, int *buflen, DWORD dwValue, WORD wType, BYTE always); +void ppackLETLVLNTS(PBYTE *buf, int *buflen, const char *str, WORD wType, BYTE always); +void ppackLETLVWordLNTS(PBYTE *buf, int *buflen, WORD w, const char *str, WORD wType, BYTE always); +void ppackLETLVLNTSByte(PBYTE *buf, int *buflen, const char *str, BYTE b, WORD wType); + +void ppackTLVBlockItems(PBYTE *buf, int *buflen, WORD wType, int *nItems, PBYTE *pBlock, WORD *wLength, BOOL bSingleItem); +void ppackTLVBlockItem(PBYTE *buf, int *buflen, WORD wType, PBYTE *pItem, WORD *wLength); + +void __fastcall unpackByte(BYTE **pSource, BYTE *byDestination); +void __fastcall unpackWord(BYTE **pSource, WORD *wDestination); +void __fastcall unpackDWord(BYTE **pSource, DWORD *dwDestination); +void __fastcall unpackQWord(BYTE **pSource, DWORD64 *qwDestination); +void unpackString(BYTE **buf, char *string, WORD len); +void unpackWideString(BYTE **buf, WCHAR *string, WORD len); +void unpackTypedTLV(BYTE *buf, int buflen, WORD type, WORD *ttype, WORD *tlen, BYTE **ttlv); +BOOL unpackUID(BYTE **ppBuf, WORD *pwLen, DWORD *pdwUIN, uid_str *ppszUID); + +void __fastcall unpackLEWord(BYTE **buf, WORD *w); +void __fastcall unpackLEDWord(BYTE **buf, DWORD *dw); + +#endif /* __ICQ_PACKET_H */ diff --git a/protocols/IcqOscarJ/src/icq_popups.cpp b/protocols/IcqOscarJ/src/icq_popups.cpp new file mode 100644 index 0000000000..21ca36348d --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_popups.cpp @@ -0,0 +1,323 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2008 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// PopUp Plugin stuff +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +BOOL bPopUpService = FALSE; + +void InitPopUps() +{ + if (ServiceExists(MS_POPUP_ADDPOPUPEX)) + { + bPopUpService = TRUE; + } +} + +static const UINT icqPopupsControls[] = { + IDC_POPUPS_LOG_ENABLED, IDC_POPUPS_SPAM_ENABLED, IDC_PREVIEW, IDC_USESYSICONS, IDC_POPUP_LOG0_TIMEOUT, + IDC_POPUP_LOG1_TIMEOUT, IDC_POPUP_LOG2_TIMEOUT, IDC_POPUP_LOG3_TIMEOUT, IDC_POPUP_SPAM_TIMEOUT +}; + +static const UINT icqPopupColorControls[] = { + IDC_POPUP_LOG0_TEXTCOLOR, IDC_POPUP_LOG1_TEXTCOLOR, IDC_POPUP_LOG2_TEXTCOLOR, IDC_POPUP_LOG3_TEXTCOLOR, IDC_POPUP_SPAM_TEXTCOLOR, + IDC_POPUP_LOG0_BACKCOLOR, IDC_POPUP_LOG1_BACKCOLOR, IDC_POPUP_LOG2_BACKCOLOR, IDC_POPUP_LOG3_BACKCOLOR, IDC_POPUP_SPAM_BACKCOLOR +}; + +INT_PTR CALLBACK DlgProcIcqPopupOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + static bool bInitDone = true; + BYTE bEnabled; + CIcqProto* ppro = (CIcqProto*)GetWindowLongPtr( hwndDlg, GWLP_USERDATA ); + + switch (msg) { + case WM_INITDIALOG: + bInitDone = false; + TranslateDialogDefault(hwndDlg); + + ppro = (CIcqProto*)lParam; + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); + + CheckDlgButton(hwndDlg, IDC_POPUPS_LOG_ENABLED, ppro->getSettingByte(NULL,"PopupsLogEnabled",DEFAULT_LOG_POPUPS_ENABLED)); + CheckDlgButton(hwndDlg, IDC_POPUPS_SPAM_ENABLED, ppro->getSettingByte(NULL,"PopupsSpamEnabled",DEFAULT_SPAM_POPUPS_ENABLED)); + SendDlgItemMessage(hwndDlg, IDC_POPUP_LOG0_TEXTCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"Popups0TextColor",DEFAULT_LOG0_TEXT_COLORS)); + SendDlgItemMessage(hwndDlg, IDC_POPUP_LOG0_BACKCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"Popups0BackColor",DEFAULT_LOG0_BACK_COLORS)); + SetDlgItemInt(hwndDlg, IDC_POPUP_LOG0_TIMEOUT, ppro->getSettingDword(NULL,"Popups0Timeout",DEFAULT_LOG0_TIMEOUT),FALSE); + SendDlgItemMessage(hwndDlg, IDC_POPUP_LOG1_TEXTCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"Popups1TextColor",DEFAULT_LOG1_TEXT_COLORS)); + SendDlgItemMessage(hwndDlg, IDC_POPUP_LOG1_BACKCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"Popups1BackColor",DEFAULT_LOG1_BACK_COLORS)); + SetDlgItemInt(hwndDlg, IDC_POPUP_LOG1_TIMEOUT, ppro->getSettingDword(NULL,"Popups1Timeout",DEFAULT_LOG1_TIMEOUT),FALSE); + SendDlgItemMessage(hwndDlg, IDC_POPUP_LOG2_TEXTCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"Popups2TextColor",DEFAULT_LOG2_TEXT_COLORS)); + SendDlgItemMessage(hwndDlg, IDC_POPUP_LOG2_BACKCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"Popups2BackColor",DEFAULT_LOG2_BACK_COLORS)); + SetDlgItemInt(hwndDlg, IDC_POPUP_LOG2_TIMEOUT, ppro->getSettingDword(NULL,"Popups2Timeout",DEFAULT_LOG2_TIMEOUT),FALSE); + SendDlgItemMessage(hwndDlg, IDC_POPUP_LOG3_TEXTCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"Popups3TextColor",DEFAULT_LOG3_TEXT_COLORS)); + SendDlgItemMessage(hwndDlg, IDC_POPUP_LOG3_BACKCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"Popups3BackColor",DEFAULT_LOG3_BACK_COLORS)); + SetDlgItemInt(hwndDlg, IDC_POPUP_LOG3_TIMEOUT, ppro->getSettingDword(NULL,"Popups3Timeout",DEFAULT_LOG3_TIMEOUT),FALSE); + SendDlgItemMessage(hwndDlg, IDC_POPUP_SPAM_TEXTCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"PopupsSpamTextColor",DEFAULT_SPAM_TEXT_COLORS)); + SendDlgItemMessage(hwndDlg, IDC_POPUP_SPAM_BACKCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"PopupsSpamBackColor",DEFAULT_SPAM_BACK_COLORS)); + SetDlgItemInt(hwndDlg, IDC_POPUP_SPAM_TIMEOUT, ppro->getSettingDword(NULL,"PopupsSpamTimeout",DEFAULT_SPAM_TIMEOUT),FALSE); + bEnabled = ppro->getSettingByte(NULL,"PopupsWinColors",DEFAULT_POPUPS_WIN_COLORS); + CheckDlgButton(hwndDlg, IDC_USEWINCOLORS, bEnabled); + bEnabled |= ppro->getSettingByte(NULL,"PopupsDefColors",DEFAULT_POPUPS_DEF_COLORS); + CheckDlgButton(hwndDlg, IDC_USEDEFCOLORS, bEnabled); + icq_EnableMultipleControls(hwndDlg, icqPopupColorControls, SIZEOF(icqPopupColorControls), bEnabled); + CheckDlgButton(hwndDlg, IDC_USESYSICONS, ppro->getSettingByte(NULL,"PopupsSysIcons",DEFAULT_POPUPS_SYS_ICONS)); + bEnabled = ppro->getSettingByte(NULL,"PopupsEnabled",DEFAULT_POPUPS_ENABLED); + CheckDlgButton(hwndDlg, IDC_POPUPS_ENABLED, bEnabled); + icq_EnableMultipleControls(hwndDlg, icqPopupsControls, SIZEOF(icqPopupsControls), bEnabled); + if (bEnabled) + { + if (IsDlgButtonChecked(hwndDlg, IDC_USEDEFCOLORS)) + { + EnableWindow(GetDlgItem(hwndDlg, IDC_USEWINCOLORS), !WM_ENABLE); + EnableWindow(GetDlgItem(hwndDlg, IDC_USEDEFCOLORS), WM_ENABLE); + } + else + { + EnableWindow(GetDlgItem(hwndDlg, IDC_USEWINCOLORS), WM_ENABLE); + EnableWindow(GetDlgItem(hwndDlg, IDC_USEDEFCOLORS), !WM_ENABLE); + } + } + icq_EnableMultipleControls(hwndDlg, icqPopupColorControls, SIZEOF(icqPopupColorControls), bEnabled & (!IsDlgButtonChecked(hwndDlg,IDC_USEWINCOLORS) && !IsDlgButtonChecked(hwndDlg,IDC_USEDEFCOLORS))); + bInitDone = true; + return TRUE; + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDC_PREVIEW: + { + ppro->ShowPopUpMsg(NULL, LPGEN("Popup Title"), LPGEN("Sample Note"), LOG_NOTE); + ppro->ShowPopUpMsg(NULL, LPGEN("Popup Title"), LPGEN("Sample Warning"), LOG_WARNING); + ppro->ShowPopUpMsg(NULL, LPGEN("Popup Title"), LPGEN("Sample Error"), LOG_ERROR); + ppro->ShowPopUpMsg(NULL, LPGEN("Popup Title"), LPGEN("Sample Fatal"), LOG_FATAL); + ppro->ShowPopUpMsg(NULL, LPGEN("Popup Title"), LPGEN("Sample Spambot"), POPTYPE_SPAM); + } + return FALSE; + + case IDC_POPUPS_ENABLED: + bEnabled = IsDlgButtonChecked(hwndDlg,IDC_POPUPS_ENABLED); + if (bEnabled) + { + if (IsDlgButtonChecked(hwndDlg, IDC_USEDEFCOLORS)) + { + EnableWindow(GetDlgItem(hwndDlg, IDC_USEWINCOLORS), !WM_ENABLE); + EnableWindow(GetDlgItem(hwndDlg, IDC_USEDEFCOLORS), WM_ENABLE); + } + else + { + EnableWindow(GetDlgItem(hwndDlg, IDC_USEWINCOLORS), WM_ENABLE); + EnableWindow(GetDlgItem(hwndDlg, IDC_USEDEFCOLORS), !WM_ENABLE); + } + } + else + { + EnableWindow(GetDlgItem(hwndDlg, IDC_USEWINCOLORS), !WM_ENABLE); + EnableWindow(GetDlgItem(hwndDlg, IDC_USEDEFCOLORS), !WM_ENABLE); + } + icq_EnableMultipleControls(hwndDlg, icqPopupsControls, SIZEOF(icqPopupsControls), bEnabled); + + case IDC_USEWINCOLORS: + bEnabled = IsDlgButtonChecked(hwndDlg,IDC_POPUPS_ENABLED); + if (bEnabled) + { + if (IsDlgButtonChecked(hwndDlg, IDC_USEWINCOLORS)) + EnableWindow(GetDlgItem(hwndDlg, IDC_USEDEFCOLORS), !WM_ENABLE); + else + EnableWindow(GetDlgItem(hwndDlg, IDC_USEDEFCOLORS), WM_ENABLE); + } + icq_EnableMultipleControls(hwndDlg, icqPopupColorControls, SIZEOF(icqPopupColorControls), bEnabled & !IsDlgButtonChecked(hwndDlg,IDC_USEWINCOLORS)); + + case IDC_USEDEFCOLORS: + bEnabled = IsDlgButtonChecked(hwndDlg,IDC_POPUPS_ENABLED); + if (bEnabled) + { + if (IsDlgButtonChecked(hwndDlg, IDC_USEDEFCOLORS)) + EnableWindow(GetDlgItem(hwndDlg, IDC_USEWINCOLORS), !WM_ENABLE); + else + EnableWindow(GetDlgItem(hwndDlg, IDC_USEWINCOLORS), WM_ENABLE); + } + icq_EnableMultipleControls(hwndDlg, icqPopupColorControls, SIZEOF(icqPopupColorControls), bEnabled & !IsDlgButtonChecked(hwndDlg,IDC_USEDEFCOLORS)); + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + case IDC_POPUP_LOG0_TIMEOUT: + case IDC_POPUP_LOG1_TIMEOUT: + case IDC_POPUP_LOG2_TIMEOUT: + case IDC_POPUP_LOG3_TIMEOUT: + case IDC_POPUP_SPAM_TIMEOUT: + if ((HIWORD(wParam) == EN_CHANGE) && bInitDone) + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + default: + SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0); + break; + } + break; + + case WM_NOTIFY: + switch (((LPNMHDR)lParam)->code) { + case PSN_APPLY: + ppro->setSettingByte(NULL,"PopupsEnabled",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_POPUPS_ENABLED)); + ppro->setSettingByte(NULL,"PopupsLogEnabled",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_POPUPS_LOG_ENABLED)); + ppro->setSettingByte(NULL,"PopupsSpamEnabled",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_POPUPS_SPAM_ENABLED)); + ppro->setSettingDword(NULL,"Popups0TextColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_LOG0_TEXTCOLOR,CPM_GETCOLOUR,0,0)); + ppro->setSettingDword(NULL,"Popups0BackColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_LOG0_BACKCOLOR,CPM_GETCOLOUR,0,0)); + ppro->setSettingDword(NULL,"Popups0Timeout",GetDlgItemInt(hwndDlg, IDC_POPUP_LOG0_TIMEOUT, NULL, FALSE)); + ppro->setSettingDword(NULL,"Popups1TextColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_LOG1_TEXTCOLOR,CPM_GETCOLOUR,0,0)); + ppro->setSettingDword(NULL,"Popups1BackColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_LOG1_BACKCOLOR,CPM_GETCOLOUR,0,0)); + ppro->setSettingDword(NULL,"Popups1Timeout",GetDlgItemInt(hwndDlg, IDC_POPUP_LOG1_TIMEOUT, NULL, FALSE)); + ppro->setSettingDword(NULL,"Popups2TextColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_LOG2_TEXTCOLOR,CPM_GETCOLOUR,0,0)); + ppro->setSettingDword(NULL,"Popups2BackColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_LOG2_BACKCOLOR,CPM_GETCOLOUR,0,0)); + ppro->setSettingDword(NULL,"Popups2Timeout",GetDlgItemInt(hwndDlg, IDC_POPUP_LOG2_TIMEOUT, NULL, FALSE)); + ppro->setSettingDword(NULL,"Popups3TextColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_LOG3_TEXTCOLOR,CPM_GETCOLOUR,0,0)); + ppro->setSettingDword(NULL,"Popups3BackColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_LOG3_BACKCOLOR,CPM_GETCOLOUR,0,0)); + ppro->setSettingDword(NULL,"Popups3Timeout",GetDlgItemInt(hwndDlg, IDC_POPUP_LOG3_TIMEOUT, NULL, FALSE)); + ppro->setSettingDword(NULL,"PopupsSpamTextColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_SPAM_TEXTCOLOR,CPM_GETCOLOUR,0,0)); + ppro->setSettingDword(NULL,"PopupsSpamBackColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_SPAM_BACKCOLOR,CPM_GETCOLOUR,0,0)); + ppro->setSettingDword(NULL,"PopupsSpamTimeout",GetDlgItemInt(hwndDlg, IDC_POPUP_SPAM_TIMEOUT, NULL, FALSE)); + ppro->setSettingByte(NULL,"PopupsWinColors",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_USEWINCOLORS)); + ppro->setSettingByte(NULL,"PopupsDefColors",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_USEDEFCOLORS)); + ppro->setSettingByte(NULL,"PopupsSysIcons",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_USESYSICONS)); + return TRUE; + } + break; + } + return FALSE; +} + +int CIcqProto::ShowPopUpMsg(HANDLE hContact, const char *szTitle, const char *szMsg, BYTE bType) +{ + if (bPopUpService && getSettingByte(NULL, "PopupsEnabled", DEFAULT_POPUPS_ENABLED)) + { + POPUPDATAEX ppd = {0}; + POPUPDATAW ppdw = {0}; + LPCTSTR rsIcon; + char szPrefix[32], szSetting[32]; + + strcpy(szPrefix, "Popups"); + ppd.iSeconds = 0; + + switch(bType) { + case LOG_NOTE: + rsIcon = MAKEINTRESOURCE(IDI_INFORMATION); + ppd.colorBack = DEFAULT_LOG0_BACK_COLORS; + ppd.colorText = DEFAULT_LOG0_TEXT_COLORS; + strcat(szPrefix, "0"); + break; + + case LOG_WARNING: + rsIcon = MAKEINTRESOURCE(IDI_WARNING); + ppd.colorBack = DEFAULT_LOG1_BACK_COLORS; + ppd.colorText = DEFAULT_LOG1_TEXT_COLORS; + strcat(szPrefix, "1"); + break; + + case LOG_ERROR: + rsIcon = MAKEINTRESOURCE(IDI_ERROR); + ppd.colorBack = DEFAULT_LOG2_BACK_COLORS; + ppd.colorText = DEFAULT_LOG2_TEXT_COLORS; + strcat(szPrefix, "2"); + break; + + case LOG_FATAL: + rsIcon = MAKEINTRESOURCE(IDI_ERROR); + ppd.colorBack = DEFAULT_LOG3_BACK_COLORS; + ppd.colorText = DEFAULT_LOG3_TEXT_COLORS; + strcat(szPrefix, "3"); + break; + + case POPTYPE_SPAM: + rsIcon = MAKEINTRESOURCE(IDI_WARNING); + ppd.colorBack = DEFAULT_SPAM_BACK_COLORS; + ppd.colorText = DEFAULT_SPAM_TEXT_COLORS; + strcat(szPrefix, "Spam"); + break; + default: + return -1; + } + if (!getSettingByte(NULL, "PopupsSysIcons", DEFAULT_POPUPS_SYS_ICONS)) + ppd.lchIcon = m_hIconProtocol->GetIcon(); + else + ppd.lchIcon = (HICON)LoadImage( NULL, rsIcon, IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_SHARED); + if (getSettingByte(NULL, "PopupsWinColors", DEFAULT_POPUPS_WIN_COLORS)) + { + ppd.colorText = GetSysColor(COLOR_WINDOWTEXT); + ppd.colorBack = GetSysColor(COLOR_WINDOW); + } + else + { + if (getSettingByte(NULL, "PopupsDefColors", DEFAULT_POPUPS_DEF_COLORS)) + { + ppd.colorText = NULL; + ppd.colorBack = NULL; + } + else + { + strcpy(szSetting, szPrefix); + strcat(szSetting, "TextColor"); + ppd.colorText = getSettingDword(NULL, szSetting, ppd.colorText); + strcpy(szSetting, szPrefix); + strcat(szSetting, "BackColor"); + ppd.colorBack = getSettingDword(NULL, szSetting, ppd.colorBack); + } + } + strcpy(szSetting, szPrefix); + strcat(szSetting, "Timeout"); + ppd.iSeconds = getSettingDword(NULL, szSetting, ppd.iSeconds); + + // call unicode popup module - only on unicode OS otherwise it will not work properly :( + // due to Popup Plug bug in ADDPOPUPW implementation + if ( ServiceExists( MS_POPUP_ADDPOPUPW )) + { + char str[4096]; + + make_unicode_string_static(ICQTranslateUtfStatic(szTitle, str, sizeof(str)), ppdw.lpwzContactName, MAX_CONTACTNAME); + make_unicode_string_static(ICQTranslateUtfStatic(szMsg, str, sizeof(str)), ppdw.lpwzText, MAX_SECONDLINE); + ppdw.lchContact = hContact; + ppdw.lchIcon = ppd.lchIcon; + ppdw.colorBack = ppd.colorBack; + ppdw.colorText = ppd.colorText; + ppdw.PluginWindowProc = NULL; + ppdw.PluginData = NULL; + ppdw.iSeconds = ppd.iSeconds; + return CallService(MS_POPUP_ADDPOPUPW, (WPARAM)&ppdw, 0); + } + else + + { + char str[MAX_PATH]; + + utf8_decode_static(ICQTranslateUtfStatic(szTitle, str, MAX_PATH), ppd.lpzContactName, MAX_CONTACTNAME); + utf8_decode_static(ICQTranslateUtfStatic(szMsg, str, MAX_PATH), ppd.lpzText, MAX_SECONDLINE); + ppd.lchContact = hContact; + ppd.PluginWindowProc = NULL; + ppd.PluginData = NULL; + + return CallService(MS_POPUP_ADDPOPUPEX, (WPARAM)&ppd, 0); + } + } + return -1; // Failure +} diff --git a/protocols/IcqOscarJ/src/icq_popups.h b/protocols/IcqOscarJ/src/icq_popups.h new file mode 100644 index 0000000000..a42c230634 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_popups.h @@ -0,0 +1,41 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2008 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Headers for PopUp Plugin support +// +// ----------------------------------------------------------------------------- +#ifndef __ICQ_POPUPS_H +#define __ICQ_POPUPS_H + + +#define POPTYPE_SPAM 254 // this is for spambots + + +void InitPopUps(); +void InitPopupOpts(WPARAM wParam); + + +#endif /* __ICQ_POPUPS_H */ diff --git a/protocols/IcqOscarJ/src/icq_proto.cpp b/protocols/IcqOscarJ/src/icq_proto.cpp new file mode 100644 index 0000000000..d1490fac2e --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_proto.cpp @@ -0,0 +1,2375 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera, George Hazan +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Protocol Interface Implementation +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +#include "m_icolib.h" + +extern PLUGININFOEX pluginInfo; +extern HANDLE hExtraXStatus; + +#pragma warning(disable:4355) + +static int CompareConns(const directconnect* p1, const directconnect* p2) +{ + if (p1 < p2) + return -1; + + return (p1 == p2) ? 0 : 1; +} + +static int CompareCookies( const icq_cookie_info* p1, const icq_cookie_info* p2 ) +{ + if ( p1->dwCookie < p2->dwCookie ) + return -1; + + return ( p1->dwCookie == p2->dwCookie ) ? 0 : 1; +} + +static int CompareFT( const filetransfer* p1, const filetransfer* p2 ) +{ + if ( p1->dwCookie < p2->dwCookie ) + return -1; + + return ( p1->dwCookie == p2->dwCookie ) ? 0 : 1; +} + +static int CompareContactsCache(const icq_contacts_cache *p1, const icq_contacts_cache *p2) +{ + if (p1->dwUin < p2->dwUin) + return -1; + + if (p1->dwUin > p2->dwUin) + return 1; + + return stricmpnull(p1->szUid, p2->szUid); +} + +CIcqProto::CIcqProto( const char* aProtoName, const TCHAR* aUserName ) : +cookies(10, CompareCookies), +directConns(10, CompareConns), +expectedFileRecvs(10, CompareFT), +contactsCache(10, CompareContactsCache), +cheekySearchId( -1 ) +{ + m_iVersion = 2; + m_iStatus = ID_STATUS_OFFLINE; + m_tszUserName = mir_tstrdup( aUserName ); + m_szModuleName = mir_strdup( aProtoName ); + m_szProtoName = mir_strdup( aProtoName ); + _strlwr( m_szProtoName ); + m_szProtoName[0] = toupper( m_szProtoName[0] ); + NetLog_Server( "Setting protocol/module name to '%s/%s'", m_szProtoName, m_szModuleName ); + + oftMutex = new icq_critical_section(); + + // Initialize direct connections + directConnListMutex = new icq_critical_section(); + expectedFileRecvMutex = new icq_critical_section(); + + // Initialize server lists + servlistMutex = new icq_critical_section(); + servlistQueueMutex = new icq_critical_section(); + HookProtoEvent(ME_CLIST_GROUPCHANGE, &CIcqProto::ServListCListGroupChange); + + // Initialize status message struct + ZeroMemory(&m_modeMsgs, sizeof(icq_mode_messages)); + m_modeMsgsMutex = new icq_critical_section(); + connectionHandleMutex = new icq_critical_section(); + localSeqMutex = new icq_critical_section(); + + m_modeMsgsEvent = CreateProtoEvent(ME_ICQ_STATUSMSGREQ); + hxstatuschanged = CreateProtoEvent(ME_ICQ_CUSTOMSTATUS_CHANGED); + hxstatusiconchanged = CreateProtoEvent(ME_ICQ_CUSTOMSTATUS_EXTRAICON_CHANGED); + + // Initialize cookies + cookieMutex = new icq_critical_section(); + wCookieSeq = 2; + + // Initialize rates + m_ratesMutex = new icq_critical_section(); + + // Initialize avatars + m_avatarsMutex = new icq_critical_section(); + + // Initialize temporary DB settings + CreateResidentSetting("Status"); // NOTE: XStatus cannot be temporary + CreateResidentSetting("TemporaryVisible"); + CreateResidentSetting("TickTS"); + CreateResidentSetting("IdleTS"); + CreateResidentSetting("AwayTS"); + CreateResidentSetting("LogonTS"); + CreateResidentSetting("DCStatus"); + CreateResidentSetting("CapBuf"); //capabilities bufer + CreateResidentSetting(DBSETTING_STATUS_NOTE_TIME); + CreateResidentSetting(DBSETTING_STATUS_MOOD); + + // Setup services + CreateProtoService(PS_CREATEACCMGRUI, &CIcqProto::OnCreateAccMgrUI ); + CreateProtoService(MS_ICQ_SENDSMS, &CIcqProto::SendSms); + CreateProtoService(PS_SET_NICKNAME, &CIcqProto::SetNickName); + + CreateProtoService(PS_GETMYAWAYMSG, &CIcqProto::GetMyAwayMsg); + + CreateProtoService(PS_GETINFOSETTING, &CIcqProto::GetInfoSetting); + + CreateProtoService(PSS_ADDED, &CIcqProto::SendYouWereAdded); + // Session password API + CreateProtoService(PS_ICQ_SETPASSWORD, &CIcqProto::SetPassword); + // ChangeInfo API + CreateProtoService(PS_CHANGEINFOEX, &CIcqProto::ChangeInfoEx); + // Avatar API + CreateProtoService(PS_GETAVATARINFOT, &CIcqProto::GetAvatarInfo); + CreateProtoService(PS_GETAVATARCAPS, &CIcqProto::GetAvatarCaps); + CreateProtoService(PS_GETMYAVATART, &CIcqProto::GetMyAvatar); + CreateProtoService(PS_SETMYAVATART, &CIcqProto::SetMyAvatar); + // Custom Status API + CreateProtoService(PS_ICQ_SETCUSTOMSTATUS, &CIcqProto::SetXStatus); + CreateProtoService(PS_ICQ_GETCUSTOMSTATUS, &CIcqProto::GetXStatus); + CreateProtoService(PS_ICQ_SETCUSTOMSTATUSEX, &CIcqProto::SetXStatusEx); + CreateProtoService(PS_ICQ_GETCUSTOMSTATUSEX, &CIcqProto::GetXStatusEx); + CreateProtoService(PS_ICQ_GETCUSTOMSTATUSICON, &CIcqProto::GetXStatusIcon); + CreateProtoService(PS_ICQ_REQUESTCUSTOMSTATUS, &CIcqProto::RequestXStatusDetails); + CreateProtoService(PS_ICQ_GETADVANCEDSTATUSICON, &CIcqProto::RequestAdvStatusIconIdx); + + CreateProtoService(MS_ICQ_ADDSERVCONTACT, &CIcqProto::AddServerContact); + + CreateProtoService(MS_REQ_AUTH, &CIcqProto::RequestAuthorization); + CreateProtoService(MS_GRANT_AUTH, &CIcqProto::GrantAuthorization); + CreateProtoService(MS_REVOKE_AUTH, &CIcqProto::RevokeAuthorization); + + CreateProtoService(MS_XSTATUS_SHOWDETAILS, &CIcqProto::ShowXStatusDetails); + + // Custom caps + CreateProtoService(PS_ICQ_ADDCAPABILITY, &CIcqProto::IcqAddCapability); + CreateProtoService(PS_ICQ_CHECKCAPABILITY, &CIcqProto::IcqCheckCapability); + + + HookProtoEvent(ME_SKIN2_ICONSCHANGED, &CIcqProto::OnReloadIcons); + + { + // Initialize IconLib icons + char szSectionName[MAX_PATH], *szAccountName = tchar_to_utf8(m_tszUserName); + null_snprintf(szSectionName, sizeof(szSectionName), "Protocols/%s/Accounts", ICQ_PROTOCOL_NAME); + + TCHAR lib[MAX_PATH]; + GetModuleFileName(hInst, lib, MAX_PATH); + + m_hIconProtocol = IconLibDefine(szAccountName, szSectionName, m_szModuleName, "main", lib, -IDI_ICQ); + SAFE_FREE(&szAccountName); + } + + // Reset a bunch of session specific settings + UpdateGlobalSettings(); + ResetSettingsOnLoad(); + + // Initialize Contacts Cache + InitContactsCache(); + + // Startup Auto Info-Update thread + icq_InitInfoUpdate(); + + // Init extra statuses + InitXStatusIcons(); + + HookProtoEvent(ME_CLIST_PREBUILDSTATUSMENU, &CIcqProto::OnPreBuildStatusMenu); + + // Register netlib users + NETLIBUSER nlu = {0}; + TCHAR szBuffer[MAX_PATH + 64]; + null_snprintf(szBuffer, SIZEOF(szBuffer), TranslateT("%s server connection"), m_tszUserName); + nlu.cbSize = sizeof(nlu); + nlu.flags = NUF_OUTGOING | NUF_HTTPCONNS | NUF_TCHAR; + nlu.ptszDescriptiveName = szBuffer; + nlu.szSettingsModule = m_szModuleName; + nlu.szHttpGatewayHello = "http://http.proxy.icq.com/hello"; + nlu.szHttpGatewayUserAgent = "Mozilla/4.08 [en] (WinNT; U ;Nav)"; + nlu.pfnHttpGatewayInit = icq_httpGatewayInit; + nlu.pfnHttpGatewayBegin = icq_httpGatewayBegin; + nlu.pfnHttpGatewayWrapSend = icq_httpGatewayWrapSend; + nlu.pfnHttpGatewayUnwrapRecv = icq_httpGatewayUnwrapRecv; + m_hServerNetlibUser = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu); + + char szP2PModuleName[MAX_PATH]; + null_snprintf(szP2PModuleName, SIZEOF(szP2PModuleName), "%sP2P", m_szModuleName); + null_snprintf(szBuffer, SIZEOF(szBuffer), TranslateT("%s client-to-client connections"), m_tszUserName); + nlu.flags = NUF_OUTGOING | NUF_INCOMING | NUF_TCHAR; + nlu.ptszDescriptiveName = szBuffer; + nlu.szSettingsModule = szP2PModuleName; + nlu.minIncomingPorts = 1; + m_hDirectNetlibUser = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu); + + // Register custom database events + DBEVENTTYPEDESCR eventType = {0}; + eventType.cbSize = DBEVENTTYPEDESCR_SIZE; + eventType.eventType = ICQEVENTTYPE_MISSEDMESSAGE; + eventType.module = m_szModuleName; + eventType.descr = "Missed message notifications"; + eventType.textService = ICQ_DB_GETEVENTTEXT_MISSEDMESSAGE; + eventType.flags = DETF_HISTORY | DETF_MSGWINDOW; + // for now keep default "message" icon + CallService(MS_DB_EVENT_REGISTERTYPE, 0, (LPARAM)&eventType); + + // Protocol instance is ready + NetLog_Server("%s: Protocol instance '%s' created.", ICQ_PROTOCOL_NAME, m_szModuleName); +} + + +CIcqProto::~CIcqProto() +{ + m_bXStatusEnabled = 10; // block clist changing + m_bMoodsEnabled = 10; + + // Serv-list update board clean-up + FlushServerIDs(); + /// TODO: make sure server-list handler thread is not running + /// TODO: save state of server-list update board to DB + servlistPendingFlushOperations(); + SAFE_FREE((void**)&servlistQueueList); + + // Finalize avatars + /// TODO: cleanup remaining avatar requests + SAFE_DELETE(&m_avatarsMutex); + + // NetLib clean-up + NetLib_SafeCloseHandle(&m_hDirectNetlibUser); + NetLib_SafeCloseHandle(&m_hServerNetlibUser); + + // Destroy hookable events + if (m_modeMsgsEvent) + DestroyHookableEvent(m_modeMsgsEvent); + + if (hxstatuschanged) + DestroyHookableEvent(hxstatuschanged); + + if (hxstatusiconchanged) + DestroyHookableEvent(hxstatusiconchanged); + + // Clean-up remaining protocol instance members + cookies.destroy(); + + UninitContactsCache(); + + CustomCapList.clear(); + + SAFE_DELETE(&m_ratesMutex); + + SAFE_DELETE(&servlistMutex); + SAFE_DELETE(&servlistQueueMutex); + + SAFE_DELETE(&m_modeMsgsMutex); + SAFE_DELETE(&localSeqMutex); + SAFE_DELETE(&connectionHandleMutex); + SAFE_DELETE(&oftMutex); + SAFE_DELETE(&directConnListMutex); + SAFE_DELETE(&expectedFileRecvMutex); + SAFE_DELETE(&cookieMutex); + + SAFE_FREE(&m_modeMsgs.szOnline); + SAFE_FREE(&m_modeMsgs.szAway); + SAFE_FREE(&m_modeMsgs.szNa); + SAFE_FREE(&m_modeMsgs.szOccupied); + SAFE_FREE(&m_modeMsgs.szDnd); + SAFE_FREE(&m_modeMsgs.szFfc); + + // Remove account icons + UninitXStatusIcons(); + + IconLibRemove(&m_hIconProtocol); + + NetLog_Server("%s: Protocol instance '%s' destroyed.", ICQ_PROTOCOL_NAME, m_szModuleName); + + mir_free( m_szProtoName ); + mir_free( m_szModuleName ); + mir_free( m_tszUserName ); +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// OnModulesLoadedEx - performs hook registration + + +int CIcqProto::OnModulesLoaded( WPARAM wParam, LPARAM lParam ) +{ + char pszP2PName[MAX_PATH]; + char pszGroupsName[MAX_PATH]; + char pszSrvGroupsName[MAX_PATH]; + char* modules[5] = {0,0,0,0,0}; + + null_snprintf(pszP2PName, SIZEOF(pszP2PName), "%sP2P", m_szModuleName); + null_snprintf(pszGroupsName, SIZEOF(pszGroupsName), "%sGroups", m_szModuleName); + null_snprintf(pszSrvGroupsName, SIZEOF(pszSrvGroupsName), "%sSrvGroups", m_szModuleName); + modules[0] = m_szModuleName; + modules[1] = pszP2PName; + modules[2] = pszGroupsName; + modules[3] = pszSrvGroupsName; + CallService("DBEditorpp/RegisterModule",(WPARAM)modules,(LPARAM)4); + + HookProtoEvent(ME_OPT_INITIALISE, &CIcqProto::OnOptionsInit); + HookProtoEvent(ME_USERINFO_INITIALISE, &CIcqProto::OnUserInfoInit); + HookProtoEvent(ME_IDLE_CHANGED, &CIcqProto::OnIdleChanged); + + InitAvatars(); + + // Init extra optional modules + InitPopUps(); + InitXStatusItems(FALSE); + + if (hExtraXStatus == NULL) + { + if (HookProtoEvent(ME_CLIST_EXTRA_LIST_REBUILD, &CIcqProto::CListMW_ExtraIconsRebuild)) + { // note if the Hook was successful (e.g. clist_nicer creates them too late) + HookProtoEvent(ME_CLIST_EXTRA_IMAGE_APPLY, &CIcqProto::CListMW_ExtraIconsApply); + bXStatusExtraIconsReady = 1; + } + } + else + { + HANDLE hContact = FindFirstContact(); + while (hContact != NULL) + { + DWORD bXStatus = getContactXStatus(hContact); + if (bXStatus > 0) + setContactExtraIcon(hContact, bXStatus); + + hContact = FindNextContact(hContact); + } + } + + return 0; +} + +int CIcqProto::OnPreShutdown(WPARAM wParam,LPARAM lParam) +{ + // signal info update thread to stop + icq_InfoUpdateCleanup(); + + // Make sure all connections are closed + CloseContactDirectConns(NULL); + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// PS_AddToList - adds a contact to the contact list + +HANDLE CIcqProto::AddToList( int flags, PROTOSEARCHRESULT* psr ) +{ + if (psr) + { + if (psr->cbSize == sizeof(ICQSEARCHRESULT)) + { + ICQSEARCHRESULT *isr = (ICQSEARCHRESULT*)psr; + if (isr->uin) + return AddToListByUIN(isr->uin, flags); + else + { // aim contact + char szUid[MAX_PATH]; + + if (isr->hdr.flags & PSR_UNICODE) + unicode_to_ansi_static((WCHAR*)isr->hdr.id, szUid, MAX_PATH); + else + null_strcpy(szUid, (char*)isr->hdr.id, MAX_PATH); + + if (szUid[0] == 0) return 0; + return AddToListByUID(szUid, flags); + } + } + else + { + char szUid[MAX_PATH]; + + if (psr->flags & PSR_UNICODE) + unicode_to_ansi_static((WCHAR*)psr->id, szUid, MAX_PATH); + else + null_strcpy(szUid, (char*)psr->id, MAX_PATH); + + if (szUid[0] == 0) return 0; + if (IsStringUIN(szUid)) + return AddToListByUIN(atoi(szUid), flags); + else + return AddToListByUID(szUid, flags); + } + } + + return 0; // Failure +} + +HANDLE __cdecl CIcqProto::AddToListByEvent( int flags, int iContact, HANDLE hDbEvent ) +{ + DWORD uin = 0; + uid_str uid = {0}; + + DBEVENTINFO dbei = {0}; + dbei.cbSize = sizeof(dbei); + if ((dbei.cbBlob = CallService(MS_DB_EVENT_GETBLOBSIZE, (WPARAM)hDbEvent, 0)) == -1) + return 0; + + dbei.pBlob = (PBYTE)_alloca(dbei.cbBlob + 1); + dbei.pBlob[dbei.cbBlob] = '\0'; + + if (CallService(MS_DB_EVENT_GET, (WPARAM)hDbEvent, (LPARAM)&dbei)) + return 0; // failed to get event + + if (strcmpnull(dbei.szModule, m_szModuleName)) + return 0; // this event is not ours + + switch(dbei.eventType) { + case EVENTTYPE_CONTACTS: + { + char *pbOffset = (char*)dbei.pBlob; + char *pbEnd = pbOffset + dbei.cbBlob; + for (int i = 0; i <= iContact; i++) { + pbOffset += strlennull(pbOffset) + 1; // Nick + if (pbOffset >= pbEnd) break; + if (i == iContact) + { // we found the contact, get uid + if (IsStringUIN((char*)pbOffset)) + uin = atoi((char*)pbOffset); + else + { + uin = 0; + strcpy(uid, (char*)pbOffset); + } + } + pbOffset += strlennull(pbOffset) + 1; // Uin + if (pbOffset >= pbEnd) break; + } + } + break; + + case EVENTTYPE_AUTHREQUEST: + case EVENTTYPE_ADDED: + if ( getContactUid( DbGetAuthEventContact(&dbei), &uin, &uid)) + return 0; + + default: + return 0; + } + + if (uin != 0) + return AddToListByUIN(uin, flags); // Success + + // add aim contact + if (strlennull(uid)) + return AddToListByUID(uid, flags); // Success + + return NULL; // Failure +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// PS_AuthAllow - processes the successful authorization + +int CIcqProto::Authorize( HANDLE hDbEvent ) +{ + if (icqOnline() && hDbEvent) + { + HANDLE hContact = HContactFromAuthEvent( hDbEvent ); + if (hContact == INVALID_HANDLE_VALUE) + return 1; + + DWORD uin; + uid_str uid; + if (getContactUid(hContact, &uin, &uid)) + return 1; + + icq_sendAuthResponseServ(uin, uid, 1, _T("")); + + deleteSetting(hContact, "Grant"); + + return 0; // Success + } + + return 1; // Failure +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// PS_AuthDeny - handles the unsuccessful authorization + +int CIcqProto::AuthDeny( HANDLE hDbEvent, const TCHAR* szReason ) +{ + if (icqOnline() && hDbEvent) + { + HANDLE hContact = HContactFromAuthEvent(hDbEvent); + if (hContact == INVALID_HANDLE_VALUE) + return 1; + + DWORD uin; + uid_str uid; + if (getContactUid(hContact, &uin, &uid)) + return 1; + + icq_sendAuthResponseServ(uin, uid, 0, szReason); + + if (DBGetContactSettingByte(hContact, "CList", "NotOnList", 0)) + CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0); + + return 0; // Success + } + + return 1; // Failure +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// PSR_AUTH + +int __cdecl CIcqProto::AuthRecv( HANDLE hContact, PROTORECVEVENT* pre ) +{ + setContactHidden( hContact, 0 ); + ICQAddRecvEvent( NULL, EVENTTYPE_AUTHREQUEST, pre, pre->lParam, (PBYTE)pre->szMessage, 0 ); + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// PSS_AUTHREQUEST + +int __cdecl CIcqProto::AuthRequest( HANDLE hContact, const TCHAR* szMessage ) +{ + if ( !icqOnline()) + return 1; + + if (hContact) + { + DWORD dwUin; + uid_str szUid; + if (getContactUid(hContact, &dwUin, &szUid)) + return 1; // Invalid contact + + if (dwUin) + { + char *utf = tchar_to_utf8(szMessage); + icq_sendAuthReqServ(dwUin, szUid, utf); + SAFE_FREE(&utf); + return 0; // Success + } + } + + return 1; // Failure +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// ChangeInfo + +HANDLE __cdecl CIcqProto::ChangeInfo( int iInfoType, void* pInfoData ) +{ + return NULL; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// PS_FileAllow - starts a file transfer + +HANDLE __cdecl CIcqProto::FileAllow( HANDLE hContact, HANDLE hTransfer, const TCHAR* szPath ) +{ + DWORD dwUin; + uid_str szUid; + + if (getContactUid(hContact, &dwUin, &szUid)) + return 0; // Invalid contact + + if (icqOnline() && hContact && szPath && hTransfer) + { // approve old fashioned file transfer + basic_filetransfer *ft = (basic_filetransfer *)hTransfer; + + if (!IsValidFileTransfer(ft)) + return 0; // Invalid transfer + + if (dwUin && ft->ft_magic == FT_MAGIC_ICQ) + { + filetransfer *ft = (filetransfer *)hTransfer; + ft->szSavePath = tchar_to_utf8(szPath); + + { + icq_lock l(expectedFileRecvMutex); + expectedFileRecvs.insert(ft); + } + + // Was request received thru DC and have we a open DC, send through that + if (ft->bDC && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0)) + icq_sendFileAcceptDirect(hContact, ft); + else + icq_sendFileAcceptServ(dwUin, ft, 0); + + return hTransfer; // Success + } + else if (ft->ft_magic == FT_MAGIC_OSCAR) + { // approve oscar file transfer + return oftFileAllow(hContact, hTransfer, szPath); + } + } + + return 0; // Failure +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// PS_FileCancel - cancels a file transfer + +int __cdecl CIcqProto::FileCancel( HANDLE hContact, HANDLE hTransfer ) +{ + DWORD dwUin; + uid_str szUid; + if ( getContactUid(hContact, &dwUin, &szUid)) + return 1; // Invalid contact + + if (hContact && hTransfer) + { + basic_filetransfer *ft = (basic_filetransfer *)hTransfer; + + if (!IsValidFileTransfer(ft)) + return 1; // Invalid transfer + + if (dwUin && ft->ft_magic == FT_MAGIC_ICQ) + { // cancel old fashioned file transfer + filetransfer * ft = (filetransfer*)hTransfer; + icq_CancelFileTransfer(hContact, ft); + return 0; // Success + } + else if (ft->ft_magic == FT_MAGIC_OSCAR) + { // cancel oscar file transfer + return oftFileCancel(hContact, hTransfer); + } + } + + return 1; // Failure +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// PS_FileDeny - denies a file transfer + +int __cdecl CIcqProto::FileDeny( HANDLE hContact, HANDLE hTransfer, const TCHAR* szReason ) +{ + int nReturnValue = 1; + DWORD dwUin; + uid_str szUid; + basic_filetransfer *ft = (basic_filetransfer*)hTransfer; + + if (getContactUid(hContact, &dwUin, &szUid)) + return 1; // Invalid contact + + if (icqOnline() && hTransfer && hContact) + { + if (!IsValidFileTransfer(hTransfer)) + return 1; // Invalid transfer + + if (dwUin && ft->ft_magic == FT_MAGIC_ICQ) + { // deny old fashioned file transfer + filetransfer *ft = (filetransfer*)hTransfer; + char *szReasonUtf = tchar_to_utf8(szReason); + // Was request received thru DC and have we a open DC, send through that + if (ft->bDC && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0)) + icq_sendFileDenyDirect(hContact, ft, szReasonUtf); + else + icq_sendFileDenyServ(dwUin, ft, szReasonUtf, 0); + SAFE_FREE(&szReasonUtf); + + nReturnValue = 0; // Success + } + else if (ft->ft_magic == FT_MAGIC_OSCAR) + { // deny oscar file transfer + return oftFileDeny(hContact, hTransfer, szReason); + } + } + // Release possible orphan structure + SafeReleaseFileTransfer((void**)&ft); + + return nReturnValue; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// PS_FileResume - processes file renaming etc + +int __cdecl CIcqProto::FileResume( HANDLE hTransfer, int* action, const TCHAR** szFilename ) +{ + if (icqOnline() && hTransfer) + { + basic_filetransfer *ft = (basic_filetransfer *)hTransfer; + + if (!IsValidFileTransfer(ft)) + return 1; // Invalid transfer + + if (ft->ft_magic == FT_MAGIC_ICQ) + { + char *szFileNameUtf = tchar_to_utf8(*szFilename); + icq_sendFileResume((filetransfer *)hTransfer, *action, szFileNameUtf); + SAFE_FREE(&szFileNameUtf); + } + else if (ft->ft_magic == FT_MAGIC_OSCAR) + { + oftFileResume((oscar_filetransfer *)hTransfer, *action, *szFilename); + } + else + return 1; // Failure + + return 0; // Success + } + + return 1; // Failure +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// GetCaps - return protocol capabilities bits + +DWORD_PTR __cdecl CIcqProto::GetCaps( int type, HANDLE hContact ) +{ + DWORD_PTR nReturn = 0; + + switch ( type ) { + + case PFLAGNUM_1: + nReturn = PF1_IM | PF1_URL | PF1_AUTHREQ | PF1_BASICSEARCH | PF1_ADDSEARCHRES | + PF1_VISLIST | PF1_INVISLIST | PF1_MODEMSG | PF1_FILE | PF1_EXTSEARCH | + PF1_EXTSEARCHUI | PF1_SEARCHBYEMAIL | PF1_SEARCHBYNAME | + PF1_ADDED | PF1_CONTACT; + if (!m_bAimEnabled) + nReturn |= PF1_NUMERICUSERID; + if (m_bSsiEnabled && getSettingByte(NULL, "ServerAddRemove", DEFAULT_SS_ADDSERVER)) + nReturn |= PF1_SERVERCLIST; + break; + + case PFLAGNUM_2: + nReturn = PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND | + PF2_FREECHAT | PF2_INVISIBLE; + if (m_bAimEnabled) + nReturn |= PF2_ONTHEPHONE; + break; + + case PFLAGNUM_3: + nReturn = PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND | + PF2_FREECHAT | PF2_INVISIBLE; + break; + + case PFLAGNUM_4: + nReturn = PF4_SUPPORTIDLE | PF4_IMSENDUTF | PF4_IMSENDOFFLINE | PF4_INFOSETTINGSVC; + if (m_bAvatarsEnabled) + nReturn |= PF4_AVATARS; +#ifdef DBG_CAPMTN + nReturn |= PF4_SUPPORTTYPING; +#endif + break; + + case PFLAGNUM_5: + nReturn = PF2_FREECHAT; + if (m_bAimEnabled) + nReturn |= PF2_ONTHEPHONE; + break; + + case PFLAG_UNIQUEIDTEXT: + nReturn = (DWORD_PTR)Translate("User ID"); + break; + + case PFLAG_UNIQUEIDSETTING: + nReturn = (DWORD_PTR)UNIQUEIDSETTING; + break; + + case PFLAG_MAXCONTACTSPERPACKET: + if ( hContact ) + { // determine per contact + BYTE bClientId = getSettingByte(hContact, "ClientID", CLID_GENERIC); + + if (bClientId == CLID_MIRANDA) + { + if (CheckContactCapabilities(hContact, CAPF_CONTACTS) && getContactStatus(hContact) != ID_STATUS_OFFLINE) + nReturn = 0x100; // limited only by packet size + else + nReturn = MAX_CONTACTSSEND; + } + else if (bClientId == CLID_ICQ6) + { + if (CheckContactCapabilities(hContact, CAPF_CONTACTS)) + nReturn = 1; // crapy ICQ6 cannot handle multiple contacts in the transfer + else + nReturn = 0; // this version does not support contacts transfer at all + } + else + nReturn = MAX_CONTACTSSEND; + } + else // return generic limit + nReturn = MAX_CONTACTSSEND; + break; + + case PFLAG_MAXLENOFMESSAGE: + nReturn = MAX_MESSAGESNACSIZE-102; + } + + return nReturn; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// GetIcon - loads an icon for the contact list + +HICON __cdecl CIcqProto::GetIcon( int iconIndex ) +{ + if (LOWORD(iconIndex) == PLI_PROTOCOL) + { + if (iconIndex & PLIF_ICOLIBHANDLE) + return (HICON)m_hIconProtocol->Handle(); + + bool big = (iconIndex & PLIF_SMALL) == 0; + HICON hIcon = m_hIconProtocol->GetIcon(big); + + if (iconIndex & PLIF_ICOLIB) + return hIcon; + + hIcon = CopyIcon(hIcon); + m_hIconProtocol->ReleaseIcon(big); + return hIcon; + + } + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// GetInfo - retrieves a contact info + +int __cdecl CIcqProto::GetInfo(HANDLE hContact, int infoType) +{ + if (icqOnline()) + { + DWORD dwUin; + uid_str szUid; + + if (getContactUid(hContact, &dwUin, &szUid)) + return 1; // Invalid contact + + DWORD dwCookie; + if (dwUin) + dwCookie = icq_sendGetInfoServ(hContact, dwUin, (infoType & SGIF_ONOPEN) != 0); + else // TODO: this needs something better + dwCookie = icq_sendGetAimProfileServ(hContact, szUid); + + return (dwCookie) ? 0 : 1; + } + + return 1; // Failure +} + +//////////////////////////////////////////////////////////////////////////////////////// +// SearchBasic - searches the contact by UID + +void CIcqProto::CheekySearchThread( void* ) +{ + char szUin[UINMAXLEN]; + ICQSEARCHRESULT isr = {0}; + isr.hdr.cbSize = sizeof(isr); + + if (cheekySearchUin) + { + _itoa(cheekySearchUin, szUin, 10); + isr.hdr.id = (FNAMECHAR*)szUin; + } + else + { + isr.hdr.id = (FNAMECHAR*)cheekySearchUid; + } + isr.uin = cheekySearchUin; + + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)cheekySearchId, (LPARAM)&isr); + BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)cheekySearchId, 0); + cheekySearchId = -1; +} + + +HANDLE __cdecl CIcqProto::SearchBasic( const PROTOCHAR *pszSearch ) +{ + if (strlennull(pszSearch) == 0) + return 0; + + char pszUIN[255]; + int nHandle = 0; + int i, j; + + if (!m_bAimEnabled) + { + for (i=j=0; (i=0x30) && (pszSearch[i]<=0x39)) + { + pszUIN[j] = pszSearch[i]; + j++; + } + } + } + else + { + for (i=j=0; (i= 0x80) continue; + pszUIN[j] = pszSearch[i]; + j++; + } + } + } + pszUIN[j] = 0; + + if (strlennull(pszUIN)) + { + DWORD dwUin; + if (IsStringUIN(pszUIN)) + dwUin = atoi(pszUIN); + else + dwUin = 0; + + // Cheeky instant UIN search + if (!dwUin || GetKeyState(VK_CONTROL)&0x8000) + { + cheekySearchId = GenerateCookie(0); + cheekySearchUin = dwUin; + cheekySearchUid = null_strdup(pszUIN); + ForkThread(&CIcqProto::CheekySearchThread, 0); // The caller needs to get this return value before the results + nHandle = cheekySearchId; + } + else if (icqOnline()) + { + nHandle = SearchByUin(dwUin); + } + + // Success + return (HANDLE)nHandle; + } + + // Failure + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// SearchByEmail - searches the contact by its e-mail + +HANDLE __cdecl CIcqProto::SearchByEmail( const PROTOCHAR *email ) +{ + if (email && icqOnline() && strlennull(email) > 0) + { + DWORD dwSearchId, dwSecId; + char *szEmail = tchar_to_ansi(email); + + // Success + dwSearchId = SearchByMail(szEmail); + if (m_bAimEnabled) + dwSecId = icq_searchAimByEmail(szEmail, dwSearchId); + else + dwSecId = 0; + + SAFE_FREE(&szEmail); + + if (dwSearchId) + return ( HANDLE )dwSearchId; + else + return ( HANDLE )dwSecId; + } + + return 0; // Failure +} + +//////////////////////////////////////////////////////////////////////////////////////// +// PS_SearchByName - searches the contact by its first or last name, or by a nickname + +HANDLE __cdecl CIcqProto::SearchByName(const PROTOCHAR *nick, const PROTOCHAR *firstName, const PROTOCHAR *lastName) +{ + if (icqOnline()) + { + if (nick || firstName || lastName) + { + char *nickUtf = tchar_to_utf8(nick); + char *firstNameUtf = tchar_to_utf8(firstName); + char *lastNameUtf = tchar_to_utf8(lastName); + + // Success + HANDLE dwCookie = (HANDLE)SearchByNames(nickUtf, firstNameUtf, lastNameUtf, 0); + + SAFE_FREE(&nickUtf); + SAFE_FREE(&firstNameUtf); + SAFE_FREE(&lastNameUtf); + + return dwCookie; + } + } + + return 0; // Failure +} + + +HWND __cdecl CIcqProto::CreateExtendedSearchUI( HWND parent ) +{ + if (parent && hInst) + return CreateDialog(hInst, MAKEINTRESOURCE(IDD_ICQADVANCEDSEARCH), parent, AdvancedSearchDlgProc); + + return NULL; // Failure +} + +HWND __cdecl CIcqProto::SearchAdvanced( HWND hwndDlg ) +{ + if (icqOnline() && IsWindow(hwndDlg)) + { + int nDataLen; + BYTE* bySearchData; + + if (bySearchData = createAdvancedSearchStructure(hwndDlg, &nDataLen)) + { + int result = icq_sendAdvancedSearchServ(bySearchData, nDataLen); + SAFE_FREE((void**)&bySearchData); + return ( HWND )result; // Success + } + } + + return NULL; // Failure +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// RecvContacts + +int __cdecl CIcqProto::RecvContacts( HANDLE hContact, PROTORECVEVENT* pre ) +{ + ICQSEARCHRESULT **isrList = (ICQSEARCHRESULT**)pre->szMessage; + int i; + DWORD cbBlob = 0; + DWORD flags = 0; + + if (pre->flags & PREF_UTF || pre->flags & PREF_UNICODE) + flags |= DBEF_UTF; + + for (i = 0; i < pre->lParam; i++) + { + if (pre->flags & PREF_UNICODE) + cbBlob += get_utf8_size((WCHAR*)isrList[i]->hdr.nick) + 2; + else + cbBlob += strlennull((char*)isrList[i]->hdr.nick) + 2; // both trailing zeros + if (isrList[i]->uin) + cbBlob += getUINLen(isrList[i]->uin); + else if (pre->flags & PREF_UNICODE) + cbBlob += strlennull((WCHAR*)isrList[i]->hdr.id); + else + cbBlob += strlennull((char*)isrList[i]->hdr.id); + } + PBYTE pBlob = (PBYTE)_alloca(cbBlob), pCurBlob; + for (i = 0, pCurBlob = pBlob; i < pre->lParam; i++) + { + if (pre->flags & PREF_UNICODE) + make_utf8_string_static((WCHAR*)isrList[i]->hdr.nick, (char*)pCurBlob, cbBlob - (pCurBlob - pBlob)); + else + strcpy((char*)pCurBlob, (char*)isrList[i]->hdr.nick); + pCurBlob += strlennull((char*)pCurBlob) + 1; + if (isrList[i]->uin) + { + char szUin[UINMAXLEN]; + _itoa(isrList[i]->uin, szUin, 10); + strcpy((char*)pCurBlob, szUin); + } + else + { // aim contact + if (pre->flags & PREF_UNICODE) + unicode_to_ansi_static((WCHAR*)isrList[i]->hdr.id, (char*)pCurBlob, cbBlob - (pCurBlob - pBlob)); + else + strcpy((char*)pCurBlob, (char*)isrList[i]->hdr.id); + } + pCurBlob += strlennull((char*)pCurBlob) + 1; + } + + ICQAddRecvEvent(hContact, EVENTTYPE_CONTACTS, pre, cbBlob, pBlob, flags); + return 0; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// RecvFile + +int __cdecl CIcqProto::RecvFile( HANDLE hContact, PROTORECVFILET* evt ) +{ + return Proto_RecvFile(hContact, evt); +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// RecvMsg + +int __cdecl CIcqProto::RecvMsg( HANDLE hContact, PROTORECVEVENT* pre ) +{ + DWORD cbBlob; + DWORD flags = 0; + + cbBlob = strlennull(pre->szMessage) + 1; + // process utf-8 encoded messages + if ((pre->flags & PREF_UTF) && !IsUSASCII(pre->szMessage, strlennull(pre->szMessage))) + flags |= DBEF_UTF; + // process unicode ucs-2 messages + if ((pre->flags & PREF_UNICODE) && !IsUnicodeAscii((WCHAR*)(pre->szMessage+cbBlob), strlennull((WCHAR*)(pre->szMessage+cbBlob)))) + cbBlob *= (sizeof(WCHAR)+1); + + ICQAddRecvEvent(hContact, EVENTTYPE_MESSAGE, pre, cbBlob, (PBYTE)pre->szMessage, flags); + + // stop contact from typing - some clients do not sent stop notify + if (CheckContactCapabilities(hContact, CAPF_TYPING)) + CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hContact, PROTOTYPE_CONTACTTYPING_OFF); + + return 0; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// RecvUrl + +int __cdecl CIcqProto::RecvUrl( HANDLE hContact, PROTORECVEVENT* ) +{ + return 1; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// SendContacts + +int __cdecl CIcqProto::SendContacts( HANDLE hContact, int flags, int nContacts, HANDLE* hContactsList ) +{ + if (hContact && hContactsList) + { + int i; + DWORD dwUin; + uid_str szUid; + WORD wRecipientStatus; + DWORD dwCookie; + + if (getContactUid(hContact, &dwUin, &szUid)) + { // Invalid contact + return ReportGenericSendError(hContact, ACKTYPE_CONTACTS, "The receiver has an invalid user ID."); + } + + wRecipientStatus = getContactStatus(hContact); + + // Failures + if (!icqOnline()) + { + dwCookie = ReportGenericSendError(hContact, ACKTYPE_CONTACTS, "You cannot send messages when you are offline."); + } + else if (!hContactsList || (nContacts < 1) || (nContacts > MAX_CONTACTSSEND)) + { + dwCookie = ReportGenericSendError(hContact, ACKTYPE_CONTACTS, "Bad data (internal error #1)"); + } + // OK + else + { + if (CheckContactCapabilities(hContact, CAPF_CONTACTS) && wRecipientStatus != ID_STATUS_OFFLINE) + { // Use the new format if possible + int nDataLen, nNamesLen; + struct icq_contactsend_s* contacts = NULL; + + // Format the data part and the names part + // This is kinda messy, but there is no simple way to do it. First + // we need to calculate the length of the packet. + contacts = (struct icq_contactsend_s*)_alloca(sizeof(struct icq_contactsend_s)*nContacts); + ZeroMemory(contacts, sizeof(struct icq_contactsend_s)*nContacts); + { + nDataLen = 0; nNamesLen = 0; + for (i = 0; i < nContacts; i++) + { + uid_str szContactUid; + + if (!IsICQContact(hContactsList[i])) + break; // Abort if a non icq contact is found + if (getContactUid(hContactsList[i], &contacts[i].uin, &szContactUid)) + break; // Abort if invalid contact + contacts[i].uid = contacts[i].uin?NULL:null_strdup(szContactUid); + contacts[i].szNick = NickFromHandleUtf(hContactsList[i]); + nDataLen += getUIDLen(contacts[i].uin, contacts[i].uid) + 4; + nNamesLen += strlennull(contacts[i].szNick) + 8; + } + + if (i == nContacts) + { + icq_packet mData, mNames; + +#ifdef _DEBUG + NetLog_Server("Sending contacts to %s.", strUID(dwUin, szUid)); +#endif + // Do not calculate the exact size of the data packet - only the maximal size (easier) + // Sumarize size of group information + // - we do not utilize the full power of the protocol and send all contacts with group "General" + // just like ICQ6 does + nDataLen += 9; + nNamesLen += 9; + + // Create data structures + mData.wPlace = 0; + mData.pData = (LPBYTE)SAFE_MALLOC(nDataLen); + mData.wLen = nDataLen; + mNames.wPlace = 0; + mNames.pData = (LPBYTE)SAFE_MALLOC(nNamesLen); + + // pack Group Name + packWord(&mData, 7); + packBuffer(&mData, (LPBYTE)"General", 7); + packWord(&mNames, 7); + packBuffer(&mNames, (LPBYTE)"General", 7); + + // all contacts in one group + packWord(&mData, (WORD)nContacts); + packWord(&mNames, (WORD)nContacts); + for (i = 0; i < nContacts; i++) + { + uid_str szContactUid; + WORD wLen; + + if (contacts[i].uin) + strUID(contacts[i].uin, szContactUid); + else + strcpy(szContactUid, contacts[i].uid); + + // prepare UID + wLen = strlennull(szContactUid); + packWord(&mData, wLen); + packBuffer(&mData, (LPBYTE)szContactUid, wLen); + + // prepare Nick + wLen = strlennull(contacts[i].szNick); + packWord(&mNames, (WORD)(wLen + 4)); + packTLV(&mNames, 0x01, wLen, (LPBYTE)contacts[i].szNick); + } + + // Cleanup temporary list + for(i = 0; i < nContacts; i++) + { + SAFE_FREE(&contacts[i].szNick); + SAFE_FREE(&contacts[i].uid); + } + + // Rate check + if (IsServerOverRate(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, RML_LIMIT)) + { // rate is too high, the message will not go thru... + SAFE_FREE((void**)&mData.pData); + SAFE_FREE((void**)&mNames.pData); + + return ReportGenericSendError(hContact, ACKTYPE_CONTACTS, "The message could not be delivered. You are sending too fast. Wait a while and try again."); + } + + // Set up the ack type + cookie_message_data *pCookieData = CreateMessageCookieData(MTYPE_CONTACTS, hContact, dwUin, FALSE); + + // AIM clients do not send acknowledgement + if (!dwUin && pCookieData->nAckType == ACKTYPE_CLIENT) + pCookieData->nAckType = ACKTYPE_SERVER; + // Send the message + dwCookie = icq_SendChannel2Contacts(dwUin, szUid, hContact, (char*)mData.pData, mData.wPlace, (char*)mNames.pData, mNames.wPlace, pCookieData); + + // This will stop the message dialog from waiting for the real message delivery ack + if (pCookieData->nAckType == ACKTYPE_NONE) + { + SendProtoAck(hContact, dwCookie, ACKRESULT_SUCCESS, ACKTYPE_CONTACTS, NULL); + // We need to free this here since we will never see the real ack + // The actual cookie value will still have to be returned to the message dialog though + ReleaseCookie(dwCookie); + } + // Release our buffers + SAFE_FREE((void**)&mData.pData); + SAFE_FREE((void**)&mNames.pData); + } + else + { + dwCookie = ReportGenericSendError(hContact, ACKTYPE_CONTACTS, "Bad data (internal error #2)"); + } + + for(i = 0; i < nContacts; i++) + { + SAFE_FREE(&contacts[i].szNick); + SAFE_FREE(&contacts[i].uid); + } + } + } + else if (dwUin) + { // old format is only understood by ICQ clients + int nBodyLength; + char szContactUin[UINMAXLEN]; + char szCount[17]; + struct icq_contactsend_s* contacts = NULL; + uid_str szContactUid; + + + // Format the body + // This is kinda messy, but there is no simple way to do it. First + // we need to calculate the length of the packet. + contacts = (struct icq_contactsend_s*)_alloca(sizeof(struct icq_contactsend_s)*nContacts); + ZeroMemory(contacts, sizeof(struct icq_contactsend_s)*nContacts); + { + nBodyLength = 0; + for (i = 0; i < nContacts; i++) + { + if (!IsICQContact(hContactsList[i])) + break; // Abort if a non icq contact is found + if (getContactUid(hContactsList[i], &contacts[i].uin, &szContactUid)) + break; // Abort if invalid contact + contacts[i].uid = contacts[i].uin?NULL:null_strdup(szContactUid); + contacts[i].szNick = NickFromHandle(hContactsList[i]); + // Compute this contact's length + nBodyLength += getUIDLen(contacts[i].uin, contacts[i].uid) + 1; + nBodyLength += strlennull(contacts[i].szNick) + 1; + } + + if (i == nContacts) + { + char* pBody; + char* pBuffer; + +#ifdef _DEBUG + NetLog_Server("Sending contacts to %d.", dwUin); +#endif + // Compute count record's length + _itoa(nContacts, szCount, 10); + nBodyLength += strlennull(szCount) + 1; + + // Finally we need to copy the contact data into the packet body + pBuffer = pBody = (char *)SAFE_MALLOC(nBodyLength); + null_strcpy(pBuffer, szCount, nBodyLength - 1); + pBuffer += strlennull(pBuffer); + *pBuffer++ = (char)0xFE; + for (i = 0; i < nContacts; i++) + { + if (contacts[i].uin) + { + _itoa(contacts[i].uin, szContactUin, 10); + strcpy(pBuffer, szContactUin); + } + else + strcpy(pBuffer, contacts[i].uid); + pBuffer += strlennull(pBuffer); + *pBuffer++ = (char)0xFE; + strcpy(pBuffer, contacts[i].szNick); + pBuffer += strlennull(pBuffer); + *pBuffer++ = (char)0xFE; + } + + for (i = 0; i < nContacts; i++) + { // release memory + SAFE_FREE(&contacts[i].szNick); + SAFE_FREE(&contacts[i].uid); + } + + // Set up the ack type + cookie_message_data *pCookieData = CreateMessageCookieData(MTYPE_CONTACTS, hContact, dwUin, TRUE); + + if (m_bDCMsgEnabled && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0)) + { + int iRes = icq_SendDirectMessage(hContact, pBody, nBodyLength, 1, pCookieData, NULL); + + if (iRes) + { + SAFE_FREE((void**)&pBody); + + return iRes; // we succeded, return + } + } + + // Rate check + if (IsServerOverRate(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, RML_LIMIT)) + { // rate is too high, the message will not go thru... + SAFE_FREE((void**)&pCookieData); + SAFE_FREE((void**)&pBody); + + return ReportGenericSendError(hContact, ACKTYPE_CONTACTS, "The message could not be delivered. You are sending too fast. Wait a while and try again."); + } + // Select channel and send +/* + if (!CheckContactCapabilities(hContact, CAPF_SRV_RELAY) || wRecipientStatus == ID_STATUS_OFFLINE) + { + dwCookie = icq_SendChannel4Message(dwUin, hContact, MTYPE_CONTACTS, (WORD)nBodyLength, pBody, pCookieData); + } + else +*/ + { + WORD wPriority; + + if (wRecipientStatus == ID_STATUS_ONLINE || wRecipientStatus == ID_STATUS_FREECHAT) + wPriority = 0x0001; + else + wPriority = 0x0021; + + dwCookie = icq_SendChannel2Message(dwUin, hContact, pBody, nBodyLength, wPriority, pCookieData, NULL); + } + + // This will stop the message dialog from waiting for the real message delivery ack + if (pCookieData->nAckType == ACKTYPE_NONE) + { + SendProtoAck(hContact, dwCookie, ACKRESULT_SUCCESS, ACKTYPE_CONTACTS, NULL); + // We need to free this here since we will never see the real ack + // The actual cookie value will still have to be returned to the message dialog though + ReleaseCookie(dwCookie); + } + SAFE_FREE((void**)&pBody); + } + else + { + dwCookie = ReportGenericSendError(hContact, ACKTYPE_CONTACTS, "Bad data (internal error #2)"); + } + } + } + else + { + dwCookie = ReportGenericSendError(hContact, ACKTYPE_CONTACTS, "The reciever does not support receiving of contacts."); + } + } + return dwCookie; + } + + // Exit with Failure + return 0; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// SendFile - sends a file + +HANDLE __cdecl CIcqProto::SendFile( HANDLE hContact, const TCHAR* szDescription, TCHAR** ppszFiles ) +{ + if ( !icqOnline()) + return 0; + + if (hContact && szDescription && ppszFiles) + { + DWORD dwUin; + uid_str szUid; + + if (getContactUid(hContact, &dwUin, &szUid)) + return 0; // Invalid contact + + if (getContactStatus(hContact) != ID_STATUS_OFFLINE) + { + if (CheckContactCapabilities(hContact, CAPF_OSCAR_FILE)) + return oftInitTransfer(hContact, dwUin, szUid, (LPCTSTR*)ppszFiles, szDescription); + + if (dwUin) + { + WORD wClientVersion = getSettingWord(hContact, "Version", 7); + + if (wClientVersion < 7) + NetLog_Server("IcqSendFile() can't send to version %u", wClientVersion); + else + { + int i; + filetransfer* ft; + struct _stat statbuf; + + // Initialize filetransfer struct + ft = CreateFileTransfer(hContact, dwUin, (wClientVersion == 7) ? 7: 8); + + for (ft->dwFileCount = 0; ppszFiles[ft->dwFileCount]; ft->dwFileCount++); + ft->pszFiles = (char **)SAFE_MALLOC(sizeof(char *) * ft->dwFileCount); + ft->dwTotalSize = 0; + for (i = 0; i < (int)ft->dwFileCount; i++) + { + ft->pszFiles[i] = (ppszFiles[i]) ? tchar_to_utf8(ppszFiles[i]) : NULL; + + if (_tstat(ppszFiles[i], &statbuf)) + NetLog_Server("IcqSendFile() was passed invalid filename(s)"); + else + ft->dwTotalSize += statbuf.st_size; + } + ft->szDescription = tchar_to_utf8(szDescription); + ft->dwTransferSpeed = 100; + ft->sending = 1; + ft->fileId = -1; + ft->iCurrentFile = 0; + ft->dwCookie = AllocateCookie(CKT_FILE, 0, hContact, ft); + ft->hConnection = NULL; + + // Send file transfer request + { + char szFiles[64], tmp[64]; + char *pszFiles; + + + NetLog_Server("Init file send"); + + if (ft->dwFileCount == 1) + { + pszFiles = strchr(ft->pszFiles[0], '\\'); + if (pszFiles) + pszFiles++; + else + pszFiles = ft->pszFiles[0]; + } + else + { + null_snprintf(szFiles, SIZEOF(szFiles), ICQTranslateUtfStatic("%d Files", tmp, SIZEOF(tmp)), ft->dwFileCount); + pszFiles = szFiles; + } + + // Send packet + { + if (ft->nVersion == 7) + { + if (m_bDCMsgEnabled && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0)) + { + int iRes = icq_sendFileSendDirectv7(ft, pszFiles); + if (iRes) return ft; // Success + } + NetLog_Server("Sending v%u file transfer request through server", 7); + icq_sendFileSendServv7(ft, pszFiles); + } + else + { + if (m_bDCMsgEnabled && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0)) + { + int iRes = icq_sendFileSendDirectv8(ft, pszFiles); + if (iRes) return ft; // Success + } + NetLog_Server("Sending v%u file transfer request through server", 8); + icq_sendFileSendServv8(ft, pszFiles, ACKTYPE_NONE); + } + } + } + + return ft; // Success + } + } + } + } + + return 0; // Failure +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// PS_SendMessage - sends a message + +int __cdecl CIcqProto::SendMsg( HANDLE hContact, int flags, const char* pszSrc ) +{ + if (hContact && pszSrc) + { + DWORD dwCookie; + char* puszText = NULL; + int bNeedFreeU = 0; + cookie_message_data *pCookieData = NULL; + + // Invalid contact + DWORD dwUin; + uid_str szUID; + if (getContactUid(hContact, &dwUin, &szUID)) + return ReportGenericSendError(hContact, ACKTYPE_MESSAGE, "The receiver has an invalid user ID."); + + if (flags & PREF_UNICODE) + { + puszText = make_utf8_string((WCHAR*)(pszSrc + strlennull(pszSrc) + 1)); // get the UTF-16 part + bNeedFreeU = 1; + } + else if (flags & PREF_UTF) + puszText = (char*)pszSrc; + else + { + puszText = (char*)ansi_to_utf8(pszSrc); + bNeedFreeU = 1; + } + + WORD wRecipientStatus = getContactStatus(hContact); + + BOOL plain_ascii = IsUSASCII(puszText, strlennull(puszText)); + + BOOL oldAnsi = plain_ascii || !m_bUtfEnabled || + (!(flags & (PREF_UTF | PREF_UNICODE)) && m_bUtfEnabled == 1) || + !CheckContactCapabilities(hContact, CAPF_UTF) || + !getSettingByte(hContact, "UnicodeSend", 1); + + if (m_bTempVisListEnabled && m_iStatus == ID_STATUS_INVISIBLE) + makeContactTemporaryVisible(hContact); // make us temporarily visible to contact + + // Failure scenarios + if (!icqOnline()) + { + dwCookie = ReportGenericSendError(hContact, ACKTYPE_MESSAGE, "You cannot send messages when you are offline."); + } + else if ((wRecipientStatus == ID_STATUS_OFFLINE) && (strlennull(puszText) > 4096)) + { + dwCookie = ReportGenericSendError(hContact, ACKTYPE_MESSAGE, "Messages to offline contacts must be shorter than 4096 characters."); + } + // Looks OK + else + { +#ifdef _DEBUG + NetLog_Server("Send %smessage - Message cap is %u", puszText ? "unicode " : "", CheckContactCapabilities(hContact, CAPF_SRV_RELAY)); + NetLog_Server("Send %smessage - Contact status is %u", puszText ? "unicode " : "", wRecipientStatus); +#endif + if (dwUin && m_bDCMsgEnabled && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0)) + { // send thru direct + char *dc_msg = puszText; + char *dc_cap = plain_ascii ? NULL : CAP_UTF8MSGS; + char *szUserAnsi = NULL; + + if (!plain_ascii && oldAnsi) + { + szUserAnsi = ConvertMsgToUserSpecificAnsi(hContact, puszText); + if (szUserAnsi) + { + dc_msg = szUserAnsi; + dc_cap = NULL; + } + } + + // Set up the ack type + pCookieData = CreateMessageCookieData(MTYPE_PLAIN, hContact, dwUin, TRUE); + pCookieData->nAckType = ACKTYPE_CLIENT; + dwCookie = icq_SendDirectMessage(hContact, dc_msg, strlennull(dc_msg), 1, pCookieData, dc_cap); + + SAFE_FREE(&szUserAnsi); + if (dwCookie) + { // free the buffers if alloced + if (bNeedFreeU) SAFE_FREE(&puszText); + + return dwCookie; // we succeded, return + } + // on failure, fallback to send thru server + } + + if (!dwUin || !CheckContactCapabilities(hContact, CAPF_SRV_RELAY) || + wRecipientStatus == ID_STATUS_OFFLINE || wRecipientStatus == ID_STATUS_INVISIBLE || + getSettingByte(hContact, "OnlyServerAcks", getSettingByte(NULL, "OnlyServerAcks", DEFAULT_ONLYSERVERACKS)) || + !getSettingByte(hContact, "SlowSend", getSettingByte(NULL, "SlowSend", DEFAULT_SLOWSEND))) + { + /// TODO: add support for RTL & user customizable font + { + char *mng = MangleXml(puszText, strlennull(puszText)); + int len = strlennull(mng); + mng = (char*)SAFE_REALLOC(mng, len + 28); + memmove(mng + 12, mng, len + 1); + memcpy(mng, "", 12); + strcat(mng, ""); + if (bNeedFreeU) SAFE_FREE(&puszText); + puszText = mng; + bNeedFreeU = 1; + } + + WCHAR *pwszText = plain_ascii ? NULL : make_unicode_string(puszText); + if ((plain_ascii ? strlennull(puszText) : strlennull(pwszText) * sizeof(WCHAR)) > MAX_MESSAGESNACSIZE) + { // max length check // TLV(2) is currently limited to 0xA00 bytes in online mode + // only limit to not get disconnected, all other will be handled by error 0x0A + dwCookie = ReportGenericSendError(hContact, ACKTYPE_MESSAGE, "The message could not be delivered, it is too long."); + + // free the buffers if alloced + SAFE_FREE((void**)&pwszText); + if (bNeedFreeU) SAFE_FREE(&puszText); + + return dwCookie; + } + // Rate check + if (IsServerOverRate(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, RML_LIMIT)) + { // rate is too high, the message will not go thru... + dwCookie = ReportGenericSendError(hContact, ACKTYPE_MESSAGE, "The message could not be delivered. You are sending too fast. Wait a while and try again."); + + // free the buffers if alloced + SAFE_FREE((void**)&pwszText); + if (bNeedFreeU) SAFE_FREE(&puszText); + + return dwCookie; + } + + pCookieData = CreateMessageCookieData(MTYPE_PLAIN, hContact, dwUin, FALSE); + + if (plain_ascii) + dwCookie = icq_SendChannel1Message(dwUin, szUID, hContact, puszText, pCookieData); + else + dwCookie = icq_SendChannel1MessageW(dwUin, szUID, hContact, pwszText, pCookieData); + // free the unicode message + SAFE_FREE((void**)&pwszText); + } + else + { + WORD wPriority; + + char *srv_msg = puszText; + char *srv_cap = plain_ascii ? NULL : CAP_UTF8MSGS; + char *szUserAnsi = NULL; + + if (!plain_ascii && oldAnsi) + { + szUserAnsi = ConvertMsgToUserSpecificAnsi(hContact, puszText); + if (szUserAnsi) + { + srv_msg = szUserAnsi; + srv_cap = NULL; + } + } + + if (wRecipientStatus == ID_STATUS_ONLINE || wRecipientStatus == ID_STATUS_FREECHAT) + wPriority = 0x0001; + else + wPriority = 0x0021; + + if (strlennull(srv_msg) + (!oldAnsi ? 144 : 102) > MAX_MESSAGESNACSIZE) + { // max length check + dwCookie = ReportGenericSendError(hContact, ACKTYPE_MESSAGE, "The message could not be delivered, it is too long."); + + SAFE_FREE(&szUserAnsi); + // free the buffers if alloced + if (bNeedFreeU) SAFE_FREE(&puszText); + + return dwCookie; + } + // Rate check + if (IsServerOverRate(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, RML_LIMIT)) + { // rate is too high, the message will not go thru... + dwCookie = ReportGenericSendError(hContact, ACKTYPE_MESSAGE, "The message could not be delivered. You are sending too fast. Wait a while and try again."); + + SAFE_FREE(&szUserAnsi); + // free the buffers if alloced + if (bNeedFreeU) SAFE_FREE(&puszText); + + return dwCookie; + } + + pCookieData = CreateMessageCookieData(MTYPE_PLAIN, hContact, dwUin, TRUE); + dwCookie = icq_SendChannel2Message(dwUin, hContact, srv_msg, strlennull(srv_msg), wPriority, pCookieData, srv_cap); + SAFE_FREE(&szUserAnsi); + } + + // This will stop the message dialog from waiting for the real message delivery ack + if (pCookieData && pCookieData->nAckType == ACKTYPE_NONE) + { + SendProtoAck(hContact, dwCookie, ACKRESULT_SUCCESS, ACKTYPE_MESSAGE, NULL); + // We need to free this here since we will never see the real ack + // The actual cookie value will still have to be returned to the message dialog though + ReleaseCookie(dwCookie); + } + } + // free the buffers if alloced + if (bNeedFreeU) SAFE_FREE(&puszText); + + return dwCookie; // Success + } + + return 0; // Failure +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// SendUrl + +int __cdecl CIcqProto::SendUrl( HANDLE hContact, int flags, const char* url ) +{ + if (hContact && url) + { + DWORD dwCookie; + WORD wRecipientStatus; + DWORD dwUin; + + if (getContactUid(hContact, &dwUin, NULL)) + { // Invalid contact + return ReportGenericSendError(hContact, ACKTYPE_URL, "The receiver has an invalid user ID."); + } + + wRecipientStatus = getContactStatus(hContact); + + // Failure + if (!icqOnline()) + { + dwCookie = ReportGenericSendError(hContact, ACKTYPE_URL, "You cannot send messages when you are offline."); + } + // Looks OK + else + { + char* szDesc; + char* szBody; + int nBodyLen; + int nDescLen; + int nUrlLen; + + + // Set up the ack type + cookie_message_data *pCookieData = CreateMessageCookieData(MTYPE_URL, hContact, dwUin, TRUE); + + // Format the body + nUrlLen = strlennull(url); + szDesc = (char *)url + nUrlLen + 1; + nDescLen = strlennull(szDesc); + nBodyLen = nUrlLen + nDescLen + 2; + szBody = (char *)_alloca(nBodyLen); + strcpy(szBody, szDesc); + szBody[nDescLen] = (char)0xFE; // Separator + strcpy(szBody + nDescLen + 1, url); + + if (m_bDCMsgEnabled && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0)) + { + int iRes = icq_SendDirectMessage(hContact, szBody, nBodyLen, 1, pCookieData, NULL); + if (iRes) return iRes; // we succeded, return + } + + // Rate check + if (IsServerOverRate(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, RML_LIMIT)) + { // rate is too high, the message will not go thru... + SAFE_FREE((void**)&pCookieData); + + return ReportGenericSendError(hContact, ACKTYPE_URL, "The message could not be delivered. You are sending too fast. Wait a while and try again."); + } + // Select channel and send +/* + if (!CheckContactCapabilities(hContact, CAPF_SRV_RELAY) || + wRecipientStatus == ID_STATUS_OFFLINE) + { + dwCookie = icq_SendChannel4Message(dwUin, hContact, MTYPE_URL, + (WORD)nBodyLen, szBody, pCookieData); + } + else +*/ + { + WORD wPriority; + + if (wRecipientStatus == ID_STATUS_ONLINE || wRecipientStatus == ID_STATUS_FREECHAT) + wPriority = 0x0001; + else + wPriority = 0x0021; + + dwCookie = icq_SendChannel2Message(dwUin, hContact, szBody, nBodyLen, wPriority, pCookieData, NULL); + } + + // This will stop the message dialog from waiting for the real message delivery ack + if (pCookieData->nAckType == ACKTYPE_NONE) + { + SendProtoAck(hContact, dwCookie, ACKRESULT_SUCCESS, ACKTYPE_URL, NULL); + // We need to free this here since we will never see the real ack + // The actual cookie value will still have to be returned to the message dialog though + ReleaseCookie(dwCookie); + } + } + + return dwCookie; // Success + } + + return 0; // Failure +} + +//////////////////////////////////////////////////////////////////////////////////////// +// PS_SetApparentMode - sets the visibility status + +int __cdecl CIcqProto::SetApparentMode( HANDLE hContact, int mode ) +{ + DWORD uin; + uid_str uid; + + if (getContactUid(hContact, &uin, &uid)) + return 1; // Invalid contact + + if (hContact) + { + // Only 3 modes are supported + if (mode == 0 || mode == ID_STATUS_ONLINE || mode == ID_STATUS_OFFLINE) + { + int oldMode = getSettingWord(hContact, "ApparentMode", 0); + + // Don't send redundant updates + if (mode != oldMode) + { + setSettingWord(hContact, "ApparentMode", (WORD)mode); + + // Not being online is only an error when in SS mode. This is not handled + // yet so we just ignore this for now. + if (icqOnline()) + { + if (oldMode != 0) + { // Remove from old list + if (oldMode == ID_STATUS_OFFLINE && getSettingWord(hContact, DBSETTING_SERVLIST_IGNORE, 0)) + { // Need to remove Ignore item as well + icq_removeServerPrivacyItem(hContact, uin, uid, getSettingWord(hContact, DBSETTING_SERVLIST_IGNORE, 0), SSI_ITEM_IGNORE); + + setSettingWord(hContact, DBSETTING_SERVLIST_IGNORE, 0); + } + icq_sendChangeVisInvis(hContact, uin, uid, oldMode==ID_STATUS_OFFLINE, 0); + } + if (mode != 0) + { // Add to new list + if (mode==ID_STATUS_OFFLINE && getSettingWord(hContact, DBSETTING_SERVLIST_IGNORE, 0)) + return 0; // Success: offline by ignore item + + icq_sendChangeVisInvis(hContact, uin, uid, mode==ID_STATUS_OFFLINE, 1); + } + } + + return 0; // Success + } + } + } + + return 1; // Failure +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// PrepareStatusNote - returns correct status note for given status + +char* CIcqProto::PrepareStatusNote(int nStatus) +{ + char *szStatusNote = NULL; + BYTE bXStatus = getContactXStatus(NULL); + + // use custom status message as status note + if (bXStatus) + szStatusNote = getSettingStringUtf(NULL, DBSETTING_XSTATUS_MSG, ""); + + if (!szStatusNote || !szStatusNote[0]) + { // get standard status message (no custom status defined) + icq_lock l(m_modeMsgsMutex); + + char **pszStatusNote = MirandaStatusToAwayMsg(nStatus); + if (pszStatusNote) + szStatusNote = null_strdup(*pszStatusNote); + } + + if (!szStatusNote) + // nothing available set empty status note + szStatusNote = null_strdup(""); + + return szStatusNote; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// PS_SetStatus - sets the protocol status + +int __cdecl CIcqProto::SetStatus(int iNewStatus) +{ + int nNewStatus = MirandaStatusToSupported(iNewStatus); + + // check if netlib handles are ready + if (!m_hServerNetlibUser) + return 0; + + if (m_bTempVisListEnabled && icqOnline()) // remove temporary visible users + sendEntireListServ(ICQ_BOS_FAMILY, ICQ_CLI_REMOVETEMPVISIBLE, BUL_TEMPVISIBLE); + + if (nNewStatus == m_iStatus) + return 0; + + // clear custom status on status change + if (getSettingByte(NULL, "XStatusReset", DEFAULT_XSTATUS_RESET)) + setXStatusEx(0, 0); + + // New status is OFFLINE + if (nNewStatus == ID_STATUS_OFFLINE) + { // for quick logoff + if (icqOnline()) + { // set offline status note (otherwise the old will remain) + char *szOfflineNote = PrepareStatusNote(nNewStatus); + + // Create unnamed event to wait until the status note change process is completed + m_hNotifyNameInfoEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + + int bNoteChanged = SetStatusNote(szOfflineNote, 0, FALSE); + + SAFE_FREE(&szOfflineNote); + + // Note was changed, wait until the process is over + if (bNoteChanged) + ICQWaitForSingleObject(m_hNotifyNameInfoEvent, 4000, TRUE); + + // Release the event + CloseHandle(m_hNotifyNameInfoEvent); + m_hNotifyNameInfoEvent = NULL; + } + + m_iDesiredStatus = nNewStatus; + + if (hServerConn) + { // Connected, Send disconnect packet + icq_sendCloseConnection(); + + icq_serverDisconnect(FALSE); + + SetCurrentStatus(ID_STATUS_OFFLINE); + + NetLog_Server("Logged off."); + } + } + else + { + switch (m_iStatus) { + + // We are offline and need to connect + case ID_STATUS_OFFLINE: + { + // Update user connection settings + UpdateGlobalSettings(); + + // Read UIN from database + m_dwLocalUIN = getContactUin(NULL); + if (m_dwLocalUIN == 0) + { + SetCurrentStatus(ID_STATUS_OFFLINE); + BroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_BADUSERID); + icq_LogMessage(LOG_FATAL, LPGEN("You have not entered a ICQ number.\nConfigure this in Options->Network->ICQ and try again.")); + return 0; + } + + // Set status to 'Connecting' + m_iDesiredStatus = nNewStatus; + SetCurrentStatus(ID_STATUS_CONNECTING); + + // Read password from database + char *pszPwd = GetUserPassword(FALSE); + + if (pszPwd) + icq_login(pszPwd); + else + RequestPassword(); + + break; + } + + // We are connecting... We only need to change the going online status + case ID_STATUS_CONNECTING: + m_iDesiredStatus = nNewStatus; + break; + + // We are already connected so we should just change status + default: + SetCurrentStatus(nNewStatus); + + char *szStatusNote = PrepareStatusNote(nNewStatus); + + //! This is a bit tricky, we do trigger status note change thread and then + // change the status note right away (this spares one packet) - so SetStatusNote() + // will only change User Details Directory + SetStatusNote(szStatusNote, 6000, FALSE); + + if (m_iStatus == ID_STATUS_INVISIBLE) + { + if (m_bSsiEnabled) + updateServVisibilityCode(3); + icq_setstatus(MirandaStatusToIcq(m_iStatus), szStatusNote); + } + else + { + icq_setstatus(MirandaStatusToIcq(m_iStatus), szStatusNote); + if (m_bSsiEnabled) + updateServVisibilityCode(4); + } + SAFE_FREE(&szStatusNote); + + if (m_bAimEnabled) + { + icq_lock l(m_modeMsgsMutex); + + char ** pszStatusNote = MirandaStatusToAwayMsg(m_iStatus); + + if (pszStatusNote) + icq_sendSetAimAwayMsgServ(*pszStatusNote); + else // clear the away message + icq_sendSetAimAwayMsgServ(NULL); + } + } + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// GetAwayMsgThread - return a contact's status message + +struct status_message_thread_data +{ + HANDLE hContact; + char *szMessage; + HANDLE hProcess; +}; + +void __cdecl CIcqProto::GetAwayMsgThread( void *pStatusData ) +{ + status_message_thread_data *pThreadData = (status_message_thread_data*)pStatusData; + if (pThreadData) { + // wait a little + Sleep(100); + + setStatusMsgVar(pThreadData->hContact, pThreadData->szMessage, false); + + TCHAR *tszMsg = mir_utf8decodeT(pThreadData->szMessage); + BroadcastAck(pThreadData->hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, pThreadData->hProcess, (LPARAM)tszMsg); + mir_free(tszMsg); + + SAFE_FREE(&pThreadData->szMessage); + SAFE_FREE((void**)&pThreadData); + } +} + +//////////////////////////////////////////////////////////////////////////////////////// +// PS_GetAwayMsg - returns a contact's away message + +HANDLE __cdecl CIcqProto::GetAwayMsg( HANDLE hContact ) +{ + DWORD dwUin; + uid_str szUID; + + if (getContactUid(hContact, &dwUin, &szUID)) + return 0; // Invalid contact + + if (!dwUin || !CheckContactCapabilities(hContact, CAPF_STATUS_MESSAGES)) + { // No individual status messages, check if the contact has Status Note, if yes give it + char *szStatusNote = getSettingStringUtf(hContact, DBSETTING_STATUS_NOTE, NULL); + + if (strlennull(szStatusNote) > 0) + { // Give Status Note + status_message_thread_data *pThreadData = (status_message_thread_data*)SAFE_MALLOC(sizeof(status_message_thread_data)); + + pThreadData->hContact = hContact; + pThreadData->szMessage = szStatusNote; + pThreadData->hProcess = (HANDLE)GenerateCookie(0); + ForkThread(&CIcqProto::GetAwayMsgThread, pThreadData); + + return pThreadData->hProcess; + } + SAFE_FREE(&szStatusNote); + } + + if (!icqOnline()) + return 0; + + WORD wStatus = getContactStatus(hContact); + + if (dwUin) + { + int wMessageType = 0; + + switch(wStatus) + { + case ID_STATUS_ONLINE: + if (CheckContactCapabilities(hContact, CAPF_STATUS_MESSAGES)) + wMessageType = MTYPE_AUTOONLINE; + break; + + case ID_STATUS_AWAY: + wMessageType = MTYPE_AUTOAWAY; + break; + + case ID_STATUS_NA: + wMessageType = MTYPE_AUTONA; + break; + + case ID_STATUS_OCCUPIED: + wMessageType = MTYPE_AUTOBUSY; + break; + + case ID_STATUS_DND: + wMessageType = MTYPE_AUTODND; + break; + + case ID_STATUS_FREECHAT: + wMessageType = MTYPE_AUTOFFC; + break; + + default: + break; + } + + if (wMessageType) + { + if (m_bDCMsgEnabled && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0)) + { + int iRes = icq_sendGetAwayMsgDirect(hContact, wMessageType); + if (iRes) return (HANDLE)iRes; // we succeded, return + } + if (CheckContactCapabilities(hContact, CAPF_STATUS_MESSAGES)) + return (HANDLE)icq_sendGetAwayMsgServExt(hContact, dwUin, szUID, wMessageType, + (WORD)(getSettingWord(hContact, "Version", 0)==9?9:ICQ_VERSION)); // Success + else + return (HANDLE)icq_sendGetAwayMsgServ(hContact, dwUin, wMessageType, + (WORD)(getSettingWord(hContact, "Version", 0)==9?9:ICQ_VERSION)); // Success + } + } + else + { // AIM contact + if (wStatus == ID_STATUS_AWAY) + return (HANDLE)icq_sendGetAimAwayMsgServ(hContact, szUID, MTYPE_AUTOAWAY); + } + + return 0; // Failure +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// PSR_AWAYMSG - processes received status mode message + +int __cdecl CIcqProto::RecvAwayMsg( HANDLE hContact, int statusMode, PROTORECVEVENT* evt ) +{ + if (evt->flags & PREF_UTF) { + setStatusMsgVar(hContact, evt->szMessage, false); + + TCHAR* pszMsg = mir_utf8decodeT(evt->szMessage); + BroadcastAck(hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)evt->lParam, (LPARAM)pszMsg); + mir_free(pszMsg); + } + else { + setStatusMsgVar(hContact, evt->szMessage, true); + BroadcastAck(hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)evt->lParam, (LPARAM)(TCHAR*)_A2T(evt->szMessage)); + } + return 0; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// PSS_AWAYMSG - send status mode message (individual mode) + +int __cdecl CIcqProto::SendAwayMsg( HANDLE hContact, HANDLE hProcess, const char* msg ) +{ + return 1; +} + + +//////////////////////////////////////////////////////////////////////////////////////// +// PS_SetAwayMsg - sets the away status message + +int __cdecl CIcqProto::SetAwayMsg(int status, const TCHAR* msg) +{ + icq_lock l(m_modeMsgsMutex); + + char **ppszMsg = MirandaStatusToAwayMsg(MirandaStatusToSupported(status)); + if (!ppszMsg) + return 1; // Failure + + // Prepare UTF-8 status message + char *szNewUtf = tchar_to_utf8(msg); + + if (strcmpnull(szNewUtf, *ppszMsg)) + { + // Free old message + SAFE_FREE(ppszMsg); + + // Set new message + *ppszMsg = szNewUtf; + szNewUtf = NULL; + + if ((m_iStatus == status) && icqOnline()) + { // update current status note + char *szNote = *ppszMsg ? *ppszMsg : ""; + + BYTE bXStatus = getContactXStatus(NULL); + if (!bXStatus) + SetStatusNote(szNote, 1000, FALSE); + + if (m_bAimEnabled) + icq_sendSetAimAwayMsgServ(*ppszMsg); + } + } + SAFE_FREE(&szNewUtf); + + return 0; // Success +} + + +///////////////////////////////////////////////////////////////////////////////////////// +// GetMyAwayMsg - obtain the current away message + +INT_PTR CIcqProto::GetMyAwayMsg(WPARAM wParam, LPARAM lParam) +{ + icq_lock l(m_modeMsgsMutex); + + char **ppszMsg = MirandaStatusToAwayMsg(wParam ? wParam : m_iStatus); + + if (!ppszMsg || !*ppszMsg) + return 0; + + int nMsgLen = strlennull(*ppszMsg) + 1; + + if (lParam & SGMA_UNICODE) + { + WCHAR *szMsg = (WCHAR*)_alloca(nMsgLen * sizeof(WCHAR)); + + make_unicode_string_static(*ppszMsg, szMsg, nMsgLen); + return (INT_PTR)mir_wstrdup(szMsg); + } + else + { // convert to ansi + char *szMsg = (char*)_alloca(nMsgLen); + + if (utf8_decode_static(*ppszMsg, szMsg, nMsgLen)) + return (INT_PTR)mir_strdup(szMsg); + } + + return 0; +} + + +///////////////////////////////////////////////////////////////////////////////////////// +// PS_UserIsTyping - sends a UTN notification + +int __cdecl CIcqProto::UserIsTyping( HANDLE hContact, int type ) +{ + if (hContact && icqOnline()) + { + if (CheckContactCapabilities(hContact, CAPF_TYPING)) + { + switch (type) { + case PROTOTYPE_SELFTYPING_ON: + sendTypingNotification(hContact, MTN_BEGUN); + return 0; + + case PROTOTYPE_SELFTYPING_OFF: + sendTypingNotification(hContact, MTN_FINISHED); + return 0; + } + } + } + + return 1; +} + + +///////////////////////////////////////////////////////////////////////////////////////// +// OnEvent - maintain protocol events + +int __cdecl CIcqProto::OnEvent(PROTOEVENTTYPE eventType, WPARAM wParam, LPARAM lParam) +{ + switch( eventType ) { + case EV_PROTO_ONLOAD: + return OnModulesLoaded(0, 0); + + case EV_PROTO_ONEXIT: + return OnPreShutdown(0, 0); + + case EV_PROTO_ONOPTIONS: + return OnOptionsInit(wParam, lParam); + + case EV_PROTO_ONERASE: + { + char szDbSetting[MAX_PATH]; + + null_snprintf(szDbSetting, sizeof(szDbSetting), "%sP2P", m_szModuleName); + CallService(MS_DB_MODULE_DELETE, 0, (LPARAM)szDbSetting); + null_snprintf(szDbSetting, sizeof(szDbSetting), "%sSrvGroups", m_szModuleName); + CallService(MS_DB_MODULE_DELETE, 0, (LPARAM)szDbSetting); + null_snprintf(szDbSetting, sizeof(szDbSetting), "%sGroups", m_szModuleName); + CallService(MS_DB_MODULE_DELETE, 0, (LPARAM)szDbSetting); + break; + } + + case EV_PROTO_ONCONTACTDELETED: + return ServListDbContactDeleted(wParam, lParam); + + case EV_PROTO_DBSETTINGSCHANGED: + return ServListDbSettingChanged(wParam, lParam); + } + return 1; +} diff --git a/protocols/IcqOscarJ/src/icq_proto.h b/protocols/IcqOscarJ/src/icq_proto.h new file mode 100644 index 0000000000..aa903689ad --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_proto.h @@ -0,0 +1,984 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera, George Hazan +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Protocol Interface declarations +// +// ----------------------------------------------------------------------------- +#ifndef _ICQ_PROTO_H_ +#define _ICQ_PROTO_H_ + +#include "m_system_cpp.h" +#include "m_protoint.h" + +#define LISTSIZE 100 + +#define XSTATUS_COUNT 86 + +struct CIcqProto; +typedef void ( __cdecl CIcqProto::*IcqThreadFunc )( void* ); +typedef int ( __cdecl CIcqProto::*IcqEventFunc )( WPARAM, LPARAM ); +typedef INT_PTR ( __cdecl CIcqProto::*IcqServiceFunc )( WPARAM, LPARAM ); +typedef INT_PTR ( __cdecl CIcqProto::*IcqServiceFuncParam )( WPARAM, LPARAM, LPARAM ); + +// for InfoUpdate +struct userinfo +{ + DWORD dwUin; + HANDLE hContact; + time_t queued; +}; + +struct CIcqProto : public PROTO_INTERFACE, public MZeroedObject +{ + CIcqProto( const char*, const TCHAR* ); + ~CIcqProto(); + + //==================================================================================== + // 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 hContact ); + virtual int __cdecl AuthDeny( HANDLE hContact, 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 PROTOCHAR *id ); + virtual HANDLE __cdecl SearchByEmail( const PROTOCHAR *email ); + virtual HANDLE __cdecl SearchByName(const PROTOCHAR *nick, const PROTOCHAR *firstName, const PROTOCHAR *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 AddServerContact(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl GetInfoSetting(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl ChangeInfoEx(WPARAM wParam, LPARAM lParam); + 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 GetMyAwayMsg(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl GetXStatus(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl GetXStatusEx(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl GetXStatusIcon(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl GrantAuthorization(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl menuXStatus(WPARAM wParam,LPARAM lParam,LPARAM fParam); + INT_PTR __cdecl OpenWebProfile(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl RequestAdvStatusIconIdx(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl RequestAuthorization(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl RequestXStatusDetails(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl RevokeAuthorization(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl SendSms(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl SendYouWereAdded(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl SetMyAvatar(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl SetNickName(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl SetPassword(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl SetXStatus(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl SetXStatusEx(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl ShowXStatusDetails(WPARAM wParam, LPARAM lParam); + + INT_PTR __cdecl OnCreateAccMgrUI(WPARAM, LPARAM); + + //====| Events |====================================================================== + void __cdecl OnAddContactForever( DBCONTACTWRITESETTING* cws, HANDLE hContact ); + int __cdecl OnIdleChanged( WPARAM, LPARAM ); + int __cdecl OnModernOptInit( WPARAM, LPARAM ); + int __cdecl OnModulesLoaded( WPARAM, LPARAM ); + int __cdecl OnOptionsInit( WPARAM, LPARAM ); + int __cdecl OnPreShutdown( WPARAM, LPARAM ); + int __cdecl OnPreBuildContactMenu( WPARAM, LPARAM ); + int __cdecl OnMsgUserTyping( WPARAM, LPARAM ); + int __cdecl OnProcessSrmmIconClick( WPARAM, LPARAM ); + int __cdecl OnProcessSrmmEvent( WPARAM, LPARAM ); + int __cdecl OnReloadIcons( WPARAM, LPARAM ); + void __cdecl OnRenameContact( DBCONTACTWRITESETTING* cws, HANDLE hContact ); + void __cdecl OnRenameGroup( DBCONTACTWRITESETTING* cws, HANDLE hContact ); + int __cdecl OnUserInfoInit( WPARAM, LPARAM ); + + int __cdecl CListMW_ExtraIconsRebuild( WPARAM, LPARAM ); + int __cdecl CListMW_ExtraIconsApply( WPARAM, LPARAM ); + int __cdecl OnPreBuildStatusMenu( WPARAM, LPARAM ); + + //====| Data |======================================================================== + IcqIconHandle m_hIconProtocol; + HANDLE m_hServerNetlibUser, m_hDirectNetlibUser; + HANDLE hxstatuschanged, hxstatusiconchanged; + + BYTE m_bGatewayMode; + BYTE m_bSecureLogin; + BYTE m_bSecureConnection; + BYTE m_bAimEnabled; + BYTE m_bUtfEnabled; + WORD m_wAnsiCodepage; + BYTE m_bDCMsgEnabled; + BYTE m_bTempVisListEnabled; + BYTE m_bSsiEnabled; + BYTE m_bSsiSimpleGroups; + BYTE m_bAvatarsEnabled; + BYTE m_bXStatusEnabled; + BYTE m_bMoodsEnabled; + + icq_critical_section *localSeqMutex; + icq_critical_section *connectionHandleMutex; + + int m_bIdleAllow; + DWORD m_dwLocalUIN; + BYTE m_bConnectionLost; + + char m_szPassword[PASSWORDMAXLEN]; + BYTE m_bRememberPwd; + + int cheekySearchId; + DWORD cheekySearchUin; + char* cheekySearchUid; + + /******************************************************************* + * Function declarations + *******************************************************************/ + + //----| capabilities.cpp |------------------------------------------------------------ + // Deletes all oscar capabilities for a given contact. + void ClearAllContactCapabilities(HANDLE hContact); + + // Deletes one or many oscar capabilities for a given contact. + void ClearContactCapabilities(HANDLE hContact, DWORD fdwCapabilities); + + // Sets one or many oscar capabilities for a given contact. + void SetContactCapabilities(HANDLE hContact, DWORD fdwCapabilities); + + // Returns true if the given contact supports the requested capabilites. + BOOL CheckContactCapabilities(HANDLE hContact, DWORD fdwCapabilities); + + // Scans a binary buffer for oscar capabilities and adds them to the contact. + void AddCapabilitiesFromBuffer(HANDLE hContact, BYTE *pBuffer, int nLength); + + // Scans a binary buffer for oscar capabilities and sets them to the contact. + void SetCapabilitiesFromBuffer(HANDLE hContact, BYTE *pBuffer, int nLength, BOOL bReset); + + //----| chan_01login.cpp |------------------------------------------------------------ + void handleLoginChannel(BYTE *buf, WORD datalen, serverthread_info *info); + + //----| chan_02data.cpp |------------------------------------------------------------- + void handleDataChannel(BYTE *buf, WORD wLen, serverthread_info *info); + + void LogFamilyError(WORD wFamily, WORD wError); + + //----| chan_03error.cpp |------------------------------------------------------------ + void handleErrorChannel(unsigned char *buf, WORD datalen); + + //----| chan_04close.cpp |------------------------------------------------------------ + void handleCloseChannel(BYTE *buf, WORD datalen, serverthread_info *info); + void handleLoginReply(BYTE *buf, WORD datalen, serverthread_info *info); + void handleMigration(serverthread_info *info); + void handleSignonError(WORD wError); + + int connectNewServer(serverthread_info *info); + + //----| chan_05ping.cpp |------------------------------------------------------------- + void handlePingChannel(BYTE *buf, WORD wLen); + + void __cdecl KeepAliveThread(void *arg); + + void StartKeepAlive(serverthread_info *info); + void StopKeepAlive(serverthread_info *info); + + //----| cookies.cpp |----------------------------------------------------------------- + icq_critical_section *cookieMutex; // we want this in avatar thread, used as queue lock + LIST cookies; + WORD wCookieSeq; + + DWORD AllocateCookie(BYTE bType, WORD wIdent, HANDLE hContact, void *pvExtra); + void FreeCookie(DWORD dwCookie); + void FreeCookieByData(BYTE bType, void *pvExtra); + void ReleaseCookie(DWORD dwCookie); + DWORD GenerateCookie(WORD wIdent); + + int GetCookieType(DWORD dwCookie); + + int FindCookie(DWORD wCookie, HANDLE *phContact, void **ppvExtra); + int FindCookieByData(void *pvExtra, DWORD *pdwCookie, HANDLE *phContact); + int FindCookieByType(BYTE bType, DWORD *pdwCookie, HANDLE *phContact, void **ppvExtra); + int FindMessageCookie(DWORD dwMsgID1, DWORD dwMsgID2, DWORD *pdwCookie, HANDLE *phContact, cookie_message_data **ppvExtra); + + void InitMessageCookie(cookie_message_data *pCookie); + cookie_message_data* CreateMessageCookie(WORD bMsgType, BYTE bAckType); + cookie_message_data* CreateMessageCookieData(BYTE bMsgType, HANDLE hContact, DWORD dwUin, int bUseSrvRelay); + + void RemoveExpiredCookies(void); + + //----| directpackets.cpp |----------------------------------------------------------- + void icq_sendDirectMsgAck(directconnect* dc, WORD wCookie, BYTE bMsgType, BYTE bMsgFlags, char* szCap); + DWORD icq_sendGetAwayMsgDirect(HANDLE hContact, int type); + void icq_sendAwayMsgReplyDirect(directconnect *dc, WORD wCookie, BYTE msgType, const char** szMsg); + void icq_sendFileAcceptDirect(HANDLE hContact, filetransfer *ft); + void icq_sendFileDenyDirect(HANDLE hContact, filetransfer *ft, const char *szReason); + int icq_sendFileSendDirectv7(filetransfer *ft, const char *pszFiles); + int icq_sendFileSendDirectv8(filetransfer *ft, const char *pszFiles); + DWORD icq_SendDirectMessage(HANDLE hContact, const char *szMessage, int nBodyLength, WORD wPriority, cookie_message_data *pCookieData, char *szCap); + void icq_sendXtrazRequestDirect(HANDLE hContact, DWORD dwCookie, char* szBody, int nBodyLen, WORD wType); + void icq_sendXtrazResponseDirect(HANDLE hContact, WORD wCookie, char* szBody, int nBodyLen, WORD wType); + + //----| fam_01service.cpp |----------------------------------------------------------- + HANDLE m_hNotifyNameInfoEvent; + + void handleServiceFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader, serverthread_info *info); + char* buildUinList(int subtype, WORD wMaxLen, HANDLE *hContactResume); + void sendEntireListServ(WORD wFamily, WORD wSubtype, int listType); + void setUserInfo(void); + void handleServUINSettings(int nPort, serverthread_info *info); + + //----| fam_02location.cpp |---------------------------------------------------------- + void handleLocationFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader); + void handleLocationUserInfoReply(BYTE* buf, WORD wLen, DWORD dwCookie); + + //----| fam_03buddy.cpp |------------------------------------------------------------- + void handleBuddyFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader, serverthread_info *info); + void handleReplyBuddy(BYTE *buf, WORD wPackLen); + void handleUserOffline(BYTE *buf, WORD wPackLen); + void handleUserOnline(BYTE *buf, WORD wPackLen, serverthread_info *info); + void parseStatusNote(DWORD dwUin, char *szUid, HANDLE hContact, oscar_tlv_chain *pChain); + void handleNotifyRejected(BYTE *buf, WORD wPackLen); + + //----| fam_04message.cpp |----------------------------------------------------------- + icq_mode_messages m_modeMsgs; + icq_critical_section *m_modeMsgsMutex; + HANDLE m_modeMsgsEvent; + + void handleMsgFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader); + + void handleReplyICBM(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef); + void handleRecvServMsg(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef); + void handleRecvServMsgType1(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, DWORD dwRef); + void handleRecvServMsgType2(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, DWORD dwRef); + void handleRecvServMsgType4(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, DWORD dwRef); + void handleRecvServMsgError(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef); + void handleRecvMsgResponse(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef); + void handleServerAck(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef); + void handleStatusMsgReply(const char *szPrefix, HANDLE hContact, DWORD dwUin, WORD wVersion, int bMsgType, WORD wCookie, const char *szMsg, int nMsgFlags); + void handleTypingNotification(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef); + void handleMissedMsg(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef); + void handleOffineMessagesReply(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef); + void handleRecvServMsgContacts(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwID1, DWORD dwID2, WORD wCommand); + void handleRuntimeError(WORD wError); + + void parseServRelayData(BYTE *pDataBuf, WORD wLen, HANDLE hContact, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, WORD wAckType); + void parseServRelayPluginData(BYTE *pDataBuf, WORD wLen, HANDLE hContact, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, WORD wAckType, BYTE bFlags, WORD wStatus, WORD wCookie, WORD wVersion); + + HANDLE handleMessageAck(DWORD dwUin, char *szUID, WORD wCookie, WORD wVersion, int type, WORD wMsgLen, PBYTE buf, BYTE bFlags, int nMsgFlags); + void handleMessageTypes(DWORD dwUin, char *szUID, DWORD dwTimestamp, DWORD dwMsgID, DWORD dwMsgID2, WORD wCookie, WORD wVersion, int type, int flags, WORD wAckType, DWORD dwDataLen, WORD wMsgLen, char *pMsg, int nMsgFlags, message_ack_params *pAckParams); + void sendMessageTypesAck(HANDLE hContact, int bUnicode, message_ack_params *pArgs); + void sendTypingNotification(HANDLE hContact, WORD wMTNCode); + + int unpackPluginTypeId(BYTE **pBuffer, WORD *pwLen, int *pTypeId, WORD *pFunctionId, BOOL bThruDC); + + char* convertMsgToUserSpecificUtf(HANDLE hContact, const char *szMsg); + + //----| fam_09bos.cpp |--------------------------------------------------------------- + void handleBosFam(unsigned char *pBuffer, WORD wBufferLength, snac_header* pSnacHeader); + void handlePrivacyRightsReply(unsigned char *pBuffer, WORD wBufferLength); + void makeContactTemporaryVisible(HANDLE hContact); + + //----| fam_0alookup.cpp |------------------------------------------------------------ + void handleLookupFam(unsigned char *pBuffer, WORD wBufferLength, snac_header* pSnacHeader); + + void handleLookupEmailReply(BYTE* buf, WORD wLen, DWORD dwCookie); + void ReleaseLookupCookie(DWORD dwCookie, cookie_search *pCookie); + + //----| fam_0bstatus.cpp |------------------------------------------------------------ + void handleStatusFam(unsigned char *pBuffer, WORD wBufferLength, snac_header* pSnacHeader); + + //----| fam_13servclist.cpp |--------------------------------------------------------- + BOOL bIsSyncingCL; + + WORD m_wServerListLimits[0x20]; + WORD m_wServerListGroupMaxContacts; + WORD m_wServerListRecordNameMaxLength; + + void handleServCListFam(BYTE *pBuffer, WORD wBufferLength, snac_header* pSnacHeader, serverthread_info *info); + void handleServerCListRightsReply(BYTE *buf, WORD wLen); + void handleServerCListAck(cookie_servlist_action* sc, WORD wError); + void handleServerCListReply(BYTE *buf, WORD wLen, WORD wFlags, serverthread_info *info); + void handleServerCListItemAdd(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData); + void handleServerCListItemUpdate(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData); + void handleServerCListItemDelete(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData); + void handleRecvAuthRequest(BYTE *buf, WORD wLen); + void handleRecvAuthResponse(BYTE *buf, WORD wLen); + void handleRecvAdded(BYTE *buf, WORD wLen); + + HANDLE HContactFromRecordName(const char *szRecordName, int *bAdded); + + void processCListReply(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData); + + void icq_sendServerBeginOperation(int bImport); + void icq_sendServerEndOperation(); + void sendRosterAck(void); + + int getServerDataFromItemTLV(oscar_tlv_chain* pChain, unsigned char *buf); + DWORD updateServerGroupData(WORD wGroupId, void *groupData, int groupSize, DWORD dwOperationFlags); + void updateServAvatarHash(BYTE *pHash, int size); + void updateServVisibilityCode(BYTE bCode); + + //----| fam_15icqserver.cpp |--------------------------------------------------------- + void handleIcqExtensionsFam(BYTE *pBuffer, WORD wBufferLength, snac_header* pSnacHeader); + + void handleExtensionError(BYTE *buf, WORD wPackLen); + void handleExtensionServerInfo(BYTE *buf, WORD wPackLen, WORD wFlags); + void handleExtensionMetaResponse(BYTE *databuf, WORD wPacketLen, WORD wCookie, WORD wFlags); + + int parseUserInfoRecord(HANDLE hContact, oscar_tlv *pData, UserInfoRecordItem pRecordDef[], int nRecordDef, int nMaxRecords); + + void handleDirectoryQueryResponse(BYTE *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype, WORD wFlags); + void handleDirectoryUpdateResponse(BYTE *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype); + + void parseDirectoryUserDetailsData(HANDLE hContact, oscar_tlv_chain *cDetails, DWORD dwCookie, cookie_directory_data *pCookieData, WORD wReplySubType); + void parseDirectorySearchData(oscar_tlv_chain *cDetails, DWORD dwCookie, cookie_directory_data *pCookieData, WORD wReplySubType); + + void parseSearchReplies(unsigned char *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype, BYTE bResultCode); + void parseUserInfoUpdateAck(unsigned char *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype, BYTE bResultCode); + + void ReleaseSearchCookie(DWORD dwCookie, cookie_search *pCookie); + + //----| fam_17signon.cpp |------------------------------------------------------------ + void handleAuthorizationFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader, serverthread_info *info); + void handleAuthKeyResponse(BYTE *buf, WORD wPacketLen, serverthread_info *info); + + void sendClientAuth(const char *szKey, WORD wKeyLen, BOOL bSecure); + + //----| icq_avatars.cpp |------------------------------------------------------------- + icq_critical_section *m_avatarsMutex; + avatars_request *m_avatarsQueue; + + BOOL m_avatarsConnectionPending; + avatars_server_connection *m_avatarsConnection; + + int bAvatarsFolderInited; + HANDLE hAvatarsFolder; + + void requestAvatarConnection(); + void __cdecl AvatarThread(avatars_server_connection *pInfo); + + void handleAvatarOwnerHash(WORD wItemID, BYTE bFlags, BYTE *pData, BYTE nDataLen); + void handleAvatarContactHash(DWORD dwUIN, char *szUID, HANDLE hContact, BYTE *pHash, int nHashLen, WORD wOldStatus); + + void InitAvatars(); + avatars_request *ReleaseAvatarRequestInQueue(avatars_request *request); + + TCHAR* GetOwnAvatarFileName(); + void GetFullAvatarFileName(int dwUin, const char *szUid, int dwFormat, TCHAR *pszDest, int cbLen); + void GetAvatarFileName(int dwUin, const char *szUid, TCHAR *pszDest, int cbLen); + int IsAvatarChanged(HANDLE hContact, const BYTE *pHash, int nHashLen); + + int GetAvatarData(HANDLE hContact, DWORD dwUin, const char *szUid, const BYTE *hash, unsigned int hashlen, const TCHAR *file); + int SetAvatarData(HANDLE hContact, WORD wRef, const BYTE *data, unsigned int datalen); + + void StartAvatarThread(HANDLE hConn, char* cookie, WORD cookieLen); + void StopAvatarThread(); + + //----| icq_clients.cpp |------------------------------------------------------------- + const char* detectUserClient(HANDLE hContact, int nIsICQ, WORD wUserClass, DWORD dwOnlineSince, const char *szCurrentClient, WORD wVersion, DWORD dwFT1, DWORD dwFT2, DWORD dwFT3, BYTE bDirectFlag, DWORD dwDirectCookie, DWORD dwWebPort, BYTE *caps, WORD wLen, BYTE *bClientId, char *szClientBuf); + + //----| icq_db.cpp |------------------------------------------------------------------ + HANDLE AddEvent(HANDLE hContact, WORD wType, DWORD dwTime, DWORD flags, DWORD cbBlob, PBYTE pBlob); + void CreateResidentSetting(const char* szSetting); + HANDLE FindFirstContact(); + HANDLE FindNextContact(HANDLE hContact); + int IsICQContact(HANDLE hContact); + + int getSetting(HANDLE hContact, const char *szSetting, DBVARIANT *dbv); + BYTE getSettingByte(HANDLE hContact, const char *szSetting, BYTE byDef); + WORD getSettingWord(HANDLE hContact, const char *szSetting, WORD wDef); + DWORD getSettingDword(HANDLE hContact, const char *szSetting, DWORD dwDef); + double getSettingDouble(HANDLE hContact, const char *szSetting, double dDef); + int getSettingString(HANDLE hContact, const char *szSetting, DBVARIANT *dbv); + int getSettingStringW(HANDLE hContact, const char *szSetting, DBVARIANT *dbv); + int getSettingStringStatic(HANDLE hContact, const char *szSetting, char *dest, int dest_len); + char* getSettingStringUtf(HANDLE hContact, const char *szModule, const char *szSetting, char *szDef); + char* getSettingStringUtf(HANDLE hContact, const char *szSetting, char *szDef); + int getContactUid(HANDLE hContact, DWORD *pdwUin, uid_str *ppszUid); + DWORD getContactUin(HANDLE hContact); + WORD getContactStatus(HANDLE hContact); + char* getContactCListGroup(HANDLE hContact); + + int deleteSetting(HANDLE hContact, const char *szSetting); + + int setSettingByte(HANDLE hContact, const char *szSetting, BYTE byValue); + int setSettingWord(HANDLE hContact, const char *szSetting, WORD wValue); + int setSettingDword(HANDLE hContact, const char *szSetting, DWORD dwValue); + int setSettingDouble(HANDLE hContact, const char *szSetting, double dValue); + int setSettingString(HANDLE hContact, const char *szSetting, const char *szValue); + int setSettingStringW(HANDLE hContact, const char *szSetting, const WCHAR *wszValue); + int setSettingStringUtf(HANDLE hContact, const char *szModule, const char *szSetting, const char *szValue); + int setSettingStringUtf(HANDLE hContact, const char *szSetting, const char *szValue); + int setSettingBlob(HANDLE hContact, const char *szSetting, const BYTE *pValue, const int cbValue); + int setContactHidden(HANDLE hContact, BYTE bHidden); + void setStatusMsgVar(HANDLE hContact, char* szStatusMsg, bool isAnsi); + + //----| icq_direct.cpp |-------------------------------------------------------------- + icq_critical_section *directConnListMutex; + LIST directConns; + + icq_critical_section *expectedFileRecvMutex; + LIST expectedFileRecvs; + + void __cdecl icq_directThread(struct directthreadstartinfo* dtsi); + + void handleDirectPacket(directconnect* dc, PBYTE buf, WORD wLen); + void sendPeerInit_v78(directconnect* dc); + void sendPeerInitAck(directconnect* dc); + void sendPeerMsgInit(directconnect* dc, DWORD dwSeq); + void sendPeerFileInit(directconnect* dc); + int sendDirectPacket(directconnect* dc, icq_packet* pkt); + + void CloseContactDirectConns(HANDLE hContact); + directconnect* FindFileTransferDC(filetransfer* ft); + filetransfer* FindExpectedFileRecv(DWORD dwUin, DWORD dwTotalSize); + BOOL IsDirectConnectionOpen(HANDLE hContact, int type, int bPassive); + void OpenDirectConnection(HANDLE hContact, int type, void* pvExtra); + void CloseDirectConnection(directconnect *dc); + int SendDirectMessage(HANDLE hContact, icq_packet *pkt); + + //----| icq_directmsg.cpp |----------------------------------------------------------- + void handleDirectMessage(directconnect* dc, PBYTE buf, WORD wLen); + void handleDirectGreetingMessage(directconnect* dc, PBYTE buf, WORD wLen, WORD wCommand, WORD wCookie, BYTE bMsgType, BYTE bMsgFlags, WORD wStatus, WORD wFlags, char* pszText); + + //----| icq_filerequests.cpp |-------------------------------------------------------- + filetransfer* CreateFileTransfer(HANDLE hContact, DWORD dwUin, int nVersion); + + void handleFileAck(PBYTE buf, WORD wLen, DWORD dwUin, DWORD dwCookie, WORD wStatus, char* pszText); + void handleFileRequest(PBYTE buf, WORD wLen, DWORD dwUin, DWORD dwCookie, DWORD dwID1, DWORD dwID2, char* pszDescription, int nVersion, BOOL bDC); + void handleDirectCancel(directconnect *dc, PBYTE buf, WORD wLen, WORD wCommand, DWORD dwCookie, WORD wMessageType, WORD wStatus, WORD wFlags, char* pszText); + + void icq_CancelFileTransfer(HANDLE hContact, filetransfer* ft); + + //----| icq_filetransfer.cpp |-------------------------------------------------------- + void icq_AcceptFileTransfer(HANDLE hContact, filetransfer *ft); + void icq_sendFileResume(filetransfer *ft, int action, const char *szFilename); + void icq_InitFileSend(filetransfer *ft); + + void handleFileTransferPacket(directconnect *dc, PBYTE buf, WORD wLen); + void handleFileTransferIdle(directconnect *dc); + + //----| icq_infoupdate.cpp |---------------------------------------------------------- + icq_critical_section *infoUpdateMutex; + HANDLE hInfoQueueEvent; + int nInfoUserCount; + int bInfoPendingUsers; + BOOL bInfoUpdateEnabled; + BOOL bInfoUpdateRunning; + HANDLE hInfoThread; + DWORD dwInfoActiveRequest; + userinfo m_infoUpdateList[LISTSIZE]; + + void __cdecl InfoUpdateThread(void*); + + void icq_InitInfoUpdate(void); // Queues all outdated users + BOOL icq_QueueUser(HANDLE hContact); // Queue one UIN to the list for updating + void icq_DequeueUser(DWORD dwUin); // Remove one UIN from the list + void icq_RescanInfoUpdate(); // Add all outdated contacts to the list + void icq_InfoUpdateCleanup(void); // Clean up on exit + void icq_EnableUserLookup(BOOL bEnable); // Enable/disable user info lookups + + //----| log.cpp |----------------------------------------------------------------- + BOOL bErrorBoxVisible; + + void __cdecl icq_LogMessageThread(void* arg); + + void icq_LogMessage(int level, const char *szMsg); + void icq_LogUsingErrorCode(int level, DWORD dwError, const char *szMsg); //szMsg is optional + void icq_LogFatalParam(const char *szMsg, WORD wError); + + //----| icq_packet.cpp |-------------------------------------------------------------- + void ppackLETLVLNTSfromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wType); + void ppackLETLVWordLNTSfromDB(PBYTE *buf, int *buflen, WORD w, const char *szSetting, WORD wType); + void ppackLETLVLNTSBytefromDB(PBYTE *buf, int *buflen, const char *szSetting, BYTE b, WORD wType); + + void ppackTLVStringFromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wType); + void ppackTLVStringUtfFromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wType); + void ppackTLVDateFromDB(PBYTE *buf, int *buflen, const char *szSettingYear, const char *szSettingMonth, const char *szSettingDay, WORD wType); + + int ppackTLVWordStringItemFromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wTypeID, WORD wTypeData, WORD wID); + int ppackTLVWordStringUtfItemFromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wTypeID, WORD wTypeData, WORD wID); + + BOOL unpackUID(BYTE **ppBuf, WORD *pwLen, DWORD *pdwUIN, uid_str *ppszUID); + + //----| icq_popups.cpp |-------------------------------------------------------------- + int ShowPopUpMsg(HANDLE hContact, const char *szTitle, const char *szMsg, BYTE bType); + + //----| icq_proto.cpp |-------------------------------------------------------------- + void __cdecl CheekySearchThread( void* ); + + void __cdecl GetAwayMsgThread( void *pStatusData ); + + char* PrepareStatusNote(int nStatus); + + //----| icq_rates.cpp |--------------------------------------------------------------- + icq_critical_section *m_ratesMutex; + rates *m_rates; + + rates_queue *m_ratesQueue_Request; // rate queue for xtraz requests + rates_queue *m_ratesQueue_Response; // rate queue for msg responses + + int handleRateItem(rates_queue_item *item, int nQueueType = RQT_DEFAULT, int nMinDelay = 0, BOOL bAllowDelay = TRUE); + + void __cdecl rateDelayThread(struct rate_delay_args *pArgs); + + //----| icq_server.cpp |-------------------------------------------------------------- + HANDLE hServerConn; + WORD wListenPort; + WORD wLocalSequence; + UINT serverThreadId; + HANDLE serverThreadHandle; + + __inline bool icqOnline() const + { return (m_iStatus != ID_STATUS_OFFLINE && m_iStatus != ID_STATUS_CONNECTING); + } + + void __cdecl SendPacketAsyncThread(icq_packet* pArgs); + void __cdecl ServerThread(serverthread_start_info *infoParam); + + void icq_serverDisconnect(BOOL bBlock); + void icq_login(const char* szPassword); + + int handleServerPackets(BYTE *buf, int len, serverthread_info *info); + void sendServPacket(icq_packet *pPacket); + void sendServPacketAsync(icq_packet *pPacket); + + int IsServerOverRate(WORD wFamily, WORD wCommand, int nLevel); + + //----| icq_servlist.cpp |------------------------------------------------------------ + HANDLE hHookSettingChanged; + HANDLE hHookContactDeleted; + HANDLE hHookCListGroupChange; + icq_critical_section *servlistMutex; + + DWORD* pdwServerIDList; + int nServerIDListCount; + int nServerIDListSize; + + // server-list update board + icq_critical_section *servlistQueueMutex; + int servlistQueueCount; + int servlistQueueSize; + ssiqueueditems **servlistQueueList; + int servlistQueueState; + HANDLE servlistQueueThreadHandle; + int servlistEditCount; + + void servlistBeginOperation(int operationCount, int bImport); + void servlistEndOperation(int operationCount); + + void __cdecl servlistQueueThread(void* queueState); + + void servlistQueueAddGroupItem(servlistgroupitem* pGroupItem, int dwTimeout); + int servlistHandlePrimitives(DWORD dwOperation); + void servlistProcessLogin(); + + void servlistPostPacket(icq_packet* packet, DWORD dwCookie, DWORD dwOperation, DWORD dwTimeout); + void servlistPostPacketDouble(icq_packet* packet1, DWORD dwCookie, DWORD dwOperation, DWORD dwTimeout, icq_packet* packet2, WORD wAction2); + + // server-list pending queue + int servlistPendingCount; + int servlistPendingSize; + servlistpendingitem** servlistPendingList; + + int servlistPendingFindItem(int nType, HANDLE hContact, const char *pszGroup); + void servlistPendingAddItem(servlistpendingitem* pItem); + servlistpendingitem* servlistPendingRemoveItem(int nType, HANDLE hContact, const char *pszGroup); + + void servlistPendingAddContactOperation(HANDLE hContact, LPARAM param, PENDING_CONTACT_CALLBACK callback, DWORD flags); + void servlistPendingAddGroupOperation(const char *pszGroup, LPARAM param, PENDING_GROUP_CALLBACK callback, DWORD flags); + int servlistPendingAddContact(HANDLE hContact, WORD wContactID, WORD wGroupID, LPARAM param, PENDING_CONTACT_CALLBACK callback, int bDoInline, LPARAM operationParam = 0, PENDING_CONTACT_CALLBACK operationCallback = NULL); + int servlistPendingAddGroup(const char *pszGroup, WORD wGroupID, LPARAM param, PENDING_GROUP_CALLBACK callback, int bDoInline, LPARAM operationParam = 0, PENDING_GROUP_CALLBACK operationCallback = NULL); + void servlistPendingRemoveContact(HANDLE hContact, WORD wContactID, WORD wGroupID, int nResult); + void servlistPendingRemoveGroup(const char *pszGroup, WORD wGroupID, int nResult); + void servlistPendingFlushOperations(); + + // server-list support functions + int nJustAddedCount; + int nJustAddedSize; + HANDLE* pdwJustAddedList; + + void AddJustAddedContact(HANDLE hContact); + BOOL IsContactJustAdded(HANDLE hContact); + void FlushJustAddedContacts(); + + WORD GenerateServerID(int bGroupType, int bFlags, int wCount = 0); + void ReserveServerID(WORD wID, int bGroupType, int bFlags); + void FreeServerID(WORD wID, int bGroupType); + BOOL CheckServerID(WORD wID, unsigned int wCount); + void FlushServerIDs(); + void LoadServerIDs(); + void StoreServerIDs(); + + void* collectGroups(int *count); + void* collectBuddyGroup(WORD wGroupID, int *count); + char* getServListGroupName(WORD wGroupID); + void setServListGroupName(WORD wGroupID, const char *szGroupName); + WORD getServListGroupLinkID(const char *szPath); + void setServListGroupLinkID(const char *szPath, WORD wGroupID); + int IsServerGroupsDefined(); + char* getServListGroupCListPath(WORD wGroupId); + char* getServListUniqueGroupName(const char *szGroupName, int bAlloced); + + int __cdecl servlistCreateGroup_gotParentGroup(const char *szGroup, WORD wGroupID, LPARAM param, int nResult); + int __cdecl servlistCreateGroup_Ready(const char *szGroup, WORD groupID, LPARAM param, int nResult); + void servlistCreateGroup(const char *szGroupPath, LPARAM param, PENDING_GROUP_CALLBACK callback); + + int __cdecl servlistAddContact_gotGroup(const char *szGroup, WORD wGroupID, LPARAM lParam, int nResult); + int __cdecl servlistAddContact_Ready(HANDLE hContact, WORD wContactID, WORD wGroupID, LPARAM lParam, int nResult); + void servlistAddContact(HANDLE hContact, const char *pszGroup); + + int __cdecl servlistRemoveContact_Ready(HANDLE hContact, WORD contactID, WORD groupID, LPARAM lParam, int nResult); + void servlistRemoveContact(HANDLE hContact); + + int __cdecl servlistMoveContact_gotTargetGroup(const char *szGroup, WORD wNewGroupID, LPARAM lParam, int nResult); + int __cdecl servlistMoveContact_Ready(HANDLE hContact, WORD contactID, WORD groupID, LPARAM lParam, int nResult); + void servlistMoveContact(HANDLE hContact, const char *pszNewGroup); + + int __cdecl servlistUpdateContact_Ready(HANDLE hContact, WORD contactID, WORD groupID, LPARAM lParam, int nResult); + void servlistUpdateContact(HANDLE hContact); + + int __cdecl servlistRenameGroup_Ready(const char *szGroup, WORD wGroupID, LPARAM lParam, int nResult); + void servlistRenameGroup(char *szGroup, WORD wGroupId, char *szNewGroup); + + int __cdecl servlistRemoveGroup_Ready(const char *szGroup, WORD groupID, LPARAM lParam, int nResult); + void servlistRemoveGroup(const char *szGroup, WORD wGroupId); + + void removeGroupPathLinks(WORD wGroupID); + int getServListGroupLevel(WORD wGroupId); + + void resetServContactAuthState(HANDLE hContact, DWORD dwUin); + + void FlushSrvGroupsCache(); + int getCListGroupHandle(const char *szGroup); + int getCListGroupExists(const char *szGroup); + int moveContactToCListGroup(HANDLE hContact, const char *szGroup); /// TODO: this should be DB function + + DWORD icq_sendServerItem(DWORD dwCookie, WORD wAction, WORD wGroupId, WORD wItemId, const char *szName, BYTE *pTLVs, int nTlvLength, WORD wItemType, DWORD dwOperation, DWORD dwTimeout, void **doubleObject); + DWORD icq_sendServerContact(HANDLE hContact, DWORD dwCookie, WORD wAction, WORD wGroupId, WORD wContactId, DWORD dwOperation, DWORD dwTimeout, void **doubleObject); + DWORD icq_sendSimpleItem(DWORD dwCookie, WORD wAction, DWORD dwUin, char* szUID, WORD wGroupId, WORD wItemId, WORD wItemType, DWORD dwOperation, DWORD dwTimeout); + DWORD icq_sendServerGroup(DWORD dwCookie, WORD wAction, WORD wGroupId, const char *szName, void *pContent, int cbContent, DWORD dwOperationFlags); + + DWORD icq_modifyServerPrivacyItem(HANDLE hContact, DWORD dwUin, char *szUid, WORD wAction, DWORD dwOperation, WORD wItemId, WORD wType); + DWORD icq_removeServerPrivacyItem(HANDLE hContact, DWORD dwUin, char *szUid, WORD wItemId, WORD wType); + DWORD icq_addServerPrivacyItem(HANDLE hContact, DWORD dwUin, char *szUid, WORD wItemId, WORD wType); + + int __cdecl ServListDbSettingChanged(WPARAM wParam, LPARAM lParam); + int __cdecl ServListDbContactDeleted(WPARAM wParam, LPARAM lParam); + int __cdecl ServListCListGroupChange(WPARAM wParam, LPARAM lParam); + + //----| stdpackets.cpp |---------------------------------------------------------- + void icq_sendCloseConnection(); + + void icq_requestnewfamily(WORD wFamily, void (CIcqProto::*familyhandler)(HANDLE hConn, char* cookie, WORD cookieLen)); + + void icq_setidle(int bAllow); + void icq_setstatus(WORD wStatus, const char *szStatusNote = NULL); + DWORD icq_sendGetInfoServ(HANDLE, DWORD, int); + DWORD icq_sendGetAimProfileServ(HANDLE hContact, char *szUid); + DWORD icq_sendGetAwayMsgServ(HANDLE, DWORD, int, WORD); + DWORD icq_sendGetAwayMsgServExt(HANDLE hContact, DWORD dwUin, char *szUID, int type, WORD wVersion); + DWORD icq_sendGetAimAwayMsgServ(HANDLE hContact, char *szUID, int type); + void icq_sendSetAimAwayMsgServ(const char *szMsg); + + void icq_sendFileSendServv7(filetransfer* ft, const char *szFiles); + void icq_sendFileSendServv8(filetransfer* ft, const char *szFiles, int nAckType); + + void icq_sendFileAcceptServ(DWORD dwUin, filetransfer *ft, int nAckType); + void icq_sendFileAcceptServv7(DWORD dwUin, DWORD TS1, DWORD TS2, DWORD dwCookie, const char *szFiles, const char *szDescr, DWORD dwTotalSize, WORD wPort, BOOL accepted, int nAckType); + void icq_sendFileAcceptServv8(DWORD dwUin, DWORD TS1, DWORD TS2, DWORD dwCookie, const char *szFiles, const char *szDescr, DWORD dwTotalSize, WORD wPort, BOOL accepted, int nAckType); + + void icq_sendFileDenyServ(DWORD dwUin, filetransfer *ft, const char *szReason, int nAckType); + + DWORD icq_sendAdvancedSearchServ(BYTE *fieldsBuffer,int bufferLen); + DWORD icq_changeUserPasswordServ(const char *szPassword); + DWORD icq_changeUserDirectoryInfoServ(const BYTE *pData, WORD wDataLen, BYTE bRequestType); + void icq_sendGenericContact(DWORD dwUin, const char *szUid, WORD wFamily, WORD wSubType); + void icq_sendNewContact(DWORD dwUin, const char *szUid); + void icq_sendRemoveContact(DWORD dwUin, const char *szUid); + void icq_sendChangeVisInvis(HANDLE hContact, DWORD dwUin, char* szUID, int list, int add); + void icq_sendEntireVisInvisList(int); + void icq_sendAwayMsgReplyServ(DWORD, DWORD, DWORD, WORD, WORD, BYTE, char **); + void icq_sendAwayMsgReplyServExt(DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, WORD wCookie, WORD wVersion, BYTE msgType, char **szMsg); + + DWORD icq_sendSMSServ(const char *szPhoneNumber, const char *szMsg); + void icq_sendMessageCapsServ(DWORD dwUin); + void icq_sendRevokeAuthServ(DWORD dwUin, char *szUid); + void icq_sendGrantAuthServ(DWORD dwUin, const char *szUid, const char *szMsg); + void icq_sendAuthReqServ(DWORD dwUin, char* szUid, const char *szMsg); + void icq_sendAuthResponseServ(DWORD dwUin, char* szUid,int auth,const TCHAR *szReason); + void icq_sendYouWereAddedServ(DWORD,DWORD); + + DWORD sendDirectorySearchPacket(const BYTE *pSearchData, WORD wDataLen, WORD wPage, BOOL bOnlineUsersOnly); + DWORD sendTLVSearchPacket(BYTE bType, char* pSearchDataBuf, WORD wSearchType, WORD wInfoLen, BOOL bOnlineUsersOnly); + void sendOwnerInfoRequest(void); + DWORD sendUserInfoMultiRequest(BYTE *pRequestData, WORD wDataLen, int nItems); + + DWORD icq_SendChannel1Message(DWORD dwUin, char *szUID, HANDLE hContact, char *pszText, cookie_message_data *pCookieData); + DWORD icq_SendChannel1MessageW(DWORD dwUin, char *szUID, HANDLE hContact, WCHAR *pszText, cookie_message_data *pCookieData); // UTF-16 + DWORD icq_SendChannel2Message(DWORD dwUin, HANDLE hContact, const char *szMessage, int nBodyLength, WORD wPriority, cookie_message_data *pCookieData, char *szCap); + DWORD icq_SendChannel2Contacts(DWORD dwUin, char *szUid, HANDLE hContact, const char *pData, WORD wDataLen, const char *pNames, WORD wNamesLen, cookie_message_data *pCookieData); + DWORD icq_SendChannel4Message(DWORD dwUin, HANDLE hContact, BYTE bMsgType, WORD wMsgLen, const char *szMsg, cookie_message_data *pCookieData); + + void icq_sendAdvancedMsgAck(DWORD, DWORD, DWORD, WORD, BYTE, BYTE); + void icq_sendContactsAck(DWORD dwUin, char *szUid, DWORD dwMsgID1, DWORD dwMsgID2); + + void icq_sendReverseReq(directconnect *dc, DWORD dwCookie, cookie_message_data *pCookie); + void icq_sendReverseFailed(directconnect* dc, DWORD dwMsgID1, DWORD dwMsgID2, DWORD dwCookie); + + void icq_sendXtrazRequestServ(DWORD dwUin, DWORD dwCookie, char* szBody, int nBodyLen, cookie_message_data *pCookieData); + void icq_sendXtrazResponseServ(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szBody, int nBodyLen, int nType); + + DWORD SearchByUin(DWORD dwUin); + DWORD SearchByNames(const char *pszNick, const char *pszFirstName, const char *pszLastName, WORD wPage); + DWORD SearchByMail(const char *pszEmail); + + DWORD icq_searchAimByEmail(const char* pszEmail, DWORD dwSearchId); + + void oft_sendFileRequest(DWORD dwUin, char *szUid, oscar_filetransfer *ft, const char *pszFiles, DWORD dwLocalInternalIP); + void oft_sendFileAccept(DWORD dwUin, char *szUid, oscar_filetransfer *ft); + void oft_sendFileDeny(DWORD dwUin, char *szUid, oscar_filetransfer *ft); + void oft_sendFileCancel(DWORD dwUin, char *szUid, oscar_filetransfer *ft); + void oft_sendFileResponse(DWORD dwUin, char *szUid, oscar_filetransfer *ft, WORD wResponse); + void oft_sendFileRedirect(DWORD dwUin, char *szUid, oscar_filetransfer *ft, DWORD dwIP, WORD wPort, int bProxy); + + //---- | icq_svcs.cpp |---------------------------------------------------------------- + HANDLE AddToListByUIN(DWORD dwUin, DWORD dwFlags); + HANDLE AddToListByUID(const char *szUID, DWORD dwFlags); + + void ICQAddRecvEvent(HANDLE hContact, WORD wType, PROTORECVEVENT* pre, DWORD cbBlob, PBYTE pBlob, DWORD flags); + INT_PTR __cdecl IcqAddCapability(WPARAM wParam, LPARAM lParam); + INT_PTR __cdecl IcqCheckCapability(WPARAM wParam, LPARAM lParam); + + std::list CustomCapList; + + //----| icq_uploadui.cpp |------------------------------------------------------------ + void ShowUploadContactsDialog(void); + + //----| icq_xstatus.cpp |------------------------------------------------------------- + int m_bHideXStatusUI; + int m_bHideXStatusMenu; + int bXStatusExtraIconsReady; + HANDLE hHookExtraIconsRebuild; + HANDLE hHookStatusBuild; + HANDLE hHookExtraIconsApply; + HANDLE hXStatusExtraIcons[XSTATUS_COUNT]; + IcqIconHandle hXStatusIcons[XSTATUS_COUNT]; + HANDLE hXStatusItems[XSTATUS_COUNT + 1]; + + int hXStatusCListIcons[XSTATUS_COUNT]; + BOOL bXStatusCListIconsValid[XSTATUS_COUNT]; + + void InitXStatusItems(BOOL bAllowStatus); + BYTE getContactXStatus(HANDLE hContact); + DWORD sendXStatusDetailsRequest(HANDLE hContact, int bForced); + DWORD requestXStatusDetails(HANDLE hContact, BOOL bAllowDelay); + HICON getXStatusIcon(int bStatus, UINT flags); + void releaseXStatusIcon(int bStatus, UINT flags); + void setXStatusEx(BYTE bXStatus, BYTE bQuiet); + void setContactExtraIcon(HANDLE hContact, int xstatus); + void handleXStatusCaps(DWORD dwUIN, char *szUID, HANDLE hContact, BYTE *caps, int capsize, char *moods, int moodsize); + void updateServerCustomStatus(int fullUpdate); + + void InitXStatusIcons(); + void UninitXStatusIcons(); + + //----| icq_xtraz.cpp |--------------------------------------------------------------- + void handleXtrazNotify(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szMsg, int nMsgLen, BOOL bThruDC); + void handleXtrazNotifyResponse(DWORD dwUin, HANDLE hContact, WORD wCookie, char* szMsg, int nMsgLen); + + void handleXtrazInvitation(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szMsg, int nMsgLen, BOOL bThruDC); + void handleXtrazData(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szMsg, int nMsgLen, BOOL bThruDC); + + DWORD SendXtrazNotifyRequest(HANDLE hContact, char* szQuery, char* szNotify, int bForced); + void SendXtrazNotifyResponse(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szResponse, int nResponseLen, BOOL bThruDC); + + //----| init.cpp |-------------------------------------------------------------------- + void UpdateGlobalSettings(); + + //----| loginpassword.cpp |----------------------------------------------------------- + void RequestPassword(); + + //----| oscar_filetransfer.cpp |------------------------------------------------------ + icq_critical_section *oftMutex; + int fileTransferCount; + basic_filetransfer** fileTransferList; + + oscar_filetransfer* CreateOscarTransfer(); + filetransfer *CreateIcqFileTransfer(); + void ReleaseFileTransfer(void *ft); + void SafeReleaseFileTransfer(void **ft); + oscar_filetransfer* FindOscarTransfer(HANDLE hContact, DWORD dwID1, DWORD dwID2); + + oscar_listener* CreateOscarListener(oscar_filetransfer *ft, NETLIBNEWCONNECTIONPROC_V2 handler); + void ReleaseOscarListener(oscar_listener **pListener); + + void OpenOscarConnection(HANDLE hContact, oscar_filetransfer *ft, int type); + void CloseOscarConnection(oscar_connection *oc); + int CreateOscarProxyConnection(oscar_connection *oc); + + int getFileTransferIndex(void *ft); + int IsValidFileTransfer(void *ft); + int IsValidOscarTransfer(void *ft); + + void handleRecvServMsgOFT(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwID1, DWORD dwID2, WORD wCommand); + void handleRecvServResponseOFT(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, void* ft); + + HANDLE oftInitTransfer(HANDLE hContact, DWORD dwUin, char *szUid, const TCHAR **pszFiles, const TCHAR *szDescription); + HANDLE oftFileAllow(HANDLE hContact, HANDLE hTransfer, const TCHAR *szPath); + DWORD oftFileDeny(HANDLE hContact, HANDLE hTransfer, const TCHAR *szReason); + DWORD oftFileCancel(HANDLE hContact, HANDLE hTransfer); + void oftFileResume(oscar_filetransfer *ft, int action, const TCHAR *szFilename); + + void sendOscarPacket(oscar_connection *oc, icq_packet *packet); + void handleOFT2FramePacket(oscar_connection *oc, WORD datatype, BYTE *pBuffer, WORD wLen); + void sendOFT2FramePacket(oscar_connection *oc, WORD datatype); + + void proxy_sendInitTunnel(oscar_connection *oc); + void proxy_sendJoinTunnel(oscar_connection *oc, WORD wPort); + + //----| stdpackets.cpp |-------------------------------------------------------------- + void __cdecl oft_connectionThread(struct oscarthreadstartinfo *otsi); + + int oft_handlePackets(oscar_connection *oc, BYTE *buf, int len); + int oft_handleFileData(oscar_connection *oc, BYTE *buf, int len); + int oft_handleProxyData(oscar_connection *oc, BYTE *buf, int len); + void oft_sendFileData(oscar_connection *oc); + void oft_sendPeerInit(oscar_connection *oc); + void oft_sendFileReply(DWORD dwUin, char *szUid, oscar_filetransfer *ft, WORD wResult); + + //----| upload.cpp |------------------------------------------------------------------ + int StringToListItemId(const char *szSetting,int def); + + //----| utilities.cpp |--------------------------------------------------------------- + int BroadcastAck(HANDLE hContact,int type,int result,HANDLE hProcess,LPARAM lParam); + char* ConvertMsgToUserSpecificAnsi(HANDLE hContact, const char* szMsg); + + char* GetUserStoredPassword(char *szBuffer, int cbSize); + char* GetUserPassword(BOOL bAlways); + WORD GetMyStatusFlags(); + + DWORD ReportGenericSendError(HANDLE hContact, int nType, const char* szErrorMsg); + void SetCurrentStatus(int nStatus); + + void ForkThread( IcqThreadFunc pFunc, void* arg ); + HANDLE ForkThreadEx( IcqThreadFunc pFunc, void* arg, UINT* threadID = NULL ); + + void __cdecl ProtocolAckThread(icq_ack_args* pArguments); + void SendProtoAck(HANDLE hContact, DWORD dwCookie, int nAckResult, int nAckType, char* pszMessage); + + HANDLE CreateProtoEvent(const char* szEvent); + void CreateProtoService(const char* szService, IcqServiceFunc serviceProc); + void CreateProtoServiceParam(const char* szService, IcqServiceFuncParam serviceProc, LPARAM lParam); + HANDLE HookProtoEvent(const char* szEvent, IcqEventFunc pFunc); + + int NetLog_Server(const char *fmt,...); + int NetLog_Direct(const char *fmt,...); + int NetLog_Uni(BOOL bDC, const char *fmt,...); + + icq_critical_section *contactsCacheMutex; + LIST contactsCache; + + void AddToContactsCache(HANDLE hContact, DWORD dwUin, const char *szUid); + void DeleteFromContactsCache(HANDLE hContact); + void InitContactsCache(); + void UninitContactsCache(); + + void AddToSpammerList(DWORD dwUIN); + BOOL IsOnSpammerList(DWORD dwUIN); + + HANDLE NetLib_BindPort(NETLIBNEWCONNECTIONPROC_V2 pFunc, void* lParam, WORD* pwPort, DWORD* pdwIntIP); + + HANDLE HandleFromCacheByUid(DWORD dwUin, const char *szUid); + HANDLE HContactFromUIN(DWORD dwUin, int *Added); + HANDLE HContactFromUID(DWORD dwUin, const char *szUid, int *Added); + HANDLE HContactFromAuthEvent(HANDLE hEvent); + + void ResetSettingsOnListReload(); + void ResetSettingsOnConnect(); + void ResetSettingsOnLoad(); + + int IsMetaInfoChanged(HANDLE hContact); + + char *setStatusNoteText, *setStatusMoodData; + void __cdecl SetStatusNoteThread(void *pArguments); + int SetStatusNote(const char *szStatusNote, DWORD dwDelay, int bForced); + int SetStatusMood(const char *szMoodData, DWORD dwDelay); + + BOOL writeDbInfoSettingString(HANDLE hContact, const char* szSetting, char** buf, WORD* pwLength); + BOOL writeDbInfoSettingWord(HANDLE hContact, const char *szSetting, char **buf, WORD* pwLength); + BOOL writeDbInfoSettingWordWithTable(HANDLE hContact, const char *szSetting, const FieldNamesItem *table, char **buf, WORD* pwLength); + BOOL writeDbInfoSettingByte(HANDLE hContact, const char *pszSetting, char **buf, WORD* pwLength); + BOOL writeDbInfoSettingByteWithTable(HANDLE hContact, const char *szSetting, const FieldNamesItem *table, char **buf, WORD* pwLength); + + void writeDbInfoSettingTLVStringUtf(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv); + void writeDbInfoSettingTLVString(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv); + void writeDbInfoSettingTLVWord(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv); + void writeDbInfoSettingTLVByte(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv); + void writeDbInfoSettingTLVDouble(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv); + void writeDbInfoSettingTLVDate(HANDLE hContact, const char *szSettingYear, const char *szSettingMonth, const char *szSettingDay, oscar_tlv_chain *chain, WORD wTlv); + void writeDbInfoSettingTLVBlob(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv); + + char** MirandaStatusToAwayMsg(int nStatus); + + BOOL validateStatusMessageRequest(HANDLE hContact, WORD byMessageType); +}; + +#endif diff --git a/protocols/IcqOscarJ/src/icq_rates.cpp b/protocols/IcqOscarJ/src/icq_rates.cpp new file mode 100644 index 0000000000..e7513ec148 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_rates.cpp @@ -0,0 +1,529 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Rate Management stuff +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +// +// Rate Level 1 Management +///////////////////////////// + +rates::rates(CIcqProto *ppro, BYTE *pBuffer, WORD wLen) +{ + nGroups = 0; + memset(&groups, 0, MAX_RATES_GROUP_COUNT * sizeof(rates_group)); + this->ppro = ppro; + + // Parse Rate Data Block + WORD wCount; + unpackWord(&pBuffer, &wCount); + wLen -= 2; + + if (wCount > MAX_RATES_GROUP_COUNT) + { // just sanity check + ppro->NetLog_Server("Rates: Error: Data packet contains too many rate groups!"); + wCount = MAX_RATES_GROUP_COUNT; + } + + nGroups = wCount; + // Parse Group details + int i; + for (i=0; i= 35) + { + pBuffer += 2; // Group ID + unpackDWord(&pBuffer, &pGroup->dwWindowSize); + unpackDWord(&pBuffer, &pGroup->dwClearLevel); + unpackDWord(&pBuffer, &pGroup->dwAlertLevel); + unpackDWord(&pBuffer, &pGroup->dwLimitLevel); + pBuffer += 8; + unpackDWord(&pBuffer, &pGroup->dwMaxLevel); + pBuffer += 5; + wLen -= 35; + } + else + { // packet broken, put some basic defaults + pGroup->dwWindowSize = 10; + pGroup->dwMaxLevel = 5000; + } + pGroup->rCurrentLevel = pGroup->dwMaxLevel; + } + // Parse Group associated pairs + for (i=0; inPairs = wNum; + pGroup->pPairs = (WORD*)SAFE_MALLOC(wNum*4); + for (int n=0; npPairs[n] = wItem; + } +#ifdef _DEBUG + ppro->NetLog_Server("Rates: %d# %d pairs.", i+1, wNum); +#endif + wLen -= wNum*4; + } +} + + +rates::~rates() +{ + for (int i = 0; i < nGroups; i++) + SAFE_FREE((void**)&groups[i].pPairs); + + nGroups = 0; +} + + +WORD rates::getGroupFromSNAC(WORD wFamily, WORD wCommand) +{ + if (this) + { + for (int i = 0; i < nGroups; i++) + { + rates_group* pGroup = &groups[i]; + + for (int j = 0; j < 2 * pGroup->nPairs; j += 2) + { + if (pGroup->pPairs[j] == wFamily && pGroup->pPairs[j + 1] == wCommand) + { // we found the group + return (WORD)(i + 1); + } + } + } + _ASSERTE(0); + } + + return 0; // Failure +} + + +WORD rates::getGroupFromPacket(icq_packet *pPacket) +{ + if (this) + { + if (pPacket->nChannel == ICQ_DATA_CHAN && pPacket->wLen >= 0x10) + { + WORD wFamily, wCommand; + BYTE *pBuf = pPacket->pData + 6; + + unpackWord(&pBuf, &wFamily); + unpackWord(&pBuf, &wCommand); + + return getGroupFromSNAC(wFamily, wCommand); + } + } + return 0; +} + + +rates_group* rates::getGroup(WORD wGroup) +{ + if (this && wGroup && wGroup <= nGroups) + return &groups[wGroup - 1]; + + return NULL; +} + + +int rates::getNextRateLevel(WORD wGroup) +{ + rates_group *pGroup = getGroup(wGroup); + + if (pGroup) + { + int nLevel = pGroup->rCurrentLevel*(pGroup->dwWindowSize-1)/pGroup->dwWindowSize + (GetTickCount() - pGroup->tCurrentLevel)/pGroup->dwWindowSize; + + return nLevel < (int)pGroup->dwMaxLevel ? nLevel : pGroup->dwMaxLevel; + } + return -1; // Failure +} + + +int rates::getDelayToLimitLevel(WORD wGroup, int nLevel) +{ + rates_group *pGroup = getGroup(wGroup); + + if (pGroup) + return (getLimitLevel(wGroup, nLevel) - pGroup->rCurrentLevel)*pGroup->dwWindowSize + pGroup->rCurrentLevel; + + return 0; // Failure +} + + +void rates::packetSent(icq_packet *pPacket) +{ + if (this) + { + WORD wGroup = getGroupFromPacket(pPacket); + + if (wGroup) + updateLevel(wGroup, getNextRateLevel(wGroup)); + } +} + + +void rates::updateLevel(WORD wGroup, int nLevel) +{ + rates_group *pGroup = getGroup(wGroup); + + if (pGroup) + { + pGroup->rCurrentLevel = nLevel; + pGroup->tCurrentLevel = GetTickCount(); +#ifdef _DEBUG + ppro->NetLog_Server("Rates: New level %d for #%d", nLevel, wGroup); +#endif + } +} + + +int rates::getLimitLevel(WORD wGroup, int nLevel) +{ + rates_group *pGroup = getGroup(wGroup); + + if (pGroup) + { + switch(nLevel) + { + case RML_CLEAR: + return pGroup->dwClearLevel; + + case RML_ALERT: + return pGroup->dwAlertLevel; + + case RML_LIMIT: + return pGroup->dwLimitLevel; + + case RML_IDLE_10: + return pGroup->dwClearLevel + ((pGroup->dwMaxLevel - pGroup->dwClearLevel)/10); + + case RML_IDLE_30: + return pGroup->dwClearLevel + (3*(pGroup->dwMaxLevel - pGroup->dwClearLevel)/10); + + case RML_IDLE_50: + return pGroup->dwClearLevel + ((pGroup->dwMaxLevel - pGroup->dwClearLevel)/2); + + case RML_IDLE_70: + return pGroup->dwClearLevel + (7*(pGroup->dwMaxLevel - pGroup->dwClearLevel)/10); + } + } + return 9999; // some high number - without rates we allow anything +} + + +void rates::initAckPacket(icq_packet *pPacket) +{ + serverPacketInit(pPacket, 10 + nGroups * (int)sizeof(WORD)); + packFNACHeader(pPacket, ICQ_SERVICE_FAMILY, ICQ_CLIENT_RATE_ACK); + for (WORD wGroup = 1; wGroup <= nGroups; wGroup++) + packWord(pPacket, wGroup); +} + + + +// +// Rate Level 2 Management +///////////////////////////// + + +rates_queue_item::rates_queue_item(CIcqProto *ppro, WORD wGroup) : bCreated(FALSE), dwUin(0), szUid(NULL) +{ + this->ppro = ppro; + this->wGroup = wGroup; +} + +rates_queue_item::~rates_queue_item() +{ + if (bCreated) + { + SAFE_FREE(&szUid); + bCreated = FALSE; + } +} + + +BOOL rates_queue_item::isEqual(rates_queue_item *pItem) +{ // the same event (equal address of _vftable) for the same contact + return (pItem->hContact == this->hContact) && (*(void**)pItem == *(void**)this); +} + + +rates_queue_item* rates_queue_item::copyItem(rates_queue_item *pDest) +{ + if (!pDest) + pDest = new rates_queue_item(ppro, wGroup); + + pDest->hContact = hContact; + pDest->dwUin = dwUin; + pDest->szUid = dwUin ? null_strdup(szUid) : NULL; + pDest->bCreated = TRUE; + + return pDest; +} + + +void rates_queue_item::execute() +{ +#ifdef _DEBUG + ppro->NetLog_Server("Rates: Error executing abstract event."); +#endif +} + + +BOOL rates_queue_item::isOverRate(int nLevel) +{ + icq_lock l(ppro->m_ratesMutex); + + if (ppro->m_rates) + return ppro->m_rates->getNextRateLevel(wGroup) < ppro->m_rates->getLimitLevel(wGroup, nLevel); + + return FALSE; +} + + +rates_queue::rates_queue(CIcqProto *ppro, const char *szDescr, int nLimitLevel, int nWaitLevel, int nDuplicates) +{ + this->listsMutex = new icq_critical_section(); + this->ppro = ppro; + this->szDescr = szDescr; + limitLevel = nLimitLevel; + waitLevel = nWaitLevel; + duplicates = nDuplicates; +} + + +rates_queue::~rates_queue() +{ + cleanup(); + delete listsMutex; +} + + +// links to functions that are under Rate Control +struct rate_delay_args +{ + int nDelay; + rates_queue *queue; + IcqRateFunc delaycode; +}; + +void __cdecl CIcqProto::rateDelayThread(rate_delay_args *pArgs) +{ + SleepEx(pArgs->nDelay, TRUE); + (pArgs->queue->*pArgs->delaycode)(); + SAFE_FREE((void**)&pArgs); +} + + +void rates_queue::initDelay(int nDelay, IcqRateFunc delaycode) +{ +#ifdef _DEBUG + ppro->NetLog_Server("Rates: Delay %dms", nDelay); +#endif + + rate_delay_args *pArgs = (rate_delay_args*)SAFE_MALLOC(sizeof(rate_delay_args)); // This will be freed in the new thread + pArgs->queue = this; + pArgs->nDelay = nDelay; + pArgs->delaycode = delaycode; + + ppro->ForkThread((IcqThreadFunc)&CIcqProto::rateDelayThread, pArgs); +} + + +void rates_queue::cleanup() +{ + icq_lock l(listsMutex); + + if (pendingListSize) + ppro->NetLog_Server("Rates: Purging %d %s(s).", pendingListSize, szDescr); + + for (int i=0; i < pendingListSize; i++) + delete pendingList[i]; + SAFE_FREE((void**)&pendingList); + pendingListSize = 0; +} + + +void rates_queue::processQueue() +{ + if (!pendingList) + return; + + if (!ppro->icqOnline()) + { + cleanup(); + return; + } + // take from queue, execute + rates_queue_item *item = pendingList[0]; + + ppro->m_ratesMutex->Enter(); + listsMutex->Enter(); + if (item->isOverRate(limitLevel)) + { // the rate is higher, keep sleeping + int nDelay = ppro->m_rates->getDelayToLimitLevel(item->wGroup, ppro->m_rates->getLimitLevel(item->wGroup, waitLevel)); + + listsMutex->Leave(); + ppro->m_ratesMutex->Leave(); + if (nDelay < 10) nDelay = 10; + initDelay(nDelay, &rates_queue::processQueue); + return; + } + ppro->m_ratesMutex->Leave(); + + if (pendingListSize > 1) + { // we need to keep order + memmove(&pendingList[0], &pendingList[1], (pendingListSize - 1) * sizeof(rates_queue_item*)); + } + else + SAFE_FREE((void**)&pendingList); + + int bSetupTimer = --pendingListSize != 0; + + listsMutex->Leave(); + + if (ppro->icqOnline()) + { + ppro->NetLog_Server("Rates: Resuming %s.", szDescr); + item->execute(); + } + else + ppro->NetLog_Server("Rates: Discarding %s.", szDescr); + + if (bSetupTimer) + { + // in queue remained some items, setup timer + ppro->m_ratesMutex->Enter(); + int nDelay = ppro->m_rates->getDelayToLimitLevel(item->wGroup, waitLevel); + ppro->m_ratesMutex->Leave(); + + if (nDelay < 10) nDelay = 10; + initDelay(nDelay, &rates_queue::processQueue); + } + delete item; +} + + +void rates_queue::putItem(rates_queue_item *pItem, int nMinDelay) +{ + int bFound = FALSE; + + if (!ppro->icqOnline()) + return; + + ppro->NetLog_Server("Rates: Delaying %s.", szDescr); + + listsMutex->Enter(); + if (pendingListSize) + { + for (int i = 0; i < pendingListSize; i++) + { + if (pendingList[i]->isEqual(pItem)) + { + if (duplicates == -1) + { // discard existing, append new item + delete pendingList[i]; + memcpy(&pendingList[i], &pendingList[i + 1], (pendingListSize - i - 1) * sizeof(rates_queue_item*)); + bFound = TRUE; + } + else if (duplicates == 1) + { // keep existing, ignore new + listsMutex->Leave(); + return; + } + // otherwise keep existing and append new + } + } + } + if (!bFound) + { // not found, enlarge the queue + pendingListSize++; + pendingList = (rates_queue_item**)SAFE_REALLOC(pendingList, pendingListSize * sizeof(rates_queue_item*)); + } + pendingList[pendingListSize - 1] = pItem->copyItem(); + + if (pendingListSize == 1) + { // queue was empty setup timer + listsMutex->Leave(); + ppro->m_ratesMutex->Enter(); + int nDelay = ppro->m_rates->getDelayToLimitLevel(pItem->wGroup, waitLevel); + ppro->m_ratesMutex->Leave(); + + if (nDelay < 10) nDelay = 10; + if (nDelay < nMinDelay) nDelay = nMinDelay; + initDelay(nDelay, &rates_queue::processQueue); + } + else + listsMutex->Leave(); +} + + +int CIcqProto::handleRateItem(rates_queue_item *item, int nQueueType, int nMinDelay, BOOL bAllowDelay) +{ + rates_queue *pQueue = NULL; + + m_ratesMutex->Enter(); + switch (nQueueType) + { + case RQT_REQUEST: + pQueue = m_ratesQueue_Request; + break; + case RQT_RESPONSE: + pQueue = m_ratesQueue_Response; + break; + } + + if (pQueue) + { + if (bAllowDelay && (item->isOverRate(pQueue->waitLevel) || nMinDelay)) + { // limit reached or min delay configured, add to queue + pQueue->putItem(item, nMinDelay); + + m_ratesMutex->Leave(); + return 1; + } + } + m_ratesMutex->Leave(); + + item->execute(); + return 0; +} diff --git a/protocols/IcqOscarJ/src/icq_rates.h b/protocols/IcqOscarJ/src/icq_rates.h new file mode 100644 index 0000000000..ab16e74007 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_rates.h @@ -0,0 +1,146 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Rate management +// +// ----------------------------------------------------------------------------- +#ifndef __ICQ_RATES_H +#define __ICQ_RATES_H + +#define MAX_RATES_GROUP_COUNT 5 + +struct rates_group +{ + DWORD dwWindowSize; + DWORD dwClearLevel; + DWORD dwAlertLevel; + DWORD dwLimitLevel; + DWORD dwMaxLevel; + // current level + int rCurrentLevel; + int tCurrentLevel; + // links + WORD *pPairs; + int nPairs; +}; + +struct rates : public MZeroedObject +{ +private: + CIcqProto *ppro; + int nGroups; + rates_group groups[MAX_RATES_GROUP_COUNT]; + + rates_group *getGroup(WORD wGroup); +public: + rates(CIcqProto *ppro, BYTE *pBuffer, WORD wLen); + ~rates(); + + WORD getGroupFromSNAC(WORD wFamily, WORD wCommand); + WORD getGroupFromPacket(icq_packet *pPacket); + + int getLimitLevel(WORD wGroup, int nLevel); + int getDelayToLimitLevel(WORD wGroup, int nLevel); + int getNextRateLevel(WORD wGroup); + + void packetSent(icq_packet *pPacket); + void updateLevel(WORD wGroup, int nLevel); + + void initAckPacket(icq_packet *pPacket); +}; + +#define RML_CLEAR 0x01 +#define RML_ALERT 0x02 +#define RML_LIMIT 0x03 +#define RML_IDLE_10 0x10 +#define RML_IDLE_30 0x11 +#define RML_IDLE_50 0x12 +#define RML_IDLE_70 0x13 + +// Rates - Level 2 + +// queue types +#define RQT_DEFAULT 0 // standard - pushes all items without much delay +#define RQT_REQUEST 1 // request - pushes only first item on duplicity +#define RQT_RESPONSE 2 // response - pushes only last item on duplicity + +// +// generic queue item +// +struct rates_queue_item : public MZeroedObject +{ + friend struct rates_queue; +protected: + CIcqProto *ppro; + BOOL bCreated; + WORD wGroup; + + virtual BOOL isEqual(rates_queue_item *pItem); + virtual rates_queue_item* copyItem(rates_queue_item *pDest = NULL); +public: + rates_queue_item(CIcqProto *ppro, WORD wGroup); + virtual ~rates_queue_item(); + + BOOL isOverRate(int nLevel); + + virtual void execute(); + + HANDLE hContact; + DWORD dwUin; + char *szUid; +}; + +struct rates_queue; +typedef void (rates_queue::*IcqRateFunc)(void); + +// +// generic item queue (FIFO) +// +struct rates_queue : public MZeroedObject +{ +private: + CIcqProto *ppro; + const char *szDescr; + icq_critical_section *listsMutex; // we need to be thread safe + int pendingListSize; + rates_queue_item **pendingList; + int duplicates; +protected: + void cleanup(); + void processQueue(); + void initDelay(int nDelay, IcqRateFunc delaycode); +public: + rates_queue(CIcqProto *ppro, const char *szDescr, int nLimitLevel, int nWaitLevel, int nDuplicates = 0); + ~rates_queue(); + + void putItem(rates_queue_item *pItem, int nMinDelay); + + int limitLevel; // RML_* + int waitLevel; +}; + + +#endif /* __ICQ_RATES_H */ diff --git a/protocols/IcqOscarJ/src/icq_server.cpp b/protocols/IcqOscarJ/src/icq_server.cpp new file mode 100644 index 0000000000..63a5d63d84 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_server.cpp @@ -0,0 +1,444 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Manages main server connection, low-level communication +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +void icq_newConnectionReceived(HANDLE hNewConnection, DWORD dwRemoteIP, void *pExtra); + +///////////////////////////////////////////////////////////////////////////////////////// +// ICQ Server thread + +void __cdecl CIcqProto::ServerThread(serverthread_start_info *infoParam) +{ + serverthread_info info = {0}; + + info.isLoginServer = 1; + info.wAuthKeyLen = infoParam->wPassLen; + null_strcpy((char*)info.szAuthKey, infoParam->szPass, info.wAuthKeyLen); + // store server port + info.wServerPort = infoParam->nloc.wPort; + + srand(time(NULL)); + + ResetSettingsOnConnect(); + + // Connect to the login server + NetLog_Server("Authenticating to server"); + { + NETLIBOPENCONNECTION nloc = infoParam->nloc; + nloc.timeout = 6; + if (m_bGatewayMode) + nloc.flags |= NLOCF_HTTPGATEWAY; + + hServerConn = NetLib_OpenConnection(m_hServerNetlibUser, NULL, &nloc); + + SAFE_FREE((void**)&nloc.szHost); + SAFE_FREE((void**)&infoParam); + + if (hServerConn && m_bSecureConnection) + { + if (!CallService(MS_NETLIB_STARTSSL, (WPARAM)hServerConn, 0)) + { + icq_LogMessage(LOG_ERROR, LPGEN("Unable to connect to ICQ login server, SSL could not be negotiated")); + SetCurrentStatus(ID_STATUS_OFFLINE); + NetLib_CloseConnection(&hServerConn, TRUE); + return; + } + } + } + + // Login error + if (hServerConn == NULL) + { + DWORD dwError = GetLastError(); + + SetCurrentStatus(ID_STATUS_OFFLINE); + + icq_LogUsingErrorCode(LOG_ERROR, dwError, LPGEN("Unable to connect to ICQ login server")); + return; + } + + // Initialize direct connection ports + { + DWORD dwInternalIP; + BYTE bConstInternalIP = getSettingByte(NULL, "ConstRealIP", 0); + + info.hDirectBoundPort = NetLib_BindPort(icq_newConnectionReceived, this, &wListenPort, &dwInternalIP); + if (!info.hDirectBoundPort) + { + icq_LogUsingErrorCode(LOG_WARNING, GetLastError(), LPGEN("Miranda was unable to allocate a port to listen for direct peer-to-peer connections between clients. You will be able to use most of the ICQ network without problems but you may be unable to send or receive files.\n\nIf you have a firewall this may be blocking Miranda, in which case you should configure your firewall to leave some ports open and tell Miranda which ports to use in M->Options->ICQ->Network.")); + wListenPort = 0; + if (!bConstInternalIP) deleteSetting(NULL, "RealIP"); + } + else if (!bConstInternalIP) + setSettingDword(NULL, "RealIP", dwInternalIP); + } + + // Initialize rate limiting queues + { + icq_lock l(m_ratesMutex); + + m_ratesQueue_Request = new rates_queue(this, "request", RML_IDLE_30, RML_IDLE_50, 1); + m_ratesQueue_Response = new rates_queue(this, "response", RML_IDLE_10, RML_IDLE_30, -1); + } + + // This is the "infinite" loop that receives the packets from the ICQ server + { + int recvResult; + NETLIBPACKETRECVER packetRecv = {0}; + + info.hPacketRecver = (HANDLE)CallService(MS_NETLIB_CREATEPACKETRECVER, (WPARAM)hServerConn, 0x2400); + packetRecv.cbSize = sizeof(packetRecv); + packetRecv.dwTimeout = INFINITE; + while (serverThreadHandle) + { + if (info.bReinitRecver) + { // we reconnected, reinit struct + info.bReinitRecver = 0; + ZeroMemory(&packetRecv, sizeof(packetRecv)); + packetRecv.cbSize = sizeof(packetRecv); + packetRecv.dwTimeout = INFINITE; + } + + recvResult = CallService(MS_NETLIB_GETMOREPACKETS, (WPARAM)info.hPacketRecver, (LPARAM)&packetRecv); + + if (recvResult == 0) + { + NetLog_Server("Clean closure of server socket"); + break; + } + + if (recvResult == SOCKET_ERROR) + { + NetLog_Server("Abortive closure of server socket, error: %d", GetLastError()); + break; + } + + if (m_iDesiredStatus == ID_STATUS_OFFLINE) + { // Disconnect requested, send disconnect packet + icq_sendCloseConnection(); + + // disconnected upon request + m_bConnectionLost = FALSE; + SetCurrentStatus(ID_STATUS_OFFLINE); + + NetLog_Server("Logged off."); + break; + } + + // Deal with the packet + packetRecv.bytesUsed = handleServerPackets(packetRecv.buffer, packetRecv.bytesAvailable, &info); + } + serverThreadHandle = NULL; + + // Time to shutdown + NetLib_CloseConnection(&hServerConn, TRUE); + + // Close the packet receiver (connection may still be open) + NetLib_SafeCloseHandle(&info.hPacketRecver); + + // Close DC port + NetLib_SafeCloseHandle(&info.hDirectBoundPort); + } + + // disable auto info-update thread + icq_EnableUserLookup(FALSE); + + if (m_iStatus != ID_STATUS_OFFLINE && m_iDesiredStatus != ID_STATUS_OFFLINE) + { + if (!info.bLoggedIn) + icq_LogMessage(LOG_FATAL, LPGEN("Connection failed.\nLogin sequence failed for unknown reason.\nTry again later.")); + + // set flag indicating we were kicked out + m_bConnectionLost = TRUE; + + SetCurrentStatus(ID_STATUS_OFFLINE); + } + + // signal keep-alive thread to stop + StopKeepAlive(&info); + + // Close all open DC connections + CloseContactDirectConns(NULL); + + // Close avatar connection if any + StopAvatarThread(); + + // Offline all contacts + HANDLE hContact = FindFirstContact(); + while (hContact) + { + DWORD dwUIN; + uid_str szUID; + + if (!getContactUid(hContact, &dwUIN, &szUID)) + { + if (getContactStatus(hContact) != ID_STATUS_OFFLINE) + { + char tmp = 0; + + setSettingWord(hContact, "Status", ID_STATUS_OFFLINE); + + handleXStatusCaps(dwUIN, szUID, hContact, (BYTE*)&tmp, 0, &tmp, 0); + } + } + + hContact = FindNextContact(hContact); + } + + setSettingDword(NULL, "LogonTS", 0); // clear logon time + + servlistPendingFlushOperations(); // clear pending operations list + + // release rates queues + { + icq_lock l(m_ratesMutex); + + SAFE_DELETE((MZeroedObject**)&m_ratesQueue_Request); + SAFE_DELETE((MZeroedObject**)&m_ratesQueue_Response); + SAFE_DELETE((MZeroedObject**)&m_rates); + } + + FlushServerIDs(); // clear server IDs list + + NetLog_Server("%s thread ended.", "Server"); +} + + +void CIcqProto::icq_serverDisconnect(BOOL bBlock) +{ + if ( !hServerConn) + return; + + NetLog_Server("Server shutdown requested"); + Netlib_Shutdown(hServerConn); + + if (serverThreadHandle) { + // Not called from network thread? + if (bBlock && GetCurrentThreadId() != serverThreadId) + while (ICQWaitForSingleObject(serverThreadHandle, INFINITE) != WAIT_OBJECT_0); + + CloseHandle(serverThreadHandle); + serverThreadHandle = NULL; + } +} + + +int CIcqProto::handleServerPackets(BYTE *buf, int len, serverthread_info *info) +{ + BYTE channel; + WORD sequence; + WORD datalen; + int bytesUsed = 0; + + while (len > 0) + { + if (info->bReinitRecver) + break; + + // All FLAPS begin with 0x2a + if (*buf++ != FLAP_MARKER) + break; + + if (len < 6) + break; + + unpackByte(&buf, &channel); + unpackWord(&buf, &sequence); + unpackWord(&buf, &datalen); + + if (len < 6 + datalen) + break; + + +#ifdef _DEBUG + NetLog_Server("Server FLAP: Channel %u, Seq %u, Length %u bytes", channel, sequence, datalen); +#endif + + switch (channel) { + case ICQ_LOGIN_CHAN: + handleLoginChannel(buf, datalen, info); + break; + + case ICQ_DATA_CHAN: + handleDataChannel(buf, datalen, info); + break; + + case ICQ_ERROR_CHAN: + handleErrorChannel(buf, datalen); + break; + + case ICQ_CLOSE_CHAN: + handleCloseChannel(buf, datalen, info); + break; // we need this for walking thru proxy + + case ICQ_PING_CHAN: + handlePingChannel(buf, datalen); + break; + + default: + NetLog_Server("Warning: Unhandled Server FLAP Channel: Channel %u, Seq %u, Length %u bytes", channel, sequence, datalen); + break; + } + + /* Increase pointers so we can check for more FLAPs */ + buf += datalen; + len -= (datalen + 6); + bytesUsed += (datalen + 6); + } + + return bytesUsed; +} + + +void CIcqProto::sendServPacket(icq_packet *pPacket) +{ + // make sure to have the connection handle + connectionHandleMutex->Enter(); + + if (hServerConn) + { + int nSendResult; + + // This critsec makes sure that the sequence order doesn't get screwed up + localSeqMutex->Enter(); + + // :IMPORTANT: + // The FLAP sequence must be a WORD. When it reaches 0xFFFF it should wrap to + // 0x0000, otherwise we'll get kicked by server. + wLocalSequence++; + + // Pack sequence number + pPacket->pData[2] = ((wLocalSequence & 0xff00) >> 8); + pPacket->pData[3] = (wLocalSequence & 0x00ff); + + nSendResult = Netlib_Send(hServerConn, (const char *)pPacket->pData, pPacket->wLen, 0); + + localSeqMutex->Leave(); + connectionHandleMutex->Leave(); + + // Send error + if (nSendResult == SOCKET_ERROR) + { + icq_LogUsingErrorCode(LOG_ERROR, GetLastError(), LPGEN("Your connection with the ICQ server was abortively closed")); + icq_serverDisconnect(FALSE); + + if (m_iStatus != ID_STATUS_OFFLINE) + { + SetCurrentStatus(ID_STATUS_OFFLINE); + } + } + else + { // Rates management + icq_lock l(m_ratesMutex); + m_rates->packetSent(pPacket); + } + + } + else + { + connectionHandleMutex->Leave(); + NetLog_Server("Error: Failed to send packet (no connection)"); + } + + SAFE_FREE((void**)&pPacket->pData); +} + + +void __cdecl CIcqProto::SendPacketAsyncThread(icq_packet* pkt) +{ + sendServPacket( pkt ); + SAFE_FREE((void**)&pkt); +} + + +void CIcqProto::sendServPacketAsync(icq_packet *packet) +{ + icq_packet *pPacket; + + pPacket = (icq_packet*)SAFE_MALLOC(sizeof(icq_packet)); // This will be freed in the new thread + memcpy(pPacket, packet, sizeof(icq_packet)); + + ForkThread(( IcqThreadFunc )&CIcqProto::SendPacketAsyncThread, pPacket); +} + + +int CIcqProto::IsServerOverRate(WORD wFamily, WORD wCommand, int nLevel) +{ + icq_lock l(m_ratesMutex); + + if (m_rates) + { + WORD wGroup = m_rates->getGroupFromSNAC(wFamily, wCommand); + + // check if the rate is not over specified level + if (m_rates->getNextRateLevel(wGroup) < m_rates->getLimitLevel(wGroup, nLevel)) + return TRUE; + } + + return FALSE; +} + + +///////////////////////////////////////////////////////////////////////////////////////// +// ICQ Server thread + +void CIcqProto::icq_login(const char* szPassword) +{ + DWORD dwUin = getContactUin(NULL); + serverthread_start_info* stsi = (serverthread_start_info*)SAFE_MALLOC(sizeof(serverthread_start_info)); + + // Server host name + char szServer[MAX_PATH]; + if (getSettingStringStatic(NULL, "OscarServer", szServer, MAX_PATH)) + stsi->nloc.szHost = null_strdup(m_bSecureConnection ? DEFAULT_SERVER_HOST_SSL : DEFAULT_SERVER_HOST); + else + stsi->nloc.szHost = null_strdup(szServer); + + // Server port + stsi->nloc.wPort = getSettingWord(NULL, "OscarPort", m_bSecureConnection ? DEFAULT_SERVER_PORT_SSL : DEFAULT_SERVER_PORT); + if (stsi->nloc.wPort == 0) + stsi->nloc.wPort = RandRange(1024, 65535); + + // User password + stsi->wPassLen = strlennull(szPassword); + if (stsi->wPassLen > 8) stsi->wPassLen = 8; + null_strcpy(stsi->szPass, szPassword, stsi->wPassLen); + + // Randomize sequence + wLocalSequence = generate_flap_sequence(); + + m_dwLocalUIN = dwUin; + + // Initialize members + m_avatarsConnectionPending = TRUE; + + serverThreadHandle = ForkThreadEx(( IcqThreadFunc )&CIcqProto::ServerThread, stsi, &serverThreadId); +} diff --git a/protocols/IcqOscarJ/src/icq_server.h b/protocols/IcqOscarJ/src/icq_server.h new file mode 100644 index 0000000000..eba3ecfa42 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_server.h @@ -0,0 +1,71 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2009 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Declarations for server thread +// +// ----------------------------------------------------------------------------- +#ifndef __ICQ_SERVER_H +#define __ICQ_SERVER_H + +struct serverthread_start_info +{ + NETLIBOPENCONNECTION nloc; + WORD wPassLen; + char szPass[128]; +}; + +struct serverthread_info +{ + struct CIcqProto *ppro; + int bLoggedIn; + int isLoginServer; + BYTE szAuthKey[20]; + WORD wAuthKeyLen; + WORD wServerPort; + char *newServer; + BYTE *cookieData; + int cookieDataLen; + int newServerSSL; + int newServerReady; + int isMigrating; + HANDLE hPacketRecver; + int bReinitRecver; + int bMyAvatarInited; +// + HANDLE hDirectBoundPort; +// + HANDLE hKeepAliveEvent; +}; + +/*---------* Functions *---------------*/ + +void icq_serverDisconnect(BOOL bBlock); +void icq_login(const char *szPassword); + +int IsServerOverRate(WORD wFamily, WORD wCommand, int nLevel); + + +#endif /* __ICQ_SERVER_H */ diff --git a/protocols/IcqOscarJ/src/icq_servlist.cpp b/protocols/IcqOscarJ/src/icq_servlist.cpp new file mode 100644 index 0000000000..b2adc4831c --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_servlist.cpp @@ -0,0 +1,2829 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Functions that handles list of used server IDs, sends low-level packets for SSI information +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +// SERVER-LIST UPDATE BOARD +// + +void CIcqProto::servlistBeginOperation(int operationCount, int bImport) +{ + if (operationCount) + { // check if we should send operation begin packet + if (!servlistEditCount) + icq_sendServerBeginOperation(bImport); + // update count of active operations + servlistEditCount += operationCount; +#ifdef _DEBUG + NetLog_Server("Server-List: Begin operation processed (%d operations active)", servlistEditCount); +#endif + } +} + +void CIcqProto::servlistEndOperation(int operationCount) +{ + if (operationCount) + { + if (operationCount > servlistEditCount) + { // sanity check + NetLog_Server("Error: Server-List End operation is not paired!"); + operationCount = servlistEditCount; + } + // update count of active operations + servlistEditCount -= operationCount; + // check if we should send operation end packet + if (!servlistEditCount) + icq_sendServerEndOperation(); +#ifdef _DEBUG + NetLog_Server("Server-List: End operation processed (%d operations active)", servlistEditCount); +#endif + } +} + +void __cdecl CIcqProto::servlistQueueThread(void *param) +{ + int* queueState = ( int* )param; + +#ifdef _DEBUG + NetLog_Server("Server-List: Starting Update board."); +#endif + + SleepEx(50, FALSE); + // handle server-list requests queue + servlistQueueMutex->Enter(); + while (servlistQueueCount) + { + ssiqueueditems* pItem = NULL; + int bItemDouble; + WORD wItemAction; + icq_packet groupPacket = {0}; + icq_packet groupPacket2 = {0}; + cookie_servlist_action* pGroupCookie = NULL; + int nEndOperations; + + // first check if the state is calm + while (*queueState) + { + int i; + time_t tNow = time(NULL); + int bFound = FALSE; + + for (i = 0; i < servlistQueueCount; i++) + { // check if we do not have some expired items to handle, otherwise keep sleeping + if ((servlistQueueList[i]->tAdded + servlistQueueList[i]->dwTimeout) < tNow) + { // got expired item, stop sleep even when changes goes on + bFound = TRUE; + break; + } + } + if (bFound) break; + // reset queue state, keep sleeping + *queueState = FALSE; + servlistQueueMutex->Leave(); + SleepEx(100, TRUE); + servlistQueueMutex->Enter(); + } + if (!icqOnline()) + { // do not try to send packets if offline + servlistQueueMutex->Leave(); + SleepEx(100, TRUE); + servlistQueueMutex->Enter(); + continue; + } +#ifdef _DEBUG + NetLog_Server("Server-List: %d items in queue.", servlistQueueCount); +#endif + // take the oldest item (keep the board FIFO) + pItem = servlistQueueList[0]; // take first (queue contains at least one item here) + wItemAction = (WORD)(pItem->pItems[0]->dwOperation & SSOF_ACTIONMASK); + bItemDouble = pItem->pItems[0]->dwOperation & SSOG_DOUBLE; + // check item rate - too high -> sleep + m_ratesMutex->Enter(); + { + WORD wRateGroup = m_rates->getGroupFromSNAC(ICQ_LISTS_FAMILY, wItemAction); + int nRateLevel = bItemDouble ? RML_IDLE_30 : RML_IDLE_10; + + while (m_rates->getNextRateLevel(wRateGroup) < m_rates->getLimitLevel(wRateGroup, nRateLevel)) + { // the rate is higher, keep sleeping + int nDelay = m_rates->getDelayToLimitLevel(wRateGroup, nRateLevel); + + m_ratesMutex->Leave(); + // do not keep the queue frozen + servlistQueueMutex->Leave(); + if (nDelay < 10) nDelay = 10; +#ifdef _DEBUG + NetLog_Server("Server-List: Delaying %dms [Rates]", nDelay); +#endif + SleepEx(nDelay, FALSE); + // check if the rate is now ok + servlistQueueMutex->Enter(); + m_ratesMutex->Enter(); + } + } + m_ratesMutex->Leave(); + { // setup group packet(s) & cookie + int totalSize = 0; + int i; + cookie_servlist_action *pGroupCookie; + DWORD dwGroupCookie; + // determine the total size of the packet + for(i = 0; i < pItem->nItems; i++) + totalSize += pItem->pItems[i]->packet.wLen - 0x10; + + // process begin & end operation flags + { + int bImportOperation = FALSE; + int nBeginOperations = 0; + + nEndOperations = 0; + for(i = 0; i < pItem->nItems; i++) + { // collect begin & end operation flags + if (pItem->pItems[i]->dwOperation & SSOF_BEGIN_OPERATION) + nBeginOperations++; + if (pItem->pItems[i]->dwOperation & SSOF_END_OPERATION) + nEndOperations++; + // check if the operation is import + if (pItem->pItems[i]->dwOperation & SSOF_IMPORT_OPERATION) + bImportOperation = TRUE; + } + // really begin operation if requested + if (nBeginOperations) + servlistBeginOperation(nBeginOperations, bImportOperation); + } + + if (pItem->nItems > 1) + { // pack all packet's data, create group cookie + pGroupCookie = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + pGroupCookie->dwAction = SSA_ACTION_GROUP; + pGroupCookie->dwGroupCount = pItem->nItems; + pGroupCookie->pGroupItems = (cookie_servlist_action**)SAFE_MALLOC(pItem->nItems * sizeof(cookie_servlist_action*)); + for (i = 0; i < pItem->nItems; i++) + { // build group cookie data - assign cookies datas + pGroupCookie->pGroupItems[i] = pItem->pItems[i]->cookie; + // release the separate cookie id + FreeCookieByData(CKT_SERVERLIST, pItem->pItems[i]->cookie); + } + // allocate cookie id + dwGroupCookie = AllocateCookie(CKT_SERVERLIST, wItemAction, 0, pGroupCookie); + // prepare packet data + serverPacketInit(&groupPacket, (WORD)(totalSize + 0x0A)); // FLAP size added inside + packFNACHeader(&groupPacket, ICQ_LISTS_FAMILY, wItemAction, 0, dwGroupCookie); + for (i = 0; i < pItem->nItems; i++) + packBuffer(&groupPacket, pItem->pItems[i]->packet.pData + 0x10, (WORD)(pItem->pItems[i]->packet.wLen - 0x10)); + + if (bItemDouble) + { // prepare second packet + wItemAction = ((servlistgroupitemdouble*)(pItem->pItems[0]))->wAction2; + totalSize = 0; + // determine the total size of the packet + for(i = 0; i < pItem->nItems; i++) + totalSize += ((servlistgroupitemdouble*)(pItem->pItems[i]))->packet2.wLen - 0x10; + + pGroupCookie = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + pGroupCookie->dwAction = SSA_ACTION_GROUP; + pGroupCookie->dwGroupCount = pItem->nItems; + pGroupCookie->pGroupItems = (cookie_servlist_action**)SAFE_MALLOC(pItem->nItems * sizeof(cookie_servlist_action*)); + for (i = 0; i < pItem->nItems; i++) + pGroupCookie->pGroupItems[i] = pItem->pItems[i]->cookie; + // allocate cookie id + dwGroupCookie = AllocateCookie(CKT_SERVERLIST, wItemAction, 0, pGroupCookie); + // prepare packet data + serverPacketInit(&groupPacket2, (WORD)(totalSize + 0x0A)); // FLAP size added inside + packFNACHeader(&groupPacket2, ICQ_LISTS_FAMILY, wItemAction, 0, dwGroupCookie); + for (i = 0; i < pItem->nItems; i++) + packBuffer(&groupPacket2, ((servlistgroupitemdouble*)(pItem->pItems[i]))->packet2.pData + 0x10, (WORD)(((servlistgroupitemdouble*)(pItem->pItems[i]))->packet2.wLen - 0x10)); + } + } + else + { // just send the one packet, do not create action group + pGroupCookie = pItem->pItems[0]->cookie; + memcpy(&groupPacket, &pItem->pItems[0]->packet, sizeof(icq_packet)); + if (bItemDouble) + memcpy(&groupPacket2, &((servlistgroupitemdouble*)(pItem->pItems[0]))->packet2, sizeof(icq_packet)); + } + + { // remove grouped item from queue & release grouped item + servlistQueueCount--; + servlistQueueList[0] = servlistQueueList[servlistQueueCount]; + + for (i = 0; i < pItem->nItems; i++) + { // release memory + if (pItem->nItems > 1) + { // free the packet only if we created the group packet + SAFE_FREE((void**)&pItem->pItems[i]->packet.pData); + if (pItem->pItems[i]->dwOperation & SSOG_DOUBLE) + SAFE_FREE((void**)&((servlistgroupitemdouble*)(pItem->pItems[i]))->packet2.pData); + } + SAFE_FREE((void**)&pItem->pItems[i]); + break; + } + SAFE_FREE((void**)&pItem); + // resize the queue + if (servlistQueueSize > servlistQueueCount + 6) + { + servlistQueueSize -= 4; + servlistQueueList = (ssiqueueditems**)SAFE_REALLOC(servlistQueueList, servlistQueueSize * sizeof(ssiqueueditems*)); + } + } + } + servlistQueueMutex->Leave(); + // send group packet + sendServPacket(&groupPacket); + // send second group packet (if present) + if (bItemDouble) + sendServPacket(&groupPacket2); + // process end operation marks + if (nEndOperations) + servlistEndOperation(nEndOperations); + // loose the loop a bit + SleepEx(100, TRUE); + servlistQueueMutex->Enter(); + } + // clean-up thread + CloseHandle(servlistQueueThreadHandle); + servlistQueueThreadHandle = NULL; + servlistQueueMutex->Leave(); +#ifdef _DEBUG + NetLog_Server("Server-List: Update Board ending."); +#endif +} + +void CIcqProto::servlistQueueAddGroupItem(servlistgroupitem* pGroupItem, int dwTimeout) +{ + icq_lock l(servlistQueueMutex); + + { // add the packet to queue + DWORD dwMark = pGroupItem->dwOperation & SSOF_GROUPINGMASK; + ssiqueueditems* pItem = NULL; + + // try to find compatible item + for (int i = 0; i < servlistQueueCount; i++) + { + if ((servlistQueueList[i]->pItems[0]->dwOperation & SSOF_GROUPINGMASK) == dwMark && servlistQueueList[i]->nItems < MAX_SERVLIST_PACKET_ITEMS) + { // found compatible item, check if it does not contain operation for the same server-list item + pItem = servlistQueueList[i]; + + for (int j = 0; j < pItem->nItems; j++) + if (pItem->pItems[j]->cookie->wContactId == pGroupItem->cookie->wContactId && + pItem->pItems[j]->cookie->wGroupId == pGroupItem->cookie->wGroupId) + { + pItem = NULL; + break; + } + // cannot send two operations for the same server-list record in one packet, look for another + if (!pItem) continue; + +#ifdef _DEBUG + NetLog_Server("Server-List: Adding packet to item #%d with operation %x.", i, servlistQueueList[i]->pItems[0]->dwOperation); +#endif + break; + } + } + if (!pItem) + { // compatible item was not found, create new one, add to queue + pItem = (ssiqueueditems*)SAFE_MALLOC(sizeof(ssiqueueditems)); + pItem->tAdded = time(NULL); + pItem->dwTimeout = dwTimeout; + + if (servlistQueueCount == servlistQueueSize) + { // resize the queue - it is too small + servlistQueueSize += 4; + servlistQueueList = (ssiqueueditems**)SAFE_REALLOC(servlistQueueList, servlistQueueSize * sizeof(ssiqueueditems*)); + } + // really add to queue + servlistQueueList[servlistQueueCount++] = pItem; +#ifdef _DEBUG + NetLog_Server("Server-List: Adding new item to queue."); +#endif + } + else if (pItem->dwTimeout > dwTimeout) + { // if the timeout of currently added packet is shorter, update the previous one + pItem->dwTimeout = dwTimeout; + } + // add GroupItem to queueditems (pItem) + pItem->pItems[pItem->nItems++] = pGroupItem; + } + // wake up board thread (keep sleeping or start new one) + if (!servlistQueueThreadHandle) + { + // create new board thread + servlistQueueThreadHandle = ForkThreadEx( &CIcqProto::servlistQueueThread, &servlistQueueState ); + } + else // signal thread, that queue was changed during sleep + servlistQueueState = TRUE; +} + +int CIcqProto::servlistHandlePrimitives(DWORD dwOperation) +{ + if (dwOperation & SSO_BEGIN_OPERATION) + { // operation starting, no action ready yet + servlistBeginOperation(1, dwOperation & SSOF_IMPORT_OPERATION); + return TRUE; + } + else if (dwOperation & SSO_END_OPERATION) + { // operation ending without action + servlistEndOperation(1); + return TRUE; + } + + return FALSE; +} + +void CIcqProto::servlistPostPacket(icq_packet* packet, DWORD dwCookie, DWORD dwOperation, DWORD dwTimeout) +{ + cookie_servlist_action* pCookie; + + if (servlistHandlePrimitives(dwOperation)) + return; + + if (!FindCookie(dwCookie, NULL, (void**)&pCookie)) + return; // invalid cookie + + if (dwOperation & SSOF_SEND_DIRECTLY) + { // send directly - this is for some special cases + // begin operation if requested + if (dwOperation & SSOF_BEGIN_OPERATION) + servlistBeginOperation(1, dwOperation & SSOF_IMPORT_OPERATION); + + // send the packet + sendServPacket(packet); + + // end operation if requested + if (dwOperation & SSOF_END_OPERATION) + servlistEndOperation(1); + } + else + { // add to server-list update board + servlistgroupitem* pGroupItem; + + // prepare group item + pGroupItem = (servlistgroupitem*)SAFE_MALLOC(sizeof(servlistgroupitem)); + pGroupItem->dwOperation = dwOperation; + pGroupItem->cookie = pCookie; + // packet data are alloced, keep them until they are sent + memcpy(&pGroupItem->packet, packet, sizeof(icq_packet)); + + servlistQueueAddGroupItem(pGroupItem, dwTimeout); + } +} + +void CIcqProto::servlistPostPacketDouble(icq_packet* packet1, DWORD dwCookie, DWORD dwOperation, DWORD dwTimeout, icq_packet* packet2, WORD wAction2) +{ + cookie_servlist_action* pCookie; + + if (servlistHandlePrimitives(dwOperation)) + return; + + if (!FindCookie(dwCookie, NULL, (void**)&pCookie)) + return; // invalid cookie + + if (dwOperation & SSOF_SEND_DIRECTLY) + { // send directly - this is for some special cases + // begin operation if requested + if (dwOperation & SSOF_BEGIN_OPERATION) + servlistBeginOperation(1, dwOperation & SSOF_IMPORT_OPERATION); + + // send the packets + sendServPacket(packet1); + sendServPacket(packet2); + + // end operation if requested + if (dwOperation & SSOF_END_OPERATION) + servlistEndOperation(1); + } + else + { // add to server-list update board + servlistgroupitemdouble* pGroupItem; + + // prepare group item + pGroupItem = (servlistgroupitemdouble*)SAFE_MALLOC(sizeof(servlistgroupitemdouble)); + pGroupItem->dwOperation = dwOperation; + pGroupItem->cookie = pCookie; + pGroupItem->wAction2 = wAction2; + // packets data are alloced, keep them until they are sent + memcpy(&pGroupItem->packet, packet1, sizeof(icq_packet)); + memcpy(&pGroupItem->packet2, packet2, sizeof(icq_packet)); + + servlistQueueAddGroupItem((servlistgroupitem*)pGroupItem, dwTimeout); + } +} + +void CIcqProto::servlistProcessLogin() +{ + // reset edit state counter + servlistEditCount = 0; + + /// TODO: preserve queue state in DB! restore here! + + // if the server-list queue contains items and thread is not running, start it + if (servlistQueueCount && !servlistQueueThreadHandle) + servlistQueueThreadHandle = ForkThreadEx( &CIcqProto::servlistQueueThread, &servlistQueueState ); +} + +// HERE ENDS SERVER-LIST UPDATE BOARD IMPLEMENTATION // +/////////////////////////////////////////////////////// +//===================================================// + +// PENDING SERVER-LIST OPERATIONS +// +#define ITEM_PENDING_CONTACT 0x01 +#define ITEM_PENDING_GROUP 0x02 + +#define CALLBACK_RESULT_CONTINUE 0x00 +#define CALLBACK_RESULT_POSTPONE 0x0D +#define CALLBACK_RESULT_PURGE 0x10 + + +#define SPOF_AUTO_CREATE_ITEM 0x01 + +int CIcqProto::servlistPendingFindItem(int nType, HANDLE hContact, const char *pszGroup) +{ + if (servlistPendingList) + for (int i = 0; i < servlistPendingCount; i++) + if (servlistPendingList[i]->nType == nType) + { + if (((nType == ITEM_PENDING_CONTACT) && (servlistPendingList[i]->hContact == hContact)) || + ((nType == ITEM_PENDING_GROUP) && (!strcmpnull(servlistPendingList[i]->szGroup, pszGroup)))) + return i; + } + return -1; +} + + +void CIcqProto::servlistPendingAddItem(servlistpendingitem *pItem) +{ + if (servlistPendingCount >= servlistPendingSize) // add new + { + servlistPendingSize += 10; + servlistPendingList = (servlistpendingitem**)SAFE_REALLOC(servlistPendingList, servlistPendingSize * sizeof(servlistpendingitem*)); + } + + servlistPendingList[servlistPendingCount++] = pItem; +} + + +servlistpendingitem* CIcqProto::servlistPendingRemoveItem(int nType, HANDLE hContact, const char *pszGroup) +{ // unregister pending item, trigger pending operations + int iItem; + servlistpendingitem *pItem = NULL; + + icq_lock l(servlistMutex); + + if ((iItem = servlistPendingFindItem(nType, hContact, pszGroup)) != -1) + { // found, remove from the pending list + pItem = servlistPendingList[iItem]; + + servlistPendingList[iItem] = servlistPendingList[--servlistPendingCount]; + + if (servlistPendingCount + 10 < servlistPendingSize) + { + servlistPendingSize -= 5; + servlistPendingList = (servlistpendingitem**)SAFE_REALLOC(servlistPendingList, servlistPendingSize * sizeof(servlistpendingitem*)); + } + // was the first operation was created automatically to postpone ItemAdd? + if (pItem->operations && pItem->operations[0].flags & SPOF_AUTO_CREATE_ITEM) + { // yes, add new item + servlistpendingitem *pNewItem = (servlistpendingitem*)SAFE_MALLOC(sizeof(servlistpendingitem)); + + if (pNewItem) + { // move the remaining operations +#ifdef _DEBUG + if (pItem->nType == ITEM_PENDING_CONTACT) + NetLog_Server("Server-List: Resuming contact %x operation.", pItem->hContact); + else + NetLog_Server("Server-List: Resuming group \"%s\" operation.", pItem->szGroup); +#endif + + pNewItem->nType = pItem->nType; + pNewItem->hContact = pItem->hContact; + pNewItem->szGroup = null_strdup(pItem->szGroup); + pNewItem->wContactID = pItem->wContactID; + pNewItem->wGroupID = pItem->wGroupID; + pNewItem->operationsCount = pItem->operationsCount - 1; + pNewItem->operations = (servlistpendingoperation*)SAFE_MALLOC(pNewItem->operationsCount * sizeof(servlistpendingoperation)); + memcpy(pNewItem->operations, pItem->operations + 1, pNewItem->operationsCount * sizeof(servlistpendingoperation)); + pItem->operationsCount = 1; + + servlistPendingAddItem(pNewItem); + // clear the flag + pItem->operations[0].flags &= ~SPOF_AUTO_CREATE_ITEM; + } + } + } +#ifdef _DEBUG + else + NetLog_Server("Server-List Error: Trying to remove non-existing pending %s.", nType == ITEM_PENDING_CONTACT ? "contact" : "group"); +#endif + + return pItem; +} + + +void CIcqProto::servlistPendingAddContactOperation(HANDLE hContact, LPARAM param, PENDING_CONTACT_CALLBACK callback, DWORD flags) +{ // add postponed operation (add contact, update contact, regroup resume, etc.) + // - after contact is added + int iItem; + servlistpendingitem *pItem = NULL; + + icq_lock l(servlistMutex); + + if ((iItem = servlistPendingFindItem(ITEM_PENDING_CONTACT, hContact, NULL)) != -1) + pItem = servlistPendingList[iItem]; + + if (pItem) + { + int iOperation = pItem->operationsCount++; + + pItem->operations = (servlistpendingoperation*)SAFE_REALLOC(pItem->operations, pItem->operationsCount * sizeof(servlistpendingoperation)); + pItem->operations[iOperation].param = param; + pItem->operations[iOperation].callback = (PENDING_GROUP_CALLBACK)callback; + pItem->operations[iOperation].flags = flags; + } + else + { + NetLog_Server("Server-List Error: Trying to add pending operation to a non existing contact."); + } +} + + +void CIcqProto::servlistPendingAddGroupOperation(const char *pszGroup, LPARAM param, PENDING_GROUP_CALLBACK callback, DWORD flags) +{ // add postponed operation - after group is added + int iItem; + servlistpendingitem *pItem = NULL; + + icq_lock l(servlistMutex); + + if ((iItem = servlistPendingFindItem(ITEM_PENDING_GROUP, NULL, pszGroup)) != -1) + pItem = servlistPendingList[iItem]; + + if (pItem) + { + int iOperation = pItem->operationsCount++; + + pItem->operations = (servlistpendingoperation*)SAFE_REALLOC(pItem->operations, pItem->operationsCount * sizeof(servlistpendingoperation)); + pItem->operations[iOperation].param = param; + pItem->operations[iOperation].callback = callback; + pItem->operations[iOperation].flags = flags; + } + else + { + NetLog_Server("Server-List Error: Trying to add pending operation to a non existing group."); + } +} + + +int CIcqProto::servlistPendingAddContact(HANDLE hContact, WORD wContactID, WORD wGroupID, LPARAM param, PENDING_CONTACT_CALLBACK callback, int bDoInline, LPARAM operationParam, PENDING_CONTACT_CALLBACK operationCallback) +{ + int iItem; + servlistpendingitem *pItem = NULL; + + servlistMutex->Enter(); + + if ((iItem = servlistPendingFindItem(ITEM_PENDING_CONTACT, hContact, NULL)) != -1) + pItem = servlistPendingList[iItem]; + + if (pItem) + { +#ifdef _DEBUG + NetLog_Server("Server-List: Pending contact %x already in list; adding as operation.", hContact); +#endif + servlistPendingAddContactOperation(hContact, param, callback, SPOF_AUTO_CREATE_ITEM); + + if (operationCallback) + servlistPendingAddContactOperation(hContact, operationParam, operationCallback, 0); + + servlistMutex->Leave(); + + return 0; // Pending + } + +#ifdef _DEBUG + NetLog_Server("Server-List: Starting contact %x operation.", hContact); +#endif + + pItem = (servlistpendingitem *)SAFE_MALLOC(sizeof(servlistpendingitem)); + pItem->nType = ITEM_PENDING_CONTACT; + pItem->hContact = hContact; + pItem->wContactID = wContactID; + pItem->wGroupID = wGroupID; + + servlistPendingAddItem(pItem); + + if (operationCallback) + servlistPendingAddContactOperation(hContact, operationParam, operationCallback, 0); + + servlistMutex->Leave(); + + if (bDoInline) + { // not postponed, called directly if requested + (this->*callback)(hContact, wContactID, wGroupID, param, PENDING_RESULT_INLINE); + } + + return 1; // Ready +} + + +int CIcqProto::servlistPendingAddGroup(const char *pszGroup, WORD wGroupID, LPARAM param, PENDING_GROUP_CALLBACK callback, int bDoInline, LPARAM operationParam, PENDING_GROUP_CALLBACK operationCallback) +{ + int iItem; + servlistpendingitem *pItem = NULL; + + servlistMutex->Enter(); + + if ((iItem = servlistPendingFindItem(ITEM_PENDING_GROUP, NULL, pszGroup)) != -1) + pItem = servlistPendingList[iItem]; + + if (pItem) + { +#ifdef _DEBUG + NetLog_Server("Server-List: Pending group \"%s\" already in list; adding as operation.", pszGroup); +#endif + servlistPendingAddGroupOperation(pszGroup, param, callback, SPOF_AUTO_CREATE_ITEM); + + if (operationCallback) + servlistPendingAddGroupOperation(pszGroup, operationParam, operationCallback, 0); + + servlistMutex->Leave(); + + return 0; // Pending + } + +#ifdef _DEBUG + NetLog_Server("Server-List: Starting group \"%s\" operation.", pszGroup); +#endif + + pItem = (servlistpendingitem *)SAFE_MALLOC(sizeof(servlistpendingitem)); + pItem->nType = ITEM_PENDING_GROUP; + pItem->szGroup = null_strdup(pszGroup); + pItem->wGroupID = wGroupID; + + servlistPendingAddItem(pItem); + + if (operationCallback) + servlistPendingAddGroupOperation(pszGroup, operationParam, operationCallback, 0); + + servlistMutex->Leave(); + + if (bDoInline) + { // not postponed, called directly if requested + (this->*callback)(pszGroup, wGroupID, param, PENDING_RESULT_INLINE); + } + + return 1; // Ready +} + + +void CIcqProto::servlistPendingRemoveContact(HANDLE hContact, WORD wContactID, WORD wGroupID, int nResult) +{ +#ifdef _DEBUG + NetLog_Server("Server-List: %s contact %x operation.", (nResult != PENDING_RESULT_PURGE) ? "Ending" : "Purging", hContact); +#endif + + servlistpendingitem *pItem = servlistPendingRemoveItem(ITEM_PENDING_CONTACT, hContact, NULL); + + if (pItem) + { // process pending operations + if (pItem->operations) + { + for (int i = 0; i < pItem->operationsCount; i++) + { + int nCallbackState = (this->*(PENDING_CONTACT_CALLBACK)(pItem->operations[i].callback))(hContact, wContactID, wGroupID, pItem->operations[i].param, nResult); + + if (nResult != PENDING_RESULT_PURGE && nCallbackState == CALLBACK_RESULT_POSTPONE) + { // any following pending operations cannot be processed now, move them to the new pending contact + for (int j = i + 1; j < pItem->operationsCount; j++) + servlistPendingAddContactOperation(hContact, pItem->operations[j].param, (PENDING_CONTACT_CALLBACK)(pItem->operations[j].callback), pItem->operations[j].flags); + break; + } + else if (nCallbackState == CALLBACK_RESULT_PURGE) + { // purge all following operations - fatal failure occured + nResult = PENDING_RESULT_PURGE; + } + } + SAFE_FREE((void**)&pItem->operations); + } + // release item's memory + SAFE_FREE((void**)&pItem); + } + else + NetLog_Server("Server-List Error: Trying to remove a non existing pending contact."); +} + + +void CIcqProto::servlistPendingRemoveGroup(const char *pszGroup, WORD wGroupID, int nResult) +{ +#ifdef _DEBUG + NetLog_Server("Server-List: %s group \"%s\" operation.", (nResult != PENDING_RESULT_PURGE) ? "Ending" : "Purging", pszGroup); +#endif + + servlistpendingitem *pItem = servlistPendingRemoveItem(ITEM_PENDING_GROUP, NULL, pszGroup); + + if (pItem) + { // process pending operations + if (pItem->operations) + { + for (int i = 0; i < pItem->operationsCount; i++) + { + int nCallbackState = (this->*pItem->operations[i].callback)(pItem->szGroup, wGroupID, pItem->operations[i].param, nResult); + + if (nResult != PENDING_RESULT_PURGE && nCallbackState == CALLBACK_RESULT_POSTPONE) + { // any following pending operations cannot be processed now, move them to the new pending group + for (int j = i + 1; j < pItem->operationsCount; j++) + servlistPendingAddGroupOperation(pItem->szGroup, pItem->operations[j].param, pItem->operations[j].callback, pItem->operations[j].flags); + break; + } + else if (nCallbackState == CALLBACK_RESULT_PURGE) + { // purge all following operations - fatal failure occured + nResult = PENDING_RESULT_PURGE; + } + } + SAFE_FREE((void**)&pItem->operations); + } + // release item's memory + SAFE_FREE((void**)&pItem->szGroup); + SAFE_FREE((void**)&pItem); + } + else + NetLog_Server("Server-List Error: Trying to remove a non existing pending group."); +} + + +// Remove All pending operations +void CIcqProto::servlistPendingFlushOperations() +{ + icq_lock l(servlistMutex); + + for (int i = servlistPendingCount; i; i--) + { // purge all items + servlistpendingitem *pItem = servlistPendingList[i - 1]; + + if (pItem->nType == ITEM_PENDING_CONTACT) + servlistPendingRemoveContact(pItem->hContact, 0, 0, PENDING_RESULT_PURGE); + else if (pItem->nType == ITEM_PENDING_GROUP) + servlistPendingRemoveGroup(pItem->szGroup, 0, PENDING_RESULT_PURGE); + } + // release the list completely + SAFE_FREE((void**)&servlistPendingList); + servlistPendingCount = 0; + servlistPendingSize = 0; +} + +// END OF SERVER-LIST PENDING OPERATIONS +//// + + +// used for adding new contacts to list - sync with visible items +void CIcqProto::AddJustAddedContact(HANDLE hContact) +{ + icq_lock l(servlistMutex); + + if (nJustAddedCount >= nJustAddedSize) + { + nJustAddedSize += 10; + pdwJustAddedList = (HANDLE*)SAFE_REALLOC(pdwJustAddedList, nJustAddedSize * sizeof(HANDLE)); + } + + pdwJustAddedList[nJustAddedCount] = hContact; + nJustAddedCount++; +} + + +// was the contact added during this serv-list load +BOOL CIcqProto::IsContactJustAdded(HANDLE hContact) +{ + icq_lock l(servlistMutex); + + if (pdwJustAddedList) + { + for (int i = 0; iEnter(); + if (nServerIDListCount >= nServerIDListSize) + { + nServerIDListSize += 100; + pdwServerIDList = (DWORD*)SAFE_REALLOC(pdwServerIDList, nServerIDListSize * sizeof(DWORD)); + } + + pdwServerIDList[nServerIDListCount] = wID | (bGroupType & 0x00FF0000) | (bFlags & 0xFF000000); + nServerIDListCount++; + servlistMutex->Leave(); + + if (!bIsSyncingCL) + StoreServerIDs(); +} + + +// Remove a server ID from the list of reserved IDs. +// Used for deleting contacts and other modifications. +void CIcqProto::FreeServerID(WORD wID, int bGroupType) +{ + DWORD dwId = wID | (bGroupType & 0x00FF0000); + + icq_lock l(servlistMutex); + + if (pdwServerIDList) + { + for (int i = 0; i= wID) && ((pdwServerIDList[i] & 0xFFFF) <= wID + wCount)) + return TRUE; + } + } + + return FALSE; +} + +void CIcqProto::FlushServerIDs() +{ + icq_lock l(servlistMutex); + + SAFE_FREE((void**)&pdwServerIDList); + nServerIDListCount = 0; + nServerIDListSize = 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +struct GroupReserveIdsEnumParam +{ + CIcqProto *ppro; + char *szModule; +}; + +static int GroupReserveIdsEnumProc(const char *szSetting,LPARAM lParam) +{ + if (szSetting && strlennull(szSetting)<5) + { + // it is probably server group + GroupReserveIdsEnumParam *param = (GroupReserveIdsEnumParam*)lParam; + char val[MAX_PATH+2]; // dummy + + DBVARIANT dbv = {DBVT_DELETED}; + dbv.type = DBVT_ASCIIZ; + dbv.pszVal = val; + dbv.cchVal = MAX_PATH; + + DBCONTACTGETSETTING cgs; + cgs.szModule = param->szModule; + cgs.szSetting = szSetting; + cgs.pValue = &dbv; + if (CallService(MS_DB_CONTACT_GETSETTINGSTATIC,(WPARAM)NULL,(LPARAM)&cgs)) + { // we failed to read setting, try also utf8 - DB bug + dbv.type = DBVT_UTF8; + dbv.pszVal = val; + dbv.cchVal = MAX_PATH; + if (CallService(MS_DB_CONTACT_GETSETTINGSTATIC,(WPARAM)NULL,(LPARAM)&cgs)) + return 0; // we failed also, invalid setting + } + if (dbv.type != DBVT_ASCIIZ) + { // it is not a cached server-group name + return 0; + } + param->ppro->ReserveServerID((WORD)strtoul(szSetting, NULL, 0x10), SSIT_GROUP, 0); +#ifdef _DEBUG + param->ppro->NetLog_Server("Loaded group %u:'%s'", strtoul(szSetting, NULL, 0x10), val); +#endif + } + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Load all known server IDs from DB to list +void CIcqProto::LoadServerIDs() +{ + WORD wSrvID; + int nGroups = 0, nContacts = 0, nPermits = 0, nDenys = 0, nIgnores = 0, nUnhandled = 0; + + servlistMutex->Enter(); + if (wSrvID = getSettingWord(NULL, DBSETTING_SERVLIST_AVATAR, 0)) + ReserveServerID(wSrvID, SSIT_ITEM, 0); + if (wSrvID = getSettingWord(NULL, DBSETTING_SERVLIST_PHOTO, 0)) + ReserveServerID(wSrvID, SSIT_ITEM, 0); + if (wSrvID = getSettingWord(NULL, DBSETTING_SERVLIST_PRIVACY, 0)) + ReserveServerID(wSrvID, SSIT_ITEM, 0); + if (wSrvID = getSettingWord(NULL, DBSETTING_SERVLIST_METAINFO, 0)) + ReserveServerID(wSrvID, SSIT_ITEM, 0); + if (wSrvID = getSettingWord(NULL, "SrvImportID", 0)) + ReserveServerID(wSrvID, SSIT_ITEM, 0); + + DBCONTACTENUMSETTINGS dbces; + int nStart = nServerIDListCount; + + char szModule[MAX_PATH]; + null_snprintf(szModule, SIZEOF(szModule), "%sSrvGroups", m_szModuleName); + + GroupReserveIdsEnumParam param = { this, szModule }; + dbces.pfnEnumProc = &GroupReserveIdsEnumProc; + dbces.szModule = szModule; + dbces.lParam = (LPARAM)¶m; + CallService(MS_DB_CONTACT_ENUMSETTINGS, (WPARAM)NULL, (LPARAM)&dbces); + + nGroups = nServerIDListCount - nStart; + + HANDLE hContact = FindFirstContact(); + + while (hContact) + { // search all our contacts, reserve their server IDs + if (wSrvID = getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0)) + { + ReserveServerID(wSrvID, SSIT_ITEM, 0); + nContacts++; + } + if (wSrvID = getSettingWord(hContact, DBSETTING_SERVLIST_DENY, 0)) + { + ReserveServerID(wSrvID, SSIT_ITEM, 0); + nDenys++; + } + if (wSrvID = getSettingWord(hContact, DBSETTING_SERVLIST_PERMIT, 0)) + { + ReserveServerID(wSrvID, SSIT_ITEM, 0); + nPermits++; + } + if (wSrvID = getSettingWord(hContact, DBSETTING_SERVLIST_IGNORE, 0)) + { + ReserveServerID(wSrvID, SSIT_ITEM, 0); + nIgnores++; + } + + hContact = FindNextContact(hContact); + } + servlistMutex->Leave(); + + DBVARIANT dbv = {0}; + if (!getSetting(NULL, DBSETTING_SERVLIST_UNHANDLED, &dbv)) + { + int dataLen = dbv.cpbVal; + BYTE *pData = dbv.pbVal; + + while (dataLen >= 4) + { + BYTE bGroupType; + BYTE bFlags; + + unpackLEWord(&pData, &wSrvID); + unpackByte(&pData, &bGroupType); + unpackByte(&pData, &bFlags); + + ReserveServerID(wSrvID, bGroupType, bFlags); + dataLen -= 4; + nUnhandled++; + } + + ICQFreeVariant(&dbv); + } + + NetLog_Server("Loaded SSI: %d contacts, %d groups, %d permit, %d deny, %d ignore, %d unknown items.", nContacts, nGroups, nPermits, nDenys, nIgnores, nUnhandled); +} + + +void CIcqProto::StoreServerIDs() /// TODO: allow delayed +{ + BYTE *pUnhandled = NULL; + int cbUnhandled = 0; + + servlistMutex->Enter(); + if (pdwServerIDList) + for (int i = 0; i> 0x10); + ppackByte(&pUnhandled, &cbUnhandled, (pdwServerIDList[i] & 0xFF000000) >> 0x18); + } + servlistMutex->Leave(); + + if (pUnhandled) + setSettingBlob(NULL, DBSETTING_SERVLIST_UNHANDLED, pUnhandled, cbUnhandled); + else + deleteSetting(NULL, DBSETTING_SERVLIST_UNHANDLED); + + SAFE_FREE((void**)&pUnhandled); +} + + +// Generate server ID with wCount IDs free after it, for sub-groups. +WORD CIcqProto::GenerateServerID(int bGroupType, int bFlags, int wCount) +{ + WORD wId; + + while (TRUE) + { + // Randomize a new ID + // Max value is probably 0x7FFF, lowest value is probably 0x0001 (generated by Icq2Go) + // We use range 0x1000-0x7FFF. + wId = (WORD)RandRange(0x1000, 0x7FFF); + + if (!CheckServerID(wId, wCount)) + break; + } + + ReserveServerID(wId, bGroupType, bFlags); + + return wId; +} + + +/*********************************************** +* +* --- Low-level packet sending functions --- +* +*/ + +struct doubleServerItemObject +{ + WORD wAction; + icq_packet packet; +}; + +DWORD CIcqProto::icq_sendServerItem(DWORD dwCookie, WORD wAction, WORD wGroupId, WORD wItemId, const char *szName, BYTE *pTLVs, int nTlvLength, WORD wItemType, DWORD dwOperation, DWORD dwTimeout, void **doubleObject) +{ // generic packet + icq_packet packet; + int nNameLen; + WORD wTLVlen = (WORD)nTlvLength; + + // Prepare item name length + nNameLen = strlennull(szName); + + // Build the packet + serverPacketInit(&packet, (WORD)(nNameLen + 20 + wTLVlen)); + packFNACHeader(&packet, ICQ_LISTS_FAMILY, wAction, 0, dwCookie); + packWord(&packet, (WORD)nNameLen); + if (nNameLen) + packBuffer(&packet, (LPBYTE)szName, (WORD)nNameLen); + packWord(&packet, wGroupId); + packWord(&packet, wItemId); + packWord(&packet, wItemType); + packWord(&packet, wTLVlen); + if (wTLVlen) + packBuffer(&packet, pTLVs, wTLVlen); + + if (!doubleObject) + { // Send the packet and return the cookie + servlistPostPacket(&packet, dwCookie, dwOperation | wAction, dwTimeout); + } + else + { + if (*doubleObject) + { // Send both packets and return the cookie + doubleServerItemObject* helper = (doubleServerItemObject*)*doubleObject; + + servlistPostPacketDouble(&helper->packet, dwCookie, dwOperation | helper->wAction, dwTimeout, &packet, wAction); + SAFE_FREE(doubleObject); + } + else + { // Create helper object, return the cookie + doubleServerItemObject* helper = (doubleServerItemObject*)SAFE_MALLOC(sizeof(doubleServerItemObject)); + + if (helper) + { + helper->wAction = wAction; + memcpy(&helper->packet, &packet, sizeof(icq_packet)); + *doubleObject = helper; + } + else // memory alloc failed + return 0; + } + } + + // Force reload of server-list after change + setSettingWord(NULL, "SrvRecordCount", 0); + + return dwCookie; +} + + +DWORD CIcqProto::icq_sendServerContact(HANDLE hContact, DWORD dwCookie, WORD wAction, WORD wGroupId, WORD wContactId, DWORD dwOperation, DWORD dwTimeout, void **doubleObject) +{ + DWORD dwUin; + uid_str szUid; + icq_packet pBuffer; + char *szNick = NULL, *szNote = NULL; + BYTE *pData = NULL, *pMetaToken = NULL, *pMetaTime = NULL; + int nNickLen, nNoteLen, nDataLen = 0, nMetaTokenLen = 0, nMetaTimeLen = 0; + WORD wTLVlen; + BYTE bAuth; + int bDataTooLong = FALSE; + + // Prepare UID + if (getContactUid(hContact, &dwUin, &szUid)) + { + NetLog_Server("Buddy upload failed (UID missing)."); + return 0; + } + + bAuth = getSettingByte(hContact, "Auth", 0); + szNick = getSettingStringUtf(hContact, "CList", "MyHandle", NULL); + szNote = getSettingStringUtf(hContact, "UserInfo", "MyNotes", NULL); + + DBVARIANT dbv; + + if (!getSetting(hContact, DBSETTING_METAINFO_TOKEN, &dbv)) + { + nMetaTokenLen = dbv.cpbVal; + pMetaToken = (BYTE*)_alloca(dbv.cpbVal); + memcpy(pMetaToken, dbv.pbVal, dbv.cpbVal); + + ICQFreeVariant(&dbv); + } + if (!getSetting(hContact, DBSETTING_METAINFO_TIME, &dbv)) + { + nMetaTimeLen = dbv.cpbVal; + pMetaTime = (BYTE*)_alloca(dbv.cpbVal); + for (int i = 0; i < dbv.cpbVal; i++) + pMetaTime[i] = dbv.pbVal[dbv.cpbVal - i - 1]; + + ICQFreeVariant(&dbv); + } + + if (!getSetting(hContact, DBSETTING_SERVLIST_DATA, &dbv)) + { // read additional server item data + nDataLen = dbv.cpbVal; + pData = (BYTE*)_alloca(nDataLen); + memcpy(pData, dbv.pbVal, nDataLen); + + ICQFreeVariant(&dbv); + } + + nNickLen = strlennull(szNick); + nNoteLen = strlennull(szNote); + + // Limit the strings + if (nNickLen > MAX_SSI_TLV_NAME_SIZE) + { + bDataTooLong = TRUE; + nNickLen = null_strcut(szNick, MAX_SSI_TLV_NAME_SIZE); + } + if (nNoteLen > MAX_SSI_TLV_COMMENT_SIZE) + { + bDataTooLong = TRUE; + nNoteLen = null_strcut(szNote, MAX_SSI_TLV_COMMENT_SIZE); + } + if (bDataTooLong) + { // Inform the user + /// TODO: do something with this for Manage Server-List dialog. + if (wAction != ICQ_LISTS_REMOVEFROMLIST) // do not report this when removing from list + icq_LogMessage(LOG_WARNING, LPGEN("The contact's information was too big and was truncated.")); + } + + // Build the packet + wTLVlen = (nNickLen?4+nNickLen:0) + (nNoteLen?4+nNoteLen:0) + (bAuth?4:0) + nDataLen + (nMetaTokenLen?4+nMetaTokenLen:0) + (nMetaTimeLen?4+nMetaTimeLen:0); + + // Initialize our handy data buffer + pBuffer.wPlace = 0; + pBuffer.pData = (BYTE *)_alloca(wTLVlen); + pBuffer.wLen = wTLVlen; + + if (nNickLen) + packTLV(&pBuffer, SSI_TLV_NAME, (WORD)nNickLen, (LPBYTE)szNick); // Nickname TLV + + if (nNoteLen) + packTLV(&pBuffer, SSI_TLV_COMMENT, (WORD)nNoteLen, (LPBYTE)szNote); // Comment TLV + + if (nMetaTokenLen) + packTLV(&pBuffer, SSI_TLV_METAINFO_TOKEN, (WORD)nMetaTokenLen, pMetaToken); + + if (nMetaTimeLen) + packTLV(&pBuffer, SSI_TLV_METAINFO_TIME, (WORD)nMetaTimeLen, pMetaTime); + + if (pData) + packBuffer(&pBuffer, pData, (WORD)nDataLen); + + if (bAuth) // icq5 gives this as last TLV + packDWord(&pBuffer, 0x00660000); // "Still waiting for auth" TLV + + SAFE_FREE((void**)&szNick); + SAFE_FREE((void**)&szNote); + + return icq_sendServerItem(dwCookie, wAction, wGroupId, wContactId, strUID(dwUin, szUid), pBuffer.pData, wTLVlen, SSI_ITEM_BUDDY, dwOperation, dwTimeout, doubleObject); +} + + +DWORD CIcqProto::icq_sendSimpleItem(DWORD dwCookie, WORD wAction, DWORD dwUin, char* szUID, WORD wGroupId, WORD wItemId, WORD wItemType, DWORD dwOperation, DWORD dwTimeout) +{ // for privacy items + return icq_sendServerItem(dwCookie, wAction, wGroupId, wItemId, strUID(dwUin, szUID), NULL, 0, wItemType, dwOperation, dwTimeout, NULL); +} + + +DWORD CIcqProto::icq_sendServerGroup(DWORD dwCookie, WORD wAction, WORD wGroupId, const char *szName, void *pContent, int cbContent, DWORD dwOperationFlags) +{ + WORD wTLVlen; + icq_packet pBuffer; // I reuse the ICQ packet type as a generic buffer + // I should be ashamed! ;) + + if (strlennull(szName) == 0 && wGroupId != 0) + { + NetLog_Server("Group upload failed (GroupName missing)."); + return 0; // without name we could not change the group + } + + // Calculate buffer size + wTLVlen = (cbContent?4+cbContent:0); + + // Initialize our handy data buffer + pBuffer.wPlace = 0; + pBuffer.pData = (BYTE *)_alloca(wTLVlen); + pBuffer.wLen = wTLVlen; + + if (wTLVlen) + packTLV(&pBuffer, SSI_TLV_SUBITEMS, (WORD)cbContent, (LPBYTE)pContent); // Groups TLV + + return icq_sendServerItem(dwCookie, wAction, wGroupId, 0, szName, pBuffer.pData, wTLVlen, SSI_ITEM_GROUP, SSOP_GROUP_ACTION | dwOperationFlags, 400, NULL); +} + + +DWORD CIcqProto::icq_modifyServerPrivacyItem(HANDLE hContact, DWORD dwUin, char *szUid, WORD wAction, DWORD dwOperation, WORD wItemId, WORD wType) +{ + cookie_servlist_action *ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + DWORD dwCookie; + + if (ack) + { + ack->dwAction = dwOperation; // remove privacy item + ack->hContact = hContact; + ack->wContactId = wItemId; + + dwCookie = AllocateCookie(CKT_SERVERLIST, wAction, hContact, ack); + } + else // cookie failed + return 0; + + return icq_sendSimpleItem(dwCookie, wAction, dwUin, szUid, 0, wItemId, wType, SSOP_ITEM_ACTION, 400); +} + + +DWORD CIcqProto::icq_removeServerPrivacyItem(HANDLE hContact, DWORD dwUin, char *szUid, WORD wItemId, WORD wType) +{ + return icq_modifyServerPrivacyItem(hContact, dwUin, szUid, ICQ_LISTS_REMOVEFROMLIST, SSA_PRIVACY_REMOVE, wItemId, wType); +} + + +DWORD CIcqProto::icq_addServerPrivacyItem(HANDLE hContact, DWORD dwUin, char *szUid, WORD wItemId, WORD wType) +{ + return icq_modifyServerPrivacyItem(hContact, dwUin, szUid, ICQ_LISTS_ADDTOLIST, SSA_PRIVACY_ADD, wItemId, wType); +} + +/***************************************** +* +* --- Contact DB Utilities --- +* +*/ + + +/// TODO: do not check by plugin version, check by ServListStructures version! +int CIcqProto::IsServerGroupsDefined() +{ + int iRes = 1; + + if (getSettingDword(NULL, "Version", 0) < 0x00030608) + { // group cache & linking data too old, flush, reload from server + char szModule[MAX_PATH]; + + // flush obsolete linking data + null_snprintf(szModule, SIZEOF(szModule), "%sGroups", m_szModuleName); + CallService(MS_DB_MODULE_DELETE, 0, (LPARAM)szModule); + + iRes = 0; // no groups defined, or older version + } + // store our current version + setSettingDword(NULL, "Version", ICQ_PLUG_VERSION & 0x00FFFFFF); + + return iRes; +} + + +void CIcqProto::FlushSrvGroupsCache() +{ + char szModule[MAX_PATH]; + + null_snprintf(szModule, SIZEOF(szModule), "%sSrvGroups", m_szModuleName); + CallService(MS_DB_MODULE_DELETE, 0, (LPARAM)szModule); +} + + +// Look thru DB and collect all ContactIDs from a group +void* CIcqProto::collectBuddyGroup(WORD wGroupID, int *count) +{ + WORD* buf = NULL; + int cnt = 0; + HANDLE hContact; + WORD wItemID; + + hContact = FindFirstContact(); + + while (hContact) + { // search all contacts + if (wGroupID == getSettingWord(hContact, DBSETTING_SERVLIST_GROUP, 0)) + { // add only buddys from specified group + wItemID = getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0); + + if (wItemID) + { // valid ID, add + cnt++; + buf = (WORD*)SAFE_REALLOC(buf, cnt*sizeof(WORD)); + buf[cnt-1] = wItemID; + if (!count) break; + } + } + + hContact = FindNextContact(hContact); + } + + if (count) + *count = cnt<<1; // we return size in bytes + return buf; +} + + +// Look thru DB and collect all GroupIDs +void* CIcqProto::collectGroups(int *count) +{ + WORD* buf = NULL; + int cnt = 0; + int i; + HANDLE hContact; + WORD wGroupID; + + hContact = FindFirstContact(); + + while (hContact) + { // search all contacts + if (wGroupID = getSettingWord(hContact, DBSETTING_SERVLIST_GROUP, 0)) + { // add only valid IDs + for (i = 0; ipfnGetGroupName() +int CIcqProto::getCListGroupExists(const char *szGroup) +{ + int hGroup = 0; + CLIST_INTERFACE *clint = NULL; + + if (!szGroup) return 0; + + if (ServiceExists(MS_CLIST_RETRIEVE_INTERFACE)) + clint = (CLIST_INTERFACE*)CallService(MS_CLIST_RETRIEVE_INTERFACE, 0, 0); + + if (clint && clint->version >= 1) + { // we've got clist interface, use it + int size = strlennull(szGroup) + 2; + TCHAR *tszGroup = (TCHAR*)_alloca(size * sizeof(TCHAR)); + + if (utf8_to_tchar_static(szGroup, tszGroup, size)) + for (int i = 1; TRUE; i++) + { + TCHAR *tszGroupName = (TCHAR*)clint->pfnGetGroupName(i, NULL); + + if (!tszGroupName) break; + + if (!_tcscmp(tszGroup, tszGroupName)) + { // we have found the group + hGroup = i; + break; + } + } + } + else + { // old ansi version - no other way + int size = strlennull(szGroup) + 2; + char *aszGroup = (char*)_alloca(size); + + utf8_decode_static(szGroup, aszGroup, size); + + for (int i = 1; TRUE; i++) + { + char *paszGroup = (char*)CallService(MS_CLIST_GROUPGETNAME, i, 0); + + if (!paszGroup) break; + + if (!strcmpnull(aszGroup, paszGroup)) + { // we found the group + hGroup = i; + break; + } + } + } + + return hGroup; +} + + +int CIcqProto::moveContactToCListGroup(HANDLE hContact, const char *szGroup) +{ + int hGroup = getCListGroupHandle(szGroup); + + if (ServiceExists(MS_CLIST_CONTACTCHANGEGROUP)) + return CallService(MS_CLIST_CONTACTCHANGEGROUP, (WPARAM)hContact, hGroup); + else /// TODO: is this neccessary ? + return setSettingStringUtf(hContact, "CList", "Group", szGroup); +} + + +// utility function which counts > on start of a server group name +static int countGroupNameLevel(const char *szGroupName) +{ + int nNameLen = strlennull(szGroupName); + int i = 0; + + while (i 0) + { // it is probably a sub-group locate parent group + WORD wParentGroupId = wGroupId; + int nParentGroupLevel; + + do + { // we look for parent group at the correct level + wParentGroupId--; + nParentGroupLevel = getServListGroupLevel(wParentGroupId); + } while ((nParentGroupLevel >= nGroupLevel) && (nParentGroupLevel != -1)); + + if (nParentGroupLevel == -1) + { // that was not a sub-group, it was just a group starting with > + setServListGroupLinkID(szGroup, wGroupId); + return szGroup; + } + + { // recursively determine parent group clist path + char *szParentGroup = getServListGroupCListPath(wParentGroupId); + + /// FIXME: properly handle ~N suffixes + szParentGroup = (char*)SAFE_REALLOC(szParentGroup, strlennull(szGroup) + strlennull(szParentGroup) + 2); + strcat(szParentGroup, "\\"); + strcat(szParentGroup, (char*)szGroup + nGroupLevel); + /* if (strstrnull(szGroup, "~")) + { // check if the ~ was not added to obtain unique servlist item name + char *szUniqueMark = strrchr(szParentGroup, '~'); + + *szUniqueMark = '\0'; + // not the same group without ~, return it + if (getServListGroupLinkID(szParentGroup) != wGroupId) + *szUniqueMark = '~'; + } */ /// FIXME: this is necessary, but needs group loading changes + SAFE_FREE((void**)&szGroup); + szGroup = szParentGroup; + + + if (getServListGroupLinkID(szGroup) == wGroupId) + { // known path, give + return szGroup; + } + else + { // unknown path, setup a link + setServListGroupLinkID(szGroup, wGroupId); + return szGroup; + } + } + } + else + { // normal group, setup a link + setServListGroupLinkID(szGroup, wGroupId); + return szGroup; + } + } + } + return NULL; +} + + +static int SrvGroupNamesEnumProc(const char *szSetting, LPARAM lParam) +{ // check server-group cache item + const char **params = (const char**)lParam; + CIcqProto *ppro = (CIcqProto*)params[0]; + char *szGroupName = ppro->getSettingStringUtf(NULL, params[3], szSetting, NULL); + + if (!strcmpnull(szGroupName, params[2])) + params[1] = szSetting; // do not need the real value, just arbitrary non-NULL + + SAFE_FREE(&szGroupName); + return 0; +} + +char* CIcqProto::getServListUniqueGroupName(const char *szGroupName, int bAlloced) +{ // enum ICQSrvGroups and create unique name if neccessary + DBCONTACTENUMSETTINGS dbces; + char szModule[MAX_PATH]; + char *pars[4]; + int uniqueID = 1; + char *szGroupNameBase = (char*)szGroupName; + char *szNewGroupName = NULL; + + if (!bAlloced) + szGroupNameBase = null_strdup(szGroupName); + null_strcut(szGroupNameBase, m_wServerListRecordNameMaxLength); + + null_snprintf(szModule, SIZEOF(szModule), "%sSrvGroups", m_szModuleName); + + do { + pars[0] = (char*)this; + pars[1] = NULL; + pars[2] = szNewGroupName ? szNewGroupName : szGroupNameBase; + pars[3] = szModule; + + dbces.pfnEnumProc = &SrvGroupNamesEnumProc; + dbces.szModule = szModule; + dbces.lParam = (LPARAM)pars; + + CallService(MS_DB_CONTACT_ENUMSETTINGS, (WPARAM)NULL, (LPARAM)&dbces); + + if (pars[1]) + { // the groupname already exists, create another + SAFE_FREE((void**)&szNewGroupName); + + char szUnique[10]; + _itoa(uniqueID++, szUnique, 10); + null_strcut(szGroupNameBase, m_wServerListRecordNameMaxLength - strlennull(szUnique) - 1); + szNewGroupName = (char*)SAFE_MALLOC(strlennull(szUnique) + strlennull(szGroupNameBase) + 2); + if (szNewGroupName) + { + strcpy(szNewGroupName, szGroupNameBase); + strcat(szNewGroupName, "~"); + strcat(szNewGroupName, szUnique); + } + } + } while (pars[1] && szNewGroupName); + + if (szNewGroupName) + { + SAFE_FREE(&szGroupNameBase); + return szNewGroupName; + } + if (szGroupName != szGroupNameBase) + { + SAFE_FREE(&szGroupNameBase); + return (char*)szGroupName; + } + else + return szGroupNameBase; +} + + +// this is the second part of recursive event-driven procedure +int CIcqProto::servlistCreateGroup_gotParentGroup(const char *szGroup, WORD wGroupID, LPARAM param, int nResult) +{ + cookie_servlist_action* clue = (cookie_servlist_action*)param; + char *szSubGroupName = clue->szGroupName; + char *szSubGroup; + int wSubGroupLevel = -1; + WORD wSubGroupID; + + SAFE_FREE((void**)&clue); + + if (nResult == PENDING_RESULT_PURGE) + { // only cleanup + return CALLBACK_RESULT_CONTINUE; + } + + szSubGroup = (char*)SAFE_MALLOC(strlennull(szGroup) + strlennull(szSubGroupName) + 2); + if (szSubGroup) + { + strcpy(szSubGroup, szGroup); + strcat(szSubGroup, "\\"); + strcat(szSubGroup, szSubGroupName); + } + + if (nResult == PENDING_RESULT_SUCCESS) // if we got an id count level + wSubGroupLevel = getServListGroupLevel(wGroupID); + + if (wSubGroupLevel == -1) + { // something went wrong, give the id and go away + servlistPendingRemoveGroup(szSubGroup, wGroupID, PENDING_RESULT_FAILED); + + SAFE_FREE((void**)&szSubGroupName); + SAFE_FREE((void**)&szSubGroup); + return CALLBACK_RESULT_CONTINUE; + } + wSubGroupLevel++; // we are a sub-group + wSubGroupID = wGroupID + 1; + + // check if on that id is not group of the same or greater level, if yes, try next + while (CheckServerID(wSubGroupID, 0) && (getServListGroupLevel(wSubGroupID) >= wSubGroupLevel)) + { + wSubGroupID++; + } + + if (!CheckServerID(wSubGroupID, 0)) + { // the next id is free, so create our group with that id + cookie_servlist_action *ack; + DWORD dwCookie; + char *szSubGroupItem = (char*)SAFE_MALLOC(strlennull(szSubGroupName) + wSubGroupLevel + 1); + + if (szSubGroupItem) + { + int i; + + for (i=0; i < wSubGroupLevel; i++) + szSubGroupItem[i] = '>'; + + strcpy(szSubGroupItem + wSubGroupLevel, szSubGroupName); + szSubGroupItem[strlennull(szSubGroupName) + wSubGroupLevel] = '\0'; + SAFE_FREE((void**)&szSubGroupName); + // check and create unique group name (Miranda does allow more subgroups with the same name!) + szSubGroupItem = getServListUniqueGroupName(szSubGroupItem, TRUE); + + if (ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action))) + { // we have cookie good, go on +#ifdef _DEBUG + NetLog_Server("Server-List: Creating sub-group \"%s\", parent group \"%s\".", szSubGroupItem, szGroup); +#endif + ReserveServerID(wSubGroupID, SSIT_GROUP, 0); + + ack->wGroupId = wSubGroupID; + ack->szGroupName = szSubGroupItem; // we need that name + ack->szGroup = szSubGroup; + ack->dwAction = SSA_GROUP_ADD; + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, 0, ack); + + icq_sendServerGroup(dwCookie, ICQ_LISTS_ADDTOLIST, ack->wGroupId, szSubGroupItem, NULL, 0, SSOF_BEGIN_OPERATION); + return CALLBACK_RESULT_CONTINUE; + } + SAFE_FREE((void**)&szSubGroupItem); + } + } + // we failed to create sub-group give parent groupid + icq_LogMessage(LOG_ERROR, LPGEN("Failed to create the correct sub-group, the using closest parent group.")); + + servlistPendingRemoveGroup(szSubGroup, wGroupID, PENDING_RESULT_FAILED); + + SAFE_FREE((void**)&szSubGroupName); + SAFE_FREE((void**)&szSubGroup); + return CALLBACK_RESULT_CONTINUE; +} + + +int CIcqProto::servlistCreateGroup_Ready(const char *szGroup, WORD groupID, LPARAM param, int nResult) +{ + WORD wGroupID = 0; + + if (nResult == PENDING_RESULT_PURGE) + return CALLBACK_RESULT_CONTINUE; + + if (wGroupID = getServListGroupLinkID(szGroup)) + { // the path is known, continue the process + servlistPendingRemoveGroup(szGroup, wGroupID, PENDING_RESULT_SUCCESS); + return CALLBACK_RESULT_CONTINUE; + } + + if (!strstrnull(szGroup, "\\") || m_bSsiSimpleGroups) + { // a root group can be simply created without problems; simple groups are mapped directly + cookie_servlist_action* ack; + DWORD dwCookie; + + if (ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action))) + { // we have cookie good, go on +#ifdef _DEBUG + NetLog_Server("Server-List: Creating root group \"%s\".", szGroup); +#endif + ack->wGroupId = GenerateServerID(SSIT_GROUP, 0); + ack->szGroup = null_strdup(szGroup); // we need that name + // check if the groupname is unique - just to be sure, Miranda should handle that! + ack->szGroupName = getServListUniqueGroupName(ack->szGroup, FALSE); + ack->dwAction = SSA_GROUP_ADD; + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, 0, ack); + + icq_sendServerGroup(dwCookie, ICQ_LISTS_ADDTOLIST, ack->wGroupId, ack->szGroup, NULL, 0, SSOF_BEGIN_OPERATION); + + return CALLBACK_RESULT_POSTPONE; + } + } + else + { // this is a sub-group + char* szSub = null_strdup(szGroup); // create subgroup, recursive, event-driven, possibly relocate + cookie_servlist_action* ack; + char *szLast; + + if (strstrnull(szSub, "\\")) + { // determine parent group + szLast = strrchr(szSub, '\\') + 1; + + szLast[-1] = '\0'; + } + // make parent group id + ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + if (ack) + { + ack->szGroupName = null_strdup(szLast); // groupname + servlistCreateGroup(szSub, (LPARAM)ack, &CIcqProto::servlistCreateGroup_gotParentGroup); + SAFE_FREE((void**)&szSub); + + return CALLBACK_RESULT_POSTPONE; + } + + SAFE_FREE((void**)&szSub); + } + servlistPendingRemoveGroup(szGroup, groupID, PENDING_RESULT_FAILED); + + return CALLBACK_RESULT_CONTINUE; +} + + +// create group with this path, a bit complex task +// this supposes that all server groups are known +void CIcqProto::servlistCreateGroup(const char *szGroupPath, LPARAM param, PENDING_GROUP_CALLBACK callback) +{ + char *szGroup = (char*)szGroupPath; + + if (!strlennull(szGroup)) szGroup = DEFAULT_SS_GROUP; + + servlistPendingAddGroup(szGroup, 0, 0, &CIcqProto::servlistCreateGroup_Ready, TRUE, param, callback); +} + + +/***************************************** +* +* --- Server-List Operations --- +* +*/ + +int CIcqProto::servlistAddContact_gotGroup(const char *szGroup, WORD wGroupID, LPARAM lParam, int nResult) +{ + cookie_servlist_action* ack = (cookie_servlist_action*)lParam; + + if (ack) SAFE_FREE(&ack->szGroup); + + if (nResult == PENDING_RESULT_PURGE) + { // only cleanup + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + + if (!ack || !wGroupID) // something went wrong + { + if (ack) servlistPendingRemoveContact(ack->hContact, 0, wGroupID, PENDING_RESULT_FAILED); + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + + WORD wItemID = getSettingWord(ack->hContact, DBSETTING_SERVLIST_ID, 0); + + if (wItemID) /// TODO: redundant ??? + { // Only add the contact if it doesnt already have an ID + servlistPendingRemoveContact(ack->hContact, wItemID, wGroupID, PENDING_RESULT_SUCCESS); + NetLog_Server("Failed to add contact to server side list (%s)", "already there"); + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + + wItemID = GenerateServerID(SSIT_ITEM, 0); + + ack->dwAction = SSA_CONTACT_ADD; + ack->wGroupId = wGroupID; + ack->wContactId = wItemID; + + DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, ack->hContact, ack); + + icq_sendServerContact(ack->hContact, dwCookie, ICQ_LISTS_ADDTOLIST, wGroupID, wItemID, SSOP_ITEM_ACTION | SSOF_CONTACT | SSOF_BEGIN_OPERATION, 400, NULL); + + return CALLBACK_RESULT_CONTINUE; +} + + +// Need to be called when Pending Contact is active +int CIcqProto::servlistAddContact_Ready(HANDLE hContact, WORD wContactID, WORD wGroupID, LPARAM lParam, int nResult) +{ + cookie_servlist_action* ack = (cookie_servlist_action*)lParam; + + if (nResult == PENDING_RESULT_PURGE) + { // removing obsolete items, just free the memory + SAFE_FREE((void**)&ack->szGroup); + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + + WORD wItemID = getSettingWord(ack->hContact, DBSETTING_SERVLIST_ID, 0); + + if (wItemID) + { // Only add the contact if it doesn't already have an ID + servlistPendingRemoveContact(ack->hContact, wItemID, getSettingWord(hContact, DBSETTING_SERVLIST_GROUP, 0), PENDING_RESULT_SUCCESS); + NetLog_Server("Failed to add contact to server side list (%s)", "already there"); + SAFE_FREE((void**)&ack->szGroup); + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + + // obtain a correct groupid first + servlistCreateGroup(ack->szGroup, lParam, &CIcqProto::servlistAddContact_gotGroup); + + return CALLBACK_RESULT_POSTPONE; +} + + +// Called when contact should be added to server list, if group does not exist, create one +void CIcqProto::servlistAddContact(HANDLE hContact, const char *pszGroup) +{ + DWORD dwUin; + uid_str szUid; + cookie_servlist_action* ack; + + // Get UID + if (getContactUid(hContact, &dwUin, &szUid)) + { // Could not do anything without uid + NetLog_Server("Failed to add contact to server side list (%s)", "no UID"); + return; + } + + if (!(ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)))) + { // Could not do anything without cookie + NetLog_Server("Failed to add contact to server side list (%s)", "malloc failed"); + return; + } + else + { + ack->hContact = hContact; + ack->szGroup = null_strdup(pszGroup); + // call thru pending operations - makes sure the contact is ready to be added + servlistPendingAddContact(hContact, 0, 0, (LPARAM)ack, &CIcqProto::servlistAddContact_Ready, TRUE); + return; + } +} + + +int CIcqProto::servlistRemoveContact_Ready(HANDLE hContact, WORD contactID, WORD groupID, LPARAM lParam, int nResult) +{ + WORD wGroupID; + WORD wItemID; + cookie_servlist_action* ack = (cookie_servlist_action*)lParam; + DWORD dwCookie; + + if (nResult == PENDING_RESULT_PURGE) + { + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + + // Get the contact's group ID + if (!(wGroupID = getSettingWord(hContact, DBSETTING_SERVLIST_GROUP, 0))) + { // Could not find a usable group ID + servlistPendingRemoveContact(hContact, contactID, groupID, PENDING_RESULT_FAILED); + + NetLog_Server("Failed to remove contact from server side list (%s)", "no group ID"); + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + + // Get the contact's item ID + if (!(wItemID = getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0))) + { // Could not find usable item ID + servlistPendingRemoveContact(hContact, contactID, wGroupID, PENDING_RESULT_FAILED); + + NetLog_Server("Failed to remove contact from server side list (%s)", "no item ID"); + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + + ack->dwAction = SSA_CONTACT_REMOVE; + ack->hContact = hContact; + ack->wGroupId = wGroupID; + ack->wContactId = wItemID; + + dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_REMOVEFROMLIST, hContact, ack); + + icq_sendServerContact(hContact, dwCookie, ICQ_LISTS_REMOVEFROMLIST, wGroupID, wItemID, SSOP_ITEM_ACTION | SSOF_CONTACT | SSOF_BEGIN_OPERATION, 400, NULL); + + return CALLBACK_RESULT_POSTPONE; +} + + +// Called when contact should be removed from server list, remove group if it remain empty +void CIcqProto::servlistRemoveContact(HANDLE hContact) +{ + DWORD dwUin; + uid_str szUid; + cookie_servlist_action* ack; + + // Get UID + if (getContactUid(hContact, &dwUin, &szUid)) + { + // Could not do anything without uid + NetLog_Server("Failed to remove contact from server side list (%s)", "no UID"); + return; + } + + if (!(ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)))) + { // Could not do anything without cookie + NetLog_Server("Failed to remove contact from server side list (%s)", "malloc failed"); + return; + } + else + { + ack->hContact = hContact; + // call thru pending operations - makes sure the contact is ready to be removed + servlistPendingAddContact(hContact, 0, 0, (LPARAM)ack, &CIcqProto::servlistRemoveContact_Ready, TRUE); + return; + } +} + + +int CIcqProto::servlistMoveContact_gotTargetGroup(const char *szGroup, WORD wNewGroupID, LPARAM lParam, int nResult) +{ + cookie_servlist_action *ack = (cookie_servlist_action*)lParam; + + if (ack) SAFE_FREE(&ack->szGroup); + + if (nResult == PENDING_RESULT_PURGE) + { // removing obsolete items, just free the memory + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + + if (!ack || !wNewGroupID || !ack->hContact) // something went wrong + { + if (ack) servlistPendingRemoveContact(ack->hContact, 0, 0, PENDING_RESULT_FAILED); + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + + WORD wItemID = getSettingWord(ack->hContact, DBSETTING_SERVLIST_ID, 0); + WORD wGroupID = getSettingWord(ack->hContact, DBSETTING_SERVLIST_GROUP, 0); + + if (!wItemID) + { // We have no ID, so try to simply add the contact to serv-list + NetLog_Server("Unable to move contact (no ItemID) -> trying to add"); + // we know the GroupID, so directly call add + return servlistAddContact_gotGroup(szGroup, wNewGroupID, lParam, nResult); + } + + if (wGroupID == wNewGroupID) + { // Only move the contact if it had different GroupID + servlistPendingRemoveContact(ack->hContact, wItemID, wNewGroupID, PENDING_RESULT_SUCCESS); + NetLog_Server("Contact not moved to group on server side list (same Group)"); + return CALLBACK_RESULT_CONTINUE; + } + + ack->szGroupName = NULL; + ack->dwAction = SSA_CONTACT_SET_GROUP; + ack->wGroupId = wGroupID; + ack->wContactId = wItemID; + ack->wNewContactId = GenerateServerID(SSIT_ITEM, 0); // icq5 recreates also this, imitate + ack->wNewGroupId = wNewGroupID; + ack->lParam = 0; // we use this as a sign + + DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_REMOVEFROMLIST, ack->hContact, ack); + DWORD dwCookie2 = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, ack->hContact, ack); + + { // imitate icq5, previously here was different order, but AOL changed and it ceased to work + void *doubleObject = NULL; + + icq_sendServerContact(ack->hContact, dwCookie2, ICQ_LISTS_ADDTOLIST, wNewGroupID, ack->wNewContactId, SSO_CONTACT_SETGROUP | SSOF_BEGIN_OPERATION, 500, &doubleObject); + icq_sendServerContact(ack->hContact, dwCookie, ICQ_LISTS_REMOVEFROMLIST, wGroupID, wItemID, SSO_CONTACT_SETGROUP | SSOF_BEGIN_OPERATION, 500, &doubleObject); + } + return CALLBACK_RESULT_CONTINUE; +} + + +int CIcqProto::servlistMoveContact_Ready(HANDLE hContact, WORD contactID, WORD groupID, LPARAM lParam, int nResult) +{ + cookie_servlist_action *ack = (cookie_servlist_action*)lParam; + + if (nResult == PENDING_RESULT_PURGE) + { // removing obsolete items, just free the memory + SAFE_FREE(&ack->szGroup); + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + + WORD wItemID = getSettingWord(ack->hContact, DBSETTING_SERVLIST_ID, 0); + WORD wGroupID = getSettingWord(ack->hContact, DBSETTING_SERVLIST_GROUP, 0); + + if (!wGroupID && wItemID) + { // Only move the contact if it had an GroupID + servlistPendingRemoveContact(ack->hContact, contactID, groupID, PENDING_RESULT_FAILED); + + NetLog_Server("Failed to move contact to group on server side list (%s)", "no Group"); + SAFE_FREE(&ack->szGroup); + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + + // obtain a correct target groupid first + servlistCreateGroup(ack->szGroup, lParam, &CIcqProto::servlistMoveContact_gotTargetGroup); + + return CALLBACK_RESULT_POSTPONE; +} + + +// Called when contact should be moved from one group to another, create new, remove empty +void CIcqProto::servlistMoveContact(HANDLE hContact, const char *pszNewGroup) +{ + DWORD dwUin; + uid_str szUid; + + if (!hContact) return; // we do not move us, caused our uin was wrongly added to list + + // Get UID + if (getContactUid(hContact, &dwUin, &szUid)) + { // Could not do anything without uin + NetLog_Server("Failed to move contact to group on server side list (%s)", "no UID"); + return; + } + + if ((pszNewGroup != NULL) && (pszNewGroup[0]!='\0') && !getCListGroupExists(pszNewGroup)) + { // the contact moved to non existing group, do not do anything: MetaContact hack + NetLog_Server("Contact not moved - probably hiding by MetaContacts."); + return; + } + + if (!getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0)) /// FIXME:::: this should be in _ready + { // the contact is not stored on the server, check if we should try to add + if (!getSettingByte(NULL, "ServerAddRemove", DEFAULT_SS_ADDSERVER) || + DBGetContactSettingByte(hContact, "CList", "Hidden", 0)) + return; + } + cookie_servlist_action *ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + + if (!ack) + { // Could not do anything without cookie + NetLog_Server("Failed to add contact to server side list (%s)", "malloc failed"); + return; + } + else + { + ack->hContact = hContact; + ack->szGroup = null_strdup(pszNewGroup); + // call thru pending operations - makes sure the contact is ready to be moved + servlistPendingAddContact(hContact, 0, 0, (LPARAM)ack, &CIcqProto::servlistMoveContact_Ready, TRUE); + return; + } +} + + +int CIcqProto::servlistUpdateContact_Ready(HANDLE hContact, WORD contactID, WORD groupID, LPARAM lParam, int nResult) +{ + cookie_servlist_action *ack = (cookie_servlist_action*)lParam; + + if (nResult == PENDING_RESULT_PURGE) + { // removing obsolete items, just free the memory + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + WORD wItemID; + WORD wGroupID; + + // Get the contact's group ID + if (!(wGroupID = getSettingWord(hContact, DBSETTING_SERVLIST_GROUP, 0))) + { + servlistPendingRemoveContact(hContact, contactID, groupID, PENDING_RESULT_FAILED); + // Could not find a usable group ID + NetLog_Server("Failed to update contact's details on server side list (%s)", "no group ID"); + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + + // Get the contact's item ID + if (!(wItemID = getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0))) + { + servlistPendingRemoveContact(hContact, contactID, wGroupID, PENDING_RESULT_FAILED); + // Could not find usable item ID + NetLog_Server("Failed to update contact's details on server side list (%s)", "no item ID"); + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + + ack->dwAction = SSA_CONTACT_UPDATE; + ack->wContactId = wItemID; + ack->wGroupId = wGroupID; + ack->hContact = hContact; + + DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, hContact, ack); + + // There is no need to send ICQ_LISTS_CLI_MODIFYSTART or + // ICQ_LISTS_CLI_MODIFYEND when just changing nick name + icq_sendServerContact(hContact, dwCookie, ICQ_LISTS_UPDATEGROUP, wGroupID, wItemID, SSOP_ITEM_ACTION | SSOF_CONTACT, 400, NULL); + + return CALLBACK_RESULT_POSTPONE; +} + + +// Is called when a contact' details has been changed locally to update +// the server side details. +void CIcqProto::servlistUpdateContact(HANDLE hContact) +{ + DWORD dwUin; + uid_str szUid; + + // Get UID + if (getContactUid(hContact, &dwUin, &szUid)) + { + // Could not set nickname on server without uid + NetLog_Server("Failed to update contact's details on server side list (%s)", "no UID"); + return; + } + cookie_servlist_action *ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + + if (!ack) + { + // Could not allocate cookie - use old fake + NetLog_Server("Failed to update contact's details on server side list (%s)", "malloc failed"); + return; + } + else + { + ack->hContact = hContact; + // call thru pending operations - makes sure the contact is ready to be updated + servlistPendingAddContact(hContact, 0, 0, (LPARAM)ack, &CIcqProto::servlistUpdateContact_Ready, TRUE); + return; + } +} + + +int CIcqProto::servlistRenameGroup_Ready(const char *szGroup, WORD wGroupID, LPARAM lParam, int nResult) +{ + cookie_servlist_action *ack = (cookie_servlist_action*)lParam; + + if (nResult == PENDING_RESULT_PURGE) + { // only cleanup + if (ack) SAFE_FREE(&ack->szGroupName); + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + + if (!ack || !wGroupID) // something went wrong + { + servlistPendingRemoveGroup(szGroup, wGroupID, PENDING_RESULT_FAILED); + + if (ack) SAFE_FREE(&ack->szGroupName); + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + void *groupData; + int groupSize; + + if (groupData = collectBuddyGroup(wGroupID, &groupSize)) + { + ack->dwAction = SSA_GROUP_RENAME; + ack->wGroupId = wGroupID; + ack->szGroup = null_strdup(szGroup); // we need this name + // check if the new name is unique, create unique groupname if necessary + ack->szGroupName = getServListUniqueGroupName(ack->szGroupName, TRUE); + + DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack); + + icq_sendServerGroup(dwCookie, ICQ_LISTS_UPDATEGROUP, wGroupID, ack->szGroupName, groupData, groupSize, 0); + SAFE_FREE(&groupData); + } + return CALLBACK_RESULT_POSTPONE; +} + + +void CIcqProto::servlistRenameGroup(char *szGroup, WORD wGroupId, char *szNewGroup) +{ + char *szNewGroupName; + int nGroupLevel = getServListGroupLevel(wGroupId); + + if (nGroupLevel == -1) return; // we failed to prepare group + + if (!m_bSsiSimpleGroups) + { + char *szGroupName = szGroup; + int i = nGroupLevel; + while (i) + { // find correct part of grouppath + szGroupName = strstrnull(szGroupName, "\\"); + if (!szGroupName) return; // failed to get correct part of the grouppath + szGroupName++; + i--; + } + szNewGroupName = szNewGroup; + i = nGroupLevel; + while (i) + { // find correct part of new grouppath + szNewGroupName = strstrnull(szNewGroupName, "\\"); + if (!szNewGroupName) return; // failed to get correct part of the new grouppath + szNewGroupName++; + i--; + } + // truncate possible sub-groups + char *szLast = strstrnull(szGroupName, "\\"); + if (szLast) + szLast[0] = '\0'; + szLast = strstrnull(szNewGroupName, "\\"); + if (szLast) + szLast[0] = '\0'; + + // this group was not changed, nothing to rename + if (!strcmpnull(szGroupName, szNewGroupName)) return; + + szGroupName = szNewGroupName; + szNewGroupName = (char*)SAFE_MALLOC(strlennull(szGroupName) + 1 + nGroupLevel); + if (!szNewGroupName) return; // Failure + + for (i = 0; i < nGroupLevel; i++) + { // create level prefix + szNewGroupName[i] = '>'; + } + strcat(szNewGroupName, szGroupName); + } + else // simple groups do not require any conversion + szNewGroupName = null_strdup(szNewGroup); + + cookie_servlist_action* ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + if (!ack) + { // cookie failed + NetLog_Server("Error: Failed to allocate cookie"); + + SAFE_FREE(&szNewGroupName); + return; + } + // store new group name for future use + ack->szGroupName = szNewGroupName; + // call thru pending operations - makes sure the group is ready for rename + servlistPendingAddGroup(szGroup, wGroupId, (LPARAM)ack, &CIcqProto::servlistRenameGroup_Ready, TRUE); +} + + +int CIcqProto::servlistRemoveGroup_Ready(const char *szGroup, WORD groupID, LPARAM lParam, int nResult) +{ + cookie_servlist_action *ack = (cookie_servlist_action*)lParam; + + if (nResult == PENDING_RESULT_PURGE) + { // only cleanup + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + WORD wGroupID = getServListGroupLinkID(szGroup); + char *szGroupName; + + if (wGroupID && (szGroupName = getServListGroupName(wGroupID))) + { // the group is valid, check if it is empty + void *groupData = collectBuddyGroup(wGroupID, NULL); + + if (groupData) + { // the group is not empty, cannot delete + SAFE_FREE(&groupData); + SAFE_FREE(&szGroupName); + // end operation + servlistPendingRemoveGroup(szGroup, wGroupID, PENDING_RESULT_SUCCESS); + // cleanup + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; + } + + if (!CheckServerID((WORD)(wGroupID+1), 0) || getServListGroupLevel((WORD)(wGroupID+1)) == 0) + { // is next id an sub-group, if yes, we cannot delete this group + ack->dwAction = SSA_GROUP_REMOVE; + ack->wContactId = 0; + ack->wGroupId = wGroupID; + ack->hContact = NULL; + ack->szGroup = null_strdup(szGroup); // we need that name + ack->szGroupName = szGroupName; + DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_REMOVEFROMLIST, 0, ack); + + icq_sendServerGroup(dwCookie, ICQ_LISTS_REMOVEFROMLIST, ack->wGroupId, ack->szGroupName, NULL, 0, 0); + } + return CALLBACK_RESULT_POSTPONE; + } + // end operation + servlistPendingRemoveGroup(szGroup, groupID, PENDING_RESULT_SUCCESS); + // cleanup + SAFE_FREE((void**)&ack); + return CALLBACK_RESULT_CONTINUE; +} + + +void CIcqProto::servlistRemoveGroup(const char *szGroup, WORD wGroupId) +{ + if (!szGroup) return; + + cookie_servlist_action *ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + + if (!ack) + { // cookie failed + NetLog_Server("Error: Failed to allocate cookie"); + return; + } + + // call thru pending operations - makes sure the group is ready for removal + servlistPendingAddGroup(szGroup, wGroupId, (LPARAM)ack, &CIcqProto::servlistRemoveGroup_Ready, TRUE); +} + + +/*void CIcqProto::servlistMoveGroup(const char *szGroup, WORD wNewGroupId) +{ +// relocate the group +}*/ + + +void CIcqProto::resetServContactAuthState(HANDLE hContact, DWORD dwUin) +{ + WORD wContactId = getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0); + WORD wGroupId = getSettingWord(hContact, DBSETTING_SERVLIST_GROUP, 0); + + if (wContactId && wGroupId) + { + cookie_servlist_action *ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + + if (ack) + { // we have cookie good, go on + ack->hContact = hContact; + ack->wContactId = wContactId; + ack->wGroupId = wGroupId; + ack->dwAction = SSA_CONTACT_FIX_AUTH; + + DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, 0, hContact, ack); + + { + void *doubleObject = NULL; + + icq_sendServerContact(hContact, dwCookie, ICQ_LISTS_REMOVEFROMLIST, wGroupId, wContactId, SSO_CONTACT_FIXAUTH | SSOF_BEGIN_OPERATION | SSOF_END_OPERATION, 200, &doubleObject); + deleteSetting(hContact, DBSETTING_METAINFO_TOKEN); + deleteSetting(hContact, DBSETTING_METAINFO_TIME); + deleteSetting(hContact, DBSETTING_SERVLIST_DATA); + icq_sendServerContact(hContact, dwCookie, ICQ_LISTS_ADDTOLIST, wGroupId, wContactId, SSO_CONTACT_FIXAUTH | SSOF_BEGIN_OPERATION | SSOF_END_OPERATION, 200, &doubleObject); + } + } + else + NetLog_Server("Error: Failed to allocate cookie"); + } +} + +/***************************************** +* +* --- Miranda Contactlist Hooks --- +* +*/ + +int CIcqProto::ServListDbSettingChanged(WPARAM wParam, LPARAM lParam) +{ + DBCONTACTWRITESETTING* cws = (DBCONTACTWRITESETTING*)lParam; + + // TODO: Queue changes that occur while offline + if (!icqOnline() || !m_bSsiEnabled || bIsSyncingCL) + return 0; + +#ifdef _DEBUG + if (cws->value.type == DBVT_DELETED) + NetLog_Server("DB-Events: Module \"%s\", setting \"%s\" deleted.", cws->szModule, cws->szSetting); + else + NetLog_Server("DB-Events: Module \"%s\", setting \"%s\" changed, data type %x.", cws->szModule, cws->szSetting, cws->value.type); +#endif + + if (!strcmpnull(cws->szModule, "CList")) + { + // Has contact been renamed? + if (!strcmpnull(cws->szSetting, "MyHandle") && + getSettingByte(NULL, "StoreServerDetails", DEFAULT_SS_STORE)) + { // Update contact's details in server-list + servlistUpdateContact((HANDLE)wParam); + } + + // Has contact been moved to another group? + if (!strcmpnull(cws->szSetting, "Group") && + getSettingByte(NULL, "StoreServerDetails", DEFAULT_SS_STORE)) + { // Read group from DB + char* szNewGroup = getContactCListGroup((HANDLE)wParam); + + SAFE_FREE(&szNewGroup); + } + } + else if (!strcmpnull(cws->szModule, "UserInfo")) + { + if (!strcmpnull(cws->szSetting, "MyNotes") && + getSettingByte(NULL, "StoreServerDetails", DEFAULT_SS_STORE)) + { // Update contact's details in server-list + servlistUpdateContact((HANDLE)wParam); + } + } + + return 0; +} + + +int CIcqProto::ServListDbContactDeleted(WPARAM wParam, LPARAM lParam) +{ +#ifdef _DEBUG + NetLog_Server("DB-Events: Contact %x deleted.", wParam); +#endif + + DeleteFromContactsCache((HANDLE)wParam); + + if ( !icqOnline() && m_bSsiEnabled) + { // contact was deleted only locally - retrieve full list on next connect + setSettingWord((HANDLE)wParam, "SrvRecordCount", 0); + } + + if ( !icqOnline() || !m_bSsiEnabled) + return 0; + + { // we need all server contacts on local buddy list + DWORD dwUIN; + uid_str szUID; + + if (getContactUid((HANDLE)wParam, &dwUIN, &szUID)) + return 0; + + WORD wContactID = getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_ID, 0); + WORD wGroupID = getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_GROUP, 0); + WORD wVisibleID = getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_PERMIT, 0); + WORD wInvisibleID = getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_DENY, 0); + WORD wIgnoreID = getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_IGNORE, 0); + + // Remove from queue for user details request + icq_DequeueUser(dwUIN); + + // Close all opened peer connections + CloseContactDirectConns((HANDLE)wParam); + + if ((wGroupID && wContactID) || wVisibleID || wInvisibleID || wIgnoreID) + { + if (wContactID) + { // delete contact from server + servlistRemoveContact((HANDLE)wParam); + } + + if (wVisibleID) + { // detete permit record + icq_removeServerPrivacyItem((HANDLE)wParam, dwUIN, szUID, wVisibleID, SSI_ITEM_PERMIT); + } + + if (wInvisibleID) + { // delete deny record + icq_removeServerPrivacyItem((HANDLE)wParam, dwUIN, szUID, wInvisibleID, SSI_ITEM_DENY); + } + + if (wIgnoreID) + { // delete ignore record + icq_removeServerPrivacyItem((HANDLE)wParam, dwUIN, szUID, wIgnoreID, SSI_ITEM_IGNORE); + } + } + } + + return 0; +} + + +int CIcqProto::ServListCListGroupChange(WPARAM wParam, LPARAM lParam) +{ + HANDLE hContact = (HANDLE)wParam; + CLISTGROUPCHANGE *grpchg = (CLISTGROUPCHANGE*)lParam; + + if (!icqOnline() || !m_bSsiEnabled || bIsSyncingCL) + return 0; + + // only change server-list if it is allowed + if (!getSettingByte(NULL, "StoreServerDetails", DEFAULT_SS_STORE)) + return 0; + + + if (hContact == NULL) + { // change made to group + if (grpchg->pszNewName == NULL && grpchg->pszOldName != NULL) + { // group removed + char *szOldName = tchar_to_utf8(grpchg->pszOldName); + WORD wGroupId = getServListGroupLinkID(szOldName); + +#ifdef _DEBUG + NetLog_Server("CList-Events: Group %x:\"%s\" deleted.", wGroupId, szOldName); +#endif + if (wGroupId) + { // the group is known, remove from server + servlistPostPacket(NULL, 0, SSO_BEGIN_OPERATION, 100); // start server modifications here + servlistRemoveGroup(szOldName, wGroupId); + } + SAFE_FREE(&szOldName); + } + else if (grpchg->pszNewName != NULL && grpchg->pszOldName != NULL) + { // group renamed + char *szNewName = tchar_to_utf8(grpchg->pszNewName); + char *szOldName = tchar_to_utf8(grpchg->pszOldName); + WORD wGroupId = getServListGroupLinkID(szOldName); + +#ifdef _DEBUG + NetLog_Server("CList-Events: Group %x:\"%s\" changed to \"%s\".", wGroupId, szOldName, szNewName); +#endif + if (wGroupId) + { // group is known, rename on server + servlistRenameGroup(szOldName, wGroupId, szNewName); + } + SAFE_FREE(&szOldName); + SAFE_FREE(&szNewName); + } + } + else + { // change to contact + if (IsICQContact(hContact)) + { // our contact, fine move on the server as well + char *szNewName = grpchg->pszNewName ? tchar_to_utf8(grpchg->pszNewName) : NULL; + +#ifdef _DEBUG + NetLog_Server("CList-Events: Contact %x moved to group \"%s\".", hContact, szNewName); +#endif + servlistMoveContact(hContact, szNewName); + SAFE_FREE(&szNewName); + } + } + return 0; +} diff --git a/protocols/IcqOscarJ/src/icq_servlist.h b/protocols/IcqOscarJ/src/icq_servlist.h new file mode 100644 index 0000000000..76118ce3c0 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_servlist.h @@ -0,0 +1,172 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#ifndef __ICQ_SERVLIST_H +#define __ICQ_SERVLIST_H + +// actions: +#define SSA_CHECK_ROSTER 0 // request serv-list +#define SSA_VISIBILITY 1 // update visibility +#define SSA_CONTACT_UPDATE 2 // update contact's details +#define SSA_GROUP_RENAME 5 // rename group +#define SSA_PRIVACY_ADD 0xA // add privacy item +#define SSA_PRIVACY_REMOVE 0xB // remove privacy item +#define SSA_CONTACT_ADD 0x10 // add contact w/o auth +#define SSA_CONTACT_SET_GROUP 0x12 // move to group +#define SSA_CONTACT_REMOVE 0x13 // delete contact +#define SSA_CONTACT_FIX_AUTH 0x40 // reuploading contact for auth re-request +#define SSA_GROUP_ADD 0x15 // create group +#define SSA_GROUP_REMOVE 0x16 // delete group +#define SSA_GROUP_UPDATE 0x17 // update group +#define SSA_SERVLIST_ACK 0x20 // send proto ack only (UploadUI) +#define SSA_SETAVATAR 0x30 +#define SSA_REMOVEAVATAR 0x31 +#define SSA_IMPORT 7 +#define SSA_ACTION_GROUP 0x80 // grouped action + +struct CIcqProto; +// callback prototypes for pending operation mechanism: +typedef int (__cdecl CIcqProto::*PENDING_GROUP_CALLBACK)(const char* pszGroup, WORD wGroupId, LPARAM lParam, int nResult); +typedef int (__cdecl CIcqProto::*PENDING_CONTACT_CALLBACK)(HANDLE hContact, WORD wContactId, WORD wGroupId, LPARAM lParam, int nResult); + +// cookie struct for SSI actions +struct cookie_servlist_action +{ + HANDLE hContact; + char *szGroup; + WORD wContactId; + WORD wGroupId; + char *szGroupName; + WORD wNewContactId; + WORD wNewGroupId; + int dwAction; + LPARAM lParam; + int dwGroupCount; + cookie_servlist_action **pGroupItems; +}; + +// server id type groups +#define SSIT_ITEM 0x00000000 +#define SSIT_GROUP 0x00010000 + +// server id flags +#define SSIF_UNHANDLED 0x01000000 + + +// pending operations +#define PENDING_RESULT_SUCCESS 0x00 +#define PENDING_RESULT_INLINE 0x01 +#define PENDING_RESULT_FAILED 0x0F +#define PENDING_RESULT_PURGE 0x10 + +// serv-list update board +#define SSOG_SINGLE 0x00010000 +#define SSOG_DOUBLE 0x00020000 + +#define SSOF_CONTACT 0x00800000 +#define SSOF_BEGIN_OPERATION 0x00100000 +#define SSOF_END_OPERATION 0x00200000 +#define SSOF_IMPORT_OPERATION 0x00400000 + +#define SSOP_ITEM_ACTION 0x01000000 | SSOG_SINGLE +// SSA_PRIVACY_ADD +// SSA_CONTACT_ADD +// SSA_CONTACT_UPDATE +// SSA_VISIBILITY +// SSA_PRIVACY_REMOVE +// SSA_CONTACT_REMOVE +// SSA_SETAVATAR +// SSA_REMOVEAVATAR +#define SSOP_GROUP_ACTION 0x02000000 | SSOG_SINGLE +// SSA_GROUP_ADD +// SSA_GROUP_RENAME +// SSA_GROUP_UPDATE +// SSA_GROUP_REMOVE +#define SSO_CONTACT_SETGROUP 0x04000000 | SSOG_DOUBLE +// SSA_CONTACT_SET_GROUP +#define SSO_CONTACT_FIXAUTH 0x06000000 | SSOG_DOUBLE +// SSA_CONTACT_FIX_AUTH + +#define SSO_BEGIN_OPERATION 0x80000000 +#define SSO_END_OPERATION 0x40000000 + +#define SSOF_SEND_DIRECTLY 0x10000000 + +#define SSOF_ACTIONMASK 0x0000FFFF +#define SSOF_GROUPINGMASK 0x0F0FFFFF + + +#define MAX_SERVLIST_PACKET_ITEMS 200 + +// server-list request handler item +struct servlistgroupitem +{ // generic parent + DWORD dwOperation; + cookie_servlist_action* cookie; + icq_packet packet; + // perhaps add some dummy bytes +}; + +struct servlistgroupitemdouble: public servlistgroupitem +{ + icq_packet packet2; + WORD wAction2; +}; + +struct ssiqueueditems +{ + time_t tAdded; + int dwTimeout; + int nItems; + servlistgroupitem* pItems[MAX_SERVLIST_PACKET_ITEMS]; +}; + + +// cookie structs for pending records +struct servlistpendingoperation +{ + DWORD flags; + PENDING_GROUP_CALLBACK callback; + LPARAM param; +}; + +struct servlistpendingitem +{ + int nType; + HANDLE hContact; + char* szGroup; + WORD wContactID; + WORD wGroupID; + + servlistpendingoperation* operations; + int operationsCount; +}; + + +#endif /* __ICQ_SERVLIST_H */ diff --git a/protocols/IcqOscarJ/src/icq_uploadui.cpp b/protocols/IcqOscarJ/src/icq_uploadui.cpp new file mode 100644 index 0000000000..f5e5c937b3 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_uploadui.cpp @@ -0,0 +1,1019 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2008 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Implements Manage Server List dialog +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +static int bListInit = 0; +static HANDLE hItemAll; +static int dwUploadDelay = 1000; // initial setting, it is too low for icq server but good for short updates + +static HWND hwndUploadContacts=NULL; +static const UINT settingsControls[]={IDOK}; + +static WORD* pwGroupIds = NULL; +static int cbGroupIds = 0; + +// Init default clist options +static void ResetCListOptions(HWND hwndList) +{ + int i; + + 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_SETLEFTMARGIN, 2, 0); + SendMessage(hwndList, CLM_SETINDENT, 10, 0); + for(i=0; i<=FONTID_MAX; i++) + SendMessage(hwndList, CLM_SETTEXTCOLOR, i, GetSysColor(COLOR_WINDOWTEXT)); + SetWindowLongPtr(hwndList, GWL_STYLE, GetWindowLongPtr(hwndList, GWL_STYLE)|CLS_SHOWHIDDEN); + if (CallService(MS_CLUI_GETCAPS, 0, 0) & CLUIF_HIDEEMPTYGROUPS) // hide empty groups + SendMessage(hwndList, CLM_SETHIDEEMPTYGROUPS, (WPARAM) TRUE, 0); +} + +// Selects the "All contacts" checkbox if all other list entries +// are selected, deselects it if not. +static void UpdateAllContactsCheckmark(HWND hwndList, CIcqProto* ppro, HANDLE phItemAll) +{ + int check = 1; + + HANDLE hContact = ppro->FindFirstContact(); + while (hContact) + { + HANDLE hItem = (HANDLE)SendMessage(hwndList, CLM_FINDCONTACT, (WPARAM)hContact, 0); + if (hItem) + { + if (!SendMessage(hwndList, CLM_GETCHECKMARK, (WPARAM)hItem, 0)) + { // if any of our contacts is unchecked, uncheck all contacts as well + check = 0; + break; + } + } + hContact = ppro->FindNextContact(hContact); + } + + SendMessage(hwndList, CLM_SETCHECKMARK, (WPARAM)phItemAll, check); +} + +// Loop over all contacts and update the checkmark +// that indicates wether or not they are already uploaded +static int UpdateCheckmarks(HWND hwndList, CIcqProto* ppro, HANDLE phItemAll) +{ + int bAll = 1; + bListInit = 1; // lock CLC events + + HANDLE hContact = ppro->FindFirstContact(); + while (hContact) + { + HANDLE hItem = (HANDLE)SendMessage(hwndList, CLM_FINDCONTACT, (WPARAM)hContact, 0); + if (hItem) + { + if (ppro->getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0)) + SendMessage(hwndList, CLM_SETCHECKMARK, (WPARAM)hItem, 1); + else + bAll = 0; + } + hContact = ppro->FindNextContact(hContact); + } + + // Update the "All contacts" checkmark + if (phItemAll) + SendMessage(hwndList, CLM_SETCHECKMARK, (WPARAM)phItemAll, bAll); + + bListInit = 0; + + return bAll; +} + +static void DeleteOtherContactsFromControl(HWND hCtrl, CIcqProto* ppro) +{ + HANDLE hContact; + HANDLE hItem; + + hContact = db_find_first(); + while (hContact) + { + hItem = (HANDLE)SendMessage(hCtrl, CLM_FINDCONTACT, (WPARAM)hContact, 0); + if (hItem) + { + if (!ppro->IsICQContact(hContact)) + SendMessage(hCtrl, CLM_DELETEITEM, (WPARAM)hItem, 0); + } + hContact = db_find_next(hContact); + } +} + +static void AppendToUploadLog(HWND hwndDlg, const char *fmt, ...) +{ + va_list va; + char szText[1024]; + int iItem; + + va_start(va, fmt); + mir_vsnprintf(szText, sizeof(szText), fmt, va); + va_end(va); + + iItem = ListBoxAddStringUtf(GetDlgItem(hwndDlg, IDC_LOG), szText); + SendDlgItemMessage(hwndDlg, IDC_LOG, LB_SETTOPINDEX, iItem, 0); +} + +static void DeleteLastUploadLogLine(HWND hwndDlg) +{ + SendDlgItemMessage(hwndDlg, IDC_LOG, LB_DELETESTRING, SendDlgItemMessage(hwndDlg, IDC_LOG, LB_GETCOUNT, 0, 0)-1, 0); +} + +static void GetLastUploadLogLine(HWND hwndDlg, char *szBuf, size_t cbBuf) +{ + WCHAR str[MAX_PATH]; + SendDlgItemMessageW(hwndDlg, IDC_LOG, LB_GETTEXT, SendDlgItemMessage(hwndDlg, IDC_LOG, LB_GETCOUNT, 0, 0)-1, (LPARAM)str); + make_utf8_string_static(str, szBuf, cbBuf); +} + +static int GroupEnumIdsEnumProc(const char *szSetting,LPARAM lParam) +{ + if (szSetting && strlennull(szSetting)<5) + { // it is probably server group + char val[MAX_PATH+2]; // dummy + DBVARIANT dbv; + DBCONTACTGETSETTING cgs; + + dbv.type = DBVT_ASCIIZ; + dbv.pszVal = val; + dbv.cchVal = MAX_PATH; + + cgs.szModule=(char*)lParam; + cgs.szSetting=szSetting; + cgs.pValue=&dbv; + if(CallService(MS_DB_CONTACT_GETSETTINGSTATIC,(WPARAM)NULL,(LPARAM)&cgs)) + return 0; // this converts all string types to DBVT_ASCIIZ + if(dbv.type!=DBVT_ASCIIZ) + { // it is not a cached server-group name + return 0; + } + pwGroupIds = (WORD*)SAFE_REALLOC(pwGroupIds, (cbGroupIds+1)*sizeof(WORD)); + pwGroupIds[cbGroupIds] = (WORD)strtoul(szSetting, NULL, 0x10); + cbGroupIds++; + } + return 0; +} + +static void enumServerGroups(CIcqProto* ppro) +{ + DBCONTACTENUMSETTINGS dbces; + + char szModule[MAX_PATH+9]; + + strcpy(szModule, ppro->m_szModuleName); + strcat(szModule, "SrvGroups"); + + dbces.pfnEnumProc = &GroupEnumIdsEnumProc; + dbces.szModule = szModule; + dbces.lParam = (LPARAM)szModule; + + CallService(MS_DB_CONTACT_ENUMSETTINGS, (WPARAM)NULL, (LPARAM)&dbces); +} + +static DWORD sendUploadGroup(CIcqProto* ppro, WORD wAction, WORD wGroupId, char* szItemName) +{ + DWORD dwCookie; + cookie_servlist_action* ack; + + if (ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action))) + { // we have cookie good, go on + ack->wGroupId = wGroupId; + ack->dwAction = SSA_SERVLIST_ACK; + dwCookie = ppro->AllocateCookie(CKT_SERVERLIST, wAction, 0, ack); + ack->lParam = dwCookie; + + ppro->icq_sendServerGroup(dwCookie, wAction, ack->wGroupId, szItemName, NULL, 0, 0); + return dwCookie; + } + return 0; +} + +static DWORD sendUploadBuddy(CIcqProto* ppro, HANDLE hContact, WORD wAction, DWORD dwUin, char *szUID, WORD wContactId, WORD wGroupId, WORD wItemType) +{ + DWORD dwCookie; + cookie_servlist_action* ack; + + if (ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action))) + { // we have cookie good, go on + ack->hContact = hContact; + ack->wContactId = wContactId; + ack->wGroupId = wGroupId; + ack->dwAction = SSA_SERVLIST_ACK; + dwCookie = ppro->AllocateCookie(CKT_SERVERLIST, wAction, hContact, ack); + ack->lParam = dwCookie; + + if (wItemType == SSI_ITEM_BUDDY) + ppro->icq_sendServerContact(hContact, dwCookie, wAction, ack->wGroupId, ack->wContactId, SSOP_ITEM_ACTION | SSOF_CONTACT, 500, NULL); + else + ppro->icq_sendSimpleItem(dwCookie, wAction, dwUin, szUID, ack->wGroupId, ack->wContactId, wItemType, SSOP_ITEM_ACTION, 500); + + return dwCookie; + } + return 0; +} + +static char* getServerResultDesc(int wCode) +{ + switch (wCode) + { + case 0: return LPGEN("OK"); + case 2: return LPGEN("NOT FOUND"); + case 3: return LPGEN("ALREADY EXISTS"); + case 0xA: return LPGEN("INVALID DATA"); + case 0xC: return LPGEN("LIST FULL"); + default: return LPGEN("FAILED"); + } +} + +#define ACTION_NONE 0 +#define ACTION_ADDBUDDY 1 +#define ACTION_ADDBUDDYAUTH 2 +#define ACTION_REMOVEBUDDY 3 +#define ACTION_ADDGROUP 4 +#define ACTION_REMOVEGROUP 5 +#define ACTION_UPDATESTATE 6 +#define ACTION_MOVECONTACT 7 +#define ACTION_ADDVISIBLE 8 +#define ACTION_REMOVEVISIBLE 9 +#define ACTION_ADDINVISIBLE 10 +#define ACTION_REMOVEINVISIBLE 11 + +#define STATE_READY 1 +#define STATE_REGROUP 2 +#define STATE_ITEMS 3 +#define STATE_VISIBILITY 5 +#define STATE_CONSOLIDATE 4 + +#define M_PROTOACK (WM_USER+100) +#define M_UPLOADMORE (WM_USER+101) +#define M_INITCLIST (WM_USER+102) + +static INT_PTR CALLBACK DlgProcUploadList(HWND hwndDlg,UINT message,WPARAM wParam,LPARAM lParam) +{ + CIcqProto* ppro = (CIcqProto*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + + static int working; + static HANDLE hProtoAckHook; + static int currentSequence; + static int currentAction; + static int currentState; + static HANDLE hCurrentContact; + static int lastAckResult = 0; + static WORD wNewContactId; + static WORD wNewGroupId; + static char *szNewGroupName; + static WORD wNewVisibilityId; + + switch(message) { + case WM_INITDIALOG: + TranslateDialogDefault(hwndDlg); + + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam); + { + char str[MAX_PATH]; + + working = 0; + hProtoAckHook = NULL; + currentState = STATE_READY; + + ResetCListOptions(GetDlgItem(hwndDlg, IDC_CLIST)); + + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Select contacts you want to store on server."), str, MAX_PATH)); + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Ready..."), str, MAX_PATH)); + } + return TRUE; + + // The M_PROTOACK message is received when the + // server has responded to our last update packet + case M_PROTOACK: + { + int bMulti = 0; + ACKDATA *ack = (ACKDATA*)lParam; + char szLastLogLine[MAX_PATH]; + char str[MAX_PATH]; + + // Is this an ack we are waiting for? + if (strcmpnull(ack->szModule, ppro->m_szModuleName)) + break; + + if (ack->type == ICQACKTYPE_RATEWARNING) + { // we are sending tooo fast, slow down the process + if (ack->hProcess != (HANDLE)1) break; // check class + if (ack->lParam == 2 || ack->lParam == 3) // check status + { + GetLastUploadLogLine(hwndDlg, szLastLogLine, MAX_PATH); + DeleteLastUploadLogLine(hwndDlg); + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Server rate warning -> slowing down the process."), str, MAX_PATH)); + AppendToUploadLog(hwndDlg, szLastLogLine); + + dwUploadDelay *= 2; + + break; + } + if (ack->lParam == 4) dwUploadDelay /= 2; // the rate is ok, turn up + } + + if (ack->type != ICQACKTYPE_SERVERCLIST) + break; + + if ((int)ack->hProcess != currentSequence) + break; + + lastAckResult = ack->result == ACKRESULT_SUCCESS ? 0 : 1; + + switch (currentAction) { + case ACTION_ADDBUDDY: + if (ack->result == ACKRESULT_SUCCESS) + { + ppro->setSettingByte(hCurrentContact, "Auth", 0); + ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_ID, wNewContactId); + ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_GROUP, wNewGroupId); + break; + } + else + { // If the server refused to add the contact without authorization, + // we try again _with_ authorization TLV + DWORD dwUIN; + uid_str szUID; + + ppro->setSettingByte(hCurrentContact, "Auth", 1); + + if (!ppro->getContactUid(hCurrentContact, &dwUIN, &szUID)) + { + currentAction = ACTION_ADDBUDDYAUTH; + currentSequence = sendUploadBuddy(ppro, hCurrentContact, ICQ_LISTS_ADDTOLIST, dwUIN, szUID, wNewContactId, wNewGroupId, SSI_ITEM_BUDDY); + } + + return FALSE; + } + + case ACTION_ADDBUDDYAUTH: + if (ack->result == ACKRESULT_SUCCESS) + { + ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_ID, wNewContactId); + ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_GROUP, wNewGroupId); + } + else + { + ppro->deleteSetting(hCurrentContact, "Auth"); + ppro->FreeServerID(wNewContactId, SSIT_ITEM); + } + + break; + + case ACTION_REMOVEBUDDY: + if (ack->result == ACKRESULT_SUCCESS) + { // clear obsolete settings + ppro->FreeServerID(wNewContactId, SSIT_ITEM); + ppro->deleteSetting(hCurrentContact, DBSETTING_SERVLIST_ID); + ppro->deleteSetting(hCurrentContact, DBSETTING_SERVLIST_GROUP); + ppro->deleteSetting(hCurrentContact, "Auth"); + } + break; + + case ACTION_ADDGROUP: + if (ack->result == ACKRESULT_SUCCESS) + { + void* groupData; + int groupSize; + cookie_servlist_action* ack; + + ppro->setServListGroupName(wNewGroupId, szNewGroupName); // add group to list + ppro->setServListGroupLinkID(szNewGroupName, wNewGroupId); // grouppath is known + + groupData = ppro->collectGroups(&groupSize); + groupData = SAFE_REALLOC(groupData, groupSize+2); + *(((WORD*)groupData)+(groupSize>>1)) = wNewGroupId; // add this new group id + groupSize += 2; + + ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + if (ack) + { + DWORD dwCookie; // we do not use this + + ack->dwAction = SSA_SERVLIST_ACK; + dwCookie = ppro->AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack); + + ppro->icq_sendServerGroup(dwCookie, ICQ_LISTS_UPDATEGROUP, 0, ack->szGroupName, groupData, groupSize, 0); + } + SAFE_FREE((void**)&groupData); + } + else + ppro->FreeServerID(wNewGroupId, SSIT_GROUP); + + SAFE_FREE((void**)&szNewGroupName); + break; + + case ACTION_REMOVEGROUP: + if (ack->result == ACKRESULT_SUCCESS) + { + void* groupData; + int groupSize; + cookie_servlist_action* ack; + + ppro->FreeServerID(wNewGroupId, SSIT_GROUP); + ppro->setServListGroupName(wNewGroupId, NULL); // remove group from list + ppro->removeGroupPathLinks(wNewGroupId); // grouppath is known + + groupData = ppro->collectGroups(&groupSize); + + ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + if (ack) + { + DWORD dwCookie; // we do not use this + + ack->dwAction = SSA_SERVLIST_ACK; + dwCookie = ppro->AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack); + + ppro->icq_sendServerGroup(dwCookie, ICQ_LISTS_UPDATEGROUP, 0, ack->szGroupName, groupData, groupSize, 0); + } + SAFE_FREE((void**)&groupData); + } + break; + + case ACTION_UPDATESTATE: + // do nothing + break; + + case ACTION_MOVECONTACT: + if (ack->result == ACKRESULT_SUCCESS) + { + ppro->FreeServerID(ppro->getSettingWord(hCurrentContact, DBSETTING_SERVLIST_ID, 0), SSIT_ITEM); + ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_ID, wNewContactId); + ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_GROUP, wNewGroupId); + dwUploadDelay *= 2; // we double the delay here (2 packets) + } + break; + + case ACTION_ADDVISIBLE: + if (ack->result == ACKRESULT_SUCCESS) + ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_PERMIT, wNewContactId); + else + ppro->FreeServerID(wNewContactId, SSIT_ITEM); + break; + + case ACTION_ADDINVISIBLE: + if (ack->result == ACKRESULT_SUCCESS) + ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_DENY, wNewContactId); + else + ppro->FreeServerID(wNewContactId, SSIT_ITEM); + break; + + case ACTION_REMOVEVISIBLE: + if (ack->result == ACKRESULT_SUCCESS) + { + ppro->FreeServerID(wNewContactId, SSIT_ITEM); + ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_PERMIT, 0); + } + break; + + case ACTION_REMOVEINVISIBLE: + if (ack->result == ACKRESULT_SUCCESS) + { + ppro->FreeServerID(wNewContactId, SSIT_ITEM); + ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_DENY, 0); + } + break; + } + + // Update the log window + GetLastUploadLogLine(hwndDlg, szLastLogLine, MAX_PATH); + DeleteLastUploadLogLine(hwndDlg); + AppendToUploadLog(hwndDlg, "%s%s", szLastLogLine, + ICQTranslateUtfStatic(getServerResultDesc(ack->lParam), str, MAX_PATH)); + + if (!bMulti) + { + SetTimer(hwndDlg, M_UPLOADMORE, dwUploadDelay, 0); // delay + } + } + break; + + case WM_TIMER: + { + switch (wParam) + { + case M_UPLOADMORE: + KillTimer(hwndDlg, M_UPLOADMORE); + if (currentAction == ACTION_MOVECONTACT) + dwUploadDelay /= 2; // turn it back + + PostMessage(hwndDlg, M_UPLOADMORE, 0, 0); + + return 0; + } + } + + // The M_UPLOADMORE window message is received when the user presses 'Update' + // and every time an ack from the server has been taken care of. + case M_UPLOADMORE: + { + HANDLE hContact; + HANDLE hItem; + DWORD dwUin; + uid_str szUid; + char *pszNick; + char *pszGroup; + int isChecked; + int isOnServer; + BOOL bUidOk; + char str[MAX_PATH]; + HWND hwndList = GetDlgItem(hwndDlg, IDC_CLIST); + + switch (currentState) + { + case STATE_REGROUP: + + // TODO: iterate over all checked groups and create if needed + // if creation requires reallocation of groups do it here + + currentState = STATE_ITEMS; + hCurrentContact = NULL; + PostMessage(hwndDlg, M_UPLOADMORE, 0, 0); + break; + + case STATE_ITEMS: + // Iterate over all contacts until one is found that + // needs to be updated on the server + if (hCurrentContact == NULL) + hContact = ppro->FindFirstContact(); + else // we do not want to go thru all contacts over and over again + { + hContact = hCurrentContact; + if (lastAckResult) // if the last operation on this contact fail, do not do it again, go to next + hContact = ppro->FindNextContact(hContact); + } + + while (hContact) + { + hCurrentContact = hContact; + + hItem = (HANDLE)SendMessage(hwndList, CLM_FINDCONTACT, (WPARAM)hContact, 0); + if (hItem) + { + isChecked = SendMessage(hwndList, CLM_GETCHECKMARK, (WPARAM)hItem, 0) != 0; + isOnServer = ppro->getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0) != 0; + + bUidOk = !ppro->getContactUid(hContact, &dwUin, &szUid); + + // Is this one out of sync? + if (bUidOk && (isChecked != isOnServer)) + { + // Only upload custom nicks + pszNick = ppro->getSettingStringUtf(hContact, "CList", "MyHandle", NULL); + + if (isChecked) + { // Queue for uploading + pszGroup = ppro->getContactCListGroup(hContact); + if (!strlennull(pszGroup)) + pszGroup = null_strdup(DEFAULT_SS_GROUP); + + // Get group ID from cache, if not ready use parent group, if still not ready create one + wNewGroupId = ppro->getServListGroupLinkID(pszGroup); + if (!wNewGroupId && strstrnull(pszGroup, "\\") != NULL) + { // if it is sub-group, take master parent + strstrnull(pszGroup, "\\")[0] = '\0'; + wNewGroupId = ppro->getServListGroupLinkID(pszGroup); + } + if (!wNewGroupId && currentAction != ACTION_ADDGROUP) + { // if the group still does not exist and there was no try before, try to add group + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Adding group \"%s\"..."), str, MAX_PATH), pszGroup); + + wNewGroupId = ppro->GenerateServerID(SSIT_GROUP, 0); // ??? + szNewGroupName = pszGroup; + currentAction = ACTION_ADDGROUP; + currentSequence = sendUploadGroup(ppro, ICQ_LISTS_ADDTOLIST, wNewGroupId, pszGroup); + SAFE_FREE(&pszNick); + + return FALSE; + } + + SAFE_FREE(&pszGroup); + + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Uploading %s..."), str, MAX_PATH), pszNick ? pszNick : strUID(dwUin, szUid)); + + currentAction = ACTION_ADDBUDDY; + + if (wNewGroupId) + { + wNewContactId = ppro->GenerateServerID(SSIT_ITEM, 0); + + currentSequence = sendUploadBuddy(ppro, hCurrentContact, ICQ_LISTS_ADDTOLIST, dwUin, szUid, + wNewContactId, wNewGroupId, SSI_ITEM_BUDDY); + SAFE_FREE(&pszNick); + + return FALSE; + } + else + { + char szLastLogLine[MAX_PATH]; + // Update the log window with the failure and continue with next contact + GetLastUploadLogLine(hwndDlg, szLastLogLine, MAX_PATH); + DeleteLastUploadLogLine(hwndDlg); + AppendToUploadLog(hwndDlg, "%s%s", szLastLogLine, ICQTranslateUtfStatic(LPGEN("FAILED"), str, MAX_PATH)); + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("No upload group available"), str, MAX_PATH)); + ppro->NetLog_Server("Upload failed, no group"); + currentState = STATE_READY; + } + } + else + { // Queue for deletion + if (pszNick) + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Deleting %s..."), str, MAX_PATH), pszNick); + else + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Deleting %s..."), str, MAX_PATH), strUID(dwUin, szUid)); + + wNewGroupId = ppro->getSettingWord(hContact, DBSETTING_SERVLIST_GROUP, 0); + wNewContactId = ppro->getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0); + currentAction = ACTION_REMOVEBUDDY; + currentSequence = sendUploadBuddy(ppro, hContact, ICQ_LISTS_REMOVEFROMLIST, dwUin, szUid, + wNewContactId, wNewGroupId, SSI_ITEM_BUDDY); + } + SAFE_FREE((void**)&pszNick); + + break; + } + else if (bUidOk && isChecked) + { // the contact is and should be on server, check if it is in correct group, move otherwise + WORD wCurrentGroupId = ppro->getSettingWord(hContact, DBSETTING_SERVLIST_GROUP, 0); + + pszGroup = ppro->getContactCListGroup(hContact); + if (!strlennull(pszGroup)) + pszGroup = null_strdup(DEFAULT_SS_GROUP); + wNewGroupId = ppro->getServListGroupLinkID(pszGroup); + if (!wNewGroupId && strstrnull(pszGroup, "\\") != NULL) + { // if it is sub-group, take master parent + strstrnull(pszGroup, "\\")[0] = '\0'; + wNewGroupId = ppro->getServListGroupLinkID(pszGroup); + } + if (!wNewGroupId && currentAction != ACTION_ADDGROUP) + { // if the group still does not exist and there was no try before, try to add group + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Adding group \"%s\"..."), str, MAX_PATH), pszGroup); + + wNewGroupId = ppro->GenerateServerID(SSIT_GROUP, 0); + szNewGroupName = pszGroup; + currentAction = ACTION_ADDGROUP; + currentSequence = sendUploadGroup(ppro, ICQ_LISTS_ADDTOLIST, wNewGroupId, pszGroup); + + return FALSE; + } + if (wNewGroupId && (wNewGroupId != wCurrentGroupId)) + { // we have a group the contact should be in, move it + WORD wCurrentContactId = ppro->getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0); + BYTE bAuth = ppro->getSettingByte(hContact, "Auth", 0); + + pszNick = ppro->getSettingStringUtf(hContact, "CList", "MyHandle", NULL); + + if (pszNick) + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Moving %s to group \"%s\"..."), str, MAX_PATH), pszNick, pszGroup); + else + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Moving %s to group \"%s\"..."), str, MAX_PATH), strUID(dwUin, szUid), pszGroup); + + currentAction = ACTION_MOVECONTACT; + wNewContactId = ppro->GenerateServerID(SSIT_ITEM, 0); + sendUploadBuddy(ppro, hContact, ICQ_LISTS_REMOVEFROMLIST, dwUin, szUid, wCurrentContactId, wCurrentGroupId, SSI_ITEM_BUDDY); + currentSequence = sendUploadBuddy(ppro, hContact, ICQ_LISTS_ADDTOLIST, dwUin, szUid, wNewContactId, wNewGroupId, SSI_ITEM_BUDDY); + SAFE_FREE((void**)&pszNick); + SAFE_FREE((void**)&pszGroup); + + break; + } + SAFE_FREE((void**)&pszGroup); + } + } + hContact = db_find_next(hContact); + } + if (!hContact) + { + currentState = STATE_VISIBILITY; + hCurrentContact = NULL; + PostMessage(hwndDlg, M_UPLOADMORE, 0, 0); + } + break; + + case STATE_VISIBILITY: + // Iterate over all contacts until one is found that + // needs to be updated on the server + if (hCurrentContact == NULL) + hContact = ppro->FindFirstContact(); + else // we do not want to go thru all contacts over and over again + { + hContact = hCurrentContact; + if (lastAckResult) // if the last operation on this contact fail, do not do it again, go to next + hContact = ppro->FindNextContact(hContact); + } + + while (hContact) + { + WORD wApparentMode = ppro->getSettingWord(hContact, "ApparentMode", 0); + WORD wDenyId = ppro->getSettingWord(hContact, DBSETTING_SERVLIST_DENY, 0); + WORD wPermitId = ppro->getSettingWord(hContact, DBSETTING_SERVLIST_PERMIT, 0); + WORD wIgnoreId = ppro->getSettingWord(hContact, DBSETTING_SERVLIST_IGNORE, 0); + + hCurrentContact = hContact; + ppro->getContactUid(hContact, &dwUin, &szUid); + + if (wApparentMode == ID_STATUS_ONLINE) + { // contact is on the visible list + if (wPermitId == 0) + { + currentAction = ACTION_ADDVISIBLE; + wNewContactId = ppro->GenerateServerID(SSIT_ITEM, 0); + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Adding %s to visible list..."), str, MAX_PATH), strUID(dwUin, szUid)); + currentSequence = sendUploadBuddy(ppro, hContact, ICQ_LISTS_ADDTOLIST, dwUin, szUid, wNewContactId, 0, SSI_ITEM_PERMIT); + break; + } + } + if (wApparentMode == ID_STATUS_OFFLINE) + { // contact is on the invisible list + if (wDenyId == 0 && wIgnoreId == 0) + { + currentAction = ACTION_ADDINVISIBLE; + wNewContactId = ppro->GenerateServerID(SSIT_ITEM, 0); + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Adding %s to invisible list..."), str, MAX_PATH), strUID(dwUin, szUid)); + currentSequence = sendUploadBuddy(ppro, hContact, ICQ_LISTS_ADDTOLIST, dwUin, szUid, wNewContactId, 0, SSI_ITEM_DENY); + break; + } + } + if (wApparentMode != ID_STATUS_ONLINE) + { // contact is not on visible list + if (wPermitId != 0) + { + currentAction = ACTION_REMOVEVISIBLE; + wNewContactId = wPermitId; + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Deleting %s from visible list..."), str, MAX_PATH), strUID(dwUin, szUid)); + currentSequence = sendUploadBuddy(ppro, hContact, ICQ_LISTS_REMOVEFROMLIST, dwUin, szUid, wNewContactId, 0, SSI_ITEM_PERMIT); + break; + } + } + if (wApparentMode != ID_STATUS_OFFLINE) + { // contact is not on invisible list + if (wDenyId != 0) + { + currentAction = ACTION_REMOVEINVISIBLE; + wNewContactId = wDenyId; + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Deleting %s from invisible list..."), str, MAX_PATH), strUID(dwUin, szUid)); + currentSequence = sendUploadBuddy(ppro, hContact, ICQ_LISTS_REMOVEFROMLIST, dwUin, szUid, wNewContactId, 0, SSI_ITEM_DENY); + break; + } + } + hContact = db_find_next(hContact); + } + if (!hContact) + { + currentState = STATE_CONSOLIDATE; + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Cleaning groups"), str, MAX_PATH)); + EnableDlgItem(hwndDlg, IDCANCEL, FALSE); + enumServerGroups(ppro); + PostMessage(hwndDlg, M_UPLOADMORE, 0, 0); + } + break; + + case STATE_CONSOLIDATE: // updage group data, remove redundant groups + if (currentAction == ACTION_UPDATESTATE) + DeleteLastUploadLogLine(hwndDlg); + + if (cbGroupIds) // some groups in the list + { + void* groupData; + int groupSize; + + cbGroupIds--; + wNewGroupId = pwGroupIds[cbGroupIds]; + + if (groupData = ppro->collectBuddyGroup(wNewGroupId, &groupSize)) + { // the group is still not empty, just update it + char* pszGroup = ppro->getServListGroupName(wNewGroupId); + cookie_servlist_action* ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + + ack->dwAction = SSA_SERVLIST_ACK; + ack->wGroupId = wNewGroupId; + currentSequence = ppro->AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack); + ack->lParam = currentSequence; + currentAction = ACTION_UPDATESTATE; + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Updating group \"%s\"..."), str, MAX_PATH), pszGroup); + + ppro->icq_sendServerGroup(currentSequence, ICQ_LISTS_UPDATEGROUP, wNewGroupId, pszGroup, groupData, groupSize, 0); + + SAFE_FREE((void**)&pszGroup); + } + else // the group is empty, delete it if it does not have sub-groups + { + if (!ppro->CheckServerID((WORD)(wNewGroupId+1), 0) || ppro->getServListGroupLevel((WORD)(wNewGroupId+1)) == 0) + { // is next id an sub-group, if yes, we cannot delete this group + char *pszGroup = ppro->getServListGroupName(wNewGroupId); + currentAction = ACTION_REMOVEGROUP; + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Deleting group \"%s\"..."), str, MAX_PATH), pszGroup); + currentSequence = sendUploadGroup(ppro, ICQ_LISTS_REMOVEFROMLIST, wNewGroupId, pszGroup); + SAFE_FREE((void**)&pszGroup); + } + else // update empty group + { + char *pszGroup = ppro->getServListGroupName(wNewGroupId); + cookie_servlist_action *ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)); + + ack->dwAction = SSA_SERVLIST_ACK; + ack->wGroupId = wNewGroupId; + currentSequence = ppro->AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack); + ack->lParam = currentSequence; + currentAction = ACTION_UPDATESTATE; + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Updating group \"%s\"..."), str, MAX_PATH), pszGroup); + + ppro->icq_sendServerGroup(currentSequence, ICQ_LISTS_UPDATEGROUP, wNewGroupId, pszGroup, 0, 0, 0); + + SAFE_FREE((void**)&pszGroup); + } + } + SAFE_FREE((void**)&groupData); // free the memory + } + else + { // all groups processed + SAFE_FREE((void**)&pwGroupIds); + currentState = STATE_READY; + } + break; + } + + if (currentState == STATE_READY) + { + // All contacts are in sync + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("All operations complete"), str, MAX_PATH)); + EnableDlgItem(hwndDlg, IDCANCEL, TRUE); + SetDlgItemTextUtf(hwndDlg, IDCANCEL, ICQTranslateUtfStatic(LPGEN("Close"), str, MAX_PATH)); + // end server modifications here + ppro->servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); + working = 0; + // SendMessage(hwndList, CLM_SETGREYOUTFLAGS,0,0); + UpdateCheckmarks(hwndList, ppro, hItemAll); + // EnableWindow(hwndList, FALSE); + if (hProtoAckHook) + UnhookEvent(hProtoAckHook); + } + break; + } + + + case WM_COMMAND: + switch (LOWORD(wParam)) { + case IDOK: + SendDlgItemMessage(hwndDlg, IDC_LOG, LB_RESETCONTENT, 0, 0); + if (!ppro->icqOnline()) + { + char str[MAX_PATH]; + AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("You have to be online to sychronize the server-list !"), str, MAX_PATH)); + break; + } + working = 1; + hCurrentContact = NULL; + currentState = STATE_REGROUP; + currentAction = ACTION_NONE; + icq_ShowMultipleControls(hwndDlg, settingsControls, SIZEOF(settingsControls), SW_HIDE); + // SendDlgItemMessage(hwndDlg, IDC_CLIST, CLM_SETGREYOUTFLAGS, 0xFFFFFFFF, 0); + // InvalidateRect(GetDlgItem(hwndDlg, IDC_CLIST), NULL, FALSE); + EnableDlgItem(hwndDlg, IDC_CLIST, FALSE); + hProtoAckHook = HookEventMessage(ME_PROTO_ACK, hwndDlg, M_PROTOACK); + // start server modifications here + ppro->servlistPostPacket(NULL, 0, SSO_BEGIN_OPERATION | SSOF_IMPORT_OPERATION, 100); + PostMessage(hwndDlg, M_UPLOADMORE, 0, 0); + break; + + case IDCANCEL: // TODO: this must be clean + DestroyWindow(hwndDlg); + break; + } + break; + + case WM_NOTIFY: + switch(((NMHDR*)lParam)->idFrom) { + case IDC_CLIST: + { + HWND hClist = GetDlgItem(hwndDlg, IDC_CLIST); + + switch(((NMHDR*)lParam)->code) { + case CLN_OPTIONSCHANGED: + ResetCListOptions(hClist); + break; + + case CLN_NEWCONTACT: + case CLN_CONTACTMOVED: + // Delete non-icq contacts + DeleteOtherContactsFromControl(hClist, ppro); + if (hItemAll) + UpdateAllContactsCheckmark(hClist, ppro, hItemAll); + break; + + case CLN_LISTREBUILT: + { + int bCheck = false; + + // Delete non-icq contacts + if ( ppro ) { + DeleteOtherContactsFromControl(hClist, ppro); + if (!bListInit) // do not enter twice + bCheck = UpdateCheckmarks(hClist, ppro, NULL); + } + + if (!hItemAll) // Add the "All contacts" item + { + CLCINFOITEM cii = {0}; + + cii.cbSize = sizeof(cii); + cii.flags = CLCIIF_GROUPFONT | CLCIIF_CHECKBOX; + cii.pszText = TranslateT(LPGEN("** All contacts **")); + hItemAll = (HANDLE)SendMessage(hClist, CLM_ADDINFOITEM, 0, (LPARAM)&cii); + } + + SendMessage(hClist, CLM_SETCHECKMARK, (WPARAM)hItemAll, bCheck); + } + break; + + case CLN_CHECKCHANGED: + { + NMCLISTCONTROL *nm = (NMCLISTCONTROL*)lParam; + HANDLE hContact; + HANDLE hItem; + + if (bListInit) break; + + if (nm->flags&CLNF_ISINFO) + { + int check; + + check = SendMessage(hClist, CLM_GETCHECKMARK, (WPARAM)hItemAll, 0); + + hContact = ppro->FindFirstContact(); + while (hContact) + { + hItem = (HANDLE)SendMessage(hClist, CLM_FINDCONTACT, (WPARAM)hContact, 0); + if (hItem) + SendMessage(hClist, CLM_SETCHECKMARK, (WPARAM)hItem, check); + hContact = ppro->FindNextContact(hContact); + } + } + else + UpdateAllContactsCheckmark(hClist, ppro, hItemAll); + } + break; + } + } + break; + } + break; + + case WM_CLOSE: + DestroyWindow(hwndDlg); + break; + + case WM_DESTROY: + if (hProtoAckHook) + UnhookEvent(hProtoAckHook); + if (working) + { // end server modifications here + ppro->servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); + } + hwndUploadContacts = NULL; + working = 0; + break; + } + + return FALSE; +} + +void CIcqProto::ShowUploadContactsDialog(void) +{ + if (hwndUploadContacts == NULL) + { + hItemAll = NULL; + hwndUploadContacts = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_ICQUPLOADLIST), NULL, DlgProcUploadList, LPARAM(this)); + } + + SetForegroundWindow(hwndUploadContacts); +} diff --git a/protocols/IcqOscarJ/src/icq_xstatus.cpp b/protocols/IcqOscarJ/src/icq_xstatus.cpp new file mode 100644 index 0000000000..b567105dbe --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_xstatus.cpp @@ -0,0 +1,1381 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Angeli-Ka, Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Support for Custom Statuses +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" +#include "m_extraicons.h" +#include "..\icons_pack\src\resource.h" + + +extern HANDLE hExtraXStatus; + +void CListShowMenuItem(HANDLE hMenuItem, BYTE bShow); + +BYTE CIcqProto::getContactXStatus(HANDLE hContact) +{ + if (!m_bXStatusEnabled && !m_bMoodsEnabled) + return 0; + + BYTE bXStatus = getSettingByte(hContact, DBSETTING_XSTATUS_ID, 0); + + if (bXStatus < 1 || bXStatus > XSTATUS_COUNT) return 0; + + return bXStatus; +} + + +DWORD CIcqProto::sendXStatusDetailsRequest(HANDLE hContact, int bForced) +{ + DWORD dwCookie = 0; + + if (m_bXStatusEnabled && getContactXStatus(hContact) != 0) + { // only request custom status detail when the contact has one + int nNotifyLen = 94 + UINMAXLEN; + char *szNotify = (char*)_alloca(nNotifyLen); + + null_snprintf(szNotify, nNotifyLen, "cAwaySrvAwayStat1%d", m_dwLocalUIN); + + dwCookie = SendXtrazNotifyRequest(hContact, "srvMng", szNotify, bForced); + } + return dwCookie; +} + + +DWORD CIcqProto::requestXStatusDetails(HANDLE hContact, BOOL bAllowDelay) +{ + if (!validateStatusMessageRequest(hContact, MTYPE_SCRIPT_NOTIFY)) + return 0; // apply privacy rules + + if (!CheckContactCapabilities(hContact, CAPF_XSTATUS)) + return 0; // contact does not have xstatus + + // delay is disabled only if fired from dialog + if (!CheckContactCapabilities(hContact, CAPF_XTRAZ) && bAllowDelay) + return 0; // Contact does not support xtraz, do not request details + + struct rates_xstatus_request: public rates_queue_item { + protected: + virtual rates_queue_item* copyItem(rates_queue_item *aDest = NULL) { + rates_xstatus_request *pDest = (rates_xstatus_request*)aDest; + if (!pDest) + pDest = new rates_xstatus_request(ppro, wGroup); + + pDest->bForced = bForced; + return rates_queue_item::copyItem(pDest); + }; + public: + rates_xstatus_request(CIcqProto *ppro, WORD wGroup): rates_queue_item(ppro, wGroup) { }; + virtual ~rates_xstatus_request() { }; + + virtual void execute() { + dwCookie = ppro->sendXStatusDetailsRequest(hContact, bForced); + }; + + BOOL bForced; + DWORD dwCookie; + }; + + m_ratesMutex->Enter(); + WORD wGroup = m_rates->getGroupFromSNAC(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND); + m_ratesMutex->Leave(); + + rates_xstatus_request rr(this, wGroup); + rr.bForced = !bAllowDelay; + rr.hContact = hContact; + + // delay at least one sec if allowed + if (!handleRateItem(&rr, RQT_REQUEST, 1000, bAllowDelay)) + return rr.dwCookie; + + return -1; // delayed +} + + +static HANDLE LoadXStatusIconLibrary(TCHAR *path, const TCHAR *sub) +{ + TCHAR* p = _tcsrchr(path, '\\'); + HANDLE hLib; + + _tcscpy(p, sub); + _tcscat(p, _T("\\xstatus_ICQ.dll")); + if (hLib = LoadLibrary(path)) return hLib; + _tcscpy(p, sub); + _tcscat(p, _T("\\xstatus_icons.dll")); + if (hLib = LoadLibrary(path)) return hLib; + _tcscpy(p, _T("\\")); + return hLib; +} + +static TCHAR *InitXStatusIconLibrary(TCHAR *buf, size_t buf_size) +{ + TCHAR path[2*MAX_PATH]; + HMODULE hXStatusIconsDLL; + + // get miranda's exe path + GetModuleFileName(NULL, path, MAX_PATH); + + hXStatusIconsDLL = (HMODULE)LoadXStatusIconLibrary(path, _T("\\Icons")); + if (!hXStatusIconsDLL) // TODO: add "Custom Folders" support + hXStatusIconsDLL = (HMODULE)LoadXStatusIconLibrary(path, _T("\\Plugins")); + + if (hXStatusIconsDLL) + { + null_strcpy(buf, path, buf_size - 1); + + char ident[MAX_PATH]; + if (LoadStringA(hXStatusIconsDLL, IDS_IDENTIFY, ident, sizeof(ident)) == 0 || strcmpnull(ident, "# Custom Status Icons #")) + { // library is invalid + *buf = 0; + } + FreeLibrary(hXStatusIconsDLL); + } + else + *buf = 0; + + return buf; +} + + +HICON CIcqProto::getXStatusIcon(int bStatus, UINT flags) +{ + HICON icon = NULL; + + if (bStatus > 0 && bStatus <= XSTATUS_COUNT) + icon = hXStatusIcons[bStatus - 1]->GetIcon((flags & LR_BIGICON) != 0); + + if (flags & LR_SHARED || !icon) + return icon; + else + return CopyIcon(icon); +} + + +void CIcqProto::releaseXStatusIcon(int bStatus, UINT flags) +{ + if (bStatus > 0 && bStatus <= XSTATUS_COUNT) { + IcqIconHandle p = hXStatusIcons[bStatus - 1]; + if (p) + p->ReleaseIcon((flags & LR_BIGICON) != 0); + } +} + + +void CIcqProto::setContactExtraIcon(HANDLE hContact, int xstatus) +{ + HANDLE hIcon; + + if (hExtraXStatus == NULL) + { + if (xstatus > 0 && bXStatusExtraIconsReady < 2) + CListMW_ExtraIconsRebuild(0, 0); + + hIcon = (xstatus <= 0 ? (HANDLE)-1 : hXStatusExtraIcons[xstatus-1]); + + IconExtraColumn iec; + + iec.cbSize = sizeof(iec); + iec.hImage = hIcon; + iec.ColumnType = EXTRA_ICON_ADV1; + CallService(MS_CLIST_EXTRA_SET_ICON, (WPARAM)hContact, (LPARAM)&iec); + } + else + { + hIcon = (HANDLE) -1; + + if (xstatus <= 0) + { + ExtraIcon_SetIcon(hExtraXStatus, hContact, (char *) NULL); + } + else + { + char szTemp[MAX_PATH]; + null_snprintf(szTemp, sizeof(szTemp), "%s_xstatus%d", m_szModuleName, xstatus-1); + ExtraIcon_SetIcon(hExtraXStatus, hContact, szTemp); + } + } + + NotifyEventHooks(hxstatusiconchanged, (WPARAM)hContact, (LPARAM)hIcon); +} + + +int CIcqProto::CListMW_ExtraIconsRebuild(WPARAM wParam, LPARAM lParam) +{ + if ((m_bXStatusEnabled || m_bMoodsEnabled) && ServiceExists(MS_CLIST_EXTRA_ADD_ICON)) + { + for (int i = 0; i < XSTATUS_COUNT; i++) + { + hXStatusExtraIcons[i] = (HANDLE)CallService(MS_CLIST_EXTRA_ADD_ICON, (WPARAM)getXStatusIcon(i + 1, LR_SHARED), 0); + releaseXStatusIcon(i + 1, 0); + } + + if (!bXStatusExtraIconsReady) + { // try to hook the events again if they did not existed during init + HookProtoEvent(ME_CLIST_EXTRA_LIST_REBUILD, &CIcqProto::CListMW_ExtraIconsRebuild); + HookProtoEvent(ME_CLIST_EXTRA_IMAGE_APPLY, &CIcqProto::CListMW_ExtraIconsApply); + } + + bXStatusExtraIconsReady = 2; + } + return 0; +} + + +int CIcqProto::CListMW_ExtraIconsApply(WPARAM wParam, LPARAM lParam) +{ + if ((m_bXStatusEnabled || m_bMoodsEnabled) && ServiceExists(MS_CLIST_EXTRA_SET_ICON)) + { + if (IsICQContact((HANDLE)wParam)) + { + // only apply icons to our contacts, do not mess others + DWORD bXStatus = getContactXStatus((HANDLE)wParam); + + if ((m_bXStatusEnabled && CheckContactCapabilities((HANDLE)wParam, CAPF_XSTATUS)) || + (m_bMoodsEnabled && CheckContactCapabilities((HANDLE)wParam, CAPF_STATUS_MOOD))) + setContactExtraIcon((HANDLE)wParam, bXStatus); + } + } + return 0; +} + +#define NULLCAP {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + +capstr capXStatus[XSTATUS_COUNT] = { + {0x01, 0xD8, 0xD7, 0xEE, 0xAC, 0x3B, 0x49, 0x2A, 0xA5, 0x8D, 0xD3, 0xD8, 0x77, 0xE6, 0x6B, 0x92}, + {0x5A, 0x58, 0x1E, 0xA1, 0xE5, 0x80, 0x43, 0x0C, 0xA0, 0x6F, 0x61, 0x22, 0x98, 0xB7, 0xE4, 0xC7}, + {0x83, 0xC9, 0xB7, 0x8E, 0x77, 0xE7, 0x43, 0x78, 0xB2, 0xC5, 0xFB, 0x6C, 0xFC, 0xC3, 0x5B, 0xEC}, + {0xE6, 0x01, 0xE4, 0x1C, 0x33, 0x73, 0x4B, 0xD1, 0xBC, 0x06, 0x81, 0x1D, 0x6C, 0x32, 0x3D, 0x81}, + {0x8C, 0x50, 0xDB, 0xAE, 0x81, 0xED, 0x47, 0x86, 0xAC, 0xCA, 0x16, 0xCC, 0x32, 0x13, 0xC7, 0xB7}, + {0x3F, 0xB0, 0xBD, 0x36, 0xAF, 0x3B, 0x4A, 0x60, 0x9E, 0xEF, 0xCF, 0x19, 0x0F, 0x6A, 0x5A, 0x7F}, + {0xF8, 0xE8, 0xD7, 0xB2, 0x82, 0xC4, 0x41, 0x42, 0x90, 0xF8, 0x10, 0xC6, 0xCE, 0x0A, 0x89, 0xA6}, + {0x80, 0x53, 0x7D, 0xE2, 0xA4, 0x67, 0x4A, 0x76, 0xB3, 0x54, 0x6D, 0xFD, 0x07, 0x5F, 0x5E, 0xC6}, + {0xF1, 0x8A, 0xB5, 0x2E, 0xDC, 0x57, 0x49, 0x1D, 0x99, 0xDC, 0x64, 0x44, 0x50, 0x24, 0x57, 0xAF}, + {0x1B, 0x78, 0xAE, 0x31, 0xFA, 0x0B, 0x4D, 0x38, 0x93, 0xD1, 0x99, 0x7E, 0xEE, 0xAF, 0xB2, 0x18}, + {0x61, 0xBE, 0xE0, 0xDD, 0x8B, 0xDD, 0x47, 0x5D, 0x8D, 0xEE, 0x5F, 0x4B, 0xAA, 0xCF, 0x19, 0xA7}, + {0x48, 0x8E, 0x14, 0x89, 0x8A, 0xCA, 0x4A, 0x08, 0x82, 0xAA, 0x77, 0xCE, 0x7A, 0x16, 0x52, 0x08}, + {0x10, 0x7A, 0x9A, 0x18, 0x12, 0x32, 0x4D, 0xA4, 0xB6, 0xCD, 0x08, 0x79, 0xDB, 0x78, 0x0F, 0x09}, + {0x6F, 0x49, 0x30, 0x98, 0x4F, 0x7C, 0x4A, 0xFF, 0xA2, 0x76, 0x34, 0xA0, 0x3B, 0xCE, 0xAE, 0xA7}, + {0x12, 0x92, 0xE5, 0x50, 0x1B, 0x64, 0x4F, 0x66, 0xB2, 0x06, 0xB2, 0x9A, 0xF3, 0x78, 0xE4, 0x8D}, + {0xD4, 0xA6, 0x11, 0xD0, 0x8F, 0x01, 0x4E, 0xC0, 0x92, 0x23, 0xC5, 0xB6, 0xBE, 0xC6, 0xCC, 0xF0}, + {0x60, 0x9D, 0x52, 0xF8, 0xA2, 0x9A, 0x49, 0xA6, 0xB2, 0xA0, 0x25, 0x24, 0xC5, 0xE9, 0xD2, 0x60}, + {0x63, 0x62, 0x73, 0x37, 0xA0, 0x3F, 0x49, 0xFF, 0x80, 0xE5, 0xF7, 0x09, 0xCD, 0xE0, 0xA4, 0xEE}, + {0x1F, 0x7A, 0x40, 0x71, 0xBF, 0x3B, 0x4E, 0x60, 0xBC, 0x32, 0x4C, 0x57, 0x87, 0xB0, 0x4C, 0xF1}, + {0x78, 0x5E, 0x8C, 0x48, 0x40, 0xD3, 0x4C, 0x65, 0x88, 0x6F, 0x04, 0xCF, 0x3F, 0x3F, 0x43, 0xDF}, + {0xA6, 0xED, 0x55, 0x7E, 0x6B, 0xF7, 0x44, 0xD4, 0xA5, 0xD4, 0xD2, 0xE7, 0xD9, 0x5C, 0xE8, 0x1F}, + {0x12, 0xD0, 0x7E, 0x3E, 0xF8, 0x85, 0x48, 0x9E, 0x8E, 0x97, 0xA7, 0x2A, 0x65, 0x51, 0xE5, 0x8D}, + {0xBA, 0x74, 0xDB, 0x3E, 0x9E, 0x24, 0x43, 0x4B, 0x87, 0xB6, 0x2F, 0x6B, 0x8D, 0xFE, 0xE5, 0x0F}, + {0x63, 0x4F, 0x6B, 0xD8, 0xAD, 0xD2, 0x4A, 0xA1, 0xAA, 0xB9, 0x11, 0x5B, 0xC2, 0x6D, 0x05, 0xA1}, + {0x2C, 0xE0, 0xE4, 0xE5, 0x7C, 0x64, 0x43, 0x70, 0x9C, 0x3A, 0x7A, 0x1C, 0xE8, 0x78, 0xA7, 0xDC}, + {0x10, 0x11, 0x17, 0xC9, 0xA3, 0xB0, 0x40, 0xF9, 0x81, 0xAC, 0x49, 0xE1, 0x59, 0xFB, 0xD5, 0xD4}, + {0x16, 0x0C, 0x60, 0xBB, 0xDD, 0x44, 0x43, 0xF3, 0x91, 0x40, 0x05, 0x0F, 0x00, 0xE6, 0xC0, 0x09}, + {0x64, 0x43, 0xC6, 0xAF, 0x22, 0x60, 0x45, 0x17, 0xB5, 0x8C, 0xD7, 0xDF, 0x8E, 0x29, 0x03, 0x52}, + {0x16, 0xF5, 0xB7, 0x6F, 0xA9, 0xD2, 0x40, 0x35, 0x8C, 0xC5, 0xC0, 0x84, 0x70, 0x3C, 0x98, 0xFA}, + {0x63, 0x14, 0x36, 0xff, 0x3f, 0x8a, 0x40, 0xd0, 0xa5, 0xcb, 0x7b, 0x66, 0xe0, 0x51, 0xb3, 0x64}, + {0xb7, 0x08, 0x67, 0xf5, 0x38, 0x25, 0x43, 0x27, 0xa1, 0xff, 0xcf, 0x4c, 0xc1, 0x93, 0x97, 0x97}, + {0xdd, 0xcf, 0x0e, 0xa9, 0x71, 0x95, 0x40, 0x48, 0xa9, 0xc6, 0x41, 0x32, 0x06, 0xd6, 0xf2, 0x80}, + NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, + NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, + NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, + NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, + NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, + NULLCAP, NULLCAP, NULLCAP, NULLCAP}; + +const char *nameXStatus[XSTATUS_COUNT] = { + LPGEN("Angry"), // 23 + LPGEN("Taking a bath"), // 1 + LPGEN("Tired"), // 2 + LPGEN("Birthday"), // 3 + LPGEN("Drinking beer"), // 4 + LPGEN("Thinking"), // 5 + LPGEN("Eating"), // 80 + LPGEN("Watching TV"), // 7 + LPGEN("Meeting"), // 8 + LPGEN("Coffee"), // 9 + LPGEN("Listening to music"),// 10 + LPGEN("Business"), // 11 + LPGEN("Shooting"), // 12 + LPGEN("Having fun"), // 13 + LPGEN("On the phone"), // 14 + LPGEN("Gaming"), // 15 + LPGEN("Studying"), // 16 + LPGEN("Shopping"), // 0 + LPGEN("Feeling sick"), // 17 + LPGEN("Sleeping"), // 18 + LPGEN("Surfing"), // 19 + LPGEN("Internet"), // 20 + LPGEN("Working"), // 21 + LPGEN("Typing"), // 22 + LPGEN("Picnic"), // 66 + LPGEN("Cooking"), + LPGEN("Smoking"), + LPGEN("I'm high"), + LPGEN("On WC"), // 68 + LPGEN("To be or not to be"),// 77 + LPGEN("Watching pro7 on TV"), + LPGEN("Love"), // 61 + LPGEN("Hot Dog"), //6 + LPGEN("Rough"), //24 + LPGEN("Rock On"), //25 + LPGEN("Baby"), //26 + LPGEN("Soccer"), //27 + LPGEN("Pirate"), //28 + LPGEN("Cyclop"), //29 + LPGEN("Monkey"), //30 + LPGEN("Birdie"), //31 + LPGEN("Cool"), //32 + LPGEN("Evil"), //33 + LPGEN("Alien"), //34 + LPGEN("Scooter"), //35 + LPGEN("Mask"), //36 + LPGEN("Money"), //37 + LPGEN("Pilot"), //38 + LPGEN("Afro"), //39 + LPGEN("St. Patrick"), //40 + LPGEN("Headmaster"), //41 + LPGEN("Lips"), //42 + LPGEN("Ice-Cream"), //43 + LPGEN("Pink Lady"), //44 + LPGEN("Up yours"), //45 + LPGEN("Laughing"), //46 + LPGEN("Dog"), //47 + LPGEN("Candy"), //48 + LPGEN("Crazy Professor"),//50 + LPGEN("Ninja"), //51 + LPGEN("Cocktail"), //52 + LPGEN("Punch"), //53 + LPGEN("Donut"), //54 + LPGEN("Feeling Good"), //55 + LPGEN("Lollypop"), //56 + LPGEN("Oink Oink"), //57 + LPGEN("Kitty"), //58 + LPGEN("Sumo"), //59 + LPGEN("Broken hearted"),//60 + LPGEN("Free for Chat"), //62 + LPGEN("@home"), //63 + LPGEN("@work"), //64 + LPGEN("Strawberry"), //65 + LPGEN("Angel"), //67 + LPGEN("Pizza"), //69 + LPGEN("Snoring"), //70 + LPGEN("On my mobile"), //71 + LPGEN("Depressed"), //72 + LPGEN("Beetle"), //73 + LPGEN("Double Rainbow"),//74 + LPGEN("Basketball"), //75 + LPGEN("Cupid shot me"), //76 + LPGEN("Celebrating"), //78 + LPGEN("Sushi"), //79 + LPGEN("Playing"), //81 + LPGEN("Writing") //84 + }; + +const int moodXStatus[XSTATUS_COUNT] = { + 23, + 1, + 2, + 3, + 4, + 5, + 80, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 0, + 17, + 18, + 19, + 20, + 21, + 22, + 66, + -1, + -1, + -1, + 68, + 77, + -1, + 61, + 6, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, + 48, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 62, + 63, + 64, + 65, + 67, + 69, + 70, + 71, + 72, + 73, + 74, + 75, + 76, + 78, + 79, + 81, + 84}; + +void CIcqProto::handleXStatusCaps(DWORD dwUIN, char *szUID, HANDLE hContact, BYTE *caps, int capsize, char *moods, int moodsize) +{ + int bChanged = FALSE; + int nCustomStatusID = 0, nMoodID = 0; + + if (!m_bXStatusEnabled && !m_bMoodsEnabled) + { + ClearContactCapabilities(hContact, CAPF_STATUS_MOOD | CAPF_XSTATUS); + return; + } + int nOldXStatusID = getContactXStatus(hContact); + + if (m_bXStatusEnabled) + { + if (caps) + { // detect custom status capabilities + if (capsize > 0) + for (int i = 0; i < XSTATUS_COUNT; i++) + { + if (MatchCapability(caps, capsize, (const capstr*)capXStatus[i], BINARY_CAP_SIZE)) + { + BYTE bXStatusId = (BYTE)(i+1); + char str[MAX_PATH]; + + SetContactCapabilities(hContact, CAPF_XSTATUS); + + if (nOldXStatusID != bXStatusId) + { // only write default name when it is really needed, i.e. on Custom Status change + setSettingByte(hContact, DBSETTING_XSTATUS_ID, bXStatusId); + setSettingStringUtf(hContact, DBSETTING_XSTATUS_NAME, ICQTranslateUtfStatic(nameXStatus[i], str, MAX_PATH)); + deleteSetting(hContact, DBSETTING_XSTATUS_MSG); + + NetLog_Server("%s changed custom status to %s.", strUID(dwUIN, szUID), ICQTranslateUtfStatic(nameXStatus[i], str, MAX_PATH)); + bChanged = TRUE; + } +#ifdef _DEBUG + else + NetLog_Server("%s has custom status %s.", strUID(dwUIN, szUID), ICQTranslateUtfStatic(nameXStatus[i], str, MAX_PATH)); +#endif + + if (getSettingByte(NULL, "XStatusAuto", DEFAULT_XSTATUS_AUTO)) + requestXStatusDetails(hContact, TRUE); + + nCustomStatusID = bXStatusId; + + break; + } + } + + if (nCustomStatusID == 0) + { +#ifdef _DEBUG + if (m_iStatus != ID_STATUS_OFFLINE && CheckContactCapabilities(hContact, CAPF_XSTATUS)) + NetLog_Server("%s has removed custom status.", strUID(dwUIN, szUID)); +#endif + ClearContactCapabilities(hContact, CAPF_XSTATUS); + } + } +#ifdef _DEBUG + else if (CheckContactCapabilities(hContact, CAPF_XSTATUS)) + { + char str[MAX_PATH]; + NetLog_Server("%s has custom status %s.", strUID(dwUIN, szUID), ICQTranslateUtfStatic(nameXStatus[nOldXStatusID-1], str, MAX_PATH)); + } +#endif + } + if (m_bMoodsEnabled) + { + if (moods && moodsize < 32) + { // process custom statuses (moods) from ICQ6 + if (moodsize > 0) + for (int i = 0; i < XSTATUS_COUNT; i++) + { + char szMoodId[32], szMoodData[32]; + + null_strcpy(szMoodData, moods, moodsize); + + if (moodXStatus[i] == -1) continue; + null_snprintf(szMoodId, SIZEOF(szMoodId), "0icqmood%d", moodXStatus[i]); + if (!strcmpnull(szMoodId, szMoodData)) + { + BYTE bXStatusId = (BYTE)(i+1); + char str[MAX_PATH]; + + SetContactCapabilities(hContact, CAPF_STATUS_MOOD); + + if (nCustomStatusID == 0 && nOldXStatusID != bXStatusId) + { // only write default name when it is really needed, i.e. on Custom Status change + setSettingByte(hContact, DBSETTING_XSTATUS_ID, bXStatusId); + setSettingStringUtf(hContact, DBSETTING_XSTATUS_NAME, ICQTranslateUtfStatic(nameXStatus[i], str, MAX_PATH)); + deleteSetting(hContact, DBSETTING_XSTATUS_MSG); + + NetLog_Server("%s changed mood to %s.", strUID(dwUIN, szUID), ICQTranslateUtfStatic(nameXStatus[i], str, MAX_PATH)); + bChanged = TRUE; + } +#ifdef _DEBUG + else if (nOldXStatusID != bXStatusId) + NetLog_Server("%s changed mood to %s.", strUID(dwUIN, szUID), ICQTranslateUtfStatic(nameXStatus[i], str, MAX_PATH)); + else + NetLog_Server("%s has mood %s.", strUID(dwUIN, szUID), ICQTranslateUtfStatic(nameXStatus[i], str, MAX_PATH)); +#endif + // cannot retrieve mood details here - need to be processed with new user details + nMoodID = bXStatusId; + + break; + } + } + + if (nMoodID == 0 && moods) + { +#ifdef _DEBUG + if (m_iStatus != ID_STATUS_OFFLINE && CheckContactCapabilities(hContact, CAPF_STATUS_MOOD)) + NetLog_Server("%s has removed mood.", strUID(dwUIN, szUID)); +#endif + ClearContactCapabilities(hContact, CAPF_STATUS_MOOD); + } + } +#ifdef _DEBUG + else if (CheckContactCapabilities(hContact, CAPF_STATUS_MOOD)) + { // Mood was not changed, but contact has one, add a small log notice + char str[MAX_PATH]; + NetLog_Server("%s has mood %s.", strUID(dwUIN, szUID), ICQTranslateUtfStatic(nameXStatus[nOldXStatusID-1], str, MAX_PATH)); + } +#endif + } + + if (nCustomStatusID != 0 && nMoodID != 0 && nCustomStatusID != nMoodID) + NetLog_Server("Warning: Diverse custom statuses detected, using custom status."); + + if ((nCustomStatusID == 0 && (caps || !m_bXStatusEnabled)) && (nMoodID == 0 && (moods || !m_bMoodsEnabled))) + { + if (getSettingByte(hContact, DBSETTING_XSTATUS_ID, -1) != -1) + bChanged = TRUE; + deleteSetting(hContact, DBSETTING_XSTATUS_ID); + deleteSetting(hContact, DBSETTING_XSTATUS_NAME); + deleteSetting(hContact, DBSETTING_XSTATUS_MSG); + } + + if (m_bXStatusEnabled != 10 && m_bMoodsEnabled != 10) + { + setContactExtraIcon(hContact, nCustomStatusID ? nCustomStatusID : (nMoodID ? nMoodID : (moods ? 0 : nOldXStatusID))); + + if (bChanged) + NotifyEventHooks(hxstatuschanged, (WPARAM)hContact, 0); + } +} + + +void CIcqProto::updateServerCustomStatus(int fullUpdate) +{ + BYTE bXStatus = getContactXStatus(NULL); + + if (fullUpdate) + { // update client capabilities + if (m_bXStatusEnabled) + setUserInfo(); + + char szMoodData[32]; + + // prepare mood id + if (m_bMoodsEnabled && bXStatus && moodXStatus[bXStatus-1] != -1) + null_snprintf(szMoodData, SIZEOF(szMoodData), "0icqmood%d", moodXStatus[bXStatus-1]); + else + szMoodData[0] = '\0'; + + SetStatusMood(szMoodData, 1500); + } + + char *szStatusNote = NULL; + + if (bXStatus && (m_bXStatusEnabled || m_bMoodsEnabled)) + { // use custom status message as status note + szStatusNote = getSettingStringUtf(NULL, DBSETTING_XSTATUS_MSG, ""); + } + else + { // retrieve standard status message (e.g. custom status set to none) + char **pszMsg = MirandaStatusToAwayMsg(m_iStatus); + + m_modeMsgsMutex->Enter(); + if (pszMsg) + szStatusNote = null_strdup(*pszMsg); + m_modeMsgsMutex->Leave(); + // no default status message, set empty + if (!szStatusNote) + szStatusNote = null_strdup(""); + } + + if (szStatusNote) + SetStatusNote(szStatusNote, 1500, FALSE); + + SAFE_FREE(&szStatusNote); +} + + +static WNDPROC OldMessageEditProc; + +static LRESULT CALLBACK MessageEditSubclassProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam) +{ + switch(msg) { + case WM_CHAR: + if(wParam=='\n' && GetKeyState(VK_CONTROL)&0x8000) + { + PostMessage(GetParent(hwnd),WM_COMMAND,IDOK,0); + return 0; + } + if (wParam == 1 && GetKeyState(VK_CONTROL) & 0x8000) + { // ctrl-a + SendMessage(hwnd, EM_SETSEL, 0, -1); + return 0; + } + if (wParam == 23 && GetKeyState(VK_CONTROL) & 0x8000) + { // ctrl-w + SendMessage(GetParent(hwnd), WM_CLOSE, 0, 0); + return 0; + } + if (wParam == 127 && GetKeyState(VK_CONTROL) & 0x8000) + { // ctrl-backspace + DWORD start, end; + WCHAR *text; + + SendMessage(hwnd, EM_GETSEL, (WPARAM) & end, (LPARAM) (PDWORD) NULL); + SendMessage(hwnd, WM_KEYDOWN, VK_LEFT, 0); + SendMessage(hwnd, EM_GETSEL, (WPARAM) & start, (LPARAM) (PDWORD) NULL); + text = GetWindowTextUcs(hwnd); + MoveMemory(text + start, text + end, sizeof(WCHAR) * (strlennull(text) + 1 - end)); + SetWindowTextUcs(hwnd, text); + SAFE_FREE(&text); + SendMessage(hwnd, EM_SETSEL, start, start); + SendMessage(GetParent(hwnd), WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(hwnd), EN_CHANGE), (LPARAM) hwnd); + return 0; + } + break; + } + return CallWindowProc(OldMessageEditProc,hwnd,msg,wParam,lParam); +} + +struct SetXStatusData +{ + CIcqProto* ppro; + BYTE bAction; + BYTE bXStatus; + HANDLE hContact; + HANDLE hEvent; + DWORD iEvent; + int countdown; + char* okButtonFormat; +}; + +struct InitXStatusData +{ + CIcqProto* ppro; + BYTE bAction; + BYTE bXStatus; + char* szXStatusName; + char* szXStatusMsg; + HANDLE hContact; +}; + +#define HM_PROTOACK (WM_USER+10) +static INT_PTR CALLBACK SetXStatusDlgProc(HWND hwndDlg,UINT message,WPARAM wParam,LPARAM lParam) +{ + SetXStatusData *dat = (SetXStatusData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA); + char str[MAX_PATH]; + + switch(message) { + case HM_PROTOACK: + { + ACKDATA *ack = (ACKDATA*)lParam; + if (ack->type != ICQACKTYPE_XSTATUS_RESPONSE) break; + if (ack->hContact != dat->hContact) break; + if ((DWORD)ack->hProcess != dat->iEvent) break; + + ShowDlgItem(hwndDlg, IDC_RETRXSTATUS, SW_HIDE); + ShowDlgItem(hwndDlg, IDC_XMSG, SW_SHOW); + ShowDlgItem(hwndDlg, IDC_XTITLE, SW_SHOW); + SetDlgItemTextUtf(hwndDlg,IDOK,ICQTranslateUtfStatic(LPGEN("Close"), str, MAX_PATH)); + UnhookEvent(dat->hEvent); dat->hEvent = NULL; + char *szText = dat->ppro->getSettingStringUtf(dat->hContact, DBSETTING_XSTATUS_NAME, ""); + SetDlgItemTextUtf(hwndDlg, IDC_XTITLE, szText); + SAFE_FREE(&szText); + szText = dat->ppro->getSettingStringUtf(dat->hContact, DBSETTING_XSTATUS_MSG, ""); + SetDlgItemTextUtf(hwndDlg, IDC_XMSG, szText); + SAFE_FREE(&szText); + } + break; + + case WM_INITDIALOG: + { + InitXStatusData *init = (InitXStatusData*)lParam; + + TranslateDialogDefault(hwndDlg); + dat = (SetXStatusData*)SAFE_MALLOC(sizeof(SetXStatusData)); + dat->ppro = init->ppro; + SetWindowLongPtr(hwndDlg,GWLP_USERDATA,(LONG_PTR)dat); + dat->bAction = init->bAction; + + if (!init->bAction) + { // set our xStatus + dat->bXStatus = init->bXStatus; + SendDlgItemMessage(hwndDlg, IDC_XMSG, EM_LIMITTEXT, 1024, 0); + OldMessageEditProc = (WNDPROC)SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_XMSG),GWLP_WNDPROC,(LONG_PTR)MessageEditSubclassProc); + SetDlgItemTextUtf(hwndDlg, IDC_XMSG, init->szXStatusMsg); + + if (dat->ppro->m_bXStatusEnabled) + { // custom status enabled, prepare title edit + SendDlgItemMessage(hwndDlg, IDC_XTITLE, EM_LIMITTEXT, 256, 0); + OldMessageEditProc = (WNDPROC)SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_XTITLE),GWLP_WNDPROC,(LONG_PTR)MessageEditSubclassProc); + SetDlgItemTextUtf(hwndDlg, IDC_XTITLE, init->szXStatusName); + } + else + { // only moods enabled, hide title, resize message edit control + ShowDlgItem(hwndDlg, IDC_XTITLE_STATIC, SW_HIDE); + ShowDlgItem(hwndDlg, IDC_XTITLE, SW_HIDE); + MoveDlgItem(hwndDlg, IDC_XMSG_STATIC, 5, 0, 179, 8); + MoveDlgItem(hwndDlg, IDC_XMSG, 5, 9, 179, 65); + } + + dat->okButtonFormat = GetDlgItemTextUtf(hwndDlg,IDOK); + dat->countdown = 5; + SendMessage(hwndDlg, WM_TIMER, 0, 0); + SetTimer(hwndDlg,1,1000,0); + } + else + { // retrieve contact's xStatus + dat->hContact = init->hContact; + dat->bXStatus = dat->ppro->getContactXStatus(dat->hContact); + dat->okButtonFormat = NULL; + SendMessage(GetDlgItem(hwndDlg, IDC_XTITLE), EM_SETREADONLY, 1, 0); + SendMessage(GetDlgItem(hwndDlg, IDC_XMSG), EM_SETREADONLY, 1, 0); + + if (dat->ppro->CheckContactCapabilities(dat->hContact, CAPF_XSTATUS) && !dat->ppro->getSettingByte(NULL, "XStatusAuto", DEFAULT_XSTATUS_AUTO)) + { + SetDlgItemTextUtf(hwndDlg,IDOK,ICQTranslateUtfStatic(LPGEN("Cancel"), str, MAX_PATH)); + dat->hEvent = HookEventMessage(ME_PROTO_ACK, hwndDlg, HM_PROTOACK); + ShowDlgItem(hwndDlg, IDC_RETRXSTATUS, SW_SHOW); + ShowDlgItem(hwndDlg, IDC_XMSG, SW_HIDE); + ShowDlgItem(hwndDlg, IDC_XTITLE, SW_HIDE); + dat->iEvent = dat->ppro->requestXStatusDetails(dat->hContact, FALSE); + } + else + { + SetDlgItemTextUtf(hwndDlg,IDOK,ICQTranslateUtfStatic(LPGEN("Close"), str, MAX_PATH)); + dat->hEvent = NULL; + char *szText = dat->ppro->getSettingStringUtf(dat->hContact, DBSETTING_XSTATUS_NAME, ""); + SetDlgItemTextUtf(hwndDlg, IDC_XTITLE, szText); + SAFE_FREE(&szText); + + if (dat->ppro->CheckContactCapabilities(dat->hContact, CAPF_STATUS_MOOD) && !dat->ppro->CheckContactCapabilities(dat->hContact, CAPF_XSTATUS)) + { // only for clients supporting just moods and not custom status + szText = dat->ppro->getSettingStringUtf(dat->hContact, DBSETTING_STATUS_NOTE, ""); + // hide title, resize message edit control + ShowDlgItem(hwndDlg, IDC_XTITLE_STATIC, SW_HIDE); + ShowDlgItem(hwndDlg, IDC_XTITLE, SW_HIDE); + MoveDlgItem(hwndDlg, IDC_XMSG_STATIC, 5, 0, 179, 8); + MoveDlgItem(hwndDlg, IDC_XMSG, 5, 9, 179, 65); + } + else + szText = dat->ppro->getSettingStringUtf(dat->hContact, DBSETTING_XSTATUS_MSG, ""); + + SetDlgItemTextUtf(hwndDlg, IDC_XMSG, szText); + SAFE_FREE(&szText); + } + } + + if (dat->bXStatus) + { + SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)dat->ppro->getXStatusIcon(dat->bXStatus, LR_SHARED | LR_BIGICON)); + SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)dat->ppro->getXStatusIcon(dat->bXStatus, LR_SHARED)); + } + + char buf[MAX_PATH]; + char *format = GetWindowTextUtf(hwndDlg); + + null_snprintf(str, sizeof(str), format, dat->bXStatus?ICQTranslateUtfStatic(nameXStatus[dat->bXStatus-1], buf, MAX_PATH):""); + SetWindowTextUtf(hwndDlg, str); + SAFE_FREE(&format); + return TRUE; + } + case WM_TIMER: + if(dat->countdown==-1) + { + DestroyWindow(hwndDlg); + break; + } + { + null_snprintf(str,sizeof(str),dat->okButtonFormat,dat->countdown); + SetDlgItemTextUtf(hwndDlg,IDOK,str); + } + dat->countdown--; + break; + + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDOK: + DestroyWindow(hwndDlg); + break; + case IDC_XTITLE: + case IDC_XMSG: + if (!dat->bAction) + { // set our xStatus + KillTimer(hwndDlg,1); + SetDlgItemTextUtf(hwndDlg,IDOK,ICQTranslateUtfStatic(LPGEN("OK"), str, MAX_PATH)); + } + break; + } + break; + + case WM_DESTROY: + if (!dat->bAction) + { // set our xStatus + char szSetting[64]; + char *szValue; + + dat->ppro->setSettingByte(NULL, DBSETTING_XSTATUS_ID, dat->bXStatus); + szValue = GetDlgItemTextUtf(hwndDlg,IDC_XMSG); + null_snprintf(szSetting, 64, "XStatus%dMsg", dat->bXStatus); + dat->ppro->setSettingStringUtf(NULL, szSetting, szValue); + dat->ppro->setSettingStringUtf(NULL, DBSETTING_XSTATUS_MSG, szValue); + SAFE_FREE(&szValue); + + if (dat->ppro->m_bXStatusEnabled) + { + szValue = GetDlgItemTextUtf(hwndDlg,IDC_XTITLE); + null_snprintf(szSetting, 64, "XStatus%dName", dat->bXStatus); + dat->ppro->setSettingStringUtf(NULL, szSetting, szValue); + dat->ppro->setSettingStringUtf(NULL, DBSETTING_XSTATUS_NAME, szValue); + SAFE_FREE(&szValue); + + if (dat->bXStatus) + { + dat->ppro->releaseXStatusIcon(dat->bXStatus, LR_BIGICON); + dat->ppro->releaseXStatusIcon(dat->bXStatus, 0); + } + } + dat->ppro->updateServerCustomStatus(TRUE); + + SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_XMSG),GWLP_WNDPROC,(LONG_PTR)OldMessageEditProc); + if (dat->ppro->m_bXStatusEnabled) + SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_XTITLE),GWLP_WNDPROC,(LONG_PTR)OldMessageEditProc); + } + if (dat->hEvent) UnhookEvent(dat->hEvent); + SAFE_FREE(&dat->okButtonFormat); + SetWindowLongPtr(hwndDlg, GWLP_USERDATA, NULL); + SAFE_FREE((void**)&dat); + break; + + case WM_CLOSE: + DestroyWindow(hwndDlg); + break; + } + return FALSE; +} + + +void CIcqProto::setXStatusEx(BYTE bXStatus, BYTE bQuiet) +{ + CLISTMENUITEM mi = {0}; + BYTE bOldXStatus = getSettingByte(NULL, DBSETTING_XSTATUS_ID, 0); + + mi.cbSize = sizeof(mi); + + if (!m_bHideXStatusUI) + { + if (bOldXStatus <= XSTATUS_COUNT) + { + mi.flags = CMIM_FLAGS; + CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hXStatusItems[bOldXStatus], (LPARAM)&mi); + } + + mi.flags = CMIM_FLAGS | CMIF_CHECKED; + CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hXStatusItems[bXStatus], (LPARAM)&mi); + } + + if (bXStatus) + { + char szSetting[64]; + char str[MAX_PATH]; + char *szName = NULL, *szMsg = NULL; + + if (m_bXStatusEnabled) + { + null_snprintf(szSetting, 64, "XStatus%dName", bXStatus); + szName = getSettingStringUtf(NULL, szSetting, ICQTranslateUtfStatic(nameXStatus[bXStatus-1], str, MAX_PATH)); + } + null_snprintf(szSetting, 64, "XStatus%dMsg", bXStatus); + szMsg = getSettingStringUtf(NULL, szSetting, ""); + + null_snprintf(szSetting, 64, "XStatus%dStat", bXStatus); + if (!bQuiet && !getSettingByte(NULL, szSetting, 0)) + { + InitXStatusData init; + init.ppro = this; + init.bAction = 0; // set + init.bXStatus = bXStatus; + init.szXStatusName = szName; + init.szXStatusMsg = szMsg; + CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_SETXSTATUS), NULL, SetXStatusDlgProc, (LPARAM)&init); + } + else + { + setSettingByte(NULL, DBSETTING_XSTATUS_ID, bXStatus); + if (m_bXStatusEnabled) + setSettingStringUtf(NULL, DBSETTING_XSTATUS_NAME, szName); + setSettingStringUtf(NULL, DBSETTING_XSTATUS_MSG, szMsg); + + updateServerCustomStatus(TRUE); + } + SAFE_FREE(&szName); + SAFE_FREE(&szMsg); + } + else + { + setSettingByte(NULL, DBSETTING_XSTATUS_ID, bXStatus); + deleteSetting(NULL, DBSETTING_XSTATUS_NAME); + deleteSetting(NULL, DBSETTING_XSTATUS_MSG); + + updateServerCustomStatus(TRUE); + } +} + + +INT_PTR CIcqProto::menuXStatus(WPARAM wParam,LPARAM lParam,LPARAM fParam) +{ + setXStatusEx((BYTE)fParam, 0); + return 0; +} + + +void CIcqProto::InitXStatusItems(BOOL bAllowStatus) +{ + CLISTMENUITEM mi; + int i = 0, len = strlennull(m_szModuleName); + char srvFce[MAX_PATH + 64]; + char szItem[MAX_PATH + 64]; + int bXStatusMenuBuilt = 0; + + BYTE bXStatus = getContactXStatus(NULL); + + if (!m_bXStatusEnabled && !m_bMoodsEnabled) return; + + if (!bAllowStatus) return; + + // Custom Status UI is disabled, no need to continue items' init + if (m_bHideXStatusUI || m_bHideXStatusMenu) return; + + null_snprintf(szItem, sizeof(szItem), Translate("%s Custom Status"), m_szModuleName); + mi.cbSize = sizeof(mi); + mi.pszPopupName = szItem; + mi.popupPosition = 500084000; + mi.position = 2000040000; + + for (i = 0; i <= XSTATUS_COUNT; i++) + { + null_snprintf(srvFce, sizeof(srvFce), "%s/menuXStatus%d", m_szModuleName, i); + + mi.position++; + + if (!i) + bXStatusMenuBuilt = ServiceExists(srvFce); + + if (!bXStatusMenuBuilt) + CreateProtoServiceParam(srvFce+len, &CIcqProto::menuXStatus, i); + + mi.flags = (i ? CMIF_ICONFROMICOLIB : 0) | (bXStatus == i?CMIF_CHECKED:0); + mi.icolibItem = i ? hXStatusIcons[i-1]->Handle() : NULL; + mi.pszName = i ? (char*)nameXStatus[i-1] : (char *)LPGEN("None"); + mi.pszService = srvFce; + mi.pszContactOwner = m_szModuleName; + + hXStatusItems[i] = Menu_AddStatusMenuItem(&mi); + + // CMIF_HIDDEN does not work for adding services + CListShowMenuItem(hXStatusItems[i], !(m_bHideXStatusUI || m_bHideXStatusMenu)); + } +} + + +void CIcqProto::InitXStatusIcons() +{ + if (!m_bXStatusEnabled && !m_bMoodsEnabled) + return; + + TCHAR lib[2*MAX_PATH] = {0}; + TCHAR *icon_lib = InitXStatusIconLibrary(lib, SIZEOF(lib)); + + char szSection[MAX_PATH + 64], *szAccountName = tchar_to_utf8(m_tszUserName); + null_snprintf(szSection, sizeof(szSection), "Status Icons/%s/Custom Status", szAccountName); + SAFE_FREE(&szAccountName); + + for (int i = 0; i < XSTATUS_COUNT; i++) + { + char szTemp[64]; + + null_snprintf(szTemp, sizeof(szTemp), "xstatus%d", i); + hXStatusIcons[i] = IconLibDefine(nameXStatus[i], szSection, m_szModuleName, szTemp, icon_lib, -(IDI_XSTATUS1+i)); + } + + // initialize arrays for CList custom status icons + memset(bXStatusCListIconsValid, 0, sizeof(bXStatusCListIconsValid)); + memset(hXStatusCListIcons, -1, sizeof(hXStatusCListIcons)); +} + + +void CIcqProto::UninitXStatusIcons() +{ + for (int i = 0; i < XSTATUS_COUNT; i++) + IconLibRemove(&hXStatusIcons[i]); + + // clear clist icon state indicators + memset(bXStatusCListIconsValid, 0, sizeof(bXStatusCListIconsValid)); +} + + +INT_PTR CIcqProto::ShowXStatusDetails(WPARAM wParam, LPARAM lParam) +{ + InitXStatusData init; + init.ppro = this; + init.bAction = 1; // retrieve + init.hContact = (HANDLE)wParam; + CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_SETXSTATUS), NULL, SetXStatusDlgProc, (LPARAM)&init); + + return 0; +} + +INT_PTR CIcqProto::SetXStatus(WPARAM wParam, LPARAM lParam) +{ // obsolete (TODO: remove in next version) + if (!m_bXStatusEnabled && !m_bMoodsEnabled) return 0; + + if (wParam >= 0 && wParam <= XSTATUS_COUNT) + { + setXStatusEx((BYTE)wParam, 1); + return wParam; + } + return 0; +} + + +INT_PTR CIcqProto::GetXStatus(WPARAM wParam, LPARAM lParam) +{ // obsolete (TODO: remove in next version) + if (!m_bXStatusEnabled && !m_bMoodsEnabled) return 0; + + if (!icqOnline()) return 0; + + BYTE status = getContactXStatus(NULL); + + if (wParam) *((char**)wParam) = m_bXStatusEnabled ? DBSETTING_XSTATUS_NAME : NULL; + if (lParam) *((char**)lParam) = DBSETTING_XSTATUS_MSG; + + return status; +} + + +INT_PTR CIcqProto::SetXStatusEx(WPARAM wParam, LPARAM lParam) +{ + ICQ_CUSTOM_STATUS *pData = (ICQ_CUSTOM_STATUS*)lParam; + + if (!m_bXStatusEnabled && !m_bMoodsEnabled) return 1; + + if (pData->cbSize < sizeof(ICQ_CUSTOM_STATUS)) return 1; // Failure + + if (pData->flags & CSSF_MASK_STATUS) + { // set custom status + int status = *pData->status; + + if (status >= 0 && status <= XSTATUS_COUNT) + setXStatusEx((BYTE)status, 1); + else + return 1; // Failure + } + + if (pData->flags & (CSSF_MASK_NAME | CSSF_MASK_MESSAGE)) + { + BYTE status = getContactXStatus(NULL); + + if (!status) return 1; // Failure + + if (m_bXStatusEnabled && (pData->flags & CSSF_MASK_NAME)) + { // set custom status name + if (pData->flags & CSSF_UNICODE) + setSettingStringW(NULL, DBSETTING_XSTATUS_NAME, pData->pwszName); + else + setSettingString(NULL, DBSETTING_XSTATUS_NAME, pData->pszName); + } + if (pData->flags & CSSF_MASK_MESSAGE) + { // set custom status message + if (pData->flags & CSSF_UNICODE) + setSettingStringW(NULL, DBSETTING_XSTATUS_MSG, pData->pwszMessage); + else + setSettingString(NULL, DBSETTING_XSTATUS_MSG, pData->pszMessage); + + // update status note if used for custom status message + updateServerCustomStatus(FALSE); + } + } + + if (pData->flags & CSSF_DISABLE_UI) + { // hide menu items + contact menu item + m_bHideXStatusUI = (*pData->wParam) ? 0 : 1; + } + + if (pData->flags & CSSF_DISABLE_MENU) + { // hide menu items only + m_bHideXStatusMenu = (*pData->wParam) ? 0 : 1; + } + + return 0; // Success +} + + +INT_PTR CIcqProto::GetXStatusEx(WPARAM wParam, LPARAM lParam) +{ + ICQ_CUSTOM_STATUS *pData = (ICQ_CUSTOM_STATUS*)lParam; + HANDLE hContact = (HANDLE)wParam; + + if (!m_bXStatusEnabled && !m_bMoodsEnabled) return 1; + + if (pData->cbSize < sizeof(ICQ_CUSTOM_STATUS)) return 1; // Failure + + if (pData->flags & CSSF_MASK_STATUS) + { // fill status member + *pData->status = getContactXStatus(hContact); + } + + if (pData->flags & CSSF_MASK_NAME) + { // fill status name member + if (pData->flags & CSSF_DEFAULT_NAME) + { + int status = *pData->wParam; + + if (status < 1 || status > XSTATUS_COUNT) return 1; // Failure + + if (pData->flags & CSSF_UNICODE) + { + char *text = (char*)nameXStatus[status -1]; + + MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, text, -1, pData->pwszName, MAX_PATH); + } + else + strcpy(pData->pszName, (char*)nameXStatus[status - 1]); + } + else + { // moods does not support status title + if (!m_bXStatusEnabled) return 1; + + if (pData->flags & CSSF_UNICODE) + { + char *str = getSettingStringUtf(hContact, DBSETTING_XSTATUS_NAME, ""); + WCHAR *wstr = make_unicode_string(str); + + wcscpy(pData->pwszName, wstr); + SAFE_FREE(&str); + SAFE_FREE(&wstr); + } + else + { + DBVARIANT dbv = {0}; + + if (!getSettingString(hContact, DBSETTING_XSTATUS_NAME, &dbv) && dbv.pszVal) + strcpy(pData->pszName, dbv.pszVal); + else + strcpy(pData->pszName, ""); + + ICQFreeVariant(&dbv); + } + } + } + + if (pData->flags & CSSF_MASK_MESSAGE) + { // fill status message member + if (pData->flags & CSSF_UNICODE) + { + char *str = getSettingStringUtf(hContact, CheckContactCapabilities(hContact, CAPF_STATUS_MOOD) ? DBSETTING_STATUS_NOTE : DBSETTING_XSTATUS_MSG, ""); + WCHAR *wstr = make_unicode_string(str); + + wcscpy(pData->pwszMessage, wstr); + SAFE_FREE(&str); + SAFE_FREE(&wstr); + } + else + { + DBVARIANT dbv = {0}; + + if (!getSettingString(hContact, CheckContactCapabilities(hContact, CAPF_STATUS_MOOD) ? DBSETTING_STATUS_NOTE : DBSETTING_XSTATUS_MSG, &dbv) && dbv.pszVal) + strcpy(pData->pszMessage, dbv.pszVal); + else + strcpy(pData->pszMessage, ""); + + ICQFreeVariant(&dbv); + } + } + + if (pData->flags & CSSF_DISABLE_UI) + { + if (pData->wParam) *pData->wParam = !m_bHideXStatusUI; + } + + if (pData->flags & CSSF_DISABLE_MENU) + { + if (pData->wParam) *pData->wParam = !m_bHideXStatusMenu; + } + + if (pData->flags & CSSF_STATUSES_COUNT) + { + if (pData->wParam) *pData->wParam = XSTATUS_COUNT; + } + + if (pData->flags & CSSF_STR_SIZES) + { + DBVARIANT dbv = {DBVT_DELETED}; + + if (pData->wParam) + { + if (m_bXStatusEnabled && !getSettingString(hContact, DBSETTING_XSTATUS_NAME, &dbv)) + *pData->wParam = strlennull(dbv.pszVal); + else + *pData->wParam = 0; + ICQFreeVariant(&dbv); + } + if (pData->lParam) + { + if (!getSettingString(hContact, CheckContactCapabilities(hContact, CAPF_STATUS_MOOD) ? DBSETTING_STATUS_NOTE : DBSETTING_XSTATUS_MSG, &dbv)) + *pData->lParam = strlennull(dbv.pszVal); + else + *pData->lParam = 0; + ICQFreeVariant(&dbv); + } + } + + return 0; // Success +} + + +INT_PTR CIcqProto::GetXStatusIcon(WPARAM wParam, LPARAM lParam) +{ + if (!m_bXStatusEnabled && !m_bMoodsEnabled) return 0; + + if (!wParam) + wParam = getContactXStatus(NULL); + + if (wParam >= 1 && wParam <= XSTATUS_COUNT) + { + return (INT_PTR)getXStatusIcon((BYTE)wParam, lParam); + } + return 0; +} + + +INT_PTR CIcqProto::RequestXStatusDetails(WPARAM wParam, LPARAM lParam) +{ + HANDLE hContact = (HANDLE)wParam; + + if (!m_bXStatusEnabled) return 0; + + if (hContact && !getSettingByte(NULL, "XStatusAuto", DEFAULT_XSTATUS_AUTO) && + getContactXStatus(hContact) && CheckContactCapabilities(hContact, CAPF_XSTATUS)) + { // user has xstatus, no auto-retrieve details, valid contact, request details + return requestXStatusDetails(hContact, TRUE); + } + return 0; +} + + +INT_PTR CIcqProto::RequestAdvStatusIconIdx(WPARAM wParam, LPARAM lParam) +{ + if (!m_bXStatusEnabled && !m_bMoodsEnabled) return -1; + + BYTE bXStatus = getContactXStatus((HANDLE)wParam); + + if (bXStatus) + { + int idx=-1; + + if (!bXStatusCListIconsValid[bXStatus-1]) + { // adding icon + int idx = hXStatusCListIcons[bXStatus-1]; + HIMAGELIST hCListImageList = (HIMAGELIST)CallService(MS_CLIST_GETICONSIMAGELIST,0,0); + + if (hCListImageList) + { + HICON hXStatusIcon = getXStatusIcon(bXStatus, LR_SHARED); + + if (idx > 0) + ImageList_ReplaceIcon(hCListImageList, idx, hXStatusIcon); + else + hXStatusCListIcons[bXStatus-1] = ImageList_AddIcon(hCListImageList, hXStatusIcon); + // mark icon index in the array as valid + bXStatusCListIconsValid[bXStatus-1] = TRUE; + + releaseXStatusIcon(bXStatus, 0); + } + } + idx = bXStatusCListIconsValid[bXStatus-1] ? hXStatusCListIcons[bXStatus-1] : -1; + + if (idx > 0) + return (idx & 0xFFFF) << 16; + } + return -1; +} diff --git a/protocols/IcqOscarJ/src/icq_xtraz.cpp b/protocols/IcqOscarJ/src/icq_xtraz.cpp new file mode 100644 index 0000000000..2b73274365 --- /dev/null +++ b/protocols/IcqOscarJ/src/icq_xtraz.cpp @@ -0,0 +1,466 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Internal Xtraz API +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +void CIcqProto::handleXtrazNotify(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szMsg, int nMsgLen, BOOL bThruDC) +{ + char *szNotify = strstrnull(szMsg, ""); + char *szQuery = strstrnull(szMsg, ""); + + HANDLE hContact = HContactFromUIN(dwUin, NULL); + if (hContact) // user sent us xtraz, he supports it + SetContactCapabilities(hContact, CAPF_XTRAZ); + + if (szNotify && szQuery) + { // valid request + char *szWork, *szEnd; + int nNotifyLen, nQueryLen; + + szNotify += 8; + szQuery += 7; + szEnd = strstrnull(szMsg, ""); + if (!szEnd) szEnd = szMsg + nMsgLen; + nNotifyLen = (szEnd - szNotify); + szEnd = strstrnull(szMsg, ""); + if (!szEnd) szEnd = szNotify; + szNotify = DemangleXml(szNotify, nNotifyLen); + nQueryLen = (szEnd - szQuery); + szQuery = DemangleXml(szQuery, nQueryLen); + szWork = strstrnull(szQuery, ""); + szEnd = strstrnull(szQuery, ""); +#ifdef _DEBUG + NetLog_Server("Query: %s", szQuery); + NetLog_Server("Notify: %s", szNotify); +#endif + if (szWork && szEnd) + { // this is our plugin + szWork += 10; + *szEnd = '\0'; + + if (!stricmpnull(szWork, "srvMng") && strstrnull(szNotify, "AwayStat")) + { + char *szSender = strstrnull(szNotify, ""); + char *szEndSend = strstrnull(szNotify, ""); + + if (szSender && szEndSend) + { + szSender += 10; + *szEndSend = '\0'; + + if ((DWORD)atoi(szSender) == dwUin) + { + BYTE dwXId = m_bXStatusEnabled ? getContactXStatus(NULL) : 0; + + if (dwXId && validateStatusMessageRequest(hContact, MTYPE_SCRIPT_NOTIFY)) + { // apply privacy rules + NotifyEventHooks(m_modeMsgsEvent, (WPARAM)MTYPE_SCRIPT_NOTIFY, (LPARAM)dwUin); + + char *tmp = getSettingStringUtf(NULL, DBSETTING_XSTATUS_NAME, ""); + char *szXName = MangleXml(tmp, strlennull(tmp)); + SAFE_FREE(&tmp); + + tmp = getSettingStringUtf(NULL, DBSETTING_XSTATUS_MSG, ""); + char *szXMsg = MangleXml(tmp, strlennull(tmp)); + SAFE_FREE(&tmp); + + int nResponseLen = 212 + strlennull(szXName) + strlennull(szXMsg) + UINMAXLEN + 2; + char *szResponse = (char*)_alloca(nResponseLen + 1); + // send response + null_snprintf(szResponse, nResponseLen, + "" + "cAwaySrv" + "" + "" + "%d" + "%d" + "%s" + "%s", + m_dwLocalUIN, dwXId, szXName, szXMsg); + + SAFE_FREE(&szXName); + SAFE_FREE(&szXMsg); + + struct rates_xstatus_response: public rates_queue_item { + protected: + virtual rates_queue_item* copyItem(rates_queue_item *aDest = NULL) { + rates_xstatus_response *pDest = (rates_xstatus_response*)aDest; + if (!pDest) + pDest = new rates_xstatus_response(ppro, wGroup); + + pDest->bThruDC = bThruDC; + pDest->dwMsgID1 = dwMsgID1; + pDest->dwMsgID2 = dwMsgID2; + pDest->wCookie = wCookie; + pDest->szResponse = null_strdup(szResponse); + + return rates_queue_item::copyItem(pDest); + }; + public: + rates_xstatus_response(CIcqProto *ppro, WORD wGroup): rates_queue_item(ppro, wGroup), szResponse(NULL) { }; + virtual ~rates_xstatus_response() { if (bCreated) SAFE_FREE(&szResponse); }; + + virtual void execute() { + ppro->SendXtrazNotifyResponse(dwUin, dwMsgID1, dwMsgID2, wCookie, szResponse, strlennull(szResponse), bThruDC); + }; + + BOOL bThruDC; + DWORD dwMsgID1; + DWORD dwMsgID2; + WORD wCookie; + char *szResponse; + }; + + m_ratesMutex->Enter(); + WORD wGroup = m_rates->getGroupFromSNAC(ICQ_MSG_FAMILY, ICQ_MSG_RESPONSE); + m_ratesMutex->Leave(); + + rates_xstatus_response rr(this, wGroup); + rr.hContact = hContact; + rr.dwUin = dwUin; + rr.bThruDC = bThruDC; + rr.dwMsgID1 = dwMID; + rr.dwMsgID2 = dwMID2; + rr.wCookie = wCookie; + rr.szResponse = szResponse; + + handleRateItem(&rr, RQT_RESPONSE, 0, !bThruDC); + } + else if (dwXId) + NetLog_Server("Privacy: Ignoring XStatus request"); + else + NetLog_Server("Error: We are not in XStatus, skipping"); + } + else + NetLog_Server("Error: Invalid sender information"); + } + else + NetLog_Server("Error: Missing sender information"); + } + else + NetLog_Server("Error: Unknown plugin \"%s\" in Xtraz message", szWork); + } + else + NetLog_Server("Error: Missing PluginID in Xtraz message"); + + SAFE_FREE(&szNotify); + SAFE_FREE(&szQuery); + } + else + NetLog_Server("Error: Invalid Xtraz Notify message"); +} + + +void CIcqProto::handleXtrazNotifyResponse(DWORD dwUin, HANDLE hContact, WORD wCookie, char* szMsg, int nMsgLen) +{ + char *szMem, *szRes, *szEnd; + int nResLen; + +#ifdef _DEBUG + NetLog_Server("Received Xtraz Notify Response"); +#endif + + szRes = strstrnull(szMsg, ""); + szEnd = strstrnull(szMsg, ""); + + if (szRes && szEnd) + { // valid response + char *szNode, *szWork; + + szRes += 5; + nResLen = szEnd - szRes; + + szMem = szRes = DemangleXml(szRes, nResLen); + +#ifdef _DEBUG + NetLog_Server("Response: %s", szRes); +#endif + + BroadcastAck(hContact, ICQACKTYPE_XTRAZNOTIFY_RESPONSE, ACKRESULT_SUCCESS, (HANDLE)wCookie, (LPARAM)szRes); + +NextVal: + szNode = strstrnull(szRes, ""); else szEnd = NULL; + + if (szNode && szEnd) + { + *(szEnd-1) = '\0'; + szNode += 13; //one more than the length of the string to skip ' or " too + szWork = szEnd + 1; + + if (!stricmpnull(szNode, "cAwaySrv")) + { + int bChanged = FALSE; + + *szEnd = ' '; + szNode = strstrnull(szWork, ""); + szEnd = strstrnull(szWork, ""); + if (szNode && szEnd) + { + szNode += 7; + *szEnd = '\0'; + if (atoi(szNode) != getContactXStatus(hContact)) + { // this is strange - but go on + NetLog_Server("Warning: XStatusIds do not match!"); + } + *szEnd = ' '; + } + szNode = strstrnull(szWork, ""); + szEnd = strstrnull(szWork, ""); + if (szNode && szEnd) + { // we got XStatus title, save it + char *szXName, *szOldXName; + szNode += 7; + *szEnd = '\0'; + szXName = DemangleXml(szNode, strlennull(szNode)); + // check if the name changed + szOldXName = getSettingStringUtf(hContact, DBSETTING_XSTATUS_NAME, NULL); + if (strcmpnull(szOldXName, szXName)) + bChanged = TRUE; + SAFE_FREE(&szOldXName); + setSettingStringUtf(hContact, DBSETTING_XSTATUS_NAME, szXName); + SAFE_FREE(&szXName); + *szEnd = ' '; + } + szNode = strstrnull(szWork, ""); + szEnd = strstrnull(szWork, ""); + if (szNode && szEnd) + { // we got XStatus mode msg, save it + char *szXMsg, *szOldXMsg; + szNode += 6; + *szEnd = '\0'; + szXMsg = DemangleXml(szNode, strlennull(szNode)); + // check if the decription changed + szOldXMsg = getSettingStringUtf(hContact, DBSETTING_XSTATUS_NAME, NULL); + if (strcmpnull(szOldXMsg, szXMsg)) + bChanged = TRUE; + SAFE_FREE(&szOldXMsg); + setSettingStringUtf(hContact, DBSETTING_XSTATUS_MSG, szXMsg); + SAFE_FREE(&szXMsg); + } + BroadcastAck(hContact, ICQACKTYPE_XSTATUS_RESPONSE, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); + if (bChanged) + NotifyEventHooks(hxstatuschanged, (WPARAM)hContact, 0); + } + else + { + char *szSrvEnd = strstrnull(szEnd, ""); + + if (szSrvEnd && strstrnull(szSrvEnd, ""); + szEnd = strstrnull(szData, ""); + + if (szPid && szEnd) + { + szPid += 5; + + return DemangleXml(szPid, szEnd - szPid); + } + return NULL; +} + + +void CIcqProto::handleXtrazInvitation(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szMsg, int nMsgLen, BOOL bThruDC) +{ + HANDLE hContact; + char* szPluginID; + + hContact = HContactFromUIN(dwUin, NULL); + if (hContact) // user sent us xtraz, he supports it + SetContactCapabilities(hContact, CAPF_XTRAZ); + + szPluginID = getXmlPidItem(szMsg, nMsgLen); + if (!strcmpnull(szPluginID, "ICQChatRecv")) + { // it is a invitation to multi-user chat + } + else + { + NetLog_Uni(bThruDC, "Error: Unknown plugin \"%s\" in Xtraz message", szPluginID); + } + SAFE_FREE(&szPluginID); +} + + +void CIcqProto::handleXtrazData(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szMsg, int nMsgLen, BOOL bThruDC) +{ + HANDLE hContact; + char* szPluginID; + + hContact = HContactFromUIN(dwUin, NULL); + if (hContact) // user sent us xtraz, he supports it + SetContactCapabilities(hContact, CAPF_XTRAZ); + + szPluginID = getXmlPidItem(szMsg, nMsgLen); + if (!strcmpnull(szPluginID, "viewCard")) + { // it is a greeting card + char *szWork, *szEnd, *szUrl, *szNum; + + szWork = strstrnull(szMsg, ""); + szEnd = strstrnull(szMsg, ""); + if (szWork && szEnd) + { + int nDataLen = szEnd - szWork; + + szUrl = (char*)_alloca(nDataLen); + memcpy(szUrl, szWork+5, nDataLen); + szUrl[nDataLen - 5] = '\0'; + + if (!_strnicmp(szUrl, "view_", 5)) + { + szNum = szUrl + 5; + szWork = strstrnull(szUrl, ".html"); + if (szWork) + { + strcpy(szWork, ".php"); + strcat(szWork, szWork+5); + } + while (szWork = strstrnull(szUrl, "&")) + { // unescape & code + strcpy(szWork+1, szWork+5); + } + szWork = (char*)SAFE_MALLOC(nDataLen + MAX_PATH); + ICQTranslateUtfStatic(LPGEN("Greeting card:"), szWork, MAX_PATH); + strcat(szWork, "\r\nhttp://www.icq.com/friendship/pages/view_page_"); + strcat(szWork, szNum); + + // Create message to notify user + { + CCSDATA ccs; + PROTORECVEVENT pre = {0}; + int bAdded; + + ccs.szProtoService = PSR_MESSAGE; + ccs.hContact = HContactFromUIN(dwUin, &bAdded); + ccs.wParam = 0; + ccs.lParam = (LPARAM)⪯ + pre.timestamp = time(NULL); + pre.szMessage = szWork; + pre.flags = PREF_UTF; + + CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs); + } + SAFE_FREE(&szWork); + } + else + NetLog_Uni(bThruDC, "Error: Non-standard greeting card message"); + } + else + NetLog_Uni(bThruDC, "Error: Malformed greeting card message"); + } + else + { + NetLog_Uni(bThruDC, "Error: Unknown plugin \"%s\" in Xtraz message", szPluginID); + } + SAFE_FREE(&szPluginID); +} + + +// Functions really sending Xtraz stuff +DWORD CIcqProto::SendXtrazNotifyRequest(HANDLE hContact, char* szQuery, char* szNotify, int bForced) +{ + char *szQueryBody; + char *szNotifyBody; + DWORD dwUin; + int nBodyLen; + char *szBody; + DWORD dwCookie; + + if (getContactUid(hContact, &dwUin, NULL)) + return 0; // Invalid contact + + if (!CheckContactCapabilities(hContact, CAPF_XTRAZ) && !bForced) + return 0; // Contact does not support xtraz, do not send anything + + szQueryBody = MangleXml(szQuery, strlennull(szQuery)); + szNotifyBody = MangleXml(szNotify, strlennull(szNotify)); + nBodyLen = strlennull(szQueryBody) + strlennull(szNotifyBody) + 41; + szBody = (char*)_alloca(nBodyLen); + nBodyLen = null_snprintf(szBody, nBodyLen, "%s%s", szQueryBody, szNotifyBody); + SAFE_FREE((void**)&szQueryBody); + SAFE_FREE((void**)&szNotifyBody); + + // Set up the ack type + cookie_message_data *pCookieData = CreateMessageCookie(MTYPE_SCRIPT_NOTIFY, ACKTYPE_CLIENT); + dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData); + + // have we a open DC, send through that + if (m_bDCMsgEnabled && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0)) + icq_sendXtrazRequestDirect(hContact, dwCookie, szBody, nBodyLen, MTYPE_SCRIPT_NOTIFY); + else + icq_sendXtrazRequestServ(dwUin, dwCookie, szBody, nBodyLen, pCookieData); + + return dwCookie; +} + + +void CIcqProto::SendXtrazNotifyResponse(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szResponse, int nResponseLen, BOOL bThruDC) +{ + char *szResBody = MangleXml(szResponse, nResponseLen); + int nBodyLen = strlennull(szResBody) + 21; + char *szBody = (char*)_alloca(nBodyLen); + HANDLE hContact = HContactFromUIN(dwUin, NULL); + + if (hContact != INVALID_HANDLE_VALUE && !CheckContactCapabilities(hContact, CAPF_XTRAZ)) + { + SAFE_FREE(&szResBody); + return; // Contact does not support xtraz, do not send anything + } + + nBodyLen = null_snprintf(szBody, nBodyLen, "%s", szResBody); + SAFE_FREE(&szResBody); + + // Was request received thru DC and have we a open DC, send through that + if (bThruDC && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0)) + icq_sendXtrazResponseDirect(hContact, wCookie, szBody, nBodyLen, MTYPE_SCRIPT_NOTIFY); + else + icq_sendXtrazResponseServ(dwUin, dwMID, dwMID2, wCookie, szBody, nBodyLen, MTYPE_SCRIPT_NOTIFY); +} diff --git a/protocols/IcqOscarJ/src/icqosc_svcs.cpp b/protocols/IcqOscarJ/src/icqosc_svcs.cpp new file mode 100644 index 0000000000..ee6dd1731c --- /dev/null +++ b/protocols/IcqOscarJ/src/icqosc_svcs.cpp @@ -0,0 +1,816 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// High-level code for exported API services +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +INT_PTR CIcqProto::AddServerContact(WPARAM wParam, LPARAM lParam) +{ + DWORD dwUin; + uid_str szUid; + + if (!m_bSsiEnabled) return 0; + + // Does this contact have a UID? + if (!getContactUid((HANDLE)wParam, &dwUin, &szUid) && !getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_ID, 0) && !getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_IGNORE, 0)) + { /// TODO: remove possible 0x6A TLV in contact server-list data!!! + // Read group from DB + char *pszGroup = getContactCListGroup((HANDLE)wParam); + + servlistAddContact((HANDLE)wParam, pszGroup); + SAFE_FREE((void**)&pszGroup); + } + return 0; +} + + +static int LookupDatabaseSetting(const FieldNamesItem* table, int code, DBVARIANT *dbv, BYTE type) +{ + char *text = LookupFieldName(table, code); + + if (!text) + { + dbv->type = DBVT_DELETED; + return 1; + } + + if (type == DBVT_ASCIIZ) + { + dbv->pszVal = mir_strdup(Translate(text)); + dbv->type = DBVT_ASCIIZ; + } + else if (type == DBVT_UTF8 || !type) + { + char tmp[MAX_PATH]; + + dbv->pszVal = mir_strdup(ICQTranslateUtfStatic(text, tmp, MAX_PATH)); + dbv->type = DBVT_UTF8; + } + else if (type == DBVT_WCHAR) + { + WCHAR* wtext = make_unicode_string(text); + + dbv->pwszVal = mir_wstrdup(TranslateW(wtext)); + dbv->type = DBVT_WCHAR; + + SAFE_FREE((void**)&wtext); + } + return 0; // Success +} + +INT_PTR CIcqProto::GetInfoSetting(WPARAM wParam, LPARAM lParam) +{ + DBCONTACTGETSETTING *cgs = (DBCONTACTGETSETTING*)lParam; + BYTE type = cgs->pValue->type; + + cgs->pValue->type = 0; // original type without conversion + INT_PTR rc = CallService(MS_DB_CONTACT_GETSETTING_STR, wParam, lParam); + + if (!rc) + { // Success + DBVARIANT dbv; + + memcpy(&dbv, cgs->pValue, sizeof(DBVARIANT)); + + if (dbv.type == DBVT_BLOB) + { + cgs->pValue->pbVal = (BYTE*)mir_alloc(dbv.cpbVal); + + memcpy(cgs->pValue->pbVal, dbv.pbVal, dbv.cpbVal); + } + else if (dbv.type == DBVT_ASCIIZ || dbv.type == DBVT_UTF8) + { // convert to the desired type + if (!type) + type = dbv.type; + + if (dbv.type == type) + { // type is correct, only move it to miranda's heap + cgs->pValue->pszVal = mir_strdup(dbv.pszVal); + } + else if (type == DBVT_WCHAR) + { + if (dbv.type != DBVT_UTF8) + { + int len = MultiByteToWideChar(CP_ACP, 0, dbv.pszVal, -1, NULL, 0); + cgs->pValue->pwszVal = (WCHAR*)mir_alloc((len + 1)*sizeof(WCHAR)); + if (cgs->pValue->pwszVal == NULL) + rc = 1; + else + { + MultiByteToWideChar(CP_ACP, 0, dbv.pszVal, -1, cgs->pValue->pwszVal, len); + cgs->pValue->pwszVal[len] = '\0'; + } + } + else + { + char *savePtr = dbv.pszVal ? strcpy((char*)_alloca(strlennull(dbv.pszVal) + 1), dbv.pszVal) : NULL; + if (!mir_utf8decode(savePtr, &cgs->pValue->pwszVal)) + rc = 1; + } + } + else if (type == DBVT_UTF8) + { + cgs->pValue->pszVal = mir_utf8encode(dbv.pszVal); + if (cgs->pValue->pszVal == NULL) + rc = 1; + } + else if (type == DBVT_ASCIIZ) + { + cgs->pValue->pszVal = mir_strdup(dbv.pszVal); + mir_utf8decode(cgs->pValue->pszVal, NULL); + } + + cgs->pValue->type = type; + } + else if (!strcmpnull(cgs->szModule, m_szModuleName) && (dbv.type == DBVT_BYTE || dbv.type == DBVT_WORD || dbv.type == DBVT_DWORD)) + { + int code = (dbv.type == DBVT_BYTE) ? dbv.bVal : ((dbv.type == DBVT_WORD) ? dbv.wVal : dbv.dVal); + + if (!strcmpnull(cgs->szSetting, "Language1") || !strcmpnull(cgs->szSetting, "Language2") || !strcmpnull(cgs->szSetting, "Language3")) + rc = LookupDatabaseSetting(languageField, code, cgs->pValue, type); + else if (!strcmpnull(cgs->szSetting, "Country") || !strcmpnull(cgs->szSetting, "OriginCountry") || !strcmpnull(cgs->szSetting, "CompanyCountry")) + { + if (code == 420) code = 42; // conversion of obsolete codes (OMG!) + else if (code == 421) code = 4201; + else if (code == 102) code = 1201; + rc = LookupDatabaseSetting(countryField, code, cgs->pValue, type); + } + else if (!strcmpnull(cgs->szSetting, "Gender")) + rc = LookupDatabaseSetting(genderField, code, cgs->pValue, type); + else if (!strcmpnull(cgs->szSetting, "MaritalStatus")) + rc = LookupDatabaseSetting(maritalField, code, cgs->pValue, type); + else if (!strcmpnull(cgs->szSetting, "StudyLevel")) + rc = LookupDatabaseSetting(studyLevelField, code, cgs->pValue, type); + else if (!strcmpnull(cgs->szSetting, "CompanyIndustry")) + rc = LookupDatabaseSetting(industryField, code, cgs->pValue, type); + else if (!strcmpnull(cgs->szSetting, "Interest0Cat") || !strcmpnull(cgs->szSetting, "Interest1Cat") || !strcmpnull(cgs->szSetting, "Interest2Cat") || !strcmpnull(cgs->szSetting, "Interest3Cat")) + rc = LookupDatabaseSetting(interestsField, code, cgs->pValue, type); + } + // Release database memory + ICQFreeVariant(&dbv); + } + + return rc; +} + + +INT_PTR CIcqProto::ChangeInfoEx(WPARAM wParam, LPARAM lParam) +{ + if (icqOnline() && wParam) + { + PBYTE buf = NULL; + int buflen = 0; + BYTE b; + + // userinfo + ppackTLVWord(&buf, &buflen, 0x1C2, (WORD)GetACP()); + + if (wParam & CIXT_CONTACT) + { // contact information + BYTE *pBlock = NULL; + int cbBlock = 0; + int nItems = 0; + + // Emails + nItems += ppackTLVWordStringItemFromDB(&pBlock, &cbBlock, "e-mail0", 0x78, 0x64, 0x00); + nItems += ppackTLVWordStringItemFromDB(&pBlock, &cbBlock, "e-mail1", 0x78, 0x64, 0x00); + nItems += ppackTLVWordStringItemFromDB(&pBlock, &cbBlock, "e-mail2", 0x78, 0x64, 0x00); + ppackTLVBlockItems(&buf, &buflen, 0x8C, &nItems, &pBlock, (WORD*)&cbBlock, FALSE); + + // Phones + nItems += ppackTLVWordStringItemFromDB(&pBlock, &cbBlock, "Phone", 0x6E, 0x64, 0x01); + nItems += ppackTLVWordStringItemFromDB(&pBlock, &cbBlock, "CompanyPhone", 0x6E, 0x64, 0x02); + nItems += ppackTLVWordStringItemFromDB(&pBlock, &cbBlock, "Cellular", 0x6E, 0x64, 0x03); + nItems += ppackTLVWordStringItemFromDB(&pBlock, &cbBlock, "Fax", 0x6E, 0x64, 0x04); + nItems += ppackTLVWordStringItemFromDB(&pBlock, &cbBlock, "CompanyFax", 0x6E, 0x64, 0x05); + ppackTLVBlockItems(&buf, &buflen, 0xC8, &nItems, &pBlock, (WORD*)&cbBlock, FALSE); + + ppackTLVByte(&buf, &buflen, 0x1EA, getSettingByte(NULL, "AllowSpam", 0)); + } + + if (wParam & CIXT_BASIC) + { // upload basic user info + ppackTLVStringUtfFromDB(&buf, &buflen, "Nick", 0x78); + ppackTLVStringUtfFromDB(&buf, &buflen, "FirstName", 0x64); + ppackTLVStringUtfFromDB(&buf, &buflen, "LastName", 0x6E); + ppackTLVStringUtfFromDB(&buf, &buflen, "About", 0x186); + } + + if (wParam & CIXT_MORE) + { + b = getSettingByte(NULL, "Gender", 0); + ppackTLVByte(&buf, &buflen, 0x82, (BYTE)(b ? (b == 'M' ? 2 : 1) : 0)); + + ppackTLVDateFromDB(&buf, &buflen, "BirthYear", "BirthMonth", "BirthDay", 0x1A4); + + ppackTLVWord(&buf, &buflen, 0xAA, getSettingByte(NULL, "Language1", 0)); + ppackTLVWord(&buf, &buflen, 0xB4, getSettingByte(NULL, "Language2", 0)); + ppackTLVWord(&buf, &buflen, 0xBE, getSettingByte(NULL, "Language3", 0)); + + ppackTLVWord(&buf, &buflen, 0x12C, getSettingByte(NULL, "MaritalStatus", 0)); + } + + if (wParam & CIXT_WORK) + { + BYTE *pBlock = NULL; + int cbBlock = 0; + int nItems = 1; + + // Jobs + ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "CompanyPosition", 0x64); + ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "Company", 0x6E); + ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "CompanyDepartment", 0x7D); + ppackTLVStringFromDB(&pBlock, &cbBlock, "CompanyHomepage", 0x78); + ppackTLVWord(&pBlock, &cbBlock, 0x82, getSettingWord(NULL, "CompanyIndustry", 0)); + ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "CompanyStreet", 0xAA); + ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "CompanyCity", 0xB4); + ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "CompanyState", 0xBE); + ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "CompanyZIP", 0xC8); + ppackTLVDWord(&pBlock, &cbBlock, 0xD2, getSettingWord(NULL, "CompanyCountry", 0)); + /// TODO: pack unknown data (need to preserve them in Block Items) + ppackTLVBlockItems(&buf, &buflen, 0x118, &nItems, &pBlock, (WORD*)&cbBlock, TRUE); + + // ppackTLVWord(&buf, &buflen, getSettingWord(NULL, "CompanyOccupation", 0), TLV_OCUPATION, 1); // Lost In Conversion + } + + if (wParam & CIXT_EDUCATION) + { + BYTE *pBlock = NULL; + int cbBlock = 0; + int nItems = 1; + + // Studies + ppackTLVWord(&pBlock, &cbBlock, 0x64, getSettingWord(NULL, "StudyLevel", 0)); + ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "StudyInstitute", 0x6E); + ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "StudyDegree", 0x78); + ppackTLVWord(&pBlock, &cbBlock, 0x8C, getSettingWord(NULL, "StudyYear", 0)); + ppackTLVBlockItems(&buf, &buflen, 0x10E, &nItems, &pBlock, (WORD*)&cbBlock, TRUE); + } + + if (wParam & CIXT_LOCATION) + { + BYTE *pBlock = NULL; + int cbBlock = 0; + int nItems = 1; + + // Home Address + ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "Street", 0x64); + ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "City", 0x6E); + ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "State", 0x78); + ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "ZIP", 0x82); + ppackTLVDWord(&pBlock, &cbBlock, 0x8C, getSettingWord(NULL, "Country", 0)); + ppackTLVBlockItems(&buf, &buflen, 0x96, &nItems, &pBlock, (WORD*)&cbBlock, TRUE); + + nItems = 1; + // Origin Address + ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "OriginStreet", 0x64); + ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "OriginCity", 0x6E); + ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "OriginState", 0x78); + ppackTLVDWord(&pBlock, &cbBlock, 0x8C, getSettingWord(NULL, "OriginCountry", 0)); + ppackTLVBlockItems(&buf, &buflen, 0xA0, &nItems, &pBlock, (WORD*)&cbBlock, TRUE); + + ppackTLVStringFromDB(&buf, &buflen, "Homepage", 0xFA); + + // Timezone + WORD wTimezone = getSettingByte(NULL, "Timezone", 0); + if ((wTimezone & 0x0080) == 0x80) wTimezone |= 0xFF00; // extend signed number + ppackTLVWord(&buf, &buflen, 0x17C, wTimezone); + } + + if (wParam & CIXT_BACKGROUND) + { + BYTE *pBlock = NULL; + int cbBlock = 0; + int nItems = 0; + + // Interests + nItems += ppackTLVWordStringUtfItemFromDB(&pBlock, &cbBlock, "Interest0Text", 0x6E, 0x64, getSettingWord(NULL, "Interest0Cat", 0)); + nItems += ppackTLVWordStringUtfItemFromDB(&pBlock, &cbBlock, "Interest1Text", 0x6E, 0x64, getSettingWord(NULL, "Interest1Cat", 0)); + nItems += ppackTLVWordStringUtfItemFromDB(&pBlock, &cbBlock, "Interest2Text", 0x6E, 0x64, getSettingWord(NULL, "Interest2Cat", 0)); + nItems += ppackTLVWordStringUtfItemFromDB(&pBlock, &cbBlock, "Interest3Text", 0x6E, 0x64, getSettingWord(NULL, "Interest3Cat", 0)); + ppackTLVBlockItems(&buf, &buflen, 0x122, &nItems, &pBlock, (WORD*)&cbBlock, FALSE); + + + /* WORD w; /// not supported anymore + + w = StringToListItemId("Past0", 0); + ppackTLVWordLNTSfromDB(&buf, &buflen, w, "Past0Text", TLV_PASTINFO); + w = StringToListItemId("Past1", 0); + ppackTLVWordLNTSfromDB(&buf, &buflen, w, "Past1Text", TLV_PASTINFO); + w = StringToListItemId("Past2", 0); + ppackTLVWordLNTSfromDB(&buf, &buflen, w, "Past2Text", TLV_PASTINFO); + + w = StringToListItemId("Affiliation0", 0); + ppackTLVWordLNTSfromDB(&buf, &buflen, w, "Affiliation0Text", TLV_AFFILATIONS); + w = StringToListItemId("Affiliation1", 0); + ppackTLVWordLNTSfromDB(&buf, &buflen, w, "Affiliation1Text", TLV_AFFILATIONS); + w = StringToListItemId("Affiliation2", 0); + ppackTLVWordLNTSfromDB(&buf, &buflen, w, "Affiliation2Text", TLV_AFFILATIONS);*/ + } + + DWORD dwCookie = icq_changeUserDirectoryInfoServ(buf, (WORD)buflen, DIRECTORYREQUEST_UPDATEOWNER); + + SAFE_FREE((void**)&buf); + + return dwCookie; + } + + return 0; // Failure +} + + +INT_PTR CIcqProto::GetAvatarCaps(WPARAM wParam, LPARAM lParam) +{ + if (wParam == AF_MAXSIZE) + { + POINT *size = (POINT*)lParam; + + if (size) + { + size->x = 64; + size->y = 64; + + return 0; + } + } + else if (wParam == AF_PROPORTION) + { + return PIP_NONE; + } + else if (wParam == AF_FORMATSUPPORTED) + { + if (lParam == PA_FORMAT_JPEG || lParam == PA_FORMAT_GIF || lParam == PA_FORMAT_XML || lParam == PA_FORMAT_BMP) + return 1; + else + return 0; + } + else if (wParam == AF_ENABLED) + { + if (m_bSsiEnabled && m_bAvatarsEnabled) + return 1; + else + return 0; + } + else if (wParam == AF_DONTNEEDDELAYS) + { + return 0; + } + else if (wParam == AF_MAXFILESIZE) + { // server accepts images of 7168 bytees, not bigger + return 7168; + } + else if (wParam == AF_DELAYAFTERFAIL) + { // do not request avatar again if server gave an error + return 1 * 60 * 60 * 1000; // one hour + } + else if (wParam == AF_FETCHALWAYS) + { // avatars can be fetched all the time (server only operation) + return 1; + } + return 0; +} + + +INT_PTR CIcqProto::GetAvatarInfo(WPARAM wParam, LPARAM lParam) +{ + PROTO_AVATAR_INFORMATIONT *pai = (PROTO_AVATAR_INFORMATIONT*)lParam; + DWORD dwUIN; + uid_str szUID; + DBVARIANT dbv = {DBVT_DELETED}; + + if (!m_bAvatarsEnabled) return GAIR_NOAVATAR; + + if (getSetting(pai->hContact, "AvatarHash", &dbv) || dbv.type != DBVT_BLOB || (dbv.cpbVal != 0x14 && dbv.cpbVal != 0x09)) + { + ICQFreeVariant(&dbv); + return GAIR_NOAVATAR; // we did not found avatar hash or hash invalid - no avatar available + } + + if (getContactUid(pai->hContact, &dwUIN, &szUID)) + { + ICQFreeVariant(&dbv); + return GAIR_NOAVATAR; // we do not support avatars for invalid contacts + } + + int dwPaFormat = getSettingByte(pai->hContact, "AvatarType", PA_FORMAT_UNKNOWN); + + if (dwPaFormat != PA_FORMAT_UNKNOWN) + { // we know the format, test file + TCHAR tszFile[MAX_PATH * 2 + 4]; + + GetFullAvatarFileName(dwUIN, szUID, dwPaFormat, tszFile, MAX_PATH * 2); + + lstrcpyn(pai->filename, tszFile, SIZEOF(pai->filename)); // Avatar API does not support unicode :-( + pai->format = dwPaFormat; + + if (!IsAvatarChanged(pai->hContact, dbv.pbVal, dbv.cpbVal)) + { // hashes are the same + if (_taccess(tszFile, 0) == 0) + { + ICQFreeVariant(&dbv); + + return GAIR_SUCCESS; // we have found the avatar file, whoala + } + } + } + + if (IsAvatarChanged(pai->hContact, dbv.pbVal, dbv.cpbVal)) + { // we didn't received the avatar before - this ensures we will not request avatar again and again + if ((wParam & GAIF_FORCE) != 0 && pai->hContact != 0) + { // request avatar data + TCHAR tszFile[MAX_PATH * 2 + 4]; + + GetAvatarFileName(dwUIN, szUID, tszFile, MAX_PATH * 2); + GetAvatarData(pai->hContact, dwUIN, szUID, dbv.pbVal, dbv.cpbVal, tszFile); + lstrcpyn(pai->filename, tszFile, SIZEOF(pai->filename)); // Avatar API does not support unicode :-( + + ICQFreeVariant(&dbv); + + return GAIR_WAITFOR; + } + } + ICQFreeVariant(&dbv); + + return GAIR_NOAVATAR; +} + + +INT_PTR CIcqProto::GetMyAvatar(WPARAM wParam, LPARAM lParam) +{ + if (!m_bAvatarsEnabled) return -2; + + if (!wParam) return -3; + + TCHAR *tszFile = GetOwnAvatarFileName(); + if (tszFile && !_taccess(tszFile, 0)) + { + _tcsncpy((TCHAR*)wParam, tszFile, (int)lParam); + SAFE_FREE(&tszFile); + return 0; + } + + SAFE_FREE(&tszFile); + return -1; +} + + +INT_PTR CIcqProto::GrantAuthorization(WPARAM wParam, LPARAM lParam) +{ + if (icqOnline() && wParam != 0) + { + DWORD dwUin; + uid_str szUid; + + if (getContactUid((HANDLE)wParam, &dwUin, &szUid)) + return 0; // Invalid contact + + // send without reason, do we need any ? + icq_sendGrantAuthServ(dwUin, szUid, NULL); + // auth granted, remove contact menu item + deleteSetting((HANDLE)wParam, "Grant"); + } + + return 0; +} + +int CIcqProto::OnIdleChanged(WPARAM wParam, LPARAM lParam) +{ + int bIdle = (lParam&IDF_ISIDLE); + int bPrivacy = (lParam&IDF_PRIVACY); + + if (bPrivacy) return 0; + + setSettingDword(NULL, "IdleTS", bIdle ? time(0) : 0); + + if (m_bTempVisListEnabled) // remove temporary visible users + sendEntireListServ(ICQ_BOS_FAMILY, ICQ_CLI_REMOVETEMPVISIBLE, BUL_TEMPVISIBLE); + + icq_setidle(bIdle ? 1 : 0); + + return 0; +} + +INT_PTR CIcqProto::RevokeAuthorization(WPARAM wParam, LPARAM lParam) +{ + if (icqOnline() && wParam != 0) + { + DWORD dwUin; + uid_str szUid; + + if (getContactUid((HANDLE)wParam, &dwUin, &szUid)) + return 0; // Invalid contact + + if (MessageBoxUtf(NULL, LPGEN("Are you sure you want to revoke user's authorization (this will remove you from his/her list on some clients) ?"), LPGEN("Confirmation"), MB_ICONQUESTION | MB_YESNO) != IDYES) + return 0; + + icq_sendRevokeAuthServ(dwUin, szUid); + } + + return 0; +} + + +INT_PTR CIcqProto::SendSms(WPARAM wParam, LPARAM lParam) +{ + if (icqOnline() && wParam && lParam) + return icq_sendSMSServ((const char *)wParam, (const char *)lParam); + + return 0; // Failure +} + +INT_PTR CIcqProto::SendYouWereAdded(WPARAM wParam, LPARAM lParam) +{ + if (lParam && icqOnline()) + { + CCSDATA* ccs = (CCSDATA*)lParam; + if (ccs->hContact) + { + DWORD dwUin, dwMyUin; + + if (getContactUid(ccs->hContact, &dwUin, NULL)) + return 1; // Invalid contact + + dwMyUin = getContactUin(NULL); + + if (dwUin) + { + icq_sendYouWereAddedServ(dwUin, dwMyUin); + return 0; // Success + } + } + } + + return 1; // Failure +} + +INT_PTR CIcqProto::SetMyAvatar(WPARAM wParam, LPARAM lParam) +{ + TCHAR* tszFile = (TCHAR*)lParam; + int iRet = -1; + + if (!m_bAvatarsEnabled || !m_bSsiEnabled) return -2; + + if (tszFile) + { // set file for avatar + int dwPaFormat = DetectAvatarFormat(tszFile); + if (dwPaFormat != PA_FORMAT_XML) + { + // if it should be image, check if it is valid + HBITMAP avt = (HBITMAP)CallService(MS_UTILS_LOADBITMAPT, 0, (WPARAM)tszFile); + if (!avt) return iRet; + DeleteObject(avt); + } + + TCHAR tszMyFile[MAX_PATH+1]; + GetFullAvatarFileName(0, NULL, dwPaFormat, tszMyFile, MAX_PATH); + // if not in our storage, copy + if (lstrcmp(tszFile, tszMyFile) && !CopyFile(tszFile, tszMyFile, FALSE)) + { + NetLog_Server("Failed to copy our avatar to local storage."); + return iRet; + } + + BYTE *hash = calcMD5HashOfFile(tszMyFile); + if (hash) + { + BYTE* ihash = (BYTE*)_alloca(0x14); + // upload hash to server + ihash[0] = 0; //unknown + ihash[1] = dwPaFormat == PA_FORMAT_XML ? AVATAR_HASH_FLASH : AVATAR_HASH_STATIC; //hash type + ihash[2] = 1; //hash status + ihash[3] = 0x10; //hash len + memcpy(ihash+4, hash, 0x10); + updateServAvatarHash(ihash, 0x14); + + if (setSettingBlob(NULL, "AvatarHash", ihash, 0x14)) + { + NetLog_Server("Failed to save avatar hash."); + } + + TCHAR tmp[MAX_PATH]; + CallService(MS_UTILS_PATHTORELATIVET, (WPARAM)tszMyFile, (LPARAM)tmp); + setSettingStringT(NULL, "AvatarFile", tmp); + + iRet = 0; + + SAFE_FREE((void**)&hash); + } + } + else + { // delete user avatar + deleteSetting(NULL, "AvatarFile"); + setSettingBlob(NULL, "AvatarHash", hashEmptyAvatar, 9); + updateServAvatarHash(hashEmptyAvatar, 9); // set blank avatar + iRet = 0; + } + + return iRet; +} + +INT_PTR CIcqProto::SetNickName(WPARAM wParam, LPARAM lParam) +{ + if (icqOnline()) + { + setSettingString(NULL, "Nick", (char*)lParam); + + return ChangeInfoEx(CIXT_BASIC, 0); + } + + return 0; // Failure +} + +INT_PTR CIcqProto::SetPassword(WPARAM wParam, LPARAM lParam) +{ + char *pwd = (char*)lParam; + int len = strlennull(pwd); + + if (len && len < PASSWORDMAXLEN) + { + strcpy(m_szPassword, pwd); + m_bRememberPwd = TRUE; + } + return 0; +} + + +// TODO: Adding needs some more work in general + +HANDLE CIcqProto::AddToListByUIN(DWORD dwUin, DWORD dwFlags) +{ + int bAdded; + HANDLE hContact = HContactFromUIN(dwUin, &bAdded); + if (hContact) + { + if (!(dwFlags & PALF_TEMPORARY) && DBGetContactSettingByte(hContact, "CList", "NotOnList", 0)) + { + setContactHidden(hContact, 0); + DBDeleteContactSetting(hContact, "CList", "NotOnList"); + } + + return hContact; // Success + } + + return NULL; // Failure +} + + +HANDLE CIcqProto::AddToListByUID(const char *szUID, DWORD dwFlags) +{ + int bAdded; + HANDLE hContact = HContactFromUID(0, szUID, &bAdded); + if (hContact) + { + if (!(dwFlags & PALF_TEMPORARY) && DBGetContactSettingByte(hContact, "CList", "NotOnList", 0)) + { + setContactHidden(hContact, 0); + DBDeleteContactSetting(hContact, "CList", "NotOnList"); + } + + return hContact; // Success + } + + return NULL; // Failure +} + + +///////////////////////////////////////////////////////////////////////////////////////// + +void CIcqProto::ICQAddRecvEvent(HANDLE hContact, WORD wType, PROTORECVEVENT* pre, DWORD cbBlob, PBYTE pBlob, DWORD flags) +{ + if (pre->flags & PREF_CREATEREAD) + flags |= DBEF_READ; + + if (pre->flags & PREF_UTF) + flags |= DBEF_UTF; + + if (hContact && DBGetContactSettingByte(hContact, "CList", "Hidden", 0)) + { + DWORD dwUin; + uid_str szUid; + + //setContactHidden(hContact, 0); + + // if the contact was hidden, add to client-list if not in server-list authed + if (!getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0) || getSettingByte(hContact, "Auth", 0)) + { + getContactUid(hContact, &dwUin, &szUid); + icq_sendNewContact(dwUin, szUid); /// FIXME + } + } + + AddEvent(hContact, wType, pre->timestamp, flags, cbBlob, pBlob); +} + +INT_PTR __cdecl CIcqProto::IcqAddCapability(WPARAM wParam, LPARAM lParam) +{ + ICQ_CUSTOMCAP *icqCustomCapIn = (ICQ_CUSTOMCAP *)lParam; + ICQ_CUSTOMCAP *icqCustomCap = (ICQ_CUSTOMCAP *)malloc(sizeof(ICQ_CUSTOMCAP)); + memcpy(icqCustomCap, icqCustomCapIn, sizeof(ICQ_CUSTOMCAP)); + CustomCapList.push_back(icqCustomCap); +// MessageBoxA(NULL, ((ICQ_CUSTOMCAP *)(lstCustomCaps->items[lstCustomCaps->realCount-1]))->name, "custom cap", MB_OK); + return 0; +} + + +INT_PTR __cdecl CIcqProto::IcqCheckCapability(WPARAM wParam, LPARAM lParam) +{ + int res = 0; + DBCONTACTGETSETTING dbcgs; + DBVARIANT dbvariant; + HANDLE hContact = (HANDLE)wParam; + ICQ_CUSTOMCAP *icqCustomCap = (ICQ_CUSTOMCAP *)lParam; + dbcgs.pValue = &dbvariant; + dbcgs.szModule = m_szModuleName; + dbcgs.szSetting = "CapBuf"; + + CallService(MS_DB_CONTACT_GETSETTING, (WPARAM)hContact, (LPARAM)&dbcgs); + + if (dbvariant.type == DBVT_BLOB) + { + res = MatchCapability(dbvariant.pbVal, dbvariant.cpbVal, (const capstr*)&icqCustomCap->caps, 0x10)?1:0; // FIXME: Why icqCustomCap->caps is not capstr? + } + + CallService(MS_DB_CONTACT_FREEVARIANT,0,(LPARAM)(DBVARIANT*)&dbvariant); + + return res; +} + + + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR icq_getEventTextMissedMessage(WPARAM wParam, LPARAM lParam) +{ + DBEVENTGETTEXT *pEvent = (DBEVENTGETTEXT *)lParam; + + INT_PTR nRetVal = 0; + char *pszText = NULL; + + if (pEvent->dbei->cbBlob > 1) + { + switch (((WORD*)pEvent->dbei->pBlob)[0]) + { + case 0: + pszText = LPGEN("** This message was blocked by the ICQ server ** The message was invalid."); + break; + + case 1: + pszText = LPGEN("** This message was blocked by the ICQ server ** The message was too long."); + break; + + case 2: + pszText = LPGEN("** This message was blocked by the ICQ server ** The sender has flooded the server."); + break; + + case 4: + pszText = LPGEN("** This message was blocked by the ICQ server ** You are too evil."); + break; + + default: + pszText = LPGEN("** Unknown missed message event."); + break; + } + if (pEvent->datatype == DBVT_WCHAR) + { + WCHAR *pwszText; + int wchars = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, pszText, strlennull(pszText), NULL, 0); + + pwszText = (WCHAR*)_alloca((wchars + 1) * sizeof(WCHAR)); + pwszText[wchars] = 0; + + MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, pszText, strlennull(pszText), pwszText, wchars); + + nRetVal = (INT_PTR)mir_wstrdup(TranslateW(pwszText)); + } + else if (pEvent->datatype == DBVT_ASCIIZ) + nRetVal = (INT_PTR)mir_strdup(Translate(pszText)); + } + + return nRetVal; +} diff --git a/protocols/IcqOscarJ/src/icqosc_svcs.h b/protocols/IcqOscarJ/src/icqosc_svcs.h new file mode 100644 index 0000000000..844eecee51 --- /dev/null +++ b/protocols/IcqOscarJ/src/icqosc_svcs.h @@ -0,0 +1,39 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2008 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#ifndef __ICQOSC_SVCS_H +#define __ICQOSC_SVCS_H + + +#define ICQ_DB_GETEVENTTEXT_MISSEDMESSAGE "ICQ/GetEventTextMissedMessage" + +INT_PTR icq_getEventTextMissedMessage(WPARAM wParam, LPARAM lParam); + + +#endif /* __ICQOSC_SVCS_H */ diff --git a/protocols/IcqOscarJ/src/icqoscar.cpp b/protocols/IcqOscarJ/src/icqoscar.cpp new file mode 100644 index 0000000000..5ce67145cd --- /dev/null +++ b/protocols/IcqOscarJ/src/icqoscar.cpp @@ -0,0 +1,35 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000,2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001,2002 Jon Keating, Richard Hughes +// Copyright © 2002,2003,2004 Martin Öberg, Sam Kothari, 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// The only purpose of this file is to make sure that the precompiled headers +// are included and compiled. The Visual Studio settings for this file must be +// 'Create precompiled header file' and all the other .c files must be set to +// 'User precompiled header file'. Remember to check this when adding new +// files to the project... +// +// ----------------------------------------------------------------------------- + + +#include "icqoscar.h" diff --git a/protocols/IcqOscarJ/src/icqoscar.h b/protocols/IcqOscarJ/src/icqoscar.h new file mode 100644 index 0000000000..ef0e32a6fd --- /dev/null +++ b/protocols/IcqOscarJ/src/icqoscar.h @@ -0,0 +1,134 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Includes all header files that should be precompiled to speed up compilation. +// +// ----------------------------------------------------------------------------- +#define MIRANDA_VER 0x0A00 + +#define _WIN32_WINNT 0x0501 +#define _WIN32_IE 0x0501 + +#include + +// Windows includes +#include +#include +#include + +// Standard includes +#include +#include +#include +#include +#include +#include +#include +#include + +//C++ +#include + +#ifndef _DEBUG +#ifdef _MSC_VER + #include +#endif +#endif + +#ifndef AW_VER_POSITIVE +#define AW_VER_POSITIVE 0x00000004 +#endif + +#ifndef _ASSERTE +#define _ASSERTE(x) +#endif + +// Miranda IM SDK includes +#include // This must be included first +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Project resources +#include "resource.h" + +// ICQ plugin includes +#include "version.h" +#include "iconlib.h" +#include "globals.h" +#include "i18n.h" +#include "icq_db.h" +#include "cookies.h" +#include "icq_packet.h" +#include "utilities.h" +#include "oscar_filetransfer.h" +#include "icq_direct.h" +#include "icq_server.h" +#include "icqosc_svcs.h" +#include "icq_servlist.h" +#include "icq_http.h" +#include "icq_fieldnames.h" +#include "icq_constants.h" +#include "capabilities.h" +#include "guids.h" +#include "init.h" +#include "stdpackets.h" +#include "tlv.h" +#include "channels.h" +#include "families.h" +#include "m_icq.h" +#include "m_icqplus.h" +#include "icq_advsearch.h" +#include "log.h" + +#include "icq_rates.h" + +#include "icq_avatar.h" + +#include "changeinfo/changeinfo.h" +#include "icq_popups.h" +#include "icq_proto.h" + +extern LIST g_Instances; diff --git a/protocols/IcqOscarJ/src/init.cpp b/protocols/IcqOscarJ/src/init.cpp new file mode 100644 index 0000000000..450ea73b16 --- /dev/null +++ b/protocols/IcqOscarJ/src/init.cpp @@ -0,0 +1,245 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" +#include "m_extraicons.h" + +HINSTANCE hInst; +int hLangpack; + +HANDLE hStaticServices[1]; +IcqIconHandle hStaticIcons[4]; +HANDLE hStaticHooks[1];; +HANDLE hExtraXStatus = NULL; + +PLUGININFOEX pluginInfo = { + sizeof(PLUGININFOEX), + "IcqOscarJ Protocol", + __VERSION_DWORD, + "Support for ICQ network, enhanced.", + "Joe Kucera, Bio, Martin Öberg, Richard Hughes, Jon Keating, etc", + "jokusoftware@miranda-im.org", + "(C) 2000-2010 M.Öberg, R.Hughes, J.Keating, Bio, Angeli-Ka, G.Hazan, J.Kucera", + "http://miranda-ng.org/", + UNICODE_AWARE, //doesn't replace anything built-in + {0x73a9615c, 0x7d4e, 0x4555, {0xba, 0xdb, 0xee, 0x5, 0xdc, 0x92, 0x8e, 0xff}} // {73A9615C-7D4E-4555-BADB-EE05DC928EFF} +}; + +extern "C" PLUGININFOEX __declspec(dllexport) *MirandaPluginInfoEx(DWORD mirandaVersion) +{ + return &pluginInfo; +} + +extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = {MIID_PROTOCOL, MIID_LAST}; + +extern "C" BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved) +{ + hInst = hinstDLL; + return TRUE; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static PROTO_INTERFACE* icqProtoInit( const char* pszProtoName, const TCHAR* tszUserName ) +{ + CIcqProto *ppro = new CIcqProto(pszProtoName, tszUserName); + g_Instances.insert(ppro); + return ppro; +} + + +static int icqProtoUninit( PROTO_INTERFACE* ppro ) +{ + g_Instances.remove(( CIcqProto* )ppro); + delete ( CIcqProto* )ppro; + return 0; +} + + +static int OnModulesLoaded( WPARAM, LPARAM ) +{ + hExtraXStatus = ExtraIcon_Register("xstatus", "ICQ XStatus"); + return 0; +} + + +extern "C" int __declspec(dllexport) Load(void) +{ + mir_getLP( &pluginInfo ); + + srand(time(NULL)); + _tzset(); + + // Register the module + PROTOCOLDESCRIPTOR pd = {0}; + pd.cbSize = sizeof(pd); + pd.szName = ICQ_PROTOCOL_NAME; + pd.type = PROTOTYPE_PROTOCOL; + pd.fnInit = icqProtoInit; + pd.fnUninit = icqProtoUninit; + CallService(MS_PROTO_REGISTERMODULE, 0, (LPARAM)&pd); + + // Initialize charset conversion routines + InitI18N(); + + // Register static services + hStaticServices[0] = CreateServiceFunction(ICQ_DB_GETEVENTTEXT_MISSEDMESSAGE, icq_getEventTextMissedMessage); + + { + // Define global icons + char szSectionName[MAX_PATH]; + null_snprintf(szSectionName, sizeof(szSectionName), "Protocols/%s", ICQ_PROTOCOL_NAME); + + TCHAR lib[MAX_PATH]; + GetModuleFileName(hInst, lib, MAX_PATH); + hStaticIcons[ISI_AUTH_REQUEST] = IconLibDefine(LPGEN("Request authorization"), szSectionName, NULL, "req_auth", lib, -IDI_AUTH_ASK); + hStaticIcons[ISI_AUTH_GRANT] = IconLibDefine(LPGEN("Grant authorization"), szSectionName, NULL, "grant_auth", lib, -IDI_AUTH_GRANT); + hStaticIcons[ISI_AUTH_REVOKE] = IconLibDefine(LPGEN("Revoke authorization"), szSectionName, NULL, "revoke_auth", lib, -IDI_AUTH_REVOKE); + hStaticIcons[ISI_ADD_TO_SERVLIST] = IconLibDefine(LPGEN("Add to server list"), szSectionName, NULL, "add_to_server", lib, -IDI_SERVLIST_ADD); + } + + hStaticHooks[0] = HookEvent(ME_SYSTEM_MODULESLOADED, OnModulesLoaded); + + g_MenuInit(); + return 0; +} + + +extern "C" int __declspec(dllexport) Unload(void) +{ + int i; + + // Release static icon handles + for (i = 0; i < SIZEOF(hStaticIcons); i++) + IconLibRemove(&hStaticIcons[i]); + + // Release static event hooks + for (i = 0; i < SIZEOF(hStaticHooks); i++) + if (hStaticHooks[i]) + UnhookEvent(hStaticHooks[i]); + + // destroying contact menu + g_MenuUninit(); + + // Destroy static service functions + for (i = 0; i < SIZEOF(hStaticServices); i++) + if (hStaticServices[i]) + DestroyServiceFunction(hStaticServices[i]); + + g_Instances.destroy(); + + return 0; +} + + +///////////////////////////////////////////////////////////////////////////////////////// +// OnPrebuildContactMenu event + +void CListShowMenuItem(HANDLE hMenuItem, BYTE bShow) +{ + CLISTMENUITEM mi = {0}; + + mi.cbSize = sizeof(mi); + if (bShow) + mi.flags = CMIM_FLAGS; + else + mi.flags = CMIM_FLAGS | CMIF_HIDDEN; + + CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hMenuItem, (LPARAM)&mi); +} + +static void CListSetMenuItemIcon(HANDLE hMenuItem, HICON hIcon) +{ + CLISTMENUITEM mi = {0}; + + mi.cbSize = sizeof(mi); + mi.flags = CMIM_FLAGS | CMIM_ICON; + + mi.hIcon = hIcon; + CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hMenuItem, (LPARAM)&mi); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// OnReloadIcons event + +int CIcqProto::OnReloadIcons(WPARAM wParam, LPARAM lParam) +{ + memset(bXStatusCListIconsValid, 0, sizeof(bXStatusCListIconsValid)); + return 0; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// UpdateGlobalSettings event + +void CIcqProto::UpdateGlobalSettings() +{ + char szServer[MAX_PATH] = ""; + getSettingStringStatic(NULL, "OscarServer", szServer, MAX_PATH); + + m_bSecureConnection = getSettingByte(NULL, "SecureConnection", DEFAULT_SECURE_CONNECTION); + if (szServer[0]) + { + if (strstr(szServer, "aol.com")) + setSettingString(NULL, "OscarServer", m_bSecureConnection ? DEFAULT_SERVER_HOST_SSL : DEFAULT_SERVER_HOST); + + if (m_bSecureConnection && !_strnicmp(szServer, "login.", 6)) + { + setSettingString(NULL, "OscarServer", DEFAULT_SERVER_HOST_SSL); + setSettingWord(NULL, "OscarPort", DEFAULT_SERVER_PORT_SSL); + } + } + + if (m_hServerNetlibUser) + { + NETLIBUSERSETTINGS nlus = {0}; + + nlus.cbSize = sizeof(NETLIBUSERSETTINGS); + if (!m_bSecureConnection && CallService(MS_NETLIB_GETUSERSETTINGS, (WPARAM)m_hServerNetlibUser, (LPARAM)&nlus)) + { + if (nlus.useProxy && nlus.proxyType == PROXYTYPE_HTTP) + m_bGatewayMode = 1; + else + m_bGatewayMode = 0; + } + else + m_bGatewayMode = 0; + } + + m_bSecureLogin = getSettingByte(NULL, "SecureLogin", DEFAULT_SECURE_LOGIN); + m_bAimEnabled = getSettingByte(NULL, "AimEnabled", DEFAULT_AIM_ENABLED); + m_bUtfEnabled = getSettingByte(NULL, "UtfEnabled", DEFAULT_UTF_ENABLED); + m_wAnsiCodepage = getSettingWord(NULL, "AnsiCodePage", DEFAULT_ANSI_CODEPAGE); + m_bDCMsgEnabled = getSettingByte(NULL, "DirectMessaging", DEFAULT_DCMSG_ENABLED); + m_bTempVisListEnabled = getSettingByte(NULL, "TempVisListEnabled", DEFAULT_TEMPVIS_ENABLED); + m_bSsiEnabled = getSettingByte(NULL, "UseServerCList", DEFAULT_SS_ENABLED); + m_bSsiSimpleGroups = FALSE; /// TODO: enable, after server-list revolution is over + m_bAvatarsEnabled = getSettingByte(NULL, "AvatarsEnabled", DEFAULT_AVATARS_ENABLED); + m_bXStatusEnabled = getSettingByte(NULL, "XStatusEnabled", DEFAULT_XSTATUS_ENABLED); + m_bMoodsEnabled = getSettingByte(NULL, "MoodsEnabled", DEFAULT_MOODS_ENABLED); +} diff --git a/protocols/IcqOscarJ/src/init.h b/protocols/IcqOscarJ/src/init.h new file mode 100644 index 0000000000..0e93a8a89f --- /dev/null +++ b/protocols/IcqOscarJ/src/init.h @@ -0,0 +1,40 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2008 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +// Debug defines +#define DBG_CAPHTML +#define DBG_CAPMTN +#define DBG_CAPXTRAZ +#undef DBG_CAPXTRAZ_MUC +#define DBG_NEWCAPS +#define DBG_OSCARFT +#define DBG_AIMCONTACTSEND + +void g_MenuInit(); +void g_MenuUninit(); diff --git a/protocols/IcqOscarJ/src/log.cpp b/protocols/IcqOscarJ/src/log.cpp new file mode 100644 index 0000000000..402863a456 --- /dev/null +++ b/protocols/IcqOscarJ/src/log.cpp @@ -0,0 +1,161 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2009 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +extern BOOL bPopUpService; + +static const char *szLevelDescr[] = {LPGEN("ICQ Note"), LPGEN("ICQ Warning"), LPGEN("ICQ Error"), LPGEN("ICQ Fatal")}; + +struct LogMessageInfo { + const char *szMsg; + const char *szTitle; + BYTE bLevel; +}; + + +void __cdecl CIcqProto::icq_LogMessageThread(void* arg) +{ + LogMessageInfo *err = (LogMessageInfo*)arg; + if (!err) + return; + + if (bPopUpService && getSettingByte(NULL, "PopupsLogEnabled", DEFAULT_LOG_POPUPS_ENABLED)) + { + ShowPopUpMsg(NULL, err->szTitle, err->szMsg, err->bLevel); + + SAFE_FREE((void**)&err->szMsg); + SAFE_FREE((void**)&err); + + return; + } + + bErrorBoxVisible = TRUE; + if (err->szMsg && err->szTitle) + MessageBoxUtf(NULL, err->szMsg, err->szTitle, MB_OK); + SAFE_FREE((void**)&err->szMsg); + SAFE_FREE((void**)&err); + bErrorBoxVisible = FALSE; +} + + +void CIcqProto::icq_LogMessage(int level, const char *szMsg) +{ + NetLog_Server("%s", szMsg); + + int displayLevel = getSettingByte(NULL, "ShowLogLevel", LOG_WARNING); + if (level >= displayLevel) + { + if (!bErrorBoxVisible || !getSettingByte(NULL, "IgnoreMultiErrorBox", 0)) + { + // error not shown or allowed multi - show messagebox + LogMessageInfo *lmi = (LogMessageInfo*)SAFE_MALLOC(sizeof(LogMessageInfo)); + lmi->bLevel = (BYTE)level; + lmi->szMsg = null_strdup(szMsg); + lmi->szTitle = szLevelDescr[level]; + ForkThread( &CIcqProto::icq_LogMessageThread, lmi); + } + } +} + +void CIcqProto::icq_LogUsingErrorCode(int level, DWORD dwError, const char *szMsg) +{ + char szBuf[1024]; + char str[1024]; + char str2[64]; + char szErrorMsg[512]; + char *pszErrorMsg = NULL; + int bNeedFree = FALSE; + + switch(dwError) { + case ERROR_TIMEOUT: + case WSAETIMEDOUT: + pszErrorMsg = LPGEN("The server did not respond to the connection attempt within a reasonable time, it may be temporarily down. Try again later."); + break; + + case ERROR_GEN_FAILURE: + pszErrorMsg = LPGEN("The connection with the server was abortively closed during the connection attempt. You may have lost your local network connection."); + break; + + case WSAEHOSTUNREACH: + case WSAENETUNREACH: + pszErrorMsg = LPGEN("Miranda was unable to resolve the name of a server to its numeric address. This is most likely caused by a catastrophic loss of your network connection (for example, your modem has disconnected), but if you are behind a proxy, you may need to use the 'Resolve hostnames through proxy' option in M->Options->Network."); + break; + + case WSAEHOSTDOWN: + case WSAENETDOWN: + case WSAECONNREFUSED: + pszErrorMsg = LPGEN("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."); + break; + + case ERROR_ACCESS_DENIED: + pszErrorMsg = LPGEN("Your proxy rejected the user name and password that you provided. Please check them in M->Options->Network."); + break; + + case WSAHOST_NOT_FOUND: + case WSANO_DATA: + pszErrorMsg = LPGEN("The server to which you are trying to connect does not exist. Check your spelling in M->Options->Network->ICQ."); + break; + + default: + { + TCHAR err[512]; + + if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, 0, err, SIZEOF(err), NULL)) + { + + pszErrorMsg = make_utf8_string(err); + + bNeedFree = TRUE; + } + break; + } + } + + null_snprintf(szBuf, sizeof(szBuf), "%s%s%s (%s %d)", + szMsg ? ICQTranslateUtfStatic(szMsg, str, 1024) : "", + szMsg ? "\r\n\r\n" : "", + ICQTranslateUtfStatic(pszErrorMsg, szErrorMsg, 512), + ICQTranslateUtfStatic(LPGEN("error"), str2, 64), + dwError); + + if (bNeedFree) + SAFE_FREE(&pszErrorMsg); + + icq_LogMessage(level, szBuf); +} + +void CIcqProto::icq_LogFatalParam(const char *szMsg, WORD wError) +{ + char str[MAX_PATH]; + char buf[MAX_PATH]; + + null_snprintf(buf, MAX_PATH, ICQTranslateUtfStatic(szMsg, str, MAX_PATH), wError); + icq_LogMessage(LOG_FATAL, buf); +} diff --git a/protocols/IcqOscarJ/src/log.h b/protocols/IcqOscarJ/src/log.h new file mode 100644 index 0000000000..6da25df0d7 --- /dev/null +++ b/protocols/IcqOscarJ/src/log.h @@ -0,0 +1,38 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2008 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#ifndef __LOG_H +#define __LOG_H + +#define LOG_NOTE 0 //trivial problems or problems that will already have been reported elsewhere +#define LOG_WARNING 1 //problems that may have caused data loss +#define LOG_ERROR 2 //problems that cause a disconnection from the network +#define LOG_FATAL 3 //problems requiring user intervention: password wrong, rate exceeded, etc. + +#endif /* __LOG_H */ diff --git a/protocols/IcqOscarJ/src/oscar_filetransfer.cpp b/protocols/IcqOscarJ/src/oscar_filetransfer.cpp new file mode 100644 index 0000000000..9630abc070 --- /dev/null +++ b/protocols/IcqOscarJ/src/oscar_filetransfer.cpp @@ -0,0 +1,2447 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// OSCAR File-Transfers implementation +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +struct oscarthreadstartinfo +{ + int type; + int incoming; + HANDLE hContact; + HANDLE hConnection; + DWORD dwRemoteIP; + oscar_filetransfer *ft; + oscar_listener *listener; +}; + + +// small utility function +extern void NormalizeBackslash(char* path); + +// +// Common functions +///////////////////////////// + +char *FindFilePathContainer(const char **files, int iFile, char *szContainer) +{ + const char *szThisFile = files[iFile]; + char *szFileName = (char*)ExtractFileName(szThisFile); + + szContainer[0] = '\0'; + + if (szThisFile != szFileName) + { // find an earlier subdirectory to be used as a container + for (int i = iFile - 1; i >= 0; i--) + { + int len = strlennull(files[i]); + + if (!_strnicmp(files[i], szThisFile, len) && (szThisFile[len] == '\\' || szThisFile[len] == '/')) + { + const char *pszLastBackslash; + + if (((pszLastBackslash = strrchr(files[i], '\\')) == NULL) && + ((pszLastBackslash = strrchr(files[i], '/')) == NULL)) + { + strcpy(szContainer, files[i]); + } + else + { + len = pszLastBackslash - files[i] + 1; + null_strcpy(szContainer, szThisFile + len, szFileName - szThisFile - len); + } + } + } + } + return szFileName; +} + + +// +// Utility functions +///////////////////////////// + +oscar_filetransfer* CIcqProto::CreateOscarTransfer() +{ + oscar_filetransfer* ft = (oscar_filetransfer*)SAFE_MALLOC(sizeof(oscar_filetransfer)); + + ft->ft_magic = FT_MAGIC_OSCAR; // Setup signature + // Init members + ft->fileId = -1; + + icq_lock l(oftMutex); + + fileTransferList = (basic_filetransfer**)SAFE_REALLOC(fileTransferList, sizeof(basic_filetransfer*)*(fileTransferCount + 1)); + fileTransferList[fileTransferCount++] = ft; + +#ifdef _DEBUG + NetLog_Direct("OFT: FT struct 0x%x created", ft); +#endif + + return ft; +} + + +filetransfer *CIcqProto::CreateIcqFileTransfer() +{ + filetransfer *ft = (filetransfer*)SAFE_MALLOC(sizeof(filetransfer)); + + ft->ft_magic = FT_MAGIC_ICQ; + + icq_lock l(oftMutex); + + fileTransferList = (basic_filetransfer**)SAFE_REALLOC(fileTransferList, sizeof(basic_filetransfer*)*(fileTransferCount + 1)); + fileTransferList[fileTransferCount++] = (basic_filetransfer*)ft; + +#ifdef _DEBUG + NetLog_Direct("FT struct 0x%x created", ft); +#endif + + return ft; +} + + +int CIcqProto::getFileTransferIndex(void *ft) +{ + for (int i = 0; i < fileTransferCount; i++) + { + if (fileTransferList[i] == ft) + return i; + } + return -1; +} + + +void CIcqProto::ReleaseFileTransfer(void *ft) +{ + int i = getFileTransferIndex(ft); + + if (i != -1) + { + fileTransferCount--; + fileTransferList[i] = fileTransferList[fileTransferCount]; + fileTransferList = (basic_filetransfer**)SAFE_REALLOC(fileTransferList, sizeof(basic_filetransfer*)*fileTransferCount); + } +} + + +int CIcqProto::IsValidFileTransfer(void *ft) +{ + icq_lock l(oftMutex); + + if (getFileTransferIndex(ft) != -1) return 1; + + return 0; +} + + +int CIcqProto::IsValidOscarTransfer(void *ft) +{ + icq_lock l(oftMutex); + + if (getFileTransferIndex(ft) != -1 && ((basic_filetransfer*)ft)->ft_magic == FT_MAGIC_OSCAR) + return 1; + + return 0; +} + + +oscar_filetransfer* CIcqProto::FindOscarTransfer(HANDLE hContact, DWORD dwID1, DWORD dwID2) +{ + icq_lock l(oftMutex); + + for (int i = 0; i < fileTransferCount; i++) + { + if (fileTransferList[i]->ft_magic == FT_MAGIC_OSCAR) + { + oscar_filetransfer *oft = (oscar_filetransfer*)fileTransferList[i]; + + if (oft->hContact == hContact && oft->pMessage.dwMsgID1 == dwID1 && oft->pMessage.dwMsgID2 == dwID2) + return oft; + } + } + + return NULL; +} + + +// Release file transfer structure +void CIcqProto::SafeReleaseFileTransfer(void **ft) +{ + basic_filetransfer **bft = (basic_filetransfer**)ft; + + icq_lock l(oftMutex); + + // Check for filetransfer validity + if (getFileTransferIndex(*ft) == -1) + return; + + if (*bft) + { + if ((*bft)->ft_magic == FT_MAGIC_ICQ) + { // release ICQ filetransfer structure and its contents + filetransfer *ift = (filetransfer*)(*bft); + + SAFE_FREE(&ift->szFilename); + SAFE_FREE(&ift->szDescription); + SAFE_FREE(&ift->szSavePath); + SAFE_FREE(&ift->szThisFile); + SAFE_FREE(&ift->szThisSubdir); + if (ift->pszFiles) + { + for (int i = 0; i < (int)ift->dwFileCount; i++) + SAFE_FREE(&ift->pszFiles[i]); + SAFE_FREE((void**)&ift->pszFiles); + } + // Invalidate transfer + ReleaseFileTransfer(ift); +#ifdef _DEBUG + NetLog_Direct("FT struct 0x%x released", ft); +#endif + // Release memory + SAFE_FREE((void**)ft); + } + else if ((*bft)->ft_magic == FT_MAGIC_OSCAR) + { // release oscar filetransfer structure and its contents + oscar_filetransfer *oft = (oscar_filetransfer*)(*bft); + // If connected, close connection + if (oft->connection) + CloseOscarConnection(oft->connection); + // Release oscar listener + if (oft->listener) + ReleaseOscarListener((oscar_listener**)&oft->listener); + // Release cookie + if (oft->dwCookie) + FreeCookie(oft->dwCookie); + // Release all dynamic members + SAFE_FREE(&oft->rawFileName); + SAFE_FREE(&oft->szSavePath); + SAFE_FREE(&oft->szThisFile); + SAFE_FREE(&oft->szThisPath); + SAFE_FREE(&oft->szDescription); + if (oft->files) + { + for (int i = 0; i < oft->wFilesCount; i++) + SAFE_FREE(&oft->files[i].szFile); + SAFE_FREE((void**)&oft->files); + } + if (oft->files_list) + { +/* for (int i = 0; i < oft->wFilesCount; i++) + SAFE_FREE(&oft->files_list[i]);*/ + SAFE_FREE((void**)&oft->files_list); + } + if (oft->file_containers) + { + for (int i = 0; i < oft->containerCount; i++) + SAFE_FREE(&oft->file_containers[i]); + SAFE_FREE((void**)&oft->file_containers); + } + if (oft->fileId != -1) + { +#ifdef _DEBUG + NetLog_Direct("OFT: _close(%u)", oft->fileId); +#endif + _close(oft->fileId); + } + // Invalidate transfer + ReleaseFileTransfer(oft); +#ifdef _DEBUG + NetLog_Direct("OFT: FT struct 0x%x released", ft); +#endif + // Release memory + SAFE_FREE((void**)ft); + } + } +} + + +// Calculate oft checksum of buffer +// -------------------------------- +// Information was gathered from Gaim's sources, thanks +// +DWORD oft_calc_checksum(int offset, const BYTE *buffer, int len, DWORD dwChecksum) +{ + DWORD checksum = (dwChecksum >> 16) & 0xffff; + + for (int i = 0; i < len; i++) + { + WORD val = buffer[i]; + DWORD oldchecksum = checksum; + + if (((i + offset) & 1) == 0) + val = val << 8; + + if (checksum < val) + checksum -= val + 1; + else // simulate carry + checksum -= val; + } + checksum = ((checksum & 0x0000ffff) + (checksum >> 16)); + checksum = ((checksum & 0x0000ffff) + (checksum >> 16)); + return checksum << 16; +} + + +DWORD oft_calc_file_checksum(int hFile, __int64 maxSize) +{ + BYTE buf[OFT_BUFFER_SIZE]; + int bytesRead; + __int64 offset = 0; + DWORD dwCheck = 0xFFFF0000; + + _lseek(hFile, 0, SEEK_SET); + bytesRead = _read(hFile, buf, (maxSize < sizeof(buf)) ? maxSize : (unsigned)sizeof(buf)); + if (bytesRead == -1) + return dwCheck; + + while(bytesRead) + { + dwCheck = oft_calc_checksum((int)offset, buf, bytesRead, dwCheck); + offset += bytesRead; + bytesRead = _read(hFile, buf, sizeof(buf)); + if (bytesRead + offset > maxSize) bytesRead = (int)(maxSize - offset); + } + _lseek(hFile, 0, SEEK_SET); // back to beginning + + return dwCheck; +} + + +oscar_listener* CIcqProto::CreateOscarListener(oscar_filetransfer *ft, NETLIBNEWCONNECTIONPROC_V2 handler) +{ + oscar_listener *listener = (oscar_listener*)SAFE_MALLOC(sizeof(oscar_listener)); + + if (listener) + { + listener->ppro = this; + listener->ft = ft; + if (listener->hBoundPort = NetLib_BindPort(handler, listener, &listener->wPort, NULL)) + return listener; // Success + + SAFE_FREE((void**)&listener); + } + + return NULL; // Failure +} + + +void CIcqProto::ReleaseOscarListener(oscar_listener **pListener) +{ + oscar_listener *listener = *pListener; + + if (listener) + { // Close listening port + if (listener->hBoundPort) + NetLib_SafeCloseHandle(&listener->hBoundPort); + + NetLog_Direct("Oscar listener on port %d released.", listener->wPort); + } + SAFE_FREE((void**)pListener); +} + + +// +// Miranda FT interface handlers & services +///////////////////////////// + +void CIcqProto::handleRecvServMsgOFT(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwID1, DWORD dwID2, WORD wCommand) +{ + HANDLE hContact = HContactFromUID(dwUin, szUID, NULL); + + if (wCommand == 0) + { // this is OFT request + oscar_tlv_chain* chain = readIntoTLVChain(&buf, wLen, 0); + + if (chain) + { + WORD wAckType = chain->getWord(0x0A, 1); + + if (wAckType == 1) + { // This is first request in this OFT + oscar_filetransfer *ft = CreateOscarTransfer(); + char *pszFileName = NULL; + char *pszDescription = NULL; + WORD wFilenameLength; + + NetLog_Server("This is a file request"); + + // This TLV chain may contain the following TLVs: + // TLV(A): Acktype 0x0001 - file request / abort request + // 0x0002 - file ack + // TLV(F): Unknown + // TLV(E): Language ? + // TLV(2): Proxy IP + // TLV(16): Proxy IP Check + // TLV(3): External IP + // TLV(4): Internal IP + // TLV(5): Port + // TLV(17): Port Check + // TLV(10): Proxy Flag + // TLV(D): Charset of User Message + // TLV(C): User Message (ICQ_COOL_FT) + // TLV(2711): FT info + // TLV(2712): Charset of file name + + // init filetransfer structure + ft->pMessage.dwMsgID1 = dwID1; + ft->pMessage.dwMsgID2 = dwID2; + ft->bUseProxy = chain->getTLV(0x10, 1) ? 1 : 0; + ft->dwProxyIP = chain->getDWord(0x02, 1); + ft->dwRemoteInternalIP = chain->getDWord(0x03, 1); + ft->dwRemoteExternalIP = chain->getDWord(0x04, 1); + ft->wRemotePort = chain->getWord(0x05, 1); + ft->wReqNum = wAckType; + + { // User Message + oscar_tlv* tlv = chain->getTLV(0x0C, 1); + + if (tlv) + { // parse User Message + BYTE* tBuf = tlv->pData; + + pszDescription = (char*)_alloca(tlv->wLen + 2); + unpackString(&tBuf, (char*)pszDescription, tlv->wLen); + pszDescription[tlv->wLen] = '\0'; + pszDescription[tlv->wLen+1] = '\0'; + { // apply User Message encoding + oscar_tlv *charset = chain->getTLV(0x0D, 1); + char *str = pszDescription; + char *bTag,*eTag; + + if (charset) + { // decode charset + char *szEnc = (char*)_alloca(charset->wLen + 1); + + null_strcpy(szEnc, (char*)charset->pData, charset->wLen); + str = ApplyEncoding((char*)pszDescription, szEnc); + } + else + str = null_strdup(str); + // eliminate HTML tags + pszDescription = EliminateHtml(str, strlennull(str)); + + bTag = strstrnull(pszDescription, ""); + if (bTag) + { // take special Description - ICQJ's extension + eTag = strstrnull(bTag, ""); + if (eTag) + { + *eTag = '\0'; + str = null_strdup(bTag + 6); + SAFE_FREE(&pszDescription); + pszDescription = str; + } + } + else + { + bTag = strstrnull(pszDescription, ""); + if (bTag) + { // take only - Description tag if present + eTag = strstrnull(bTag, ""); + if (eTag) + { + *eTag = '\0'; + str = null_strdup(bTag + 4); + SAFE_FREE(&pszDescription); + pszDescription = str; + } + } + } + } + } + if (!strlennull(pszDescription)) + { + SAFE_FREE(&pszDescription); + pszDescription = ICQTranslateUtf(LPGEN("No description given")); + } + } + { // parse File Transfer Info block + oscar_tlv* tlv = chain->getTLV(0x2711, 1); + + // sanity check + if (!tlv || tlv->wLen < 8) + { + NetLog_Server("Error: Malformed file request"); + // release structures + SafeReleaseFileTransfer((void**)&ft); + SAFE_FREE(&pszDescription); + return; + } + + BYTE* tBuf = tlv->pData; + WORD tLen = tlv->wLen; + WORD wFlag; + + unpackWord(&tBuf, &wFlag); // FT flag + unpackWord(&tBuf, &ft->wFilesCount); + unpackDWord(&tBuf, (DWORD*)&ft->qwTotalSize); + tLen -= 8; + // Filename / Directory Name + if (tLen) + { // some filename specified, unpack + wFilenameLength = tLen - 1; + pszFileName = (char*)_alloca(tLen); + unpackString(&tBuf, (char*)pszFileName, wFilenameLength); + pszFileName[wFilenameLength] = '\0'; + } + else if (ft->wFilesCount == 1) // give some generic file name + pszFileName = "unnamed_file"; + else // or empty directory name + pszFileName = ""; + + // apply Filename / Directory Name encoding + oscar_tlv* charset = chain->getTLV(0x2712, 1); + if (charset) { + char* szEnc = (char*)_alloca(charset->wLen + 1); + null_strcpy(szEnc, (char*)charset->pData, charset->wLen); + pszFileName = ApplyEncoding(pszFileName, szEnc); + } + else pszFileName = ansi_to_utf8(pszFileName); + + if (ft->wFilesCount == 1) + { // Filename - use for DB event + char *szFileName = (char*)_alloca(strlennull(pszFileName) + 1); + + strcpy(szFileName, pszFileName); + SAFE_FREE(&pszFileName); + pszFileName = szFileName; + } + else + { // Save Directory name for future use + ft->szThisPath = pszFileName; + // for multi-file transfer we do not display "folder" name, but create only a simple notice + pszFileName = (char*)_alloca(64); + + char tmp[64]; + null_snprintf(pszFileName, 64, ICQTranslateUtfStatic(LPGEN("%d Files"), tmp, SIZEOF(tmp)), ft->wFilesCount); + } + } + // Total Size TLV (ICQ 6 and AIM 6) + { + oscar_tlv *tlv = chain->getTLV(0x2713, 1); + + if (tlv && tlv->wLen >= 8) + { + BYTE *tBuf = tlv->pData; + + unpackQWord(&tBuf, &ft->qwTotalSize); + } + } + int bAdded; + HANDLE hContact = HContactFromUID(dwUin, szUID, &bAdded); + + ft->hContact = hContact; + ft->fileId = -1; + + // Send chain event + char *szBlob = (char*)_alloca(sizeof(DWORD) + strlennull(pszFileName) + strlennull(pszDescription) + 2); + *(PDWORD)szBlob = 0; + strcpy(szBlob + sizeof(DWORD), pszFileName); + strcpy(szBlob + sizeof(DWORD) + strlennull(pszFileName) + 1, pszDescription); + + TCHAR* ptszFileName = mir_utf8decodeT(pszFileName); + + PROTORECVFILET pre = {0}; + pre.flags = PREF_TCHAR; + pre.fileCount = 1; + pre.timestamp = time(NULL); + pre.tszDescription = mir_utf8decodeT(pszDescription); + pre.ptszFiles = &ptszFileName; + pre.lParam = (LPARAM)ft; + + CCSDATA ccs; + ccs.szProtoService = PSR_FILE; + ccs.hContact = hContact; + ccs.wParam = 0; + ccs.lParam = (LPARAM)⪯ + CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs); + + mir_free(pre.tszDescription); + mir_free(ptszFileName); + } + else if (wAckType == 2) + { // First attempt failed, reverse requested + oscar_filetransfer *ft = FindOscarTransfer(hContact, dwID1, dwID2); + + if (ft) + { + NetLog_Direct("OFT: Redirect received (%d)", wAckType); + + ft->wReqNum = wAckType; + + if (ft->flags & OFTF_SENDING) + { + ReleaseOscarListener((oscar_listener**)&ft->listener); + + ft->bUseProxy = chain->getTLV(0x10, 1) ? 1 : 0; + ft->dwProxyIP = chain->getDWord(0x02, 1); + ft->dwRemoteInternalIP = chain->getDWord(0x03, 1); + ft->dwRemoteExternalIP = chain->getDWord(0x04, 1); + ft->wRemotePort = chain->getWord(0x05, 1); + + OpenOscarConnection(hContact, ft, ft->bUseProxy ? OCT_PROXY_RECV: OCT_REVERSE); + } + else + { // Just sanity + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)ft, 0); + // Release transfer + SafeReleaseFileTransfer((void**)&ft); + } + } + else + NetLog_Server("Error: Invalid request, no such transfer"); + } + else if (wAckType == 3) + { // Transfering thru proxy, join tunnel + oscar_filetransfer *ft = FindOscarTransfer(hContact, dwID1, dwID2); + + if (ft) + { // release possible previous listener + NetLog_Direct("OFT: Redirect received (%d)", wAckType); + + ft->wReqNum = wAckType; + + ReleaseOscarListener((oscar_listener**)&ft->listener); + + ft->bUseProxy = chain->getTLV(0x10, 1) ? 1 : 0; + ft->dwProxyIP = chain->getDWord(0x02, 1); + ft->wRemotePort = chain->getWord(0x05, 1); + + if (ft->bUseProxy && ft->dwProxyIP) + { // Init proxy connection + OpenOscarConnection(hContact, ft, OCT_PROXY_RECV); + } + else + { // try Stage 4 + OpenOscarConnection(hContact, ft, OCT_PROXY); + } + } + else + NetLog_Server("Error: Invalid request, no such transfer"); + } + else if (wAckType == 4) + { + oscar_filetransfer *ft = FindOscarTransfer(hContact, dwID1, dwID2); + + if (ft) + { + NetLog_Direct("OFT: Redirect received (%d)", wAckType); + + ft->wReqNum = wAckType; + ft->bUseProxy = chain->getTLV(0x10, 1) ? 1 : 0; + ft->dwProxyIP = chain->getDWord(0x02, 1); + ft->wRemotePort = chain->getWord(0x05, 1); + + if (ft->bUseProxy && ft->dwProxyIP) + { // Init proxy connection + OpenOscarConnection(hContact, ft, OCT_PROXY_RECV); + } + else + NetLog_Server("Error: Invalid request, IP missing."); + } + else + NetLog_Server("Error: Invalid request, no such transfer"); + } + else + NetLog_Server("Error: Uknown Stage %d request", wAckType); + + disposeChain(&chain); + } + else + NetLog_Server("Error: Missing TLV chain in OFT request"); + } + else if (wCommand == 1) + { // transfer cancelled/aborted + oscar_filetransfer *ft = FindOscarTransfer(hContact, dwID1, dwID2); + + if (ft) + { + NetLog_Server("OFT: File transfer cancelled by %s", strUID(dwUin, szUID)); + + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)ft, 0); + // Notify user, that the FT was cancelled // TODO: new ACKRESULT_? + icq_LogMessage(LOG_ERROR, LPGEN("The file transfer was aborted by the other user.")); + // Release transfer + SafeReleaseFileTransfer((void**)&ft); + } + else + NetLog_Server("Error: Invalid request, no such transfer"); + } + else if (wCommand == 2) + { // transfer accepted - connection established + oscar_filetransfer *ft = FindOscarTransfer(hContact, dwID1, dwID2); + + if (ft) + { + NetLog_Direct("OFT: Session established."); + // Init connection + if (ft->flags & OFTF_SENDING) + { + if (ft->connection && ft->connection->status == OCS_CONNECTED) + { + if (!(ft->flags & OFTF_FILE_REQUEST_SENT)) + { + ft->flags |= OFTF_FILE_REQUEST_SENT; + // proceed with first file + oft_sendPeerInit(ft->connection); + } + } + ft->flags |= OFTF_INITIALIZED; // accept was received + } + else + NetLog_Server("Warning: Received invalid rendezvous accept"); + } + else + NetLog_Server("Error: Invalid request, no such transfer"); + } + else + { + NetLog_Server("Error: Unknown wCommand=0x%x in OFT request", wCommand); + } +} + + +void CIcqProto::handleRecvServResponseOFT(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, void* ft) +{ + WORD wDataLen; + + if (wLen < 2) return; + + unpackWord(&buf, &wDataLen); + + if (wDataLen == 2) + { + oscar_filetransfer *oft = (oscar_filetransfer*)ft; + WORD wStatus; + + unpackWord(&buf, &wStatus); + + switch (wStatus) + { + case 1: + { // FT denied (icq5) + NetLog_Server("OFT: File transfer denied by %s", strUID(dwUin, szUID)); + + BroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_DENIED, (HANDLE)oft, 0); + // Release transfer + SafeReleaseFileTransfer((void**)&oft); + } + break; + + case 4: // Proxy error + { + icq_LogMessage(LOG_ERROR, LPGEN("The file transfer failed: Proxy error")); + + BroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)oft, 0); + // Release transfer + SafeReleaseFileTransfer((void**)&oft); + } + break; + + case 5: // Invalid request + { + icq_LogMessage(LOG_ERROR, LPGEN("The file transfer failed: Invalid request")); + + BroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)oft, 0); + // Release transfer + SafeReleaseFileTransfer((void**)&oft); + } + break; + + case 6: // Proxy Failed (IP = 0) + { + icq_LogMessage(LOG_ERROR, LPGEN("The file transfer failed: Proxy unavailable")); + + BroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)oft, 0); + // Release transfer + SafeReleaseFileTransfer((void**)&oft); + } + break; + + default: + { + NetLog_Server("OFT: Uknown request response code 0x%x", wStatus); + + BroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)oft, 0); + // Release transfer + SafeReleaseFileTransfer((void**)&oft); + } + } + } +} + + +// This function is called from the Netlib when someone is connecting to our oscar_listener +static void oft_newConnectionReceived(HANDLE hNewConnection, DWORD dwRemoteIP, void *pExtra) +{ + oscarthreadstartinfo *otsi = (oscarthreadstartinfo*)SAFE_MALLOC(sizeof(oscarthreadstartinfo)); + oscar_listener *listener = (oscar_listener*)pExtra; + + otsi->type = listener->ft->flags & OFTF_SENDING ? OCT_NORMAL : OCT_REVERSE; + otsi->incoming = 1; + otsi->hConnection = hNewConnection; + otsi->dwRemoteIP = dwRemoteIP; + otsi->listener = listener; + + // Start a new thread for the incomming connection + listener->ppro->ForkThread(( IcqThreadFunc )&CIcqProto::oft_connectionThread, otsi ); +} + + +static char *oftGetFileContainer(oscar_filetransfer* oft, const char** files, int iFile) +{ + char szPath[MAX_PATH]; + char* szFileName = FindFilePathContainer(files, iFile, szPath); + char *szPathUtf = ansi_to_utf8(szPath); + int i; + + // try to find existing container + for (i = 0; i < oft->containerCount; i++) + if (!strcmpnull(szPathUtf, oft->file_containers[i])) + { + SAFE_FREE((void**)&szPathUtf); + + return oft->file_containers[i]; + } + + // create new container + i = oft->containerCount++; + oft->file_containers = (char**)SAFE_REALLOC(oft->file_containers, (sizeof(char*) * oft->containerCount)); + oft->file_containers[i] = szPathUtf; + + return oft->file_containers[i]; +} + + +HANDLE CIcqProto::oftInitTransfer(HANDLE hContact, DWORD dwUin, char* szUid, const TCHAR** files, const TCHAR* pszDesc) +{ + oscar_filetransfer *ft; + int i, filesCount; + struct _stati64 statbuf; + char ** filesUtf; + + // Initialize filetransfer struct + NetLog_Server("Init file send"); + + ft = CreateOscarTransfer(); + ft->hContact = hContact; + ft->pMessage.bMessageType = MTYPE_FILEREQ; + InitMessageCookie(&ft->pMessage); + + for (filesCount = 0; files[filesCount]; filesCount++); + ft->files = (oft_file_record *)SAFE_MALLOC(sizeof(oft_file_record) * filesCount); + ft->files_list = (char**)SAFE_MALLOC(sizeof(TCHAR *) * filesCount); + ft->qwTotalSize = 0; + + filesUtf = (char**)SAFE_MALLOC(sizeof(char *) * filesCount); + for(i = 0; i < filesCount; i++) filesUtf[i] = FileNameToUtf(files[i]); + + // Prepare files arrays + for (i = 0; i < filesCount; i++) + { + if (_tstati64(files[i], &statbuf)) + NetLog_Server("IcqSendFile() was passed invalid filename \"%s\"", files[i]); + else + { + if (!(statbuf.st_mode&_S_IFDIR)) + { // take only files + ft->files[ft->wFilesCount].szFile = ft->files_list[ft->wFilesCount] = null_strdup(filesUtf[i]); + ft->files[ft->wFilesCount].szContainer = oftGetFileContainer(ft, (LPCSTR*) filesUtf, i); + + ft->wFilesCount++; + ft->qwTotalSize += statbuf.st_size; + } + } + } + + for (i = 0; i < filesCount; i++) + SAFE_FREE(&filesUtf[i]); + SAFE_FREE((void**)&filesUtf); + + if (!ft->wFilesCount) + { // found no valid files to send + icq_LogMessage(LOG_ERROR, LPGEN("Failed to Initialize File Transfer. No valid files were specified.")); + // Notify UI + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)ft, 0); + // Release transfer + SafeReleaseFileTransfer((void**)&ft); + + return 0; // Failure + } +#ifdef __GNUC__ +#define OSCAR_MAX_SIZE 0x100000000ULL +#else +#define OSCAR_MAX_SIZE 0x100000000 +#endif + if (ft->qwTotalSize >= OSCAR_MAX_SIZE && ft->wFilesCount > 1) + { // file larger than 4GB can be send only as single + icq_LogMessage(LOG_ERROR, LPGEN("The files are too big to be sent at once. Files bigger than 4GB can be sent only separately.")); + // Notify UI + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)ft, 0); + // Release transfer + SafeReleaseFileTransfer((void**)&ft); + + return 0; // Failure + } + + NetLog_Server("OFT: Found %d files.", ft->wFilesCount); + + ft->szDescription = tchar_to_utf8(pszDesc); + ft->flags = OFTF_SENDING; + ft->fileId = -1; + ft->iCurrentFile = 0; + ft->dwCookie = AllocateCookie(CKT_FILE, ICQ_MSG_SRV_SEND, hContact, ft); + + // Init oscar fields + { + ft->wEncrypt = 0; + ft->wCompress = 0; + ft->wPartsCount = 1; + ft->wPartsLeft = 1; + strcpy(ft->rawIDString, "Cool FileXfer"); + ft->bHeaderFlags = 0x20; + ft->bNameOff = 0x1C; + ft->bSizeOff = 0x11; + ft->dwRecvForkCheck = 0xFFFF0000; + ft->dwThisForkCheck = 0xFFFF0000; + ft->dwRecvFileCheck = 0xFFFF0000; + } + + // Send file transfer request + { + char *pszFiles; + + if (ft->wFilesCount == 1) + { // transfering single file, give filename + pszFiles = (char*)ExtractFileName(ft->files[0].szFile); + } + else + { // check if transfering one directory + char *szFirstDiv, *szFirstDir = ft->file_containers[0]; + int nFirstDirLen; + + // default is no root dir + pszFiles = ""; + + if ((szFirstDiv = strstrnull(szFirstDir, "\\")) || (szFirstDiv = strstrnull(szFirstDir, "/"))) + nFirstDirLen = szFirstDiv - szFirstDir; + else + nFirstDirLen = strlennull(szFirstDir); + + if (nFirstDirLen) + { // got root dir from first container, check if others are only sub-dirs + for (i = 0; i < ft->containerCount; i++) + { + if (_strnicmp((char*)ft->file_containers[i], (char*)szFirstDir, nFirstDirLen)) + { + szFirstDir = NULL; + break; + } + } + if (szFirstDir) + { // fine, we are sending only one directory + pszFiles = szFirstDir; + if (szFirstDiv) szFirstDiv[0] = '\0'; + nFirstDirLen++; // include backslash + // cut all files container by root dir - it is transferred as root separately + for (i = 0; i < ft->wFilesCount; i++) + ft->files[i].szContainer += nFirstDirLen; + } + } + } + + // Create listener + ft->listener = CreateOscarListener(ft, oft_newConnectionReceived); + + // Send packet + if (ft->listener) + { + oft_sendFileRequest(dwUin, szUid, ft, pszFiles, getSettingDword(NULL, "RealIP", 0)); + } + else + { // try stage 1 proxy + ft->szThisFile = null_strdup(pszFiles); + OpenOscarConnection(hContact, ft, OCT_PROXY_INIT); + } + } + + return ft; // Success +} + + +HANDLE CIcqProto::oftFileAllow(HANDLE hContact, HANDLE hTransfer, const TCHAR *szPath) +{ + oscar_filetransfer *ft = (oscar_filetransfer*)hTransfer; + DWORD dwUin; + uid_str szUid; + + if (getContactUid(hContact, &dwUin, &szUid)) + return 0; // Invalid contact + + if (!IsValidOscarTransfer(ft)) + return 0; // Invalid transfer + + ft->szSavePath = tchar_to_utf8(szPath); + + if (ft->szThisPath) + { // Append Directory name to the save path, when transfering a directory + ft->szSavePath = (char*)SAFE_REALLOC(ft->szSavePath, strlennull(ft->szSavePath) + strlennull(ft->szThisPath) + 4); + NormalizeBackslash(ft->szSavePath); + strcat(ft->szSavePath, ft->szThisPath); + NormalizeBackslash(ft->szSavePath); + } +#ifdef _DEBUG + NetLog_Direct("OFT: Request accepted, saving to '%s'.", ft->szSavePath); +#endif + + // Create cookie + ft->dwCookie = AllocateCookie(CKT_FILE, ICQ_MSG_SRV_SEND, hContact, ft); + + OpenOscarConnection(hContact, ft, ft->bUseProxy ? OCT_PROXY_RECV: OCT_NORMAL); + return hTransfer; // Success +} + + +DWORD CIcqProto::oftFileDeny(HANDLE hContact, HANDLE hTransfer, const TCHAR *szReason) +{ + oscar_filetransfer *ft = (oscar_filetransfer*)hTransfer; + DWORD dwUin; + uid_str szUid; + + if (getContactUid(hContact, &dwUin, &szUid)) + return 1; // Invalid contact + + if (IsValidOscarTransfer(ft)) + { + if (ft->hContact != hContact) + return 1; // Bad contact or hTransfer + +#ifdef _DEBUG + NetLog_Direct("OFT: Request denied."); +#endif + + oft_sendFileDeny(dwUin, szUid, ft); + + // Release structure + SafeReleaseFileTransfer((void**)&ft); + + return 0; // Success + } + return 1; // Invalid transfer +} + + +DWORD CIcqProto::oftFileCancel(HANDLE hContact, HANDLE hTransfer) +{ + oscar_filetransfer* ft = (oscar_filetransfer*)hTransfer; + DWORD dwUin; + uid_str szUid; + + if (getContactUid(hContact, &dwUin, &szUid)) + return 1; // Invalid contact + + if (IsValidOscarTransfer(ft)) + { + if (ft->hContact != hContact) + return 1; // Bad contact or hTransfer + +#ifdef _DEBUG + NetLog_Direct("OFT: Transfer cancelled."); +#endif + + oft_sendFileCancel(dwUin, szUid, ft); + + BroadcastAck(hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0); + + // Release structure + SafeReleaseFileTransfer((void**)&ft); + + return 0; // Success + } + return 1; // Invalid transfer +} + + +void CIcqProto::oftFileResume(oscar_filetransfer *ft, int action, const TCHAR *szFilename) +{ + int openFlags; + + if (ft->connection == NULL) + return; + + oscar_connection *oc = ft->connection; + +#ifdef _DEBUG + NetLog_Direct("OFT: Resume Transfer, Action: %d, FileName: '%s'", action, szFilename); +#endif + + switch (action) + { + case FILERESUME_RESUME: + openFlags = _O_BINARY | _O_RDWR; + break; + + case FILERESUME_OVERWRITE: + openFlags = _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY; + ft->qwFileBytesDone = 0; + break; + + case FILERESUME_SKIP: + openFlags = _O_BINARY | _O_WRONLY; + ft->qwFileBytesDone = ft->qwThisFileSize; + break; + + case FILERESUME_RENAME: + openFlags = _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY; + SAFE_FREE(&ft->szThisFile); + ft->szThisFile = tchar_to_utf8(szFilename); + ft->qwFileBytesDone = 0; + break; + + default: // workaround for bug in Miranda Core + if (ft->resumeAction == FILERESUME_RESUME) + openFlags = _O_BINARY | _O_RDWR; + else + { // default to overwrite + openFlags = _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY; + ft->qwFileBytesDone = 0; + } + } + ft->resumeAction = action; + + ft->fileId = OpenFileUtf(ft->szThisFile, openFlags, _S_IREAD | _S_IWRITE); +#ifdef _DEBUG + NetLog_Direct("OFT: OpenFileUtf(%s, %u) returned %u", ft->szThisFile, openFlags, ft->fileId); +#endif + if (ft->fileId == -1) + { +#ifdef _DEBUG + NetLog_Direct("OFT: errno=%d", errno); +#endif + icq_LogMessage(LOG_ERROR, LPGEN("Your file receive has been aborted because Miranda could not open the destination file in order to write to it. You may be trying to save to a read-only folder.")); + + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0); + // Release transfer + SafeReleaseFileTransfer((void**)&oc->ft); + return; + } + + if (action == FILERESUME_RESUME) + ft->qwFileBytesDone = _lseeki64(ft->fileId, 0, SEEK_END); + else + _lseeki64(ft->fileId, ft->qwFileBytesDone, SEEK_SET); + + ft->qwBytesDone += ft->qwFileBytesDone; + + if (action == FILERESUME_RESUME) + { // use smart-resume + oc->status = OCS_RESUME; + ft->dwRecvFileCheck = oft_calc_file_checksum(ft->fileId, ft->qwFileBytesDone); + _lseek(ft->fileId, 0, SEEK_END); + +#ifdef _DEBUG + NetLog_Direct("OFT: Starting Smart-Resume"); +#endif + + sendOFT2FramePacket(oc, OFT_TYPE_RESUMEREQUEST); + + return; + } + else if (action == FILERESUME_SKIP) + { // we are skipping the file, send "we are done" + oc->status = OCS_NEGOTIATION; + } + else + { // Send "we are ready" + oc->status = OCS_DATA; + ft->flags |= OFTF_FILE_RECEIVING; + + sendOFT2FramePacket(oc, OFT_TYPE_READY); + } + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0); + + if (!ft->qwThisFileSize || action == FILERESUME_SKIP) + { // if the file is empty we will not receive any data + BYTE buf; + oft_handleFileData(oc, &buf, 0); + } +} + + +static void oft_buildProtoFileTransferStatus(oscar_filetransfer* ft, PROTOFILETRANSFERSTATUS* pfts) +{ + ZeroMemory(pfts, sizeof(PROTOFILETRANSFERSTATUS)); + pfts->cbSize = sizeof(PROTOFILETRANSFERSTATUS); + pfts->hContact = ft->hContact; + pfts->flags = PFTS_UTF + ((ft->flags & OFTF_SENDING) ? PFTS_SENDING : PFTS_RECEIVING); + if (ft->flags & OFTF_SENDING) + pfts->pszFiles = ft->files_list; + else + pfts->pszFiles = NULL; /* FIXME */ + pfts->totalFiles = ft->wFilesCount; + pfts->currentFileNumber = ft->iCurrentFile; + pfts->totalBytes = ft->qwTotalSize; + pfts->totalProgress = ft->qwBytesDone; + pfts->szWorkingDir = ft->szThisPath; + pfts->szCurrentFile = ft->szThisFile; + pfts->currentFileSize = ft->qwThisFileSize; + pfts->currentFileTime = ft->dwThisFileDate; + pfts->currentFileProgress = ft->qwFileBytesDone; +} + + +void CIcqProto::CloseOscarConnection(oscar_connection *oc) +{ + icq_lock l(oftMutex); + + if (oc) + { + oc->type = OCT_CLOSING; + + if (oc->hConnection) + { // we need this for Netlib handle consistency + NetLib_CloseConnection(&oc->hConnection, FALSE); + } + } +} + + +///////////////////////////////////////////////////////////////////////////////////////// + +void CIcqProto::OpenOscarConnection(HANDLE hContact, oscar_filetransfer *ft, int type) +{ + oscarthreadstartinfo *otsi = (oscarthreadstartinfo*)SAFE_MALLOC(sizeof(oscarthreadstartinfo)); + + otsi->hContact = hContact; + otsi->type = type; + otsi->ft = ft; + + ForkThread(( IcqThreadFunc )&CIcqProto::oft_connectionThread, otsi ); +} + + +int CIcqProto::CreateOscarProxyConnection(oscar_connection *oc) +{ + NETLIBOPENCONNECTION nloc = {0}; + + // inform UI + BroadcastAck(oc->ft->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTPROXY, oc->ft, 0); + + nloc.szHost = OSCAR_PROXY_HOST; + nloc.wPort = getSettingWord(NULL, "OscarPort", m_bSecureConnection ? DEFAULT_SERVER_PORT_SSL : DEFAULT_SERVER_PORT); + if (nloc.wPort == 0) + nloc.wPort = RandRange(1024, 65535); + if (m_bGatewayMode) + nloc.flags |= NLOCF_HTTPGATEWAY; + + oc->hConnection = NetLib_OpenConnection(m_hServerNetlibUser, "Proxy ", &nloc); + if (!oc->hConnection) + { // proxy connection failed + return 0; + } + oc->type = OCT_PROXY; + oc->status = OCS_PROXY; + oc->ft->connection = oc; + // init proxy + proxy_sendInitTunnel(oc); + + return 1; // Success +} + + +void __cdecl CIcqProto::oft_connectionThread( oscarthreadstartinfo *otsi ) +{ + oscar_connection oc = {0}; + oscar_listener *source; + NETLIBPACKETRECVER packetRecv={0}; + HANDLE hPacketRecver; + + oc.hContact = otsi->hContact; + oc.hConnection = otsi->hConnection; + oc.type = otsi->type; + oc.incoming = otsi->incoming; + oc.ft = otsi->ft; + source = otsi->listener; + if (oc.incoming) + { + if (IsValidOscarTransfer(source->ft)) + { + oc.ft = source->ft; + oc.ft->dwRemoteExternalIP = otsi->dwRemoteIP; + oc.hContact = oc.ft->hContact; + oc.ft->connection = &oc; + oc.status = OCS_CONNECTED; + } + else + { // FT is already over, kill listener + NetLog_Direct("Received unexpected connection, closing."); + + CloseOscarConnection(&oc); + ReleaseOscarListener(&source); + + SAFE_FREE((void**)&otsi); + return; + } + } + SAFE_FREE((void**)&otsi); + + if (oc.hContact) + { // Load contact information + getContactUid(oc.hContact, &oc.dwUin, &oc.szUid); + } + + // Load local IP information + oc.dwLocalExternalIP = getSettingDword(NULL, "IP", 0); + oc.dwLocalInternalIP = getSettingDword(NULL, "RealIP", 0); + + if (!oc.incoming) + { // create outgoing connection + if (oc.type == OCT_NORMAL || oc.type == OCT_REVERSE) + { // create outgoing connection to peer + NETLIBOPENCONNECTION nloc = {0}; + IN_ADDR addr = {0}, addr2 = {0}; + + if (oc.ft->dwRemoteExternalIP == oc.dwLocalExternalIP && oc.ft->dwRemoteInternalIP) + addr.S_un.S_addr = htonl(oc.ft->dwRemoteInternalIP); + else if (oc.ft->dwRemoteExternalIP) + { + addr.S_un.S_addr = htonl(oc.ft->dwRemoteExternalIP); + // for different internal, try it also (for LANs with multiple external IP, VPNs, etc.) + if (oc.ft->dwRemoteInternalIP != oc.ft->dwRemoteExternalIP) + addr2.S_un.S_addr = htonl(oc.ft->dwRemoteInternalIP); + } + else // try LAN + addr.S_un.S_addr = htonl(oc.ft->dwRemoteInternalIP); + + // Inform UI that we will attempt to connect + BroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTING, oc.ft, 0); + + if (!addr.S_un.S_addr && oc.type == OCT_NORMAL) + { // IP to connect to is empty, request reverse + oscar_listener* listener = CreateOscarListener(oc.ft, oft_newConnectionReceived); + + if (listener) + { // we got listening port, fine send request + oc.ft->listener = listener; + // notify UI + BroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_LISTENING, oc.ft, 0); + + oft_sendFileRedirect(oc.dwUin, oc.szUid, oc.ft, oc.dwLocalInternalIP, listener->wPort, FALSE); + return; + } + if (!CreateOscarProxyConnection(&oc)) + { // normal connection failed, notify peer, wait for error or stage 3 proxy + oft_sendFileRedirect(oc.dwUin, oc.szUid, oc.ft, 0, 0, FALSE); + // stage 3 can follow + return; + } + } + else if (addr.S_un.S_addr && oc.ft->wRemotePort) + { + nloc.szHost = inet_ntoa(addr); + nloc.wPort = oc.ft->wRemotePort; + nloc.timeout = 8; // 8 secs to connect + oc.hConnection = NetLib_OpenConnection(m_hDirectNetlibUser, oc.type==OCT_REVERSE?"Reverse ":NULL, &nloc); + if (!oc.hConnection && addr2.S_un.S_addr) + { // first address failed, try second one if available + nloc.szHost = inet_ntoa(addr2); + oc.hConnection = NetLib_OpenConnection(m_hDirectNetlibUser, oc.type==OCT_REVERSE?"Reverse ":NULL, &nloc); + } + if (!oc.hConnection) + { + if (oc.type == OCT_NORMAL) + { // connection failed, try reverse + oscar_listener* listener = CreateOscarListener(oc.ft, oft_newConnectionReceived); + + if (listener) + { // we got listening port, fine send request + oc.ft->listener = listener; + // notify UI that we await connection + BroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_LISTENING, oc.ft, 0); + + oft_sendFileRedirect(oc.dwUin, oc.szUid, oc.ft, oc.dwLocalInternalIP, listener->wPort, FALSE); + return; + } + } + if (!CreateOscarProxyConnection(&oc)) + { // proxy connection failed, notify peer, wait for error or stage 4 proxy + oft_sendFileRedirect(oc.dwUin, oc.szUid, oc.ft, 0, 0, FALSE); + // stage 3 or stage 4 can follow + return; + } + } + else + { + oc.status = OCS_CONNECTED; + // ack normal connection + oc.ft->connection = &oc; + // acknowledge OFT - connection is ready + oft_sendFileAccept(oc.dwUin, oc.szUid, oc.ft); + // signal UI + BroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTED, oc.ft, 0); + } + } + else + { // try proxy, stage 3 (sending) + if (!CreateOscarProxyConnection(&oc)) + { // proxy connection failed, notify peer, wait for error or stage 4 proxy + oft_sendFileRedirect(oc.dwUin, oc.szUid, oc.ft, 0, 0, FALSE); + // stage 4 can follow + return; + } + } + } + else if (oc.type == OCT_PROXY_RECV) + { // stage 2 & stage 4 + if (oc.ft->dwProxyIP && oc.ft->wRemotePort) + { // create proxy connection, join tunnel + NETLIBOPENCONNECTION nloc = {0}; + IN_ADDR addr = {0}; + + // inform UI that we will connect to file proxy + BroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTPROXY, oc.ft, 0); + + addr.S_un.S_addr = htonl(oc.ft->dwProxyIP); + nloc.szHost = inet_ntoa(addr); + nloc.wPort = getSettingWord(NULL, "OscarPort", m_bSecureConnection ? DEFAULT_SERVER_PORT_SSL : DEFAULT_SERVER_PORT); + if (nloc.wPort == 0) + nloc.wPort = RandRange(1024, 65535); + if (m_bGatewayMode) + nloc.flags |= NLOCF_HTTPGATEWAY; + oc.hConnection = NetLib_OpenConnection(m_hServerNetlibUser, "Proxy ", &nloc); + if (!oc.hConnection) + { // proxy connection failed, we are out of possibilities + BroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc.ft, 0); + // notify the other side, that we failed + oft_sendFileResponse(oc.dwUin, oc.szUid, oc.ft, 0x04); + // Release structure + SafeReleaseFileTransfer((void**)&oc.ft); + return; + } + oc.status = OCS_PROXY; + oc.ft->connection = &oc; + // Join proxy tunnel + proxy_sendJoinTunnel(&oc, oc.ft->wRemotePort); + } + else // stage 2 failed (empty IP) + { // try stage 3, or send response error 0x06 + if (!CreateOscarProxyConnection(&oc)) + { + oft_sendFileResponse(oc.dwUin, oc.szUid, oc.ft, 0x06); + // notify UI + BroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc.ft, 0); + // Release structure + SafeReleaseFileTransfer((void**)&oc.ft); + return; + } + } + } + else if (oc.type == OCT_PROXY) + { // stage 4 + if (!CreateOscarProxyConnection(&oc)) + { // proxy connection failed, we are out of possibilities + BroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc.ft, 0); + // notify the other side, that we failed + oft_sendFileResponse(oc.dwUin, oc.szUid, oc.ft, 0x06); + // Release structure + SafeReleaseFileTransfer((void**)&oc.ft); + return; + } + } + else if (oc.type == OCT_PROXY_INIT) + { // stage 1 + if (!CreateOscarProxyConnection(&oc)) + { // We failed to init transfer, notify UI + icq_LogMessage(LOG_ERROR, LPGEN("Failed to Initialize File Transfer. Unable to bind local port and File proxy unavailable.")); + // Release transfer + SafeReleaseFileTransfer((void**)&oc.ft); + return; + } + else + oc.type = OCT_PROXY_INIT; + } + } + if (!oc.hConnection) + { // one more sanity check + NetLog_Direct("Error: No OFT connection."); + return; + } + if (oc.status != OCS_PROXY) + { // Connected, notify FT UI + BroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, oc.ft, 0); + + // send init OFT frame - just for different order of packets (just like Trillian) + if (oc.status == OCS_CONNECTED && (oc.ft->flags & OFTF_SENDING) && ((oc.ft->flags & OFTF_INITIALIZED) || oc.type == OCT_REVERSE) && !(oc.ft->flags & OFTF_FILE_REQUEST_SENT)) + { + oc.ft->flags |= OFTF_FILE_REQUEST_SENT; + // proceed with first file + oft_sendPeerInit(&oc); + } + } + hPacketRecver = (HANDLE)CallService(MS_NETLIB_CREATEPACKETRECVER, (WPARAM)oc.hConnection, 8192); + packetRecv.cbSize = sizeof(packetRecv); + + // Packet receiving loop + + while (oc.hConnection) + { + int recvResult; + + packetRecv.dwTimeout = oc.wantIdleTime ? 0 : 120000; + + recvResult = CallService(MS_NETLIB_GETMOREPACKETS, (WPARAM)hPacketRecver, (LPARAM)&packetRecv); + if (!recvResult) + { + NetLog_Direct("Clean closure of oscar socket (%p)", oc.hConnection); + break; + } + + if (recvResult == SOCKET_ERROR) + { + if (GetLastError() == ERROR_TIMEOUT) + { // TODO: this will not work on some systems + if (oc.wantIdleTime) + { // here we want to send file data packets + oft_sendFileData(&oc); + } + else if (oc.status != OCS_WAITING) + { + NetLog_Direct("Connection timeouted, closing."); + break; + } + } + else if (oc.type != OCT_CLOSING || GetLastError() != 87) + { // log only significant errors, not "connection killed by us" + NetLog_Direct("Abortive closure of oscar socket (%p) (%d)", oc.hConnection, GetLastError()); + break; + } + } + + if (oc.type == OCT_CLOSING) + packetRecv.bytesUsed = packetRecv.bytesAvailable; + else + packetRecv.bytesUsed = oft_handlePackets(&oc, packetRecv.buffer, packetRecv.bytesAvailable); + } + + // End of packet receiving loop + + NetLib_SafeCloseHandle(&hPacketRecver); + + CloseOscarConnection(&oc); + + { // Clean up + icq_lock l(oftMutex); + + if (getFileTransferIndex(oc.ft) != -1) + oc.ft->connection = NULL; // release link + } + // Give server some time for abort/cancel to arrive + SleepEx(1000, TRUE); + // Error handling + if (IsValidOscarTransfer(oc.ft)) + { + if (oc.status == OCS_DATA) + { + BroadcastAck(oc.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc.ft, 0); + + icq_LogMessage(LOG_ERROR, LPGEN("Connection lost during file transfer.")); + // Release structure + SafeReleaseFileTransfer((void**)&oc.ft); + } + else if (oc.status == OCS_NEGOTIATION) + { + BroadcastAck(oc.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc.ft, 0); + + icq_LogMessage(LOG_ERROR, LPGEN("File transfer negotiation failed for unknown reason.")); + // Release structure + SafeReleaseFileTransfer((void**)&oc.ft); + } + } +} + + +void CIcqProto::sendOscarPacket(oscar_connection *oc, icq_packet *packet) +{ + if (oc->hConnection) + { + int nResult; + + nResult = Netlib_Send(oc->hConnection, (const char*)packet->pData, packet->wLen, 0); + + if (nResult == SOCKET_ERROR) + { + NetLog_Direct("Oscar %p socket error: %d, closing", oc->hConnection, GetLastError()); + CloseOscarConnection(oc); + } + } + + SAFE_FREE((void**)&packet->pData); +} + + +int CIcqProto::oft_handlePackets(oscar_connection *oc, BYTE *buf, int len) +{ + int bytesUsed = 0; + + while (len > 0) + { + if (oc->status == OCS_DATA && (oc->ft->flags & OFTF_FILE_RECEIVING)) + { + return oft_handleFileData(oc, buf, len); + } + else if (oc->status == OCS_PROXY) + { + return oft_handleProxyData(oc, buf, len); + } + if (len < 6) + break; + + BYTE *pBuf = buf; + DWORD dwHead; + unpackDWord(&pBuf, &dwHead); + if (dwHead != 0x4F465432) + { // bad packet + NetLog_Direct("OFT: Received invalid packet (dwHead = 0x%x).", dwHead); + + CloseOscarConnection(oc); + break; + } + + WORD datalen; + unpackWord(&pBuf, &datalen); + + if (len < datalen) // wait for whole packet + break; + + WORD datatype; + unpackWord(&pBuf, &datatype); +#ifdef _DEBUG + NetLog_Direct("OFT2: Type %u, Length %u bytes", datatype, datalen); +#endif + handleOFT2FramePacket(oc, datatype, pBuf, (WORD)(datalen - 8)); + + /* Increase pointers so we can check for more data */ + buf += datalen; + len -= datalen; + bytesUsed += datalen; + } + + return bytesUsed; +} + + +int CIcqProto::oft_handleProxyData(oscar_connection *oc, BYTE *buf, int len) +{ + oscar_filetransfer *ft = oc->ft; + BYTE *pBuf; + WORD datalen; + WORD wCommand; + int bytesUsed = 0; + + + while (len > 2) + { + pBuf = buf; + + unpackWord(&pBuf, &datalen); + datalen += 2; + + if (len < datalen) + break; // packet is not complete + + if (datalen < 12) + { // malformed packet + CloseOscarConnection(oc); + break; + } + pBuf += 2; // packet version + unpackWord(&pBuf, &wCommand); + pBuf += 6; + // handle packet + switch (wCommand) + { + case 0x01: // Error + { + WORD wError; + char* szError; + + unpackWord(&pBuf, &wError); + switch(wError) + { + case 0x0D: + szError = "Bad request"; + break; + case 0x0E: + szError = "Malformed packet"; + break; + case 0x10: + szError = "Initial request timeout"; + break; + case 0x1A: + szError = "Accept period timeout"; + break; + case 0x1C: + szError = "Invalid data"; + break; + + default: + szError = "Unknown"; + } + // Notify peer + oft_sendFileResponse(oc->dwUin, oc->szUid, oc->ft, 0x06); + + NetLog_Server("Proxy Error: %s (0x%x)", szError, wError); + // Notify UI + BroadcastAck(oc->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc->ft, 0); + // Release structure + SafeReleaseFileTransfer((void**)&oc->ft); + } + break; + + case 0x03: // Tunnel created + { + WORD wCode; + DWORD dwIP; + + unpackWord(&pBuf, &wCode); + unpackDWord(&pBuf, &dwIP); + + if (oc->type == OCT_PROXY_INIT) + { // Proxy ready, send Stage 1 Request + ft->bUseProxy = 1; + ft->wRemotePort = wCode; + ft->dwProxyIP = dwIP; + oft_sendFileRequest(oc->dwUin, oc->szUid, ft, ft->szThisFile, 0); + SAFE_FREE(&ft->szThisFile); + // Notify UI + BroadcastAck(oc->hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, oc->ft, 0); + } + else + { + NetLog_Server("Proxy Tunnel ready, notify peer."); + oft_sendFileRedirect(oc->dwUin, oc->szUid, ft, dwIP, wCode, TRUE); + } + } + break; + + case 0x05: // Connection ready + oc->status = OCS_CONNECTED; // connection ready to send packets + // Notify UI + BroadcastAck(oc->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTED, oc->ft, 0); + // signal we are ready + if (oc->type == OCT_PROXY_RECV) + { + oft_sendFileAccept(oc->dwUin, oc->szUid, ft); + if (ft->flags & OFTF_SENDING) // connection is ready for transfer (sending only) + ft->flags |= OFTF_INITIALIZED; + } + + NetLog_Server("Proxy Tunnel established"); + + if ((ft->flags & OFTF_INITIALIZED) && (ft->flags & OFTF_SENDING) && !(ft->flags & OFTF_FILE_REQUEST_SENT)) + { + ft->flags |= OFTF_FILE_REQUEST_SENT; + // proceed with first file + oft_sendPeerInit(ft->connection); + } + break; + + default: + NetLog_Server("Unknown proxy command 0x%x", wCommand); + } + + buf += datalen; + len -= datalen; + bytesUsed += datalen; + } + + return bytesUsed; +} + + +int CIcqProto::oft_handleFileData(oscar_connection *oc, BYTE *buf, int len) +{ + oscar_filetransfer *ft = oc->ft; + DWORD dwLen = len; + int bytesUsed = 0; + + // do not accept more data than expected + if (ft->qwThisFileSize - ft->qwFileBytesDone < dwLen) + dwLen = (int)(ft->qwThisFileSize - ft->qwFileBytesDone); + + if (ft->fileId == -1) + { // something went terribly bad +#ifdef _DEBUG + NetLog_Direct("Error: handleFileData(%u bytes) without fileId!", len); +#endif + CloseOscarConnection(oc); + return 0; + } + _write(ft->fileId, buf, dwLen); + // update checksum + ft->dwRecvFileCheck = oft_calc_checksum((int)ft->qwFileBytesDone, buf, dwLen, ft->dwRecvFileCheck); + bytesUsed += dwLen; + ft->qwBytesDone += dwLen; + ft->qwFileBytesDone += dwLen; + + if (GetTickCount() > ft->dwLastNotify + 700 || ft->qwFileBytesDone == ft->qwThisFileSize) + { // notify FT UI of our progress, at most every 700ms - do not be faster than Miranda + PROTOFILETRANSFERSTATUS pfts; + + oft_buildProtoFileTransferStatus(ft, &pfts); + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&pfts); + ft->dwLastNotify = GetTickCount(); + } + if (ft->qwFileBytesDone == ft->qwThisFileSize) + { + /* EOF */ + ft->flags &= ~OFTF_FILE_RECEIVING; + +#ifdef _DEBUG + NetLog_Direct("OFT: _close(%u)", ft->fileId); +#endif + _close(ft->fileId); + ft->fileId = -1; + + if (ft->resumeAction != FILERESUME_SKIP && ft->dwRecvFileCheck != ft->dwThisFileCheck) + { + NetLog_Direct("Error: File checksums does not match!"); + { // Notify UI + char *pszMsg = ICQTranslateUtf(LPGEN("The checksum of file \"%s\" does not match, the file is probably damaged.")); + char szBuf[MAX_PATH]; + + null_snprintf(szBuf, MAX_PATH, pszMsg, ExtractFileName(ft->szThisFile)); + icq_LogMessage(LOG_ERROR, szBuf); + + SAFE_FREE(&pszMsg); + } + } // keep transfer going (icq6 ignores checksums completely) + else if (ft->resumeAction == FILERESUME_SKIP) + NetLog_Direct("OFT: File receive skipped."); + else + NetLog_Direct("OFT: File received successfully."); + + if ((DWORD)(ft->iCurrentFile + 1) == ft->wFilesCount) + { + ft->bHeaderFlags = 0x01; // the whole process is over + // ack received file + sendOFT2FramePacket(oc, OFT_TYPE_DONE); + oc->type = OCT_CLOSING; + NetLog_Direct("File Transfer completed successfully."); + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, ft, 0); + // Release transfer + SafeReleaseFileTransfer((void**)&ft); + } + else + { // ack received file + sendOFT2FramePacket(oc, OFT_TYPE_DONE); + oc->status = OCS_NEGOTIATION; + } + + } + return bytesUsed; +} + + +void CIcqProto::handleOFT2FramePacket(oscar_connection *oc, WORD datatype, BYTE *pBuffer, WORD wLen) +{ + oscar_filetransfer *ft = oc->ft; + DWORD dwID1; + DWORD dwID2; + + if (wLen < 232) + { // allow shorter packets, but at least with filename + NetLog_Direct("Error: Malformed OFT2 Frame, ignoring."); + return; + } + + unpackLEDWord(&pBuffer, &dwID1); + wLen -= 4; + unpackLEDWord(&pBuffer, &dwID2); + wLen -= 4; + + if (datatype == OFT_TYPE_REQUEST && !(ft->flags & OFTF_FILE_REQUEST_RECEIVED)) + { // first request does not contain MsgIDs we need to send them in ready packet + dwID1 = ft->pMessage.dwMsgID1; + dwID2 = ft->pMessage.dwMsgID2; + } + + if (ft->pMessage.dwMsgID1 != dwID1 || ft->pMessage.dwMsgID2 != dwID2) + { // this is not the right packet - bad Message IDs + NetLog_Direct("Error: Invalid Packet Cookie, closing."); + CloseOscarConnection(oc); + + return; + } + + switch (datatype) { + + case OFT_TYPE_REQUEST: + { // Sender ready + if (ft->flags & OFTF_SENDING) + { // just sanity check - this is only for receiving client + NetLog_Direct("Error: Invalid Packet, closing."); + CloseOscarConnection(oc); + return; + } + + // Read Frame data + if (!(ft->flags & OFTF_FILE_REQUEST_RECEIVED)) + { + unpackWord(&pBuffer, &ft->wEncrypt); + unpackWord(&pBuffer, &ft->wCompress); + unpackWord(&pBuffer, &ft->wFilesCount); + } + else + pBuffer += 6; + unpackWord(&pBuffer, &ft->wFilesLeft); + ft->iCurrentFile = ft->wFilesCount - ft->wFilesLeft; + if (!(ft->flags & OFTF_FILE_REQUEST_RECEIVED)) + unpackWord(&pBuffer, &ft->wPartsCount); + else + pBuffer += 2; + unpackWord(&pBuffer, &ft->wPartsLeft); + if (!(ft->flags & OFTF_FILE_REQUEST_RECEIVED)) + { // just check it + DWORD dwSize; + + unpackDWord(&pBuffer, &dwSize); + if (dwSize != (DWORD)ft->qwTotalSize) + { // the 32bits does not match, use them as full size + ft->qwTotalSize = dwSize; + + NetLog_Server("Warning: Invalid total size."); + } + } + else + pBuffer += 4; + { // this allows us to receive single >4GB file correctly + DWORD dwSize; + + unpackDWord(&pBuffer, &dwSize); + if (dwSize == (DWORD)ft->qwTotalSize && ft->wFilesCount == 1) + ft->qwThisFileSize = ft->qwTotalSize; + else + ft->qwThisFileSize = dwSize; + } + unpackDWord(&pBuffer, &ft->dwThisFileDate); + unpackDWord(&pBuffer, &ft->dwThisFileCheck); + unpackDWord(&pBuffer, &ft->dwRecvForkCheck); + unpackDWord(&pBuffer, &ft->dwThisForkSize); + unpackDWord(&pBuffer, &ft->dwThisFileCreation); + unpackDWord(&pBuffer, &ft->dwThisForkCheck); + pBuffer += 4; // File Bytes Done + unpackDWord(&pBuffer, &ft->dwRecvFileCheck); + if (!(ft->flags & OFTF_FILE_REQUEST_RECEIVED)) + unpackString(&pBuffer, ft->rawIDString, 32); + else + pBuffer += 32; + unpackByte(&pBuffer, &ft->bHeaderFlags); + unpackByte(&pBuffer, &ft->bNameOff); + unpackByte(&pBuffer, &ft->bSizeOff); + if (!(ft->flags & OFTF_FILE_REQUEST_RECEIVED)) + { + unpackString(&pBuffer, (char*)ft->rawDummy, 69); + unpackString(&pBuffer, (char*)ft->rawMacInfo, 16); + } + else + pBuffer += 85; + unpackWord(&pBuffer, &ft->wEncoding); + unpackWord(&pBuffer, &ft->wSubEncoding); + ft->cbRawFileName = wLen - 176; + SAFE_FREE((void**)&ft->rawFileName); // release previous buffers + SAFE_FREE(&ft->szThisFile); + ft->rawFileName = (char*)SAFE_MALLOC(ft->cbRawFileName + 2); + unpackString(&pBuffer, ft->rawFileName, ft->cbRawFileName); + // Prepare file + if (ft->wEncoding == 2) + { // UCS-2 encoding + ft->szThisFile = ApplyEncoding(ft->rawFileName, "unicode-2-0"); + } + else + { + ft->szThisFile = ansi_to_utf8(ft->rawFileName); + } + + { // convert dir markings to normal backslashes + int i; + + for (i = 0; i < strlennull(ft->szThisFile); i++) + { + if (ft->szThisFile[i] == 0x01) ft->szThisFile[i] = '\\'; + } + } + + ft->flags |= OFTF_FILE_REQUEST_RECEIVED; // First Frame Processed + + NetLog_Direct("File '%s', %I64u Bytes", ft->szThisFile, ft->qwThisFileSize); + + { // Prepare Path Information + char *szFile = strrchr(ft->szThisFile, '\\'); + + SAFE_FREE(&ft->szThisPath); // release previous path + if (szFile) + { + ft->szThisPath = ft->szThisFile; + szFile[0] = '\0'; // split that strings + ft->szThisFile = null_strdup(szFile + 1); + // no cheating with paths + if (!IsValidRelativePath(ft->szThisPath)) + { + NetLog_Direct("Invalid path information"); + break; + } + } + else + ft->szThisPath = null_strdup(""); + } + + /* no cheating with paths */ + if (!IsValidRelativePath(ft->szThisFile)) + { + NetLog_Direct("Invalid path information"); + break; + } + char *szFullPath = (char*)SAFE_MALLOC(strlennull(ft->szSavePath)+strlennull(ft->szThisPath)+strlennull(ft->szThisFile)+3); + strcpy(szFullPath, ft->szSavePath); + NormalizeBackslash(szFullPath); + strcat(szFullPath, ft->szThisPath); + NormalizeBackslash(szFullPath); + // make sure the dest dir exists + if (MakeDirUtf(szFullPath)) + NetLog_Direct("Failed to create destination directory!"); + + strcat(szFullPath, ft->szThisFile); + // we joined the full path to dest file + SAFE_FREE(&ft->szThisFile); + ft->szThisFile = szFullPath; + + ft->qwFileBytesDone = 0; + + { + /* file resume */ + PROTOFILETRANSFERSTATUS pfts; + + oft_buildProtoFileTransferStatus(ft, &pfts); + if (BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FILERESUME, ft, (LPARAM)&pfts)) + { + oc->status = OCS_WAITING; + break; /* UI supports resume: it will call PS_FILERESUME */ + } + + ft->fileId = OpenFileUtf(ft->szThisFile, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY, _S_IREAD | _S_IWRITE); +#ifdef _DEBUG + NetLog_Direct("OFT: OpenFileUtf(%s, %u) returned %u", ft->szThisFile, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY, ft->fileId); +#endif + if (ft->fileId == -1) + { +#ifdef _DEBUG + NetLog_Direct("OFT: errno=%d", errno); +#endif + icq_LogMessage(LOG_ERROR, LPGEN("Your file receive has been aborted because Miranda could not open the destination file in order to write to it. You may be trying to save to a read-only folder.")); + + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0); + // Release transfer + SafeReleaseFileTransfer((void**)&oc->ft); + return; + } + } + // Send "we are ready" + oc->status = OCS_DATA; + ft->flags |= OFTF_FILE_RECEIVING; + + sendOFT2FramePacket(oc, OFT_TYPE_READY); + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0); + if (!ft->qwThisFileSize) + { // if the file is empty we will not receive any data + BYTE buf; + + oft_handleFileData(oc, &buf, 0); + } + return; + } + + case OFT_TYPE_READY: + case OFT_TYPE_RESUMEACK: + { // Receiver is ready + oc->status = OCS_DATA; + oc->wantIdleTime = 1; + ft->flags |= OFTF_FILE_SENDING; + + NetLog_Direct("OFT: Receiver ready."); + } + break; + + case OFT_TYPE_RESUMEREQUEST: + { // Receiver wants to resume file transfer from point + DWORD dwResumeCheck, dwResumeOffset, dwFileCheck; + + if (!(ft->flags & OFTF_SENDING)) + { // just sanity check - this is only for sending client + NetLog_Direct("Error: Invalid Packet, closing."); + CloseOscarConnection(oc); + + return; + } + // Read Resume Frame data + pBuffer += 44; + unpackDWord(&pBuffer, &dwResumeOffset); + unpackDWord(&pBuffer, &dwResumeCheck); + + dwFileCheck = oft_calc_file_checksum(ft->fileId, dwResumeOffset); + if (dwFileCheck == dwResumeCheck && dwResumeOffset <= ft->qwThisFileSize) + { // resume seems ok + ft->qwFileBytesDone = dwResumeOffset; + ft->qwBytesDone += dwResumeOffset; + _lseek(ft->fileId, dwResumeOffset, SEEK_SET); + + NetLog_Direct("OFT: Resume request, ready."); + } + else + NetLog_Direct("OFT: Resume request, restarting."); + + // Ready for resume + sendOFT2FramePacket(oc, OFT_TYPE_RESUMEREADY); + } + break; + + case OFT_TYPE_RESUMEREADY: + { // Process Smart-resume reply + DWORD dwResumeOffset, dwResumeCheck; + + if (ft->flags & OFTF_SENDING) + { // just sanity check - this is only for receiving client + NetLog_Direct("Error: Invalid Packet, closing."); + CloseOscarConnection(oc); + + return; + } + // Read Resume Reply data + pBuffer += 44; + unpackDWord(&pBuffer, &dwResumeOffset); + unpackDWord(&pBuffer, &dwResumeCheck); + + if (ft->qwFileBytesDone != dwResumeOffset) + { + ft->qwBytesDone -= (ft->qwFileBytesDone - dwResumeOffset); + ft->qwFileBytesDone = dwResumeOffset; + if (dwResumeOffset) + ft->dwRecvFileCheck = dwResumeCheck; + else // Restarted resume (data mismatch) + ft->dwRecvFileCheck = 0xFFFF0000; + } + _lseek(ft->fileId, dwResumeOffset, SEEK_SET); + + if (ft->qwThisFileSize != ft->qwFileBytesDone) + NetLog_Direct("OFT: Resuming from offset %u.", dwResumeOffset); + + // Prepare to receive data + oc->status = OCS_DATA; + + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0); + + // Ready for receive + sendOFT2FramePacket(oc, OFT_TYPE_RESUMEACK); + + if (ft->qwThisFileSize == ft->qwFileBytesDone) + { // all data already processed + BYTE buf; + + oft_handleFileData(oc, &buf, 0); + } + } + break; + + case OFT_TYPE_DONE: + { // File done + oc->status = OCS_NEGOTIATION; + oc->wantIdleTime = 0; + + ft->flags &= ~OFTF_FILE_SENDING; + + NetLog_Direct("OFT: File sent successfully."); + +#ifdef _DEBUG + NetLog_Direct("OFT: _close(%u)", ft->fileId); +#endif + _close(ft->fileId); // FIXME: this needs fix for "skip file" feature + ft->fileId = -1; + ft->iCurrentFile++; + // continue with next file + oft_sendPeerInit(oc); + } + break; + + default: + NetLog_Direct("Error: Uknown OFT frame type 0x%x", datatype); + } +} + + +// +// Proxy packets +///////////////////////////// + +void CIcqProto::proxy_sendInitTunnel(oscar_connection *oc) +{ + icq_packet packet; + WORD wLen = 39 + getUINLen(m_dwLocalUIN); + + packet.wLen = wLen; + init_generic_packet(&packet, 2); + + packWord(&packet, wLen); + packWord(&packet, OSCAR_PROXY_VERSION); + packWord(&packet, 0x02); // wCommand + packDWord(&packet, 0); // Unknown + packWord(&packet, 0); // Flags? + packUIN(&packet, m_dwLocalUIN); + packLEDWord(&packet, oc->ft->pMessage.dwMsgID1); + packLEDWord(&packet, oc->ft->pMessage.dwMsgID2); + packDWord(&packet, 0x00010010); // TLV(1) + packGUID(&packet, MCAP_FILE_TRANSFER); + + sendOscarPacket(oc, &packet); +} + +void CIcqProto::proxy_sendJoinTunnel(oscar_connection *oc, WORD wPort) +{ + icq_packet packet; + WORD wLen = 41 + getUINLen(m_dwLocalUIN); + + packet.wLen = wLen; + init_generic_packet(&packet, 2); + + packWord(&packet, wLen); + packWord(&packet, OSCAR_PROXY_VERSION); + packWord(&packet, 0x04); // wCommand + packDWord(&packet, 0); // Unknown + packWord(&packet, 0); // Flags? + packUIN(&packet, m_dwLocalUIN); + packWord(&packet, wPort); + packLEDWord(&packet, oc->ft->pMessage.dwMsgID1); + packLEDWord(&packet, oc->ft->pMessage.dwMsgID2); + packDWord(&packet, 0x00010010); // TLV(1) + packGUID(&packet, MCAP_FILE_TRANSFER); + + sendOscarPacket(oc, &packet); +} + +// +// Direct packets +///////////////////////////// + +void CIcqProto::oft_sendFileData(oscar_connection *oc) +{ + oscar_filetransfer *ft = oc->ft; + BYTE buf[OFT_BUFFER_SIZE]; + + if (ft->fileId == -1) + return; + + int bytesRead = _read(ft->fileId, buf, sizeof(buf)); + if (bytesRead == -1) + return; + + if (!bytesRead) + { // + oc->wantIdleTime = 0; + return; + } + + if ((DWORD)bytesRead > (ft->qwThisFileSize - ft->qwFileBytesDone)) + { // do not send more than expected, limit to known size + bytesRead = (DWORD)(ft->qwThisFileSize - ft->qwFileBytesDone); + oc->wantIdleTime = 0; + } + + if (bytesRead) + { + icq_packet packet; + + packet.wLen = bytesRead; + init_generic_packet(&packet, 0); + packBuffer(&packet, buf, (WORD)bytesRead); // we are sending raw data + sendOscarPacket(oc, &packet); + + ft->qwBytesDone += bytesRead; + ft->qwFileBytesDone += bytesRead; + } + + if (GetTickCount() > ft->dwLastNotify + 700 || oc->wantIdleTime == 0 || ft->qwFileBytesDone == ft->qwThisFileSize) + { // notify only once a while or after last data packet sent + PROTOFILETRANSFERSTATUS pfts; + + oft_buildProtoFileTransferStatus(ft, &pfts); + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&pfts); + ft->dwLastNotify = GetTickCount(); + } +} + +void CIcqProto::oft_sendPeerInit(oscar_connection *oc) +{ + icq_lock l(oftMutex); + + oscar_filetransfer *ft = oc->ft; + struct _stati64 statbuf; + char *pszThisFileName; + + // prepare init frame + if (ft->iCurrentFile >= (int)ft->wFilesCount) + { // All files done, great! + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, ft, 0); + // Release transfer + SafeReleaseFileTransfer((void**)&oc->ft); + return; + } + + SAFE_FREE(&ft->szThisFile); + ft->szThisFile = null_strdup(ft->files[ft->iCurrentFile].szFile); + if (FileStatUtf(ft->szThisFile, &statbuf)) + { + icq_LogMessage(LOG_ERROR, LPGEN("Your file transfer has been aborted because one of the files that you selected to send is no longer readable from the disk. You may have deleted or moved it.")); + + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0); + // Release transfer + SafeReleaseFileTransfer((void**)&oc->ft); + return; + } + + { // create full relative filename + char* szThisContainer = ft->files[ft->iCurrentFile].szContainer; + + pszThisFileName = (char*)SAFE_MALLOC(strlennull(ft->szThisFile) + strlennull(szThisContainer) + 4); + strcpy(pszThisFileName, szThisContainer); + NormalizeBackslash(pszThisFileName); + strcat(pszThisFileName, ExtractFileName(ft->szThisFile)); + } + { // convert backslashes to dir markings + int i; + for (i = 0; i < strlennull(pszThisFileName); i++) + if (pszThisFileName[i] == '\\' || pszThisFileName[i] == '/') + pszThisFileName[i] = 0x01; + } + + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0); + + ft->fileId = OpenFileUtf(ft->szThisFile, _O_BINARY | _O_RDONLY, 0); +#ifdef _DEBUG + NetLog_Direct("OFT: OpenFileUtf(%s, %u) returned %u", ft->szThisFile, _O_BINARY | _O_RDONLY, ft->fileId); +#endif + if (ft->fileId == -1) + { +#ifdef _DEBUG + NetLog_Direct("OFT: errno=%d", errno); +#endif + SAFE_FREE((void**)&pszThisFileName); + icq_LogMessage(LOG_ERROR, LPGEN("Your file transfer has been aborted because one of the files that you selected to send is no longer readable from the disk. You may have deleted or moved it.")); + // + BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0); + // Release transfer + SafeReleaseFileTransfer((void**)&oc->ft); + return; + } + + ft->qwThisFileSize = statbuf.st_size; + ft->dwThisFileDate = statbuf.st_mtime; + ft->dwThisFileCreation = statbuf.st_ctime; + ft->dwThisFileCheck = oft_calc_file_checksum(ft->fileId, ft->qwThisFileSize); + ft->qwFileBytesDone = 0; + ft->dwRecvFileCheck = 0xFFFF0000; + SAFE_FREE((void**)&ft->rawFileName); + + if (IsUSASCII(pszThisFileName, strlennull(pszThisFileName))) + { + ft->wEncoding = 0; // ascii + ft->cbRawFileName = strlennull(pszThisFileName) + 1; + if (ft->cbRawFileName < 64) ft->cbRawFileName = 64; + ft->rawFileName = (char*)SAFE_MALLOC(ft->cbRawFileName); + strcpy(ft->rawFileName, (char*)pszThisFileName); + SAFE_FREE((void**)&pszThisFileName); + } + else + { + ft->wEncoding = 2; // ucs-2 + WCHAR *pwsThisFile = make_unicode_string(pszThisFileName); + SAFE_FREE((void**)&pszThisFileName); + ft->cbRawFileName = strlennull(pwsThisFile) * (int)sizeof(WCHAR) + 2; + if (ft->cbRawFileName < 64) ft->cbRawFileName = 64; + ft->rawFileName = (char*)SAFE_MALLOC(ft->cbRawFileName); + // convert to LE ordered string + BYTE *pwsThisFileBuf = (BYTE*)pwsThisFile; // need this - unpackWideString moves the address! + unpackWideString(&pwsThisFileBuf, (WCHAR*)ft->rawFileName, (WORD)(strlennull(pwsThisFile) * sizeof(WCHAR))); + SAFE_FREE((void**)&pwsThisFile); + } + ft->wFilesLeft = (WORD)(ft->wFilesCount - ft->iCurrentFile); + + sendOFT2FramePacket(oc, OFT_TYPE_REQUEST); +} + +void CIcqProto::sendOFT2FramePacket(oscar_connection *oc, WORD datatype) +{ + oscar_filetransfer *ft = oc->ft; + icq_packet packet; + + packet.wLen = 192 + ft->cbRawFileName; + init_generic_packet(&packet, 0); + // Basic Oscar Frame + packDWord(&packet, 0x4F465432); // Magic + packWord(&packet, packet.wLen); + packWord(&packet, datatype); + // Cookie + packLEDWord(&packet, ft->pMessage.dwMsgID1); + packLEDWord(&packet, ft->pMessage.dwMsgID2); + packWord(&packet, ft->wEncrypt); + packWord(&packet, ft->wCompress); + packWord(&packet, ft->wFilesCount); + packWord(&packet, ft->wFilesLeft); + packWord(&packet, ft->wPartsCount); + packWord(&packet, ft->wPartsLeft); + packDWord(&packet, (DWORD)ft->qwTotalSize); + packDWord(&packet, (DWORD)ft->qwThisFileSize); + packDWord(&packet, ft->dwThisFileDate); + packDWord(&packet, ft->dwThisFileCheck); + packDWord(&packet, ft->dwRecvForkCheck); + packDWord(&packet, ft->dwThisForkSize); + packDWord(&packet, ft->dwThisFileCreation); + packDWord(&packet, ft->dwThisForkCheck); + packDWord(&packet, (DWORD)ft->qwFileBytesDone); + packDWord(&packet, ft->dwRecvFileCheck); + packBuffer(&packet, (LPBYTE)ft->rawIDString, 32); + packByte(&packet, ft->bHeaderFlags); + packByte(&packet, ft->bNameOff); + packByte(&packet, ft->bSizeOff); + packBuffer(&packet, ft->rawDummy, 69); + packBuffer(&packet, ft->rawMacInfo, 16); + packWord(&packet, ft->wEncoding); + packWord(&packet, ft->wSubEncoding); + packBuffer(&packet, (LPBYTE)ft->rawFileName, ft->cbRawFileName); + + sendOscarPacket(oc, &packet); +} diff --git a/protocols/IcqOscarJ/src/oscar_filetransfer.h b/protocols/IcqOscarJ/src/oscar_filetransfer.h new file mode 100644 index 0000000000..fa6ec9169e --- /dev/null +++ b/protocols/IcqOscarJ/src/oscar_filetransfer.h @@ -0,0 +1,164 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// OSCAR File-Transfers headers +// +// ----------------------------------------------------------------------------- +#ifndef __OSCAR_FILETRANSFER_H +#define __OSCAR_FILETRANSFER_H + + +#define FT_MAGIC_ICQ 0x00 +#define FT_MAGIC_OSCAR 0x4F + +struct basic_filetransfer +{ + cookie_message_data pMessage; + BYTE ft_magic; +}; + +#define OFT_BUFFER_SIZE 8192 + +struct oft_file_record +{ + char *szContainer; + char *szFile; +}; + +char *FindFilePathContainer(const char **files, int iFile, char *szContainer); + + +// file-transfer status flags +#define OFTF_INITIALIZED 0x0001 // connection established (ack received) +#define OFTF_SENDING 0x0002 // sending files (receiving otherwise) +#define OFTF_FILE_REQUEST_SENT 0x0004 // request sent (sending only) +#define OFTF_FILE_REQUEST_RECEIVED 0x0008 // first request processed (receiving only) +#define OFTF_FILE_SENDING 0x0010 // sending file contents +#define OFTF_FILE_RECEIVING 0x0020 // receiving file contents +#define OFTF_FILE_DONE 0x0040 // file finished + +struct oscar_filetransfer: public basic_filetransfer +{ + HANDLE hContact; + int flags; // combination of OFTF_* + int containerCount; + char **file_containers; + oft_file_record *files; + char **files_list; // sending only + int iCurrentFile; + int currentIsDir; + int bUseProxy; + DWORD dwProxyIP; + DWORD dwRemoteInternalIP; + DWORD dwRemoteExternalIP; + WORD wRemotePort; + char *szSavePath; + char *szDescription; + char *szThisFile; + char *szThisPath; + // Request sequence + DWORD dwCookie; + WORD wReqNum; + // OFT2 header data + WORD wEncrypt, wCompress; + WORD wFilesCount,wFilesLeft; + WORD wPartsCount, wPartsLeft; + DWORD64 qwTotalSize; + DWORD64 qwThisFileSize; + DWORD dwThisFileDate; // modification date + DWORD dwThisFileCheck; + DWORD dwRecvForkCheck, dwThisForkSize; + DWORD dwThisFileCreation; // creation date (not used) + DWORD dwThisForkCheck; + DWORD64 qwBytesDone; + DWORD dwRecvFileCheck; + char rawIDString[32]; + BYTE bHeaderFlags; + BYTE bNameOff, bSizeOff; + BYTE rawDummy[69]; + BYTE rawMacInfo[16]; + WORD wEncoding, wSubEncoding; + WORD cbRawFileName; + char *rawFileName; + // helper data + DWORD64 qwFileBytesDone; + int fileId; + struct oscar_connection *connection; + struct oscar_listener *listener; + DWORD dwLastNotify; + int resumeAction; +}; + +#define OFT_TYPE_REQUEST 0x0101 // I am going to send you this file, is that ok? +#define OFT_TYPE_READY 0x0202 // Yes, it is ok for you to send me that file +#define OFT_TYPE_DONE 0x0204 // I received that file with no problems +#define OFT_TYPE_RESUMEREQUEST 0x0205 // Resume transferring from position +#define OFT_TYPE_RESUMEREADY 0x0106 // Ok, I am ready to send it +#define OFT_TYPE_RESUMEACK 0x0207 // Fine, ready to receive + +void SafeReleaseFileTransfer(void **ft); + +struct oscar_connection +{ + HANDLE hContact; + HANDLE hConnection; + int status; + DWORD dwUin; + uid_str szUid; + DWORD dwLocalInternalIP; + DWORD dwLocalExternalIP; + int type; + int incoming; + oscar_filetransfer *ft; + int wantIdleTime; +}; + +#define OCT_NORMAL 0 +#define OCT_REVERSE 1 +#define OCT_PROXY 2 +#define OCT_PROXY_INIT 3 +#define OCT_PROXY_RECV 4 +#define OCT_CLOSING 10 + +#define OCS_READY 0 +#define OCS_CONNECTED 1 +#define OCS_NEGOTIATION 2 +#define OCS_RESUME 3 +#define OCS_DATA 4 +#define OCS_PROXY 8 +#define OCS_WAITING 10 + +struct oscar_listener +{ + CIcqProto *ppro; + WORD wPort; + HANDLE hBoundPort; + oscar_filetransfer *ft; +}; + + +#endif /* __OSCAR_FILETRANSFER_H */ + diff --git a/protocols/IcqOscarJ/src/resource.h b/protocols/IcqOscarJ/src/resource.h new file mode 100644 index 0000000000..1dbd905dde --- /dev/null +++ b/protocols/IcqOscarJ/src/resource.h @@ -0,0 +1,176 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by resources.rc +// +#define IDI_ICQ 101 +#define IDS_IDENTIFY 102 +#define IDD_ICQACCOUNT 103 +#define IDD_ASKAUTH 104 +#define IDD_LOGINPW 105 +#define IDD_OPT_ICQ 108 +#define IDD_OPT_ICQCONTACTS 109 +#define IDD_OPT_ICQFEATURES 110 +#define IDD_OPT_ICQPRIVACY 111 +#define IDI_AUTH_ASK 150 +#define IDI_AUTH_GRANT 151 +#define IDI_AUTH_REVOKE 152 +#define IDI_SERVLIST_ADD 160 +#define IDD_INFO_ICQ 240 +#define IDD_ICQADVANCEDSEARCH 242 +#define IDD_ICQUPLOADLIST 253 +#define IDD_SETXSTATUS 256 +#define IDD_PWCONFIRM 300 +#define IDD_INFO_CHANGEINFO 301 +#define IDD_OPT_POPUPS 400 +#define IDC_POPUPS_ENABLED 410 +#define IDC_POPUPS_LOG_ENABLED 411 +#define IDC_POPUPS_SPAM_ENABLED 412 +#define IDC_POPUP_LOG0_TEXTCOLOR 420 +#define IDC_POPUP_LOG1_TEXTCOLOR 421 +#define IDC_POPUP_LOG2_TEXTCOLOR 422 +#define IDC_POPUP_LOG3_TEXTCOLOR 423 +#define IDC_POPUP_SPAM_TEXTCOLOR 425 +#define IDC_POPUP_LOG0_BACKCOLOR 430 +#define IDC_POPUP_LOG1_BACKCOLOR 431 +#define IDC_POPUP_LOG2_BACKCOLOR 432 +#define IDC_POPUP_LOG3_BACKCOLOR 433 +#define IDC_POPUP_SPAM_BACKCOLOR 435 +#define IDC_POPUP_LOG0_TIMEOUT 440 +#define IDC_POPUP_LOG1_TIMEOUT 441 +#define IDC_POPUP_LOG2_TIMEOUT 442 +#define IDC_POPUP_LOG3_TIMEOUT 443 +#define IDC_POPUP_SPAM_TIMEOUT 444 +#define IDC_USEWINCOLORS 450 +#define IDC_USESYSICONS 451 +#define IDC_PREVIEW 455 +#define IDC_LOG 1001 +#define IDI_EXPANDSTRINGEDIT 1001 +#define IDC_SAVEPASS 1004 +#define IDC_RETRXSTATUS 1005 +#define IDC_XTITLE_STATIC 1006 +#define IDC_XMSG_STATIC 1007 +#define IDC_SSL 1008 +#define IDC_MD5LOGIN 1009 +#define IDC_UTFENABLE 1010 +#define IDC_XTITLE 1010 +#define IDC_KEEPALIVE 1011 +#define IDC_XMSG 1011 +#define IDC_UTFALL 1012 +#define IDC_UTFSTATIC 1013 +#define IDC_UTFCODEPAGE 1014 +#define IDC_PW 1015 +#define IDC_TEMPVISIBLE 1015 +#define IDC_REGISTER 1016 +#define IDC_EDITAUTH 1017 +#define IDC_LOGINPW 1018 +#define IDC_INSTRUCTION 1019 +#define IDC_PASSWORD 1020 +#define IDC_SUPTIME 1020 +#define IDC_DCENABLE 1020 +#define IDC_DCPASSIVE 1021 +#define IDC_OLDPASS 1021 +#define IDC_ICQNUM 1022 +#define IDC_USEPOPUPCOLORS 1023 +#define IDC_USEDEFCOLORS 1024 +#define IDC_AIMENABLE 1030 +#define IDC_CLIST 1035 +#define IDC_XSTATUSENABLE 1040 +#define IDC_XSTATUSAUTO 1041 +#define IDC_XSTATUSRESET 1042 +#define IDC_MOODSENABLE 1043 +#define IDC_KILLSPAMBOTS 1045 +#define IDC_EMAIL 1048 +#define IDC_NICK 1053 +#define IDC_GENDER 1060 +#define IDC_CITY 1061 +#define IDC_STATE 1062 +#define IDC_COUNTRY 1063 +#define IDC_COMPANY 1066 +#define IDC_DEPARTMENT 1067 +#define IDC_POSITION 1069 +#define IDC_IP 1094 +#define IDC_UINSTATIC 1122 +#define IDC_UIN 1123 +#define IDC_STATIC11 1154 +#define IDC_STATIC12 1155 +#define IDC_ICQSERVER 1171 +#define IDC_ICQPORT 1172 +#define IDC_VERSION 1179 +#define IDC_FIRSTNAME 1224 +#define IDC_LASTNAME 1225 +#define IDC_REALIP 1230 +#define IDC_RECONNECTREQD 1239 +#define IDC_OFFLINETOENABLE 1240 +#define IDC_PORT 1249 +#define IDC_MIRVER 1251 +#define IDC_ONLINESINCE 1252 +#define IDC_SYSTEMUPTIME 1253 +#define IDC_IDLETIME 1254 +#define IDC_STATUS 1255 +#define IDC_SLOWSEND 1301 +#define IDC_ONLYSERVERACKS 1302 +#define IDC_LOGLEVEL 1331 +#define IDC_LEVELDESCR 1332 +#define IDC_NOERRMULTI 1333 +#define IDC_STICQGROUP 1374 +#define IDC_AGERANGE 1410 +#define IDC_MARITALSTATUS 1411 +#define IDC_KEYWORDS 1412 +#define IDC_LANGUAGE 1414 +#define IDC_WORKFIELD 1421 +#define IDC_PASTCAT 1422 +#define IDC_PASTKEY 1423 +#define IDC_INTERESTSCAT 1424 +#define IDC_INTERESTSKEY 1425 +#define IDC_ORGANISATION 1426 +#define IDC_ORGKEYWORDS 1427 +#define IDC_OTHERGROUP 1429 +#define IDC_ONLINEONLY 1430 +#define IDC_HOMEPAGECAT 1431 +#define IDC_HOMEPAGEKEY 1432 +#define IDC_SUMMARYGROUP 1434 +#define IDC_WORKGROUP 1435 +#define IDC_LOCATIONGROUP 1436 +#define IDC_BACKGROUNDGROUP 1437 +#define IDC_NEWUINLINK 1438 +#define IDC_LOOKUPLINK 1439 +#define IDC_RESETSERVER 1472 +#define IDC_UPLOADNOW 1521 +#define IDC_GROUPS 1522 +#define IDC_ALLGROUPS 1526 +#define IDC_VISIBILITY 1527 +#define IDC_IGNORE 1528 +#define IDC_ENABLE 1529 +#define IDC_LOADFROMSERVER 1530 +#define IDC_ADDSERVER 1532 +#define IDC_SAVETOSERVER 1533 +#define IDC_ENABLEAVATARS 1536 +#define IDC_AUTOLOADAVATARS 1537 +#define IDC_STRICTAVATARCHECK 1539 +#define IDC_WEBAWARE 1546 +#define IDC_DCALLOW_ANY 1547 +#define IDC_DCALLOW_CLIST 1548 +#define IDC_DCALLOW_AUTH 1549 +#define IDC_PUBLISHPRIMARY 1550 +#define IDC_ADD_ANY 1551 +#define IDC_ADD_AUTH 1552 +#define IDC_STATUSMSG_CLIST 1553 +#define IDC_STATUSMSG_VISIBLE 1554 +#define IDC_STATIC_NOTONLINE 1555 +#define IDC_STATIC_DC2 1556 +#define IDC_STATIC_DC1 1557 +#define IDC_STATIC_CLIST 1558 +#define IDC_SAVE 1600 +#define IDC_LIST 1601 +#define IDC_UPLOADING 1602 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 113 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1026 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/protocols/IcqOscarJ/src/stdpackets.cpp b/protocols/IcqOscarJ/src/stdpackets.cpp new file mode 100644 index 0000000000..3bc9502560 --- /dev/null +++ b/protocols/IcqOscarJ/src/stdpackets.cpp @@ -0,0 +1,1895 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + +extern const int moodXStatus[]; + +/***************************************************************************** +* +* Some handy extra pack functions for basic message type headers +* +*/ + +// This is the part of the message header that is common for all message channels +static void packServMsgSendHeader(icq_packet *p, DWORD dwSequence, DWORD dwID1, DWORD dwID2, DWORD dwUin, const char *szUID, WORD wFmt, WORD wLen) +{ + serverPacketInit(p, (WORD)(21 + getUIDLen(dwUin, szUID) + wLen)); + packFNACHeader(p, ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, 0, dwSequence | ICQ_MSG_SRV_SEND<<0x10); + packLEDWord(p, dwID1); // Msg ID part 1 + packLEDWord(p, dwID2); // Msg ID part 2 + packWord(p, wFmt); // Message channel + packUID(p, dwUin, szUID); // User ID +} + + +static void packServIcqExtensionHeader(icq_packet *p, CIcqProto *ppro, WORD wLen, WORD wType, WORD wSeq, WORD wCmd = ICQ_META_CLI_REQUEST) +{ + serverPacketInit(p, (WORD)(24 + wLen)); + packFNACHeader(p, ICQ_EXTENSIONS_FAMILY, ICQ_META_CLI_REQUEST, 0, wSeq | (wCmd<<0x10)); + packWord(p, 0x01); // TLV type 1 + packWord(p, (WORD)(10 + wLen)); // TLV len + packLEWord(p, (WORD)(8 + wLen)); // Data chunk size (TLV.Length-2) + packLEDWord(p, ppro->m_dwLocalUIN); // My UIN + packLEWord(p, wType); // Request type + packWord(p, wSeq); +} + + +static void packServIcqDirectoryHeader(icq_packet *p, CIcqProto *ppro, WORD wLen, WORD wType, WORD wCommand, WORD wSeq, WORD wSubCommand = ICQ_META_CLI_REQUEST) +{ + packServIcqExtensionHeader(p, ppro, wLen + 0x1E, CLI_META_INFO_REQ, wSeq, wSubCommand); + packLEWord(p, wType); + packLEWord(p, wLen + 0x1A); + packFNACHeader(p, 0x5b9, wCommand, 0, 0, 2); + packWord(p, 0); + packWord(p, (WORD)GetACP()); + packDWord(p, 2); +} + + +static void packServTLV5HeaderBasic(icq_packet *p, WORD wLen, DWORD ID1, DWORD ID2, WORD wCommand, const plugin_guid pGuid) +{ + // TLV(5) header + packWord(p, 0x05); // Type + packWord(p, (WORD)(26 + wLen)); // Len + // TLV(5) data + packWord(p, wCommand); // Command + packLEDWord(p, ID1); // msgid1 + packLEDWord(p, ID2); // msgid2 + packGUID(p, pGuid); // capabilities (4 dwords) +} + + +static void packServTLV5HeaderMsg(icq_packet *p, WORD wLen, DWORD ID1, DWORD ID2, WORD wAckType) +{ + packServTLV5HeaderBasic(p, (WORD)(wLen + 10), ID1, ID2, 0, MCAP_SRV_RELAY_FMT); + + packTLVWord(p, 0x0A, wAckType); // TLV: 0x0A Acktype: 1 for normal, 2 for ack + packDWord(p, 0x000F0000); // TLV: 0x0F empty +} + + +static void packServTLV2711Header(icq_packet *packet, WORD wCookie, WORD wVersion, BYTE bMsgType, BYTE bMsgFlags, WORD X1, WORD X2, int nLen) +{ + packWord(packet, 0x2711); // Type + packWord(packet, (WORD)(51 + nLen)); // Len + // TLV(0x2711) data + packLEWord(packet, 0x1B); // Unknown + packByte(packet, (BYTE)wVersion); // Client (message) version + packGUID(packet, PSIG_MESSAGE); + packDWord(packet, CLIENTFEATURES); + packDWord(packet, DC_TYPE); + packLEWord(packet, wCookie); // Reference cookie + packLEWord(packet, 0x0E); // Unknown + packLEWord(packet, wCookie); // Reference cookie again + packDWord(packet, 0); // Unknown (12 bytes) + packDWord(packet, 0); // - + packDWord(packet, 0); // - + packByte(packet, bMsgType); // Message type + packByte(packet, bMsgFlags); // Flags + packLEWord(packet, X1); // Accepted + packWord(packet, X2); // Unknown, priority? +} + + +static void packServDCInfo(icq_packet *p, CIcqProto* ppro, BOOL bEmpty) +{ + packTLVDWord(p, 0x03, bEmpty ? 0 : ppro->getSettingDword(NULL, "RealIP", 0)); // TLV: 0x03 DWORD IP + packTLVWord(p, 0x05, (WORD)(bEmpty ? 0 : ppro->wListenPort)); // TLV: 0x05 Listen port +} + + +static void packServChannel2Header(icq_packet *p, CIcqProto* ppro, DWORD dwUin, WORD wLen, DWORD dwID1, DWORD dwID2, DWORD dwCookie, WORD wVersion, BYTE bMsgType, BYTE bMsgFlags, WORD wPriority, int isAck, int includeDcInfo, BYTE bRequestServerAck) +{ + packServMsgSendHeader(p, dwCookie, dwID1, dwID2, dwUin, NULL, 0x0002, (WORD)(wLen + 95 + (bRequestServerAck?4:0) + (includeDcInfo?14:0))); + + packWord(p, 0x05); // TLV type + packWord(p, (WORD)(wLen + 91 + (includeDcInfo?14:0))); /* TLV len */ + packWord(p, (WORD)(isAck ? 2: 0)); /* not aborting anything */ + packLEDWord(p, dwID1); // Msg ID part 1 + packLEDWord(p, dwID2); // Msg ID part 2 + packGUID(p, MCAP_SRV_RELAY_FMT); /* capability (4 dwords) */ + packDWord(p, 0x000A0002); // TLV: 0x0A WORD: 1 for normal, 2 for ack + packWord(p, (WORD)(isAck ? 2 : 1)); + + if (includeDcInfo) + packServDCInfo(p, ppro, FALSE); + + packDWord(p, 0x000F0000); // TLV: 0x0F empty + + packServTLV2711Header(p, (WORD)dwCookie, wVersion, bMsgType, bMsgFlags, (WORD)MirandaStatusToIcq(ppro->m_iStatus), wPriority, wLen); +} + + +static void packServAdvancedReply(icq_packet *p, DWORD dwUin, const char *szUid, DWORD dwID1, DWORD dwID2, WORD wCookie, WORD wLen) +{ + serverPacketInit(p, (WORD)(getUIDLen(dwUin, szUid) + 23 + wLen)); + packFNACHeader(p, ICQ_MSG_FAMILY, ICQ_MSG_RESPONSE, 0, ICQ_MSG_RESPONSE<<0x10 | (wCookie & 0x7FFF)); + packLEDWord(p, dwID1); // Msg ID part 1 + packLEDWord(p, dwID2); // Msg ID part 2 + packWord(p, 0x02); // Channel + packUID(p, dwUin, szUid); // Contact UID + packWord(p, 0x03); // Msg specific formating +} + + +static void packServAdvancedMsgReply(icq_packet *p, DWORD dwUin, const char *szUid, DWORD dwID1, DWORD dwID2, WORD wCookie, WORD wVersion, BYTE bMsgType, BYTE bMsgFlags, WORD wLen) +{ + packServAdvancedReply(p, dwUin, szUid, dwID1, dwID2, wCookie, (WORD)(wLen + 51)); + + packLEWord(p, 0x1B); // Unknown + packByte(p, (BYTE)wVersion); // Protocol version + packGUID(p, PSIG_MESSAGE); + packDWord(p, CLIENTFEATURES); + packDWord(p, DC_TYPE); + packLEWord(p, wCookie); // Reference + packLEWord(p, 0x0E); // Unknown + packLEWord(p, wCookie); // Reference + packDWord(p, 0); // Unknown + packDWord(p, 0); // Unknown + packDWord(p, 0); // Unknown + packByte(p, bMsgType); // Message type + packByte(p, bMsgFlags); // Message flags + packLEWord(p, 0); // Ack status code ( 0 = accepted, this is hardcoded because + // it is only used this way yet) + packLEWord(p, 0); // Unused priority field +} + + +void packMsgColorInfo(icq_packet *packet) +{ // TODO: make configurable + packLEDWord(packet, 0x00000000); // Foreground colour + packLEDWord(packet, 0x00FFFFFF); // Background colour +} + + +void packEmptyMsg(icq_packet *packet) +{ + packLEWord(packet, 1); + packByte(packet, 0); +} + +/***************************************************************************** +* +* Functions to actually send the stuff +* +*/ + +void CIcqProto::icq_sendCloseConnection() +{ + icq_packet packet; + + packet.wLen = 0; + write_flap(&packet, ICQ_CLOSE_CHAN); + sendServPacket(&packet); +} + + +void CIcqProto::icq_requestnewfamily(WORD wFamily, void (CIcqProto::*familyhandler)(HANDLE hConn, char* cookie, WORD cookieLen)) +{ + icq_packet packet; + cookie_family_request *request; + int bRequestSSL = m_bSecureConnection && (wFamily != ICQ_AVATAR_FAMILY); // Avatar servers does not support SSL + + request = (cookie_family_request*)SAFE_MALLOC(sizeof(cookie_family_request)); + request->wFamily = wFamily; + request->familyHandler = familyhandler; + + DWORD dwCookie = AllocateCookie(CKT_SERVICEREQUEST, ICQ_CLIENT_NEW_SERVICE, 0, request); // generate and alloc cookie + + serverPacketInit(&packet, 12 + (bRequestSSL ? 4 : 0)); + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_NEW_SERVICE, 0, dwCookie); + packWord(&packet, wFamily); + if (bRequestSSL) + packDWord(&packet, 0x008C0000); // use SSL + + sendServPacket(&packet); +} + + +void CIcqProto::icq_setidle(int bAllow) +{ + icq_packet packet; + + if (bAllow != m_bIdleAllow) + { + /* SNAC 1,11 */ + serverPacketInit(&packet, 14); + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_SET_IDLE); + if (bAllow==1) + packDWord(&packet, 0x0000003C); + else + packDWord(&packet, 0x00000000); + + m_bIdleAllow = bAllow; + sendServPacket(&packet); + } +} + + +void CIcqProto::icq_setstatus(WORD wStatus, const char *szStatusNote) +{ + icq_packet packet; + char *szCurrentStatusNote = szStatusNote ? getSettingStringUtf(NULL, DBSETTING_STATUS_NOTE, NULL) : NULL; + WORD wStatusMoodLen = 0, wStatusNoteLen = 0, wSessionDataLen = 0; + char *szMoodData = NULL; + + if (szStatusNote && strcmpnull(szCurrentStatusNote, szStatusNote)) + { // status note was changed, update now + DBVARIANT dbv = {DBVT_DELETED}; + + if (m_bMoodsEnabled && !getSettingString(NULL, DBSETTING_STATUS_MOOD, &dbv)) + szMoodData = null_strdup(dbv.pszVal); + + ICQFreeVariant(&dbv); + + wStatusNoteLen = strlennull(szStatusNote); + wStatusMoodLen = strlennull(szMoodData); + + wSessionDataLen = (wStatusNoteLen ? wStatusNoteLen + 4 : 0) + 4 + wStatusMoodLen + 4; + } + SAFE_FREE(&szCurrentStatusNote); + + // Pack data in packet + serverPacketInit(&packet, (WORD)(18 + (wSessionDataLen ? wSessionDataLen + 4 : 0))); + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_SET_STATUS); + packWord(&packet, 0x06); // TLV 6 + packWord(&packet, 0x04); // TLV length + packWord(&packet, GetMyStatusFlags()); // Status flags + packWord(&packet, wStatus); // Status + if (wSessionDataLen) + { // Pack session data + packWord(&packet, 0x1D); // TLV 1D + packWord(&packet, wSessionDataLen); // TLV length + packWord(&packet, 0x02); // Item Type + if (wStatusNoteLen) + { + packWord(&packet, 0x400 | (WORD)(wStatusNoteLen + 4)); // Flags + Item Length + packWord(&packet, wStatusNoteLen); // Text Length + packBuffer(&packet, (LPBYTE)szStatusNote, wStatusNoteLen); + packWord(&packet, 0); // Encoding not specified (utf-8 is default) + } + else + packWord(&packet, 0); // Flags + Item Length + packWord(&packet, 0x0E); // Item Type + packWord(&packet, wStatusMoodLen); // Flags + Item Length + if (wStatusMoodLen) + packBuffer(&packet, (LPBYTE)szMoodData, wStatusMoodLen); // Mood + + // Save current status note + setSettingStringUtf(NULL, DBSETTING_STATUS_NOTE, szStatusNote); + } + // Release memory + SAFE_FREE(&szMoodData); + + // Send packet + sendServPacket(&packet); +} + + +DWORD CIcqProto::icq_SendChannel1Message(DWORD dwUin, char *szUID, HANDLE hContact, char *pszText, cookie_message_data *pCookieData) +{ + icq_packet packet; + WORD wPacketLength; + + WORD wMessageLen = strlennull(pszText); + DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData); + + if (pCookieData->nAckType == ACKTYPE_SERVER) + wPacketLength = 25; + else + wPacketLength = 21; + + // Pack the standard header + packServMsgSendHeader(&packet, dwCookie, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwUin, szUID, 1, (WORD)(wPacketLength + wMessageLen)); + + // Pack first TLV + packWord(&packet, 0x0002); // TLV(2) + packWord(&packet, (WORD)(wMessageLen + 13)); // TLV len + + // Pack client features + packWord(&packet, 0x0501); // TLV(501) + packWord(&packet, 0x0001); // TLV len + packByte(&packet, 0x1); // Features, meaning unknown, duplicated from ICQ Lite + + // Pack text TLV + packWord(&packet, 0x0101); // TLV(2) + packWord(&packet, (WORD)(wMessageLen + 4)); // TLV len + packWord(&packet, 0x0003); // Message charset number, again copied from ICQ Lite + packWord(&packet, 0x0000); // Message charset subset + packBuffer(&packet, (LPBYTE)pszText, (WORD)(wMessageLen)); // Message text + + // Pack request server ack TLV + if (pCookieData->nAckType == ACKTYPE_SERVER) + packDWord(&packet, 0x00030000); // TLV(3) + + // Pack store on server TLV + packDWord(&packet, 0x00060000); // TLV(6) + + sendServPacket(&packet); + + return dwCookie; +} + + +DWORD CIcqProto::icq_SendChannel1MessageW(DWORD dwUin, char *szUID, HANDLE hContact, WCHAR *pszText, cookie_message_data *pCookieData) +{ + icq_packet packet; + WORD wMessageLen; + DWORD dwCookie; + WORD wPacketLength; + WCHAR *ppText; + int i; + + wMessageLen = strlennull(pszText) * (int)sizeof(WCHAR); + dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData); + + if (pCookieData->nAckType == ACKTYPE_SERVER) + wPacketLength = 26; + else + wPacketLength = 22; + + // Pack the standard header + packServMsgSendHeader(&packet, dwCookie, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwUin, szUID, 1, (WORD)(wPacketLength + wMessageLen)); + + // Pack first TLV + packWord(&packet, 0x0002); // TLV(2) + packWord(&packet, (WORD)(wMessageLen + 14)); // TLV len + + // Pack client features + packWord(&packet, 0x0501); // TLV(501) + packWord(&packet, 0x0002); // TLV len + packWord(&packet, 0x0106); // Features, meaning unknown, duplicated from ICQ 2003b + + // Pack text TLV + packWord(&packet, 0x0101); // TLV(2) + packWord(&packet, (WORD)(wMessageLen + 4)); // TLV len + packWord(&packet, 0x0002); // Message charset number, again copied from ICQ 2003b + packWord(&packet, 0x0000); // Message charset subset + ppText = pszText; // we must convert the widestring + for (i = 0; inAckType == ACKTYPE_SERVER) + packDWord(&packet, 0x00030000); // TLV(3) + + // Pack store on server TLV + packDWord(&packet, 0x00060000); // TLV(6) + + sendServPacket(&packet); + return dwCookie; +} + + +DWORD CIcqProto::icq_SendChannel2Message(DWORD dwUin, HANDLE hContact, const char *szMessage, int nBodyLen, WORD wPriority, cookie_message_data *pCookieData, char *szCap) +{ + icq_packet packet; + + DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData); + + // Pack the standard header + packServChannel2Header(&packet, this, dwUin, (WORD)(nBodyLen + (szCap ? 53:11)), pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwCookie, ICQ_VERSION, (BYTE)pCookieData->bMessageType, 0, + wPriority, 0, 0, (BYTE)((pCookieData->nAckType == ACKTYPE_SERVER)?1:0)); + + packLEWord(&packet, (WORD)(nBodyLen+1)); // Length of message + packBuffer(&packet, (LPBYTE)szMessage, (WORD)(nBodyLen+1)); // Message + packMsgColorInfo(&packet); + + if (szCap) + { + packLEDWord(&packet, 0x00000026); // length of GUID + packBuffer(&packet, (LPBYTE)szCap, 0x26); // UTF-8 GUID + } + + // Pack request server ack TLV + if (pCookieData->nAckType == ACKTYPE_SERVER) + packDWord(&packet, 0x00030000); // TLV(3) + + sendServPacket(&packet); + return dwCookie; +} + + +DWORD CIcqProto::icq_SendChannel2Contacts(DWORD dwUin, char *szUid, HANDLE hContact, const char *pData, WORD wDataLen, const char *pNames, WORD wNamesLen, cookie_message_data *pCookieData) +{ + icq_packet packet; + + DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, pCookieData); + + WORD wPacketLength = wDataLen + wNamesLen + 0x12; + + // Pack the standard header + packServMsgSendHeader(&packet, dwCookie, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwUin, szUid, 2, (WORD)(wPacketLength + ((pCookieData->nAckType == ACKTYPE_SERVER)?0x22:0x1E))); + + packServTLV5HeaderBasic(&packet, wPacketLength, pCookieData->dwMsgID1, pCookieData->dwMsgID2, 0, MCAP_CONTACTS); + + packTLVWord(&packet, 0x0A, 1); // TLV: 0x0A Acktype: 1 for normal, 2 for ack + packDWord(&packet, 0x000F0000); // TLV: 0x0F empty + packTLV(&packet, 0x2711, wDataLen, (LPBYTE)pData); // TLV: 0x2711 Content (Contact UIDs) + packTLV(&packet, 0x2712, wNamesLen, (LPBYTE)pNames);// TLV: 0x2712 Extended Content (Contact NickNames) + + // Pack request ack TLV + if (pCookieData->nAckType == ACKTYPE_SERVER) + { + packDWord(&packet, 0x00030000); // TLV(3) + } + + sendServPacket(&packet); + + return dwCookie; +} + + +DWORD CIcqProto::icq_SendChannel4Message(DWORD dwUin, HANDLE hContact, BYTE bMsgType, WORD wMsgLen, const char *szMsg, cookie_message_data *pCookieData) +{ + icq_packet packet; + WORD wPacketLength; + DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData); + + if (pCookieData->nAckType == ACKTYPE_SERVER) + wPacketLength = 28; + else + wPacketLength = 24; + + // Pack the standard header + packServMsgSendHeader(&packet, dwCookie, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwUin, NULL, 4, (WORD)(wPacketLength + wMsgLen)); + + // Pack first TLV + packWord(&packet, 0x05); // TLV(5) + packWord(&packet, (WORD)(wMsgLen + 16)); // TLV len + packLEDWord(&packet, m_dwLocalUIN); // My UIN + packByte(&packet, bMsgType); // Message type + packByte(&packet, 0); // Message flags + packLEWord(&packet, wMsgLen); // Message length + packBuffer(&packet, (LPBYTE)szMsg, wMsgLen); // Message text + packMsgColorInfo(&packet); + + // Pack request ack TLV + if (pCookieData->nAckType == ACKTYPE_SERVER) + { + packDWord(&packet, 0x00030000); // TLV(3) + } + + // Pack store on server TLV + packDWord(&packet, 0x00060000); // TLV(6) + + sendServPacket(&packet); + + return dwCookie; +} + + +void CIcqProto::sendOwnerInfoRequest(void) +{ + icq_packet packet; + + cookie_directory_data *pCookieData = (cookie_directory_data*)SAFE_MALLOC(sizeof(cookie_directory_data)); + pCookieData->bRequestType = DIRECTORYREQUEST_INFOOWNER; + + DWORD dwCookie = AllocateCookie(CKT_DIRECTORY_QUERY, 0, NULL, (void*)pCookieData); + WORD wDataLen = getUINLen(m_dwLocalUIN) + 4; + + packServIcqDirectoryHeader(&packet, this, wDataLen + 8, META_DIRECTORY_QUERY, DIRECTORY_QUERY_INFO, (WORD)dwCookie); + packWord(&packet, 0x03); // with interests (ICQ6 uses 2 at login) + packDWord(&packet, 0x01); + packWord(&packet, wDataLen); + + packTLVUID(&packet, 0x32, m_dwLocalUIN, NULL); + + sendServPacket(&packet); +} + + +DWORD CIcqProto::sendUserInfoMultiRequest(BYTE *pRequestData, WORD wDataLen, int nItems) +{ + icq_packet packet; + + cookie_directory_data *pCookieData = (cookie_directory_data*)SAFE_MALLOC(sizeof(cookie_directory_data)); + if (!pCookieData) return 0; // Failure + pCookieData->bRequestType = DIRECTORYREQUEST_INFOMULTI; + + DWORD dwCookie = AllocateCookie(CKT_DIRECTORY_QUERY, 0, NULL, (void*)pCookieData); + + packServIcqDirectoryHeader(&packet, this, wDataLen + 2, META_DIRECTORY_QUERY, DIRECTORY_QUERY_MULTI_INFO, (WORD)dwCookie); + packWord(&packet, nItems); + packBuffer(&packet, pRequestData, wDataLen); + + sendServPacket(&packet); + + return dwCookie; +} + + +DWORD CIcqProto::icq_sendGetInfoServ(HANDLE hContact, DWORD dwUin, int bManual) +{ + icq_packet packet; + DWORD dwCookie = 0; + + if (IsServerOverRate(ICQ_EXTENSIONS_FAMILY, ICQ_META_CLI_REQUEST, bManual ? RML_IDLE_10 : RML_IDLE_50)) + return dwCookie; + + DBVARIANT infoToken = {DBVT_DELETED}; + BYTE *pToken = NULL; + WORD cbToken = 0; + + if (!getSetting(hContact, DBSETTING_METAINFO_TOKEN, &infoToken)) + { // retrieve user details using privacy token + cbToken = infoToken.cpbVal; + pToken = (BYTE*)_alloca(cbToken); + memcpy(pToken, infoToken.pbVal, cbToken); + + ICQFreeVariant(&infoToken); + } + + cookie_directory_data *pCookieData = (cookie_directory_data*)SAFE_MALLOC(sizeof(cookie_directory_data)); + pCookieData->bRequestType = DIRECTORYREQUEST_INFOUSER; + + dwCookie = AllocateCookie(CKT_DIRECTORY_QUERY, 0, hContact, (void*)pCookieData); + WORD wDataLen = cbToken + getUINLen(dwUin) + (cbToken ? 8 : 4); + + packServIcqDirectoryHeader(&packet, this, wDataLen + 8, META_DIRECTORY_QUERY, DIRECTORY_QUERY_INFO, (WORD)dwCookie); + packWord(&packet, 0x03); + packDWord(&packet, 1); + packWord(&packet, wDataLen); + if (pToken) + packTLV(&packet, 0x3C, cbToken, pToken); + packTLVUID(&packet, 0x32, dwUin, NULL); + + sendServPacket(&packet); + + return dwCookie; +} + + +DWORD CIcqProto::icq_sendGetAimProfileServ(HANDLE hContact, char* szUid) +{ + icq_packet packet; + BYTE bUIDlen = strlennull(szUid); + + if (IsServerOverRate(ICQ_LOCATION_FAMILY, ICQ_LOCATION_REQ_USER_INFO, RML_IDLE_10)) + return 0; + + cookie_fam15_data *pCookieData = (cookie_fam15_data*)SAFE_MALLOC(sizeof(cookie_fam15_data)); + pCookieData->bRequestType = REQUESTTYPE_PROFILE; + + DWORD dwCookie = AllocateCookie(CKT_FAMILYSPECIAL, ICQ_LOCATION_REQ_USER_INFO, hContact, (void*)pCookieData); + + serverPacketInit(&packet, (WORD)(13 + bUIDlen)); + packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_REQ_USER_INFO, 0, dwCookie); + packWord(&packet, 0x01); // request profile info + packByte(&packet, bUIDlen); + packBuffer(&packet, (LPBYTE)szUid, bUIDlen); + + sendServPacket(&packet); + + return dwCookie; +} + + +DWORD CIcqProto::icq_sendGetAwayMsgServ(HANDLE hContact, DWORD dwUin, int type, WORD wVersion) +{ + icq_packet packet; + + if (IsServerOverRate(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, RML_IDLE_30)) + return 0; + + cookie_message_data *pCookieData = CreateMessageCookie(MTYPE_AUTOAWAY, (BYTE)type); + DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData); + + packServChannel2Header(&packet, this, dwUin, 3, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwCookie, wVersion, (BYTE)type, 3, 1, 0, 0, 0); + packEmptyMsg(&packet); // Message + sendServPacket(&packet); + + return dwCookie; +} + + +DWORD CIcqProto::icq_sendGetAwayMsgServExt(HANDLE hContact, DWORD dwUin, char *szUID, int type, WORD wVersion) +{ + icq_packet packet; + + if (IsServerOverRate(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, RML_IDLE_30)) + return 0; + + cookie_message_data *pCookieData = CreateMessageCookie(MTYPE_AUTOAWAY, (BYTE)type); + DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData); + + packServMsgSendHeader(&packet, dwCookie, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwUin, szUID, 2, 122 + getPluginTypeIdLen(type)); + + // TLV(5) header + packServTLV5HeaderMsg(&packet, 82 + getPluginTypeIdLen(type), pCookieData->dwMsgID1, pCookieData->dwMsgID2, 1); + + // TLV(0x2711) header + packServTLV2711Header(&packet, (WORD)dwCookie, wVersion, MTYPE_PLUGIN, 0, 0, 0x100, 27 + getPluginTypeIdLen(type)); + // + packLEWord(&packet, 0); // Empty msg + + packPluginTypeId(&packet, type); + + packLEDWord(&packet, 0x15); + packLEDWord(&packet, 0); + packLEDWord(&packet, 0x0D); + packBuffer(&packet, (LPBYTE)"text/x-aolrtf", 0x0D); + + // Send the monster + sendServPacket(&packet); + + return dwCookie; +} + + +DWORD CIcqProto::icq_sendGetAimAwayMsgServ(HANDLE hContact, char *szUID, int type) +{ + icq_packet packet; + BYTE bUIDlen = strlennull(szUID); + + cookie_message_data *pCookieData = CreateMessageCookie(MTYPE_AUTOAWAY, (byte)type); + DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData); + + serverPacketInit(&packet, (WORD)(13 + bUIDlen)); + packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_REQ_USER_INFO, 0, dwCookie); + packWord(&packet, 0x03); + packUID(&packet, 0, szUID); + + sendServPacket(&packet); + + return dwCookie; +} + + +void CIcqProto::icq_sendSetAimAwayMsgServ(const char *szMsg) +{ + icq_packet packet; + WORD wMsgLen = strlennull(szMsg); + + DWORD dwCookie = GenerateCookie(ICQ_LOCATION_SET_USER_INFO); + + if (wMsgLen) + { + if (wMsgLen > 0x1000) wMsgLen = 0x1000; // limit length + + if (IsUSASCII(szMsg, wMsgLen)) + { + const char* fmt = "text/x-aolrtf; charset=\"us-ascii\""; + const WORD fmtlen = (WORD)strlen(fmt); + + serverPacketInit(&packet, 23 + wMsgLen + fmtlen); + packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_SET_USER_INFO, 0, dwCookie); + packTLV(&packet, 0x0f, 1, (LPBYTE)"\x02"); + packTLV(&packet, 0x03, fmtlen, (LPBYTE)fmt); + packTLV(&packet, 0x04, wMsgLen, (LPBYTE)szMsg); + } + else + { + const char* fmt = "text/x-aolrtf; charset=\"unicode-2-0\""; + const WORD fmtlen = (WORD)strlen(fmt); + + WCHAR *szMsgW = make_unicode_string(szMsg); + wMsgLen = (WORD)strlennull(szMsgW) * sizeof(WCHAR); + + WCHAR *szMsgW2 = (WCHAR*)alloca(wMsgLen), *szMsgW3 = szMsgW; + unpackWideString((BYTE**)&szMsgW3, szMsgW2, wMsgLen); + SAFE_FREE(&szMsgW); + + serverPacketInit(&packet, 23 + wMsgLen + fmtlen); + packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_SET_USER_INFO, 0, dwCookie); + packTLV(&packet, 0x0f, 1, (LPBYTE)"\x02"); + packTLV(&packet, 0x03, fmtlen, (LPBYTE)fmt); + packTLV(&packet, 0x04, wMsgLen, (LPBYTE)szMsgW2); + } + } + else + { + serverPacketInit(&packet, 19); + packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_SET_USER_INFO, 0, dwCookie); + packTLV(&packet, 0x0f, 1, (LPBYTE)"\x02"); + packTLV(&packet, 0x04, 0, NULL); + } + + sendServPacket(&packet); +} + + +void CIcqProto::icq_sendFileSendServv7(filetransfer* ft, const char *szFiles) +{ + icq_packet packet; + WORD wDescrLen = 0, wFilesLen = 0; + char *szFilesAnsi = NULL, *szDescrAnsi = NULL; + + if (!utf8_decode(szFiles, &szFilesAnsi)) + szFilesAnsi = NULL; + else + wFilesLen = strlennull(szFilesAnsi); + + if (!utf8_decode(ft->szDescription, &szDescrAnsi)) + szDescrAnsi = NULL; + else + wDescrLen = strlennull(szDescrAnsi); + + packServChannel2Header(&packet, this, ft->dwUin, (WORD)(18 + wDescrLen + wFilesLen), ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, ft->dwCookie, ICQ_VERSION, MTYPE_FILEREQ, 0, 1, 0, 1, 1); + + packLEWord(&packet, (WORD)(wDescrLen + 1)); + packBuffer(&packet, (LPBYTE)szDescrAnsi, (WORD)(wDescrLen + 1)); + packLEDWord(&packet, 0); // unknown + packLEWord(&packet, (WORD)(wFilesLen + 1)); + packBuffer(&packet, (LPBYTE)szFilesAnsi, (WORD)(wFilesLen + 1)); + packLEDWord(&packet, ft->dwTotalSize); + packLEDWord(&packet, 0); // unknown + + SAFE_FREE(&szFilesAnsi); + SAFE_FREE(&szDescrAnsi); + + sendServPacket(&packet); +} + + +void CIcqProto::icq_sendFileSendServv8(filetransfer* ft, const char *szFiles, int nAckType) +{ + icq_packet packet; + WORD wDescrLen = 0, wFilesLen = 0; + char *szFilesAnsi = NULL, *szDescrAnsi = NULL; + + if (!utf8_decode(szFiles, &szFilesAnsi)) + szFilesAnsi = NULL; + else + wFilesLen = strlennull(szFilesAnsi); + + if (!utf8_decode(ft->szDescription, &szDescrAnsi)) + szDescrAnsi = NULL; + else + wDescrLen = strlennull(szDescrAnsi); + + // 202 + UIN len + file description (no null) + file name (null included) + // Packet size = Flap length + 4 + WORD wFlapLen = 178 + wDescrLen + wFilesLen + (nAckType == ACKTYPE_SERVER?4:0); + packServMsgSendHeader(&packet, ft->dwCookie, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, ft->dwUin, NULL, 2, wFlapLen); + + // TLV(5) header + packServTLV5HeaderMsg(&packet, (WORD)(138 + wDescrLen + wFilesLen), ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, 1); + + // Port & IP information + packServDCInfo(&packet, this, FALSE); + + // TLV(0x2711) header + packServTLV2711Header(&packet, (WORD)ft->dwCookie, ICQ_VERSION, MTYPE_PLUGIN, 0, (WORD)MirandaStatusToIcq(m_iStatus), 0x100, 69 + wDescrLen + wFilesLen); + + packEmptyMsg(&packet); // Message (unused) + + packPluginTypeId(&packet, MTYPE_FILEREQ); + + packLEDWord(&packet, (WORD)(18 + wDescrLen + wFilesLen + 1)); // Remaining length + packLEDWord(&packet, wDescrLen); // Description + packBuffer(&packet, (LPBYTE)szDescrAnsi, wDescrLen); + packWord(&packet, 0x8c82); // Unknown (port?), seen 0x80F6 + packWord(&packet, 0x0222); // Unknown, seen 0x2e01 + packLEWord(&packet, (WORD)(wFilesLen + 1)); + packBuffer(&packet, (LPBYTE)szFilesAnsi, (WORD)(wFilesLen + 1)); + packLEDWord(&packet, ft->dwTotalSize); + packLEDWord(&packet, 0x0008c82); // Unknown, (seen 0xf680 ~33000) + + SAFE_FREE(&szFilesAnsi); + SAFE_FREE(&szDescrAnsi); + + // Pack request server ack TLV + if (nAckType == ACKTYPE_SERVER) + packDWord(&packet, 0x00030000); // TLV(3) + + // Send the monster + sendServPacket(&packet); +} + + +/* also sends rejections */ +void CIcqProto::icq_sendFileAcceptServv8(DWORD dwUin, DWORD TS1, DWORD TS2, DWORD dwCookie, const char *szFiles, const char *szDescr, DWORD dwTotalSize, WORD wPort, BOOL accepted, int nAckType) +{ + icq_packet packet; + WORD wDescrLen, wFilesLen; + char *szFilesAnsi = NULL, *szDescrAnsi = NULL; + + /* if !accepted, szDescr == szReason, szFiles = "" */ + + if (!accepted) szFiles = ""; + + if (!utf8_decode(szFiles, &szFilesAnsi)) + szFilesAnsi = NULL; + + if (!utf8_decode(szDescr, &szDescrAnsi)) + szDescrAnsi = NULL; + + wDescrLen = strlennull(szDescrAnsi); + wFilesLen = strlennull(szFilesAnsi); + + // 202 + UIN len + file description (no null) + file name (null included) + // Packet size = Flap length + 4 + WORD wFlapLen = 178 + wDescrLen + wFilesLen + (nAckType == ACKTYPE_SERVER?4:0); + packServMsgSendHeader(&packet, dwCookie, TS1, TS2, dwUin, NULL, 2, wFlapLen); + + // TLV(5) header + packServTLV5HeaderMsg(&packet, (WORD)(138 + wDescrLen + wFilesLen), TS1, TS2, 2); + + // Port & IP information + packServDCInfo(&packet, this, !accepted); + + // TLV(0x2711) header + packServTLV2711Header(&packet, (WORD)dwCookie, ICQ_VERSION, MTYPE_PLUGIN, 0, (WORD)(accepted ? 0:1), 0, 69 + wDescrLen + wFilesLen); + // + packEmptyMsg(&packet); // Message (unused) + + packPluginTypeId(&packet, MTYPE_FILEREQ); + + packLEDWord(&packet, (WORD)(18 + wDescrLen + wFilesLen + 1)); // Remaining length + packLEDWord(&packet, wDescrLen); // Description + packBuffer(&packet, (LPBYTE)szDescrAnsi, wDescrLen); + packWord(&packet, wPort); // Port + packWord(&packet, 0x00); // Unknown + packLEWord(&packet, (WORD)(wFilesLen + 1)); + packBuffer(&packet, (LPBYTE)szFilesAnsi, (WORD)(wFilesLen + 1)); + packLEDWord(&packet, dwTotalSize); + packLEDWord(&packet, (DWORD)wPort); // Unknown + + SAFE_FREE(&szFilesAnsi); + SAFE_FREE(&szDescrAnsi); + + // Pack request server ack TLV + if (nAckType == ACKTYPE_SERVER) + { + packDWord(&packet, 0x00030000); // TLV(3) + } + + // Send the monster + sendServPacket(&packet); +} + + +void CIcqProto::icq_sendFileAcceptServv7(DWORD dwUin, DWORD TS1, DWORD TS2, DWORD dwCookie, const char* szFiles, const char* szDescr, DWORD dwTotalSize, WORD wPort, BOOL accepted, int nAckType) +{ + icq_packet packet; + WORD wDescrLen, wFilesLen; + char *szFilesAnsi = NULL, *szDescrAnsi = NULL; + + /* if !accepted, szDescr == szReason, szFiles = "" */ + + if (!accepted) szFiles = ""; + + if (!utf8_decode(szFiles, &szFilesAnsi)) + szFilesAnsi = NULL; + + if (!utf8_decode(szDescr, &szDescrAnsi)) + szDescrAnsi = NULL; + + wDescrLen = strlennull(szDescrAnsi); + wFilesLen = strlennull(szFilesAnsi); + + // 150 + UIN len + file description (with null) + file name (2 nulls) + // Packet size = Flap length + 4 + WORD wFlapLen = 127 + wDescrLen + 1 + wFilesLen + (nAckType == ACKTYPE_SERVER?4:0); + packServMsgSendHeader(&packet, dwCookie, TS1, TS2, dwUin, NULL, 2, wFlapLen); + + // TLV(5) header + packServTLV5HeaderMsg(&packet, (WORD)(88 + wDescrLen + wFilesLen), TS1, TS2, 2); + + // Port & IP information + packServDCInfo(&packet, this, !accepted); + + // TLV(0x2711) header + packServTLV2711Header(&packet, (WORD)dwCookie, ICQ_VERSION, MTYPE_FILEREQ, 0, (WORD)(accepted ? 0:1), 0, 19 + wDescrLen + wFilesLen); + // + packLEWord(&packet, (WORD)(wDescrLen + 1)); // Description + packBuffer(&packet, (LPBYTE)szDescrAnsi, (WORD)(wDescrLen + 1)); + packWord(&packet, wPort); // Port + packWord(&packet, 0x00); // Unknown + packLEWord(&packet, (WORD)(wFilesLen + 2)); + packBuffer(&packet, (LPBYTE)szFilesAnsi, (WORD)(wFilesLen + 1)); + packByte(&packet, 0); + packLEDWord(&packet, dwTotalSize); + packLEDWord(&packet, (DWORD)wPort); // Unknown + + SAFE_FREE(&szFilesAnsi); + SAFE_FREE(&szDescrAnsi); + + // Pack request server ack TLV + if (nAckType == ACKTYPE_SERVER) + { + packDWord(&packet, 0x00030000); // TLV(3) + } + + // Send the monster + sendServPacket(&packet); +} + + +void CIcqProto::icq_sendFileAcceptServ(DWORD dwUin, filetransfer *ft, int nAckType) +{ + char *szDesc = ft->szDescription; + + if (ft->bEmptyDesc) szDesc = ""; // keep empty if it originally was (Trillian workaround) + + if (ft->nVersion >= 8) + { + icq_sendFileAcceptServv8(dwUin, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, ft->dwCookie, ft->szFilename, szDesc, ft->dwTotalSize, wListenPort, TRUE, nAckType); + NetLog_Server("Sent file accept v%u through server, port %u", 8, wListenPort); + } + else + { + icq_sendFileAcceptServv7(dwUin, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, ft->dwCookie, ft->szFilename, szDesc, ft->dwTotalSize, wListenPort, TRUE, nAckType); + NetLog_Server("Sent file accept v%u through server, port %u", 7, wListenPort); + } +} + + +void CIcqProto::icq_sendFileDenyServ(DWORD dwUin, filetransfer *ft, const char *szReason, int nAckType) +{ + if (ft->nVersion >= 8) + { + icq_sendFileAcceptServv8(dwUin, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, ft->dwCookie, ft->szFilename, szReason, ft->dwTotalSize, wListenPort, FALSE, nAckType); + NetLog_Server("Sent file deny v%u through server", 8); + } + else + { + icq_sendFileAcceptServv7(dwUin, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, ft->dwCookie, ft->szFilename, szReason, ft->dwTotalSize, wListenPort, FALSE, nAckType); + NetLog_Server("Sent file deny v%u through server", 7); + } +} + + +void CIcqProto::icq_sendAwayMsgReplyServ(DWORD dwUin, DWORD dwMsgID1, DWORD dwMsgID2, WORD wCookie, WORD wVersion, BYTE msgType, char** szMsg) +{ + HANDLE hContact = HContactFromUIN(dwUin, NULL); + + if (validateStatusMessageRequest(hContact, msgType)) + { + NotifyEventHooks(m_modeMsgsEvent, (WPARAM)msgType, (LPARAM)dwUin); + + icq_lock l(m_modeMsgsMutex); + + if (szMsg && *szMsg) + { + char *pszMsg = NULL; + WORD wReplyVersion = ICQ_VERSION; + + if (wVersion == 9) + { + pszMsg = *szMsg; + wReplyVersion = 9; + } + else + { // only v9 protocol supports UTF-8 mode messagees + WORD wMsgLen = strlennull(*szMsg) + 1; + char *szAnsiMsg = (char*)_alloca(wMsgLen); + + utf8_decode_static(*szMsg, szAnsiMsg, wMsgLen); + pszMsg = szAnsiMsg; + } + + WORD wMsgLen = strlennull(pszMsg); + + // limit msg len to max snac size - we get disconnected if exceeded + if (wMsgLen > MAX_MESSAGESNACSIZE) + wMsgLen = MAX_MESSAGESNACSIZE; + + icq_packet packet; + + packServAdvancedMsgReply(&packet, dwUin, NULL, dwMsgID1, dwMsgID2, wCookie, wReplyVersion, msgType, 3, (WORD)(wMsgLen + 3)); + packLEWord(&packet, (WORD)(wMsgLen + 1)); + packBuffer(&packet, (LPBYTE)pszMsg, wMsgLen); + packByte(&packet, 0); + + sendServPacket(&packet); + } + } +} + + +void CIcqProto::icq_sendAwayMsgReplyServExt(DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, WORD wCookie, WORD wVersion, BYTE msgType, char **szMsg) +{ + HANDLE hContact = HContactFromUID(dwUin, szUID, NULL); + + if (validateStatusMessageRequest(hContact, msgType)) + { + NotifyEventHooks(m_modeMsgsEvent, (WPARAM)msgType, (LPARAM)dwUin); + + icq_lock l(m_modeMsgsMutex); + + if (szMsg && *szMsg) + { + char *pszMsg = NULL; + WORD wReplyVersion = ICQ_VERSION; + + if (wVersion == 9) + { + pszMsg = *szMsg; + wReplyVersion = 9; + } + else + { // only v9 protocol supports UTF-8 mode messagees + WORD wMsgLen = strlennull(*szMsg) + 1; + char *szAnsiMsg = (char*)_alloca(wMsgLen); + + utf8_decode_static(*szMsg, szAnsiMsg, wMsgLen); + pszMsg = szAnsiMsg; + } + // convert to HTML + char *mng = MangleXml(pszMsg, strlennull(pszMsg)); + pszMsg = (char*)SAFE_MALLOC(strlennull(mng) + 28); + strcpy(pszMsg, ""); /// TODO: add support for RTL & user customizable font + strcat(pszMsg, mng); + SAFE_FREE(&mng); + strcat(pszMsg, ""); + + WORD wMsgLen = strlennull(pszMsg); + + // limit msg len to max snac size - we get disconnected if exceeded /// FIXME: correct HTML cutting + if (wMsgLen > MAX_MESSAGESNACSIZE) + wMsgLen = MAX_MESSAGESNACSIZE; + + icq_packet packet; + + packServAdvancedMsgReply(&packet, dwUin, szUID, dwMsgID1, dwMsgID2, wCookie, wReplyVersion, MTYPE_PLUGIN, 0, wMsgLen + 27 + getPluginTypeIdLen(msgType)); + packLEWord(&packet, 0); // Message size + packPluginTypeId(&packet, msgType); + + packLEDWord(&packet, wMsgLen + 21); + packLEDWord(&packet, wMsgLen); + packBuffer(&packet, (LPBYTE)pszMsg, wMsgLen); + + packLEDWord(&packet, 0x0D); + packBuffer(&packet, (LPBYTE)"text/x-aolrtf", 0x0D); + + sendServPacket(&packet); + SAFE_FREE(&pszMsg); + } + } +} + + +void CIcqProto::icq_sendAdvancedMsgAck(DWORD dwUin, DWORD dwTimestamp, DWORD dwTimestamp2, WORD wCookie, BYTE bMsgType, BYTE bMsgFlags) +{ + icq_packet packet; + + packServAdvancedMsgReply(&packet, dwUin, NULL, dwTimestamp, dwTimestamp2, wCookie, ICQ_VERSION, bMsgType, bMsgFlags, 11); + packEmptyMsg(&packet); // Status message + packMsgColorInfo(&packet); + + sendServPacket(&packet); +} + + +void CIcqProto::icq_sendContactsAck(DWORD dwUin, char *szUid, DWORD dwMsgID1, DWORD dwMsgID2) +{ + icq_packet packet; + + packServMsgSendHeader(&packet, 0, dwMsgID1, dwMsgID2, dwUin, szUid, 2, 0x1E); + packServTLV5HeaderBasic(&packet, 0, dwMsgID1, dwMsgID2, 2, MCAP_CONTACTS); + + sendServPacket(&packet); +} + + +// Searches + +DWORD CIcqProto::SearchByUin(DWORD dwUin) +{ + WORD wInfoLen; + icq_packet pBuffer; // I reuse the ICQ packet type as a generic buffer + // I should be ashamed! ;) + + // Calculate data size + wInfoLen = 4 + getUINLen(dwUin); + + // Initialize our handy data buffer + pBuffer.wPlace = 0; + pBuffer.pData = (BYTE *)_alloca(wInfoLen); + pBuffer.wLen = wInfoLen; + + // Initialize our handy data buffer + packTLVUID(&pBuffer, 0x32, dwUin, NULL); + + // Send it off for further packing + return sendDirectorySearchPacket(pBuffer.pData, wInfoLen, 0, FALSE); +} + + +DWORD CIcqProto::SearchByNames(const char *pszNick, const char *pszFirstName, const char *pszLastName, WORD wPage) +{ // use directory search like ICQ6 does + WORD wInfoLen = 0; + WORD wNickLen,wFirstLen,wLastLen; + icq_packet pBuffer; // I reuse the ICQ packet type as a generic buffer + // I should be ashamed! ;) + + wNickLen = strlennull(pszNick); + wFirstLen = strlennull(pszFirstName); + wLastLen = strlennull(pszLastName); + + _ASSERTE(wFirstLen || wLastLen || wNickLen); + + + // Calculate data size + if (wFirstLen) + wInfoLen = wFirstLen + 4; + if (wLastLen) + wInfoLen += wLastLen + 4; + if (wNickLen) + wInfoLen += wNickLen + 4; + + // Initialize our handy data buffer + pBuffer.wPlace = 0; + pBuffer.pData = (BYTE *)_alloca(wInfoLen); + pBuffer.wLen = wInfoLen; + + // Pack the search details + if (wNickLen) + packTLV(&pBuffer, 0x78, wNickLen, (PBYTE)pszNick); + + if (wLastLen) + packTLV(&pBuffer, 0x6E, wLastLen, (PBYTE)pszLastName); + + if (wFirstLen) + packTLV(&pBuffer, 0x64, wFirstLen, (PBYTE)pszFirstName); + + // Send it off for further packing + if (wInfoLen) + return sendDirectorySearchPacket(pBuffer.pData, wInfoLen, wPage, FALSE); + else + return 0; // Failure +} + + +DWORD CIcqProto::SearchByMail(const char* pszEmail) +{ + DWORD dwCookie = 0; + WORD wInfoLen = 0; + WORD wEmailLen; + BYTE *pBuffer; + int pBufferPos; + + wEmailLen = strlennull(pszEmail); + + _ASSERTE(wEmailLen); + + if (wEmailLen > 0) + { + // Calculate data size + wInfoLen = wEmailLen + 7; + + // Initialize our handy data buffer + pBuffer = (BYTE *)_alloca(wInfoLen); + pBufferPos = 0; + + // Pack the search details + packLETLVLNTS(&pBuffer, &pBufferPos, pszEmail, TLV_EMAIL); + + // Send it off for further packing + dwCookie = sendTLVSearchPacket(SEARCHTYPE_EMAIL, (char*)pBuffer, META_SEARCH_EMAIL, wInfoLen, FALSE); + } + + return dwCookie; +} + + +DWORD CIcqProto::sendDirectorySearchPacket(const BYTE *pSearchData, WORD wDataLen, WORD wPage, BOOL bOnlineUsersOnly) +{ + icq_packet packet; + DWORD dwCookie; + + _ASSERTE(pSearchData); + _ASSERTE(wDataLen >= 4); + + cookie_directory_data *pCookieData = (cookie_directory_data*)SAFE_MALLOC(sizeof(cookie_directory_data)); + if (pCookieData) + { + pCookieData->bRequestType = DIRECTORYREQUEST_SEARCH; + dwCookie = AllocateCookie(CKT_DIRECTORY_QUERY, 0, NULL, (void*)pCookieData); + } + else + return 0; + + // Pack headers + packServIcqDirectoryHeader(&packet, this, wDataLen + (bOnlineUsersOnly ? 14 : 8), META_DIRECTORY_QUERY, DIRECTORY_QUERY_INFO, (WORD)dwCookie); + packWord(&packet, 0x02); + + // Pack requested page number + packWord(&packet, wPage); + + // Pack search data + packWord(&packet, 0x0001); + packWord(&packet, wDataLen + (bOnlineUsersOnly ? 6 : 0)); + packBuffer(&packet, pSearchData, wDataLen); + + if (bOnlineUsersOnly) + { // Pack "Online users only" flag + packTLVWord(&packet, 0x136, 1); + } + + // Go! + sendServPacket(&packet); + + return dwCookie; +} + + +DWORD CIcqProto::sendTLVSearchPacket(BYTE bType, char* pSearchDataBuf, WORD wSearchType, WORD wInfoLen, BOOL bOnlineUsersOnly) +{ + icq_packet packet; + cookie_search* pCookie; + + _ASSERTE(pSearchDataBuf); + _ASSERTE(wInfoLen >= 4); + + pCookie = (cookie_search*)SAFE_MALLOC(sizeof(cookie_search)); + if (!pCookie) + return 0; + + pCookie->bSearchType = bType; + DWORD dwCookie = AllocateCookie(CKT_SEARCH, 0, 0, pCookie); + + // Pack headers + packServIcqExtensionHeader(&packet, this, (WORD)(wInfoLen + (wSearchType==META_SEARCH_GENERIC?7:2)), CLI_META_INFO_REQ, (WORD)dwCookie); + + // Pack search type + packLEWord(&packet, wSearchType); + + // Pack search data + packBuffer(&packet, (LPBYTE)pSearchDataBuf, wInfoLen); + + if (wSearchType == META_SEARCH_GENERIC && bOnlineUsersOnly) + { // Pack "Online users only" flag - only for generic search + BYTE bData = 1; + packTLV(&packet, TLV_ONLINEONLY, 1, &bData); + } + + // Go! + sendServPacket(&packet); + + return dwCookie; +} + + +DWORD CIcqProto::icq_sendAdvancedSearchServ(BYTE* fieldsBuffer,int bufferLen) +{ + icq_packet packet; + DWORD dwCookie; + + cookie_search *pCookie = (cookie_search*)SAFE_MALLOC(sizeof(cookie_search)); + if (pCookie) + { + pCookie->bSearchType = SEARCHTYPE_DETAILS; + dwCookie = AllocateCookie(CKT_SEARCH, 0, 0, pCookie); + } + else + return 0; + + packServIcqExtensionHeader(&packet, this, (WORD)bufferLen, CLI_META_INFO_REQ, (WORD)dwCookie); + packBuffer(&packet, (LPBYTE)fieldsBuffer, (WORD)bufferLen); + + sendServPacket(&packet); + + return dwCookie; +} + + +DWORD CIcqProto::icq_searchAimByEmail(const char* pszEmail, DWORD dwSearchId) +{ + icq_packet packet; + DWORD dwCookie; + cookie_search* pCookie; + WORD wEmailLen; + + if (!FindCookie(dwSearchId, NULL, (void**)&pCookie)) + { + dwSearchId = 0; + pCookie = (cookie_search*)SAFE_MALLOC(sizeof(cookie_search)); + pCookie->bSearchType = SEARCHTYPE_EMAIL; + } + + if (pCookie) + { + pCookie->dwMainId = dwSearchId; + pCookie->szObject = null_strdup(pszEmail); + dwCookie = AllocateCookie(CKT_SEARCH, ICQ_LOOKUP_REQUEST, 0, pCookie); + } + else + return 0; + + wEmailLen = strlennull(pszEmail); + serverPacketInit(&packet, (WORD)(10 + wEmailLen)); + packFNACHeader(&packet, ICQ_LOOKUP_FAMILY, ICQ_LOOKUP_REQUEST, 0, dwCookie); + packBuffer(&packet, (LPBYTE)pszEmail, wEmailLen); + + sendServPacket(&packet); + + return dwCookie; +} + + +DWORD CIcqProto::icq_changeUserPasswordServ(const char *szPassword) +{ + icq_packet packet; + WORD wPasswordLen = strlennull(szPassword); + DWORD dwCookie = GenerateCookie(0); + + packServIcqExtensionHeader(&packet, this, (WORD)(wPasswordLen + 4), CLI_META_INFO_REQ, (WORD)dwCookie, ICQ_META_SRV_UPDATE); + packLEWord(&packet, META_SET_PASSWORD_REQ); + packLEWord(&packet, wPasswordLen); + packBuffer(&packet, (BYTE*)szPassword, wPasswordLen); + + sendServPacket(&packet); + + return dwCookie; +} + + +DWORD CIcqProto::icq_changeUserDirectoryInfoServ(const BYTE *pData, WORD wDataLen, BYTE bRequestType) +{ + icq_packet packet; + cookie_directory_data *pCookieData = (cookie_directory_data*)SAFE_MALLOC(sizeof(cookie_directory_data)); + pCookieData->bRequestType = bRequestType; + DWORD dwCookie = AllocateCookie(CKT_DIRECTORY_UPDATE, 0, NULL, pCookieData); + + packServIcqDirectoryHeader(&packet, this, wDataLen + 4, META_DIRECTORY_UPDATE, DIRECTORY_SET_INFO, (WORD)dwCookie, ICQ_META_SRV_UPDATE); + packWord(&packet, 0x0003); + packWord(&packet, wDataLen); + packBuffer(&packet, pData, wDataLen); + + sendServPacket(&packet); + + return dwCookie; +} + + +DWORD CIcqProto::icq_sendSMSServ(const char *szPhoneNumber, const char *szMsg) +{ + icq_packet packet; + DWORD dwCookie; + WORD wBufferLen; + char* szBuffer = NULL; + char* szMyNick = NULL; + char szTime[30]; + time_t now; + int nBufferSize; + + now = time(NULL); + strftime(szTime, sizeof(szTime), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now)); + /* Sun, 00 Jan 0000 00:00:00 GMT */ + + szMyNick = null_strdup((char *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)(HANDLE)NULL, 0)); + nBufferSize = 1 + strlennull(szMyNick) + strlennull(szPhoneNumber) + strlennull(szMsg) + sizeof("1252utf80000000000Yes"); + + if (szBuffer = (char *)_alloca(nBufferSize)) + { + + wBufferLen = null_snprintf(szBuffer, nBufferSize, + "" + "" + "%s" /* phone number */ + "" + "" + "%s" /* body */ + "" + "" + "1252" + "" + "" + "utf8" + "" + "" + "%u" /* my UIN */ + "" + "" + "%s" /* my nick */ + "" + "" + "Yes" + "" + "" + "", + szPhoneNumber, szMsg, m_dwLocalUIN, szMyNick, szTime); + + dwCookie = GenerateCookie(0); + + packServIcqExtensionHeader(&packet, this, (WORD)(wBufferLen + 27), CLI_META_INFO_REQ, (WORD)dwCookie); + packWord(&packet, 0x8214); /* send sms */ + packWord(&packet, 1); + packWord(&packet, 0x16); + packDWord(&packet, 0); + packDWord(&packet, 0); + packDWord(&packet, 0); + packDWord(&packet, 0); + packWord(&packet, 0); + packWord(&packet, (WORD)(wBufferLen + 1)); + packBuffer(&packet, (LPBYTE)szBuffer, (WORD)(1 + wBufferLen)); + + sendServPacket(&packet); + } + else + { + dwCookie = 0; + } + + SAFE_FREE((void**)&szMyNick); + return dwCookie; +} + +void CIcqProto::icq_sendGenericContact(DWORD dwUin, const char *szUid, WORD wFamily, WORD wSubType) +{ + icq_packet packet; + int nUinLen; + + nUinLen = getUIDLen(dwUin, szUid); + + serverPacketInit(&packet, (WORD)(nUinLen + 11)); + packFNACHeader(&packet, wFamily, wSubType); + packUID(&packet, dwUin, szUid); + + sendServPacket(&packet); +} + +void CIcqProto::icq_sendNewContact(DWORD dwUin, const char *szUid) +{ + /* Try to add to temporary buddy list */ + icq_sendGenericContact(dwUin, szUid, ICQ_BUDDY_FAMILY, ICQ_USER_ADDTOTEMPLIST); +} + + +void CIcqProto::icq_sendRemoveContact(DWORD dwUin, const char *szUid) +{ + /* Remove from temporary buddy list */ + icq_sendGenericContact(dwUin, szUid, ICQ_BUDDY_FAMILY, ICQ_USER_REMOVEFROMTEMPLIST); +} + + +// list==0: visible list +// list==1: invisible list +void CIcqProto::icq_sendChangeVisInvis(HANDLE hContact, DWORD dwUin, char* szUID, int list, int add) +{ // TODO: This needs grouping & rate management + // Tell server to change our server-side contact visbility list + if (m_bSsiEnabled) + { + WORD wContactId; + char* szSetting; + WORD wType; + + if (list == 0) + { + wType = SSI_ITEM_PERMIT; + szSetting = DBSETTING_SERVLIST_PERMIT; + } + else + { + wType = SSI_ITEM_DENY; + szSetting = DBSETTING_SERVLIST_DENY; + } + + if (add) + { + // check if we should make the changes, this is 2nd level check + if (getSettingWord(hContact, szSetting, 0) != 0) + return; + + // Add + wContactId = GenerateServerID(SSIT_ITEM, 0); + + icq_addServerPrivacyItem(hContact, dwUin, szUID, wContactId, wType); + + setSettingWord(hContact, szSetting, wContactId); + } + else + { + // Remove + wContactId = getSettingWord(hContact, szSetting, 0); + + if (wContactId) + { + icq_removeServerPrivacyItem(hContact, dwUin, szUID, wContactId, wType); + + deleteSetting(hContact, szSetting); + } + } + } + + // Notify server that we have changed + // our client side visibility list + { + int nUinLen; + icq_packet packet; + WORD wSnac = 0; + + if (list && m_iStatus == ID_STATUS_INVISIBLE) + return; + + if (!list && m_iStatus != ID_STATUS_INVISIBLE) + return; + + + if (list && add) + wSnac = ICQ_CLI_ADDINVISIBLE; + else if (list && !add) + wSnac = ICQ_CLI_REMOVEINVISIBLE; + else if (!list && add) + wSnac = ICQ_CLI_ADDVISIBLE; + else if (!list && !add) + wSnac = ICQ_CLI_REMOVEVISIBLE; + + nUinLen = getUIDLen(dwUin, szUID); + + serverPacketInit(&packet, (WORD)(nUinLen + 11)); + packFNACHeader(&packet, ICQ_BOS_FAMILY, wSnac); + packUID(&packet, dwUin, szUID); + + sendServPacket(&packet); + } +} + +void CIcqProto::icq_sendEntireVisInvisList(int list) +{ + if (list) + sendEntireListServ(ICQ_BOS_FAMILY, ICQ_CLI_ADDINVISIBLE, BUL_INVISIBLE); + else + sendEntireListServ(ICQ_BOS_FAMILY, ICQ_CLI_ADDVISIBLE, BUL_VISIBLE); +} + +void CIcqProto::icq_sendRevokeAuthServ(DWORD dwUin, char *szUid) +{ + icq_sendGenericContact(dwUin, szUid, ICQ_LISTS_FAMILY, ICQ_LISTS_REVOKEAUTH); +} + +void CIcqProto::icq_sendGrantAuthServ(DWORD dwUin, const char *szUid, const char *szMsg) +{ + icq_packet packet; + BYTE nUinlen; + char *szUtfMsg = NULL; + WORD nMsglen; + + nUinlen = getUIDLen(dwUin, szUid); + + // Prepare custom utf-8 message + szUtfMsg = ansi_to_utf8(szMsg); + nMsglen = strlennull(szUtfMsg); + + serverPacketInit(&packet, (WORD)(15 + nUinlen + nMsglen)); + packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_GRANTAUTH); + packUID(&packet, dwUin, szUid); + packWord(&packet, nMsglen); + packBuffer(&packet, (LPBYTE)szUtfMsg, nMsglen); + packWord(&packet, 0); + + SAFE_FREE((void**)&szUtfMsg); + + sendServPacket(&packet); +} + +void CIcqProto::icq_sendAuthReqServ(DWORD dwUin, char *szUid, const char *szMsg) +{ + icq_packet packet; + BYTE nUinlen; + WORD nMsglen; + + nUinlen = getUIDLen(dwUin, szUid); + nMsglen = strlennull(szMsg); + + serverPacketInit(&packet, (WORD)(15 + nUinlen + nMsglen)); + packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_REQUESTAUTH); + packUID(&packet, dwUin, szUid); + packWord(&packet, nMsglen); + packBuffer(&packet, (LPBYTE)szMsg, nMsglen); + packWord(&packet, 0); + + sendServPacket(&packet); +} + +void CIcqProto::icq_sendAuthResponseServ(DWORD dwUin, char* szUid, int auth, const TCHAR *szReason) +{ + icq_packet packet; + BYTE nUinLen = getUIDLen(dwUin, szUid); + + // Prepare custom utf-8 reason + char *szUtfReason = tchar_to_utf8(szReason); + WORD nReasonLen = strlennull(szUtfReason); + + serverPacketInit(&packet, (WORD)(16 + nUinLen + nReasonLen)); + packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_AUTHRESPONSE); + packUID(&packet, dwUin, szUid); + packByte(&packet, (BYTE)auth); + packWord(&packet, nReasonLen); + packBuffer(&packet, (LPBYTE)szUtfReason, nReasonLen); + packWord(&packet, 0); + + SAFE_FREE(&szUtfReason); + + sendServPacket(&packet); +} + +void CIcqProto::icq_sendYouWereAddedServ(DWORD dwUin, DWORD dwMyUin) +{ + icq_packet packet; + DWORD dwID1; + DWORD dwID2; + + dwID1 = time(NULL); + dwID2 = RandRange(0, 0x00FF); + + packServMsgSendHeader(&packet, 0, dwID1, dwID2, dwUin, NULL, 0x0004, 17); + packWord(&packet, 0x0005); // TLV(5) + packWord(&packet, 0x0009); + packLEDWord(&packet, dwMyUin); + packByte(&packet, MTYPE_ADDED); + packByte(&packet, 0); // msg-flags + packEmptyMsg(&packet); // NTS + packDWord(&packet, 0x00060000); // TLV(6) + + sendServPacket(&packet); +} + +void CIcqProto::icq_sendXtrazRequestServ(DWORD dwUin, DWORD dwCookie, char* szBody, int nBodyLen, cookie_message_data *pCookieData) +{ + icq_packet packet; + WORD wCoreLen; + + wCoreLen = 11 + getPluginTypeIdLen(pCookieData->bMessageType) + nBodyLen; + packServMsgSendHeader(&packet, dwCookie, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwUin, NULL, 2, (WORD)(99 + wCoreLen)); + + // TLV(5) header + packServTLV5HeaderMsg(&packet, (WORD)(55 + wCoreLen), pCookieData->dwMsgID1, pCookieData->dwMsgID2, 1); + + // TLV(0x2711) header + packServTLV2711Header(&packet, (WORD)dwCookie, ICQ_VERSION, MTYPE_PLUGIN, 0, 0, 0x100, wCoreLen); + // + packEmptyMsg(&packet); + + packPluginTypeId(&packet, pCookieData->bMessageType); + + packLEDWord(&packet, nBodyLen + 4); + packLEDWord(&packet, nBodyLen); + packBuffer(&packet, (LPBYTE)szBody, (WORD)nBodyLen); + + // Pack request server ack TLV + packDWord(&packet, 0x00030000); // TLV(3) + + // Send the monster + sendServPacket(&packet); +} + +void CIcqProto::icq_sendXtrazResponseServ(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szBody, int nBodyLen, int nType) +{ + icq_packet packet; + + packServAdvancedMsgReply(&packet, dwUin, NULL, dwMID, dwMID2, wCookie, ICQ_VERSION, MTYPE_PLUGIN, 0, (WORD)(getPluginTypeIdLen(nType) + 11 + nBodyLen)); + // + packEmptyMsg(&packet); + + packPluginTypeId(&packet, nType); + + packLEDWord(&packet, nBodyLen + 4); + packLEDWord(&packet, nBodyLen); + packBuffer(&packet, (LPBYTE)szBody, (WORD)nBodyLen); + + // Send the monster + sendServPacket(&packet); +} + +void CIcqProto::icq_sendReverseReq(directconnect *dc, DWORD dwCookie, cookie_message_data *pCookie) +{ + icq_packet packet; + + packServMsgSendHeader(&packet, dwCookie, pCookie->dwMsgID1, pCookie->dwMsgID2, dc->dwRemoteUin, NULL, 2, 0x47); + + packServTLV5HeaderBasic(&packet, 0x29, pCookie->dwMsgID1, pCookie->dwMsgID2, 0, MCAP_REVERSE_DC_REQ); + + packTLVWord(&packet, 0x0A, 1); // TLV: 0x0A Acktype: 1 for normal, 2 for ack + packDWord(&packet, 0x000F0000); // TLV: 0x0F empty + packDWord(&packet, 0x2711001B); // TLV: 0x2711 Content + // TLV(0x2711) data + packLEDWord(&packet, m_dwLocalUIN); // Our UIN + packDWord(&packet, dc->dwLocalExternalIP);// IP to connect to + packLEDWord(&packet, wListenPort); // Port to connect to + packByte(&packet, DC_NORMAL); // generic DC type + packDWord(&packet, dc->dwRemotePort); // unknown + packDWord(&packet, wListenPort); // port again ? + packLEWord(&packet, ICQ_VERSION); // DC Version + packLEDWord(&packet, dwCookie); // Req Cookie + + // Send the monster + sendServPacket(&packet); +} + +void CIcqProto::icq_sendReverseFailed(directconnect* dc, DWORD dwMsgID1, DWORD dwMsgID2, DWORD dwCookie) +{ + icq_packet packet; + int nUinLen = getUINLen(dc->dwRemoteUin); + + serverPacketInit(&packet, (WORD)(nUinLen + 74)); + packFNACHeader(&packet, ICQ_MSG_FAMILY, ICQ_MSG_RESPONSE, 0, ICQ_MSG_RESPONSE<<0x10 | (dwCookie & 0x7FFF)); + packLEDWord(&packet, dwMsgID1); // Msg ID part 1 + packLEDWord(&packet, dwMsgID2); // Msg ID part 2 + packWord(&packet, 0x02); + packUIN(&packet, dc->dwRemoteUin); + packWord(&packet, 0x03); + packLEDWord(&packet, dc->dwRemoteUin); + packLEDWord(&packet, dc->dwRemotePort); + packLEDWord(&packet, wListenPort); + packLEWord(&packet, ICQ_VERSION); + packLEDWord(&packet, dwCookie); + + sendServPacket(&packet); +} + + +// OSCAR file-transfer packets starts here +// +void CIcqProto::oft_sendFileRequest(DWORD dwUin, char *szUid, oscar_filetransfer *ft, const char *pszFiles, DWORD dwLocalInternalIP) +{ + icq_packet packet; + + char *szCoolStr = (char*)_alloca(strlennull(ft->szDescription)+strlennull(pszFiles) + 160); + sprintf(szCoolStr, "%s%I64u1%s", pszFiles, ft->qwTotalSize, ft->szDescription); + szCoolStr = MangleXml(szCoolStr, strlennull(szCoolStr)); + + WORD wDataLen = 93 + strlennull(szCoolStr) + strlennull(pszFiles); + if (ft->bUseProxy) wDataLen += 4; + + packServMsgSendHeader(&packet, ft->dwCookie, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, dwUin, szUid, 2, (WORD)(wDataLen + 0x1E)); + packServTLV5HeaderBasic(&packet, wDataLen, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, 0, MCAP_FILE_TRANSFER); + + packTLVWord(&packet, 0x0A, ++ft->wReqNum); // Request sequence + packDWord(&packet, 0x000F0000); // Unknown + packTLV(&packet, 0x0D, 5, (LPBYTE)"utf-8"); // Charset + packTLV(&packet, 0x0C, (WORD)strlennull(szCoolStr), (LPBYTE)szCoolStr); // User message (CoolData XML) + SAFE_FREE(&szCoolStr); + if (ft->bUseProxy) + { + packTLVDWord(&packet, 0x02, ft->dwProxyIP); // Proxy IP + packTLVDWord(&packet, 0x16, ft->dwProxyIP ^ 0x0FFFFFFFF); // Proxy IP check + } + else + { + packTLVDWord(&packet, 0x02, dwLocalInternalIP); + packTLVDWord(&packet, 0x16, dwLocalInternalIP ^ 0x0FFFFFFFF); + } + packTLVDWord(&packet, 0x03, dwLocalInternalIP); // Client IP + if (ft->bUseProxy) + { + packTLVWord(&packet, 0x05, ft->wRemotePort); + packTLVWord(&packet, 0x17, (WORD)(ft->wRemotePort ^ 0x0FFFF)); + packDWord(&packet, 0x00100000); // Proxy flag + } + else + { + oscar_listener *pListener = (oscar_listener*)ft->listener; + + packTLVWord(&packet, 0x05, pListener->wPort); + packTLVWord(&packet, 0x15, (WORD)((pListener->wPort) ^ 0x0FFFF)); + } + { // TLV(0x2711) + packWord(&packet, 0x2711); + packWord(&packet, (WORD)(9 + strlennull(pszFiles))); + packWord(&packet, (WORD)(ft->wFilesCount == 1 ? 1 : 2)); + packWord(&packet, ft->wFilesCount); + packDWord(&packet, (DWORD)ft->qwTotalSize); + packBuffer(&packet, (LPBYTE)pszFiles, (WORD)(strlennull(pszFiles) + 1)); + } + packTLV(&packet, 0x2712, 5, (LPBYTE)"utf-8"); + { // TLV(0x2713) + packWord(&packet, 0x2713); + packWord(&packet, 8); + packQWord(&packet, ft->qwTotalSize); + } + + sendServPacket(&packet); // Send the monster +} + + +void CIcqProto::oft_sendFileReply(DWORD dwUin, char *szUid, oscar_filetransfer *ft, WORD wResult) +{ + icq_packet packet; + + packServMsgSendHeader(&packet, 0, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, dwUin, szUid, 2, 0x1E); + packServTLV5HeaderBasic(&packet, 0, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, wResult, MCAP_FILE_TRANSFER); + + sendServPacket(&packet); +} + + +void CIcqProto::oft_sendFileAccept(DWORD dwUin, char *szUid, oscar_filetransfer *ft) +{ + oft_sendFileReply(dwUin, szUid, ft, 0x02); +} + + +void CIcqProto::oft_sendFileResponse(DWORD dwUin, char *szUid, oscar_filetransfer *ft, WORD wResponse) +{ + icq_packet packet; + + packServAdvancedReply(&packet, dwUin, szUid, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, 0, 4); + packWord(&packet, 0x02); // Length of following data + packWord(&packet, wResponse); // Response code + + sendServPacket(&packet); +} + + +void CIcqProto::oft_sendFileDeny(DWORD dwUin, char *szUid, oscar_filetransfer *ft) +{ + if (dwUin) + { // ICQ clients uses special deny file transfer + oft_sendFileResponse(dwUin, szUid, ft, 0x01); + } + else + oft_sendFileReply(dwUin, szUid, ft, 0x01); +} + + +void CIcqProto::oft_sendFileCancel(DWORD dwUin, char *szUid, oscar_filetransfer *ft) +{ + oft_sendFileReply(dwUin, szUid, ft, 0x01); +} + + +void CIcqProto::oft_sendFileRedirect(DWORD dwUin, char *szUid, oscar_filetransfer *ft, DWORD dwIP, WORD wPort, int bProxy) +{ + icq_packet packet; + + packServMsgSendHeader(&packet, 0, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, dwUin, szUid, 2, (WORD)(bProxy ? 0x4a : 0x4e)); + packServTLV5HeaderBasic(&packet, (WORD)(bProxy ? 0x2C : 0x30), ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, 0, MCAP_FILE_TRANSFER); + // Connection point data + packTLVWord(&packet, 0x0A, ++ft->wReqNum); // Ack Type + packTLVWord(&packet, 0x14, 0x0A); // Unknown ? + packTLVDWord(&packet, 0x02, dwIP); // Internal IP / Proxy IP + packTLVDWord(&packet, 0x16, dwIP ^ 0x0FFFFFFFF); // IP Check ? + if (!bProxy) + packTLVDWord(&packet, 0x03, dwIP); + packTLVWord(&packet, 0x05, wPort); // Listening Port + packTLVWord(&packet, 0x17, (WORD)(wPort ^ 0x0FFFF)); // Port Check ? + if (bProxy) + packDWord(&packet, 0x00100000); // Proxy Flag + + sendServPacket(&packet); +} diff --git a/protocols/IcqOscarJ/src/stdpackets.h b/protocols/IcqOscarJ/src/stdpackets.h new file mode 100644 index 0000000000..b656d713c6 --- /dev/null +++ b/protocols/IcqOscarJ/src/stdpackets.h @@ -0,0 +1,43 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2008 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Low-level functions that really sends data to server +// +// ----------------------------------------------------------------------------- +#ifndef __STDPACKETS_H +#define __STDPACKETS_H + +struct icq_contactsend_s +{ + DWORD uin; + char *uid; + char *szNick; +}; + + +void packMsgColorInfo(icq_packet *packet); + +#endif /* __STDPACKETS_H */ diff --git a/protocols/IcqOscarJ/src/tlv.cpp b/protocols/IcqOscarJ/src/tlv.cpp new file mode 100644 index 0000000000..7c4eafca3a --- /dev/null +++ b/protocols/IcqOscarJ/src/tlv.cpp @@ -0,0 +1,391 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2009 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Helper functions for Oscar TLV chains +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + + +/* set maxTlvs<=0 to get all TLVs in length, or a positive integer to get at most the first n */ +oscar_tlv_chain* readIntoTLVChain(BYTE **buf, WORD wLen, int maxTlvs) +{ + oscar_tlv_chain *now, *last, *chain = NULL; + WORD now_tlv_len; + int len = wLen; + + if (!buf || !wLen) return NULL; + + while (len > 0) /* don't use unsigned variable for this check */ + { + now = (oscar_tlv_chain *)SAFE_MALLOC(sizeof(oscar_tlv_chain)); + + if (!now) + { + disposeChain(&chain); + return NULL; + } + + unpackWord(buf, &(now->tlv.wType)); + unpackWord(buf, &now_tlv_len); + now->tlv.wLen = now_tlv_len; + len -= 4; + + if (now_tlv_len < 1) + { + now->tlv.pData = NULL; + } + else if (now_tlv_len <= len) + { + now->tlv.pData = (BYTE *)SAFE_MALLOC(now_tlv_len); + if (now->tlv.pData) + memcpy(now->tlv.pData, *buf, now_tlv_len); + } + else + { // the packet is shorter than it should be + SAFE_FREE((void**)&now); + return chain; // give at least the rest of chain + } + + if (chain) // keep the original order + last->next = now; + else + chain = now; + + last = now; + + len -= now_tlv_len; + *buf += now_tlv_len; + + if (--maxTlvs == 0) + break; + } + + return chain; +} + +// Returns a pointer to the TLV with type wType and number wIndex in the chain +// If wIndex = 1, the first matching TLV will be returned, if wIndex = 2, +// the second matching one will be returned. +// wIndex must be > 0 +oscar_tlv* oscar_tlv_chain::getTLV(WORD wType, WORD wIndex) +{ + int i = 0; + oscar_tlv_chain *list = this; + + while (list) + { + if (list->tlv.wType == wType) + i++; + if (i >= wIndex) + return &list->tlv; + list = list->next; + } + + return NULL; +} + + +WORD oscar_tlv_chain::getChainLength() +{ + int len = 0; + oscar_tlv_chain *list = this; + + while (list) + { + len += list->tlv.wLen + 4; + list = list->next; + } + return len; +} + + +oscar_tlv* oscar_tlv_chain::putTLV(WORD wType, WORD wLen, BYTE *pData, BOOL bReplace) +{ + oscar_tlv *tlv = getTLV(wType, 1); + + if (tlv && bReplace) + { + SAFE_FREE((void**)&tlv->pData); + } + else + { + oscar_tlv_chain *last = this; + + while (last && last->next) + last = last->next; + + if (last) + { + last->next = (oscar_tlv_chain*)SAFE_MALLOC(sizeof(oscar_tlv_chain)); + tlv = &last->next->tlv; + tlv->wType = wType; + } + } + if (tlv) + { + tlv->wLen = wLen; + tlv->pData = (PBYTE)SAFE_MALLOC(wLen); + memcpy(tlv->pData, pData, wLen); + } + return tlv; +} + + +oscar_tlv_chain* oscar_tlv_chain::removeTLV(oscar_tlv *tlv) +{ + oscar_tlv_chain *list = this, *prev = NULL, *chain = this; + + while (list) + { + if (&list->tlv == tlv) + { + if (prev) // relink + prev->next = list->next; + else if (list->next) + { // move second item's tlv to the first item + list->tlv = list->next->tlv; + list = list->next; + } + else // result is an empty chain (NULL) + chain = NULL; + // release chain item memory + SAFE_FREE((void**)&list->tlv.pData); + SAFE_FREE((void**)&list); + } + prev = list; + list = list->next; + } + + return chain; +} + + +WORD oscar_tlv_chain::getLength(WORD wType, WORD wIndex) +{ + oscar_tlv *tlv = getTLV(wType, wIndex); + if (tlv) + return tlv->wLen; + + return 0; +} + + +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ +/* Values are returned in MSB format */ +/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ + +DWORD oscar_tlv_chain::getDWord(WORD wType, WORD wIndex) +{ + DWORD dw = 0; + + oscar_tlv *tlv = getTLV(wType, wIndex); + if (tlv && tlv->wLen >= 4) + { + dw |= (*((tlv->pData)+0) << 24); + dw |= (*((tlv->pData)+1) << 16); + dw |= (*((tlv->pData)+2) << 8); + dw |= (*((tlv->pData)+3)); + } + + return dw; +} + + +WORD oscar_tlv_chain::getWord(WORD wType, WORD wIndex) +{ + WORD w = 0; + + oscar_tlv *tlv = getTLV(wType, wIndex); + if (tlv && tlv->wLen >= 2) + { + w |= (*((tlv->pData)+0) << 8); + w |= (*((tlv->pData)+1)); + } + + return w; +} + + +BYTE oscar_tlv_chain::getByte(WORD wType, WORD wIndex) +{ + BYTE b = 0; + + oscar_tlv *tlv = getTLV(wType, wIndex); + if (tlv && tlv->wLen) + { + b = *(tlv->pData); + } + + return b; +} + + +int oscar_tlv_chain::getNumber(WORD wType, WORD wIndex) +{ + oscar_tlv *tlv = getTLV(wType, wIndex); + + if (tlv) + { + if (tlv->wLen == 1) + return getByte(wType, wIndex); + else if (tlv->wLen == 2) + return getWord(wType, wIndex); + else if (tlv->wLen == 4) + return getDWord(wType, wIndex); + } + return 0; +} + + +double oscar_tlv_chain::getDouble(WORD wType, WORD wIndex) +{ + oscar_tlv *tlv = getTLV(wType, wIndex); + + if (tlv && tlv->wLen == 8) + { + BYTE *buf = tlv->pData; + double d = 0; + + unpackQWord(&buf, (DWORD64*)&d); + + return d; + } + return 0; +} + + +char* oscar_tlv_chain::getString(WORD wType, WORD wIndex) +{ + char *str = NULL; + + oscar_tlv *tlv = getTLV(wType, wIndex); + if (tlv) + { + str = (char*)SAFE_MALLOC(tlv->wLen + 1); /* For \0 */ + + if (!str) return NULL; + + memcpy(str, tlv->pData, tlv->wLen); + str[tlv->wLen] = '\0'; + } + + return str; +} + + +void disposeChain(oscar_tlv_chain **chain) +{ + if (!chain || !*chain) + return; + + oscar_tlv_chain *now = *chain; + + while (now) + { + oscar_tlv_chain *next = now->next; + + SAFE_FREE((void**)&now->tlv.pData); + SAFE_FREE((void**)&now); + now = next; + } + + *chain = NULL; +} + + +oscar_tlv_record_list* readIntoTLVRecordList(BYTE **buf, WORD wLen, int nCount) +{ + oscar_tlv_record_list *list = NULL, *last; + + while (wLen >= 2) + { + WORD wRecordSize; + + unpackWord(buf, &wRecordSize); + wLen -= 2; + if (wRecordSize && wRecordSize <= wLen) + { + oscar_tlv_record_list *pRecord = (oscar_tlv_record_list*)SAFE_MALLOC(sizeof(oscar_tlv_record_list)); + BYTE *pData = *buf; + + *buf += wRecordSize; + wLen -= wRecordSize; + + pRecord->item = readIntoTLVChain(&pData, wRecordSize, 0); + if (pRecord->item) + { // keep the order + if (list) + last->next = pRecord; + else + list = pRecord; + + last = pRecord; + } + else + SAFE_FREE((void**)&pRecord); + } + + if (--nCount == 0) break; + } + return list; +} + + +void disposeRecordList(oscar_tlv_record_list** list) +{ + if (!list || !*list) + return; + + oscar_tlv_record_list *now = *list; + + while (now) + { + oscar_tlv_record_list *next = now->next; + + disposeChain(&now->item); + SAFE_FREE((void**)&now); + now = next; + } + + *list = NULL; +} + + +oscar_tlv_chain* oscar_tlv_record_list::getRecordByTLV(WORD wType, int nValue) +{ + oscar_tlv_record_list *list = this; + + while (list) + { + if (list->item && list->item->getTLV(wType, 1) && list->item->getNumber(wType, 1) == nValue) + return list->item; + list = list->next; + } + + return NULL; +} diff --git a/protocols/IcqOscarJ/src/tlv.h b/protocols/IcqOscarJ/src/tlv.h new file mode 100644 index 0000000000..5cee00c7c4 --- /dev/null +++ b/protocols/IcqOscarJ/src/tlv.h @@ -0,0 +1,81 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2008 Joe Kucera +// +// 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. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#ifndef __TLV_H +#define __TLV_H + +/*---------* Structures *--------------*/ + +struct oscar_tlv +{ + WORD wType; + WORD wLen; + BYTE *pData; +}; + + +struct oscar_tlv_chain +{ + oscar_tlv tlv; + oscar_tlv_chain *next; + + WORD getChainLength(); + + oscar_tlv* getTLV(WORD wType, WORD wIndex); + oscar_tlv* putTLV(WORD wType, WORD wLen, BYTE *pData, BOOL bReplace); + oscar_tlv_chain* removeTLV(oscar_tlv *tlv); + WORD getLength(WORD wType, WORD wIndex); + + DWORD getDWord(WORD wType, WORD wIndex); + WORD getWord(WORD wType, WORD wIndex); + BYTE getByte(WORD wType, WORD wIndex); + int getNumber(WORD wType, WORD wIndex); + double getDouble(WORD wType, WORD wIndex); + char* getString(WORD wType, WORD wIndex); +}; + + +struct oscar_tlv_record_list +{ + oscar_tlv_chain *item; + oscar_tlv_record_list *next; + + oscar_tlv_chain* getRecordByTLV(WORD wType, int nValue); +}; + +/*---------* Functions *---------------*/ + +oscar_tlv_chain* readIntoTLVChain(BYTE **buf, WORD wLen, int maxTlvs); +void disposeChain(oscar_tlv_chain** chain); + +oscar_tlv_record_list* readIntoTLVRecordList(BYTE **buf, WORD wLen, int nCount); +void disposeRecordList(oscar_tlv_record_list** list); + + +#endif /* __TLV_H */ diff --git a/protocols/IcqOscarJ/src/utilities.cpp b/protocols/IcqOscarJ/src/utilities.cpp new file mode 100644 index 0000000000..70f7547108 --- /dev/null +++ b/protocols/IcqOscarJ/src/utilities.cpp @@ -0,0 +1,2183 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#include "icqoscar.h" + + +struct gateway_index +{ + HANDLE hConn; + DWORD dwIndex; +}; + +static icq_critical_section *gatewayMutex = NULL; + +static gateway_index *gateways = NULL; +static int gatewayCount = 0; + +static DWORD *spammerList = NULL; +static int spammerListCount = 0; + + +void MoveDlgItem(HWND hwndDlg, int iItem, int left, int top, int width, int height) +{ + RECT rc; + + rc.left = left; + rc.top = top; + rc.right = left + width; + rc.bottom = top + height; + MapDialogRect(hwndDlg, &rc); + MoveWindow(GetDlgItem(hwndDlg, iItem), rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, FALSE); +} + + +void EnableDlgItem(HWND hwndDlg, UINT control, int state) +{ + EnableWindow(GetDlgItem(hwndDlg, control), state); +} + + +void ShowDlgItem(HWND hwndDlg, UINT control, int state) +{ + ShowWindow(GetDlgItem(hwndDlg, control), state); +} + + +void icq_EnableMultipleControls(HWND hwndDlg, const UINT *controls, int cControls, int state) +{ + for (int i = 0; i < cControls; i++) + EnableDlgItem(hwndDlg, controls[i], state); +} + + +void icq_ShowMultipleControls(HWND hwndDlg, const UINT *controls, int cControls, int state) +{ + for (int i = 0; i < cControls; i++) + ShowDlgItem(hwndDlg, controls[i], state); +} + + +// Maps the ICQ status flag (as seen in the status change SNACS) and returns +// a Miranda style status. +int IcqStatusToMiranda(WORD nIcqStatus) +{ + int nMirandaStatus; + + // :NOTE: The order in which the flags are compared are important! + // I dont like this method but it works. + + if (nIcqStatus & ICQ_STATUSF_INVISIBLE) + nMirandaStatus = ID_STATUS_INVISIBLE; + else + if (nIcqStatus & ICQ_STATUSF_DND) + nMirandaStatus = ID_STATUS_DND; + else + if (nIcqStatus & ICQ_STATUSF_OCCUPIED) + nMirandaStatus = ID_STATUS_OCCUPIED; + else + if (nIcqStatus & ICQ_STATUSF_NA) + nMirandaStatus = ID_STATUS_NA; + else + if (nIcqStatus & ICQ_STATUSF_AWAY) + nMirandaStatus = ID_STATUS_AWAY; + else + if (nIcqStatus & ICQ_STATUSF_FFC) + nMirandaStatus = ID_STATUS_FREECHAT; + else + // Can be discussed, but I think 'online' is the most generic ICQ status + nMirandaStatus = ID_STATUS_ONLINE; + + return nMirandaStatus; +} + +WORD MirandaStatusToIcq(int nMirandaStatus) +{ + WORD nIcqStatus; + + switch (nMirandaStatus) { +case ID_STATUS_ONLINE: + nIcqStatus = ICQ_STATUS_ONLINE; + break; + +case ID_STATUS_AWAY: + nIcqStatus = ICQ_STATUS_AWAY; + break; + +case ID_STATUS_OUTTOLUNCH: +case ID_STATUS_NA: + nIcqStatus = ICQ_STATUS_NA; + break; + +case ID_STATUS_ONTHEPHONE: +case ID_STATUS_OCCUPIED: + nIcqStatus = ICQ_STATUS_OCCUPIED; + break; + +case ID_STATUS_DND: + nIcqStatus = ICQ_STATUS_DND; + break; + +case ID_STATUS_INVISIBLE: + nIcqStatus = ICQ_STATUS_INVISIBLE; + break; + +case ID_STATUS_FREECHAT: + nIcqStatus = ICQ_STATUS_FFC; + break; + +case ID_STATUS_OFFLINE: + // Oscar doesnt have anything that maps to this status. This should never happen. + _ASSERTE(nMirandaStatus != ID_STATUS_OFFLINE); + nIcqStatus = 0; + break; + +default: + // Online seems to be a good default. + // Since it cant be offline, it must be a new type of online status. + nIcqStatus = ICQ_STATUS_ONLINE; + break; + } + + return nIcqStatus; +} + +int MirandaStatusToSupported(int nMirandaStatus) +{ + int nSupportedStatus; + + switch (nMirandaStatus) { + + // These status mode does not need any mapping +case ID_STATUS_ONLINE: +case ID_STATUS_AWAY: +case ID_STATUS_NA: +case ID_STATUS_OCCUPIED: +case ID_STATUS_DND: +case ID_STATUS_INVISIBLE: +case ID_STATUS_OFFLINE: + nSupportedStatus = nMirandaStatus; + break; + +case ID_STATUS_FREECHAT: + nSupportedStatus = ID_STATUS_ONLINE; + break; + + // This mode is not support and must be mapped to something else +case ID_STATUS_OUTTOLUNCH: + nSupportedStatus = ID_STATUS_NA; + break; + + // This mode is not support and must be mapped to something else +case ID_STATUS_ONTHEPHONE: + nSupportedStatus = ID_STATUS_OCCUPIED; + break; + + // This is not supposed to happen. +default: + _ASSERTE(0); + // Online seems to be a good default. + nSupportedStatus = ID_STATUS_ONLINE; + break; + } + + return nSupportedStatus; +} + +char *MirandaStatusToString(int mirandaStatus) +{ + return (char*)CallService(MS_CLIST_GETSTATUSMODEDESCRIPTION, mirandaStatus, 0); +} + +char *MirandaStatusToStringUtf(int mirandaStatus) +{ // return miranda status description in utf-8, use unicode service is possible + return tchar_to_utf8((TCHAR*)CallService(MS_CLIST_GETSTATUSMODEDESCRIPTION, mirandaStatus, GSMDF_TCHAR)); +} + +char** CIcqProto::MirandaStatusToAwayMsg(int nStatus) +{ + switch (nStatus) { + +case ID_STATUS_ONLINE: + return &m_modeMsgs.szOnline; + +case ID_STATUS_AWAY: + return &m_modeMsgs.szAway; + +case ID_STATUS_NA: + return &m_modeMsgs.szNa; + +case ID_STATUS_OCCUPIED: + return &m_modeMsgs.szOccupied; + +case ID_STATUS_DND: + return &m_modeMsgs.szDnd; + +case ID_STATUS_FREECHAT: + return &m_modeMsgs.szFfc; + +default: + return NULL; + } +} + +int AwayMsgTypeToStatus(int nMsgType) +{ + switch (nMsgType) { +case MTYPE_AUTOONLINE: + return ID_STATUS_ONLINE; + +case MTYPE_AUTOAWAY: + return ID_STATUS_AWAY; + +case MTYPE_AUTOBUSY: + return ID_STATUS_OCCUPIED; + +case MTYPE_AUTONA: + return ID_STATUS_NA; + +case MTYPE_AUTODND: + return ID_STATUS_DND; + +case MTYPE_AUTOFFC: + return ID_STATUS_FREECHAT; + +default: + return ID_STATUS_OFFLINE; + } +} + + +void SetGatewayIndex(HANDLE hConn, DWORD dwIndex) +{ + icq_lock l(gatewayMutex); + + for (int i = 0; i < gatewayCount; i++) + { + if (hConn == gateways[i].hConn) + { + gateways[i].dwIndex = dwIndex; + return; + } + } + + gateways = (gateway_index *)SAFE_REALLOC(gateways, sizeof(gateway_index) * (gatewayCount + 1)); + gateways[gatewayCount].hConn = hConn; + gateways[gatewayCount].dwIndex = dwIndex; + gatewayCount++; +} + + +DWORD GetGatewayIndex(HANDLE hConn) +{ + icq_lock l(gatewayMutex); + + for (int i = 0; i < gatewayCount; i++) + { + if (hConn == gateways[i].hConn) + return gateways[i].dwIndex; + } + + return 1; // this is default +} + + +void FreeGatewayIndex(HANDLE hConn) +{ + icq_lock l(gatewayMutex); + + for (int i = 0; i < gatewayCount; i++) + { + if (hConn == gateways[i].hConn) + { + gatewayCount--; + memmove(&gateways[i], &gateways[i+1], sizeof(gateway_index) * (gatewayCount - i)); + gateways = (gateway_index*)SAFE_REALLOC(gateways, sizeof(gateway_index) * gatewayCount); + + // Gateway found, exit loop + break; + } + } +} + + +void CIcqProto::AddToSpammerList(DWORD dwUIN) +{ + icq_lock l(gatewayMutex); + + spammerList = (DWORD *)SAFE_REALLOC(spammerList, sizeof(DWORD) * (spammerListCount + 1)); + spammerList[spammerListCount] = dwUIN; + spammerListCount++; +} + + +BOOL CIcqProto::IsOnSpammerList(DWORD dwUIN) +{ + icq_lock l(gatewayMutex); + + for (int i = 0; i < spammerListCount; i++) + { + if (dwUIN == spammerList[i]) + return TRUE; + } + + return FALSE; +} + + +// ICQ contacts cache + +void CIcqProto::AddToContactsCache(HANDLE hContact, DWORD dwUin, const char *szUid) +{ + if (!hContact || (!dwUin && !szUid)) + return; + +#ifdef _DEBUG + NetLog_Server("Adding contact to cache: %u%s%s", dwUin, dwUin ? "" : " - ", dwUin ? "" : szUid); +#endif + + icq_contacts_cache *cache_item = (icq_contacts_cache*)SAFE_MALLOC(sizeof(icq_contacts_cache)); + cache_item->hContact = hContact; + cache_item->dwUin = dwUin; + if (!dwUin) + cache_item->szUid = null_strdup(szUid); + + icq_lock l(contactsCacheMutex); + contactsCache.insert(cache_item); +} + + +void CIcqProto::InitContactsCache() +{ + if (!gatewayMutex) + gatewayMutex = new icq_critical_section(); + else + gatewayMutex->_Lock(); + + contactsCacheMutex = new icq_critical_section(); + + // build cache + icq_lock l(contactsCacheMutex); + + HANDLE hContact = FindFirstContact(); + + while (hContact) + { + DWORD dwUin; + uid_str szUid; + + if (!getContactUid(hContact, &dwUin, &szUid)) + AddToContactsCache(hContact, dwUin, szUid); + + hContact = FindNextContact(hContact); + } +} + + +void CIcqProto::UninitContactsCache(void) +{ + contactsCacheMutex->Enter(); + + // cleanup the cache + for (int i = 0; i < contactsCache.getCount(); i++) + { + icq_contacts_cache *cache_item = contactsCache[i]; + + SAFE_FREE((void**)&cache_item->szUid); + SAFE_FREE((void**)&cache_item); + } + + contactsCache.destroy(); + + contactsCacheMutex->Leave(); + + SAFE_DELETE(&contactsCacheMutex); + + if (gatewayMutex && gatewayMutex->getLockCount() > 1) + gatewayMutex->_Release(); + else + SAFE_DELETE(&gatewayMutex); +} + + +void CIcqProto::DeleteFromContactsCache(HANDLE hContact) +{ + icq_lock l(contactsCacheMutex); + + for (int i = 0; i < contactsCache.getCount(); i++) + { + icq_contacts_cache *cache_item = contactsCache[i]; + + if (cache_item->hContact == hContact) + { +#ifdef _DEBUG + NetLog_Server("Removing contact from cache: %u%s%s, position: %u", cache_item->dwUin, cache_item->dwUin ? "" : " - ", cache_item->dwUin ? "" : cache_item->szUid, i); +#endif + contactsCache.remove(i); + // Release memory + SAFE_FREE((void**)&cache_item->szUid); + SAFE_FREE((void**)&cache_item); + break; + } + } +} + + +HANDLE CIcqProto::HandleFromCacheByUid(DWORD dwUin, const char *szUid) +{ + icq_contacts_cache cache_item = {NULL, dwUin, szUid}; + + icq_lock l(contactsCacheMutex); + // find in list + int i = contactsCache.getIndex(&cache_item); + if (i != -1) + return contactsCache[i]->hContact; + + return NULL; +} + + +HANDLE CIcqProto::HContactFromUIN(DWORD dwUin, int *Added) +{ + if (Added) *Added = 0; + + HANDLE hContact = HandleFromCacheByUid(dwUin, NULL); + if (hContact) return hContact; + + hContact = FindFirstContact(); + while (hContact) + { + DWORD dwContactUin; + + dwContactUin = getContactUin(hContact); + if (dwContactUin == dwUin) + { + AddToContactsCache(hContact, dwUin, NULL); + return hContact; + } + + hContact = FindNextContact(hContact); + } + + //not present: add + if (Added) + { + hContact = (HANDLE)CallService(MS_DB_CONTACT_ADD, 0, 0); + if (!hContact) + { + NetLog_Server("Failed to create ICQ contact %u", dwUin); + return INVALID_HANDLE_VALUE; + } + + if (CallService(MS_PROTO_ADDTOCONTACT, (WPARAM)hContact, (LPARAM)m_szModuleName) != 0) + { + // For some reason we failed to register the protocol to this contact + CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0); + NetLog_Server("Failed to register ICQ contact %u", dwUin); + return INVALID_HANDLE_VALUE; + } + + setSettingDword(hContact, UNIQUEIDSETTING, dwUin); + + if (!bIsSyncingCL) + { + DBWriteContactSettingByte(hContact, "CList", "NotOnList", 1); + setContactHidden(hContact, 1); + + setSettingWord(hContact, "Status", ID_STATUS_OFFLINE); + + icq_QueueUser(hContact); + + if (icqOnline()) + icq_sendNewContact(dwUin, NULL); + } + AddToContactsCache(hContact, dwUin, NULL); + *Added = 1; + + return hContact; + } + + // not in list, check that uin do not belong to us + if (getContactUin(NULL) == dwUin) + return NULL; + + return INVALID_HANDLE_VALUE; +} + + +HANDLE CIcqProto::HContactFromUID(DWORD dwUin, const char *szUid, int *Added) +{ + if (dwUin) + return HContactFromUIN(dwUin, Added); + + if (Added) *Added = 0; + + if (!m_bAimEnabled) return INVALID_HANDLE_VALUE; + + HANDLE hContact = HandleFromCacheByUid(dwUin, szUid); + if (hContact) return hContact; + + hContact = FindFirstContact(); + while (hContact) + { + DWORD dwContactUin; + uid_str szContactUid; + + if (!getContactUid(hContact, &dwContactUin, &szContactUid)) + { + if (!dwContactUin && !stricmpnull(szContactUid, szUid)) + { + if (strcmpnull(szContactUid, szUid)) + { // fix case in SN + setSettingString(hContact, UNIQUEIDSETTING, szUid); + } + return hContact; + } + } + hContact = FindNextContact(hContact); + } + + //not present: add + if (Added) + { + hContact = (HANDLE)CallService(MS_DB_CONTACT_ADD, 0, 0); + CallService(MS_PROTO_ADDTOCONTACT, (WPARAM)hContact, (LPARAM)m_szModuleName); + + setSettingString(hContact, UNIQUEIDSETTING, szUid); + + if (!bIsSyncingCL) + { + DBWriteContactSettingByte(hContact, "CList", "NotOnList", 1); + setContactHidden(hContact, 1); + + setSettingWord(hContact, "Status", ID_STATUS_OFFLINE); + + if (icqOnline()) + icq_sendNewContact(0, szUid); + } + AddToContactsCache(hContact, 0, szUid); + *Added = 1; + + return hContact; + } + + return INVALID_HANDLE_VALUE; +} + + +HANDLE CIcqProto::HContactFromAuthEvent(HANDLE hEvent) +{ + DBEVENTINFO dbei = { sizeof(dbei) }; + DWORD body[3]; + + dbei.cbBlob = sizeof(DWORD)*2; + dbei.pBlob = (PBYTE)&body; + + if (CallService(MS_DB_EVENT_GET, (WPARAM)hEvent, (LPARAM)&dbei)) + return INVALID_HANDLE_VALUE; + + if (dbei.eventType != EVENTTYPE_AUTHREQUEST) + return INVALID_HANDLE_VALUE; + + if (strcmpnull(dbei.szModule, m_szModuleName)) + return INVALID_HANDLE_VALUE; + + return DbGetAuthEventContact(&dbei); +} + +char *NickFromHandle(HANDLE hContact) +{ + if (hContact == INVALID_HANDLE_VALUE) + return null_strdup(Translate("")); + + return null_strdup((char *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, 0)); +} + +char *NickFromHandleUtf(HANDLE hContact) +{ + if (hContact == INVALID_HANDLE_VALUE) + return ICQTranslateUtf(LPGEN("")); + + return tchar_to_utf8((TCHAR*)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, GCDNF_TCHAR)); +} + +char *strUID(DWORD dwUIN, char *pszUID) +{ + if (dwUIN && pszUID) + _ltoa(dwUIN, pszUID, 10); + + return pszUID; +} + + +/* a strlen() that likes NULL */ +int __fastcall strlennull(const char *string) +{ + if (string) + return (int)strlen(string); + + return 0; +} + + +/* a wcslen() that likes NULL */ +int __fastcall strlennull(const WCHAR *string) +{ + if (string) + return (int)wcslen(string); + + return 0; +} + + +/* a strcmp() that likes NULL */ +int __fastcall strcmpnull(const char *str1, const char *str2) +{ + if (str1 && str2) + return strcmp(str1, str2); + + if (!str1 && !str2) + return 0; + + return 1; +} + +/* a stricmp() that likes NULL */ +int __fastcall stricmpnull(const char *str1, const char *str2) +{ + if (str1 && str2) + return _stricmp(str1, str2); + + if (!str1 && !str2) + return 0; + + return 1; +} + +char* __fastcall strstrnull(const char *str, const char *substr) +{ + if (str) + return (char*)strstr(str, substr); + + return NULL; +} + +int null_snprintf(char *buffer, size_t count, const char *fmt, ...) +{ + va_list va; + int len; + + ZeroMemory(buffer, count); + va_start(va, fmt); + len = _vsnprintf(buffer, count-1, fmt, va); + va_end(va); + return len; +} + +int null_snprintf(WCHAR *buffer, size_t count, const WCHAR *fmt, ...) +{ + va_list va; + int len; + + ZeroMemory(buffer, count * sizeof(WCHAR)); + va_start(va, fmt); + len = _vsnwprintf(buffer, count, fmt, va); + va_end(va); + return len; +} + + +char* __fastcall null_strdup(const char *string) +{ + if (string) + return _strdup(string); + + return NULL; +} + + +WCHAR* __fastcall null_strdup(const WCHAR *string) +{ + if (string) + return wcsdup(string); + + return NULL; +} + + +char* __fastcall null_strcpy(char *dest, const char *src, size_t maxlen) +{ + if (!dest) + return NULL; + + if (src && src[0]) + { + strncpy(dest, src, maxlen); + dest[maxlen] = '\0'; + } + else + dest[0] = '\0'; + + return dest; +} + + +WCHAR* __fastcall null_strcpy(WCHAR *dest, const WCHAR *src, size_t maxlen) +{ + if (!dest) + return NULL; + + if (src && src[0]) + { + wcsncpy(dest, src, maxlen); + dest[maxlen] = '\0'; + } + else + dest[0] = '\0'; + + return dest; +} + + +int __fastcall null_strcut(char *string, int maxlen) +{ // limit the string to max length (null & utf-8 strings ready) + int len = (int)strlennull(string); + + if (len < maxlen) + return len; + + len = maxlen; + + if (UTF8_IsValid(string)) // handle utf-8 string + { // find the first byte of possible multi-byte character + while ((string[len] & 0xc0) == 0x80) len--; + } + // simply cut the string + string[len] = '\0'; + + return len; +} + + +void parseServerAddress(char* szServer, WORD* wPort) +{ + int i = 0; + + while (szServer[i] && szServer[i] != ':') i++; + if (szServer[i] == ':') + { // port included + *wPort = atoi(&szServer[i + 1]); + } // otherwise do not change port + + szServer[i] = '\0'; +} + +char *DemangleXml(const char *string, int len) +{ + char *szWork = (char*)SAFE_MALLOC(len+1), *szChar = szWork; + int i; + + for (i=0; i') l += 4; else if (string[i]=='&') l += 5; else if (string[i]=='"') l += 6; else l++; + } + szChar = szWork = (char*)SAFE_MALLOC(l + 1); + for (i = 0; i') + { + *(DWORD*)szChar = ';tg&'; + szChar += 4; + } + else if (string[i]=='&') + { + *(DWORD*)szChar = 'pma&'; + szChar += 4; + *szChar = ';'; + szChar++; + } + else if (string[i]=='"') + { + *(DWORD*)szChar = 'ouq&'; + szChar += 4; + *(WORD*)szChar = ';t'; + szChar += 2; + } + else + { + *szChar = string[i]; + szChar++; + } + } + *szChar = '\0'; + + return szWork; +} + +char *EliminateHtml(const char *string, int len) +{ + char *tmp = (char*)SAFE_MALLOC(len + 1); + int i,j; + BOOL tag = FALSE; + char *res; + + for (i=0,j=0;i", 4) || !_strnicmp(string + i, "
", 5))) + { // insert newline + tmp[j] = '\r'; + j++; + tmp[j] = '\n'; + j++; + } + tag = TRUE; + } + else if (tag && string[i] == '>') + { + tag = FALSE; + } + else if (!tag) + { + tmp[j] = string[i]; + j++; + } + tmp[j] = '\0'; + } + SAFE_FREE((void**)&string); + res = DemangleXml(tmp, strlennull(tmp)); + SAFE_FREE((void**)&tmp); + + return res; +} + +char *ApplyEncoding(const char *string, const char *pszEncoding) +{ // decode encoding to Utf-8 + if (string && pszEncoding) + { // we do only encodings known to icq5.1 // TODO: check if this is enough + if (!_strnicmp(pszEncoding, "utf-8", 5)) + { // it is utf-8 encoded + return null_strdup(string); + } + if (!_strnicmp(pszEncoding, "unicode-2-0", 11)) + { // it is UCS-2 encoded + int wLen = strlennull((WCHAR*)string) + 1; + WCHAR *szStr = (WCHAR*)_alloca(wLen*2); + BYTE *tmp = (BYTE*)string; + + unpackWideString(&tmp, szStr, (WORD)(wLen*2)); + + return make_utf8_string(szStr); + } + if (!_strnicmp(pszEncoding, "iso-8859-1", 10)) + { // we use "Latin I" instead - it does the job + return ansi_to_utf8_codepage(string, 1252); + } + } + if (string) + { // consider it CP_ACP + return ansi_to_utf8(string); + } + + return NULL; +} + +void CIcqProto::ResetSettingsOnListReload() +{ + // Reset a bunch of session specific settings + setSettingWord(NULL, DBSETTING_SERVLIST_PRIVACY, 0); + setSettingWord(NULL, DBSETTING_SERVLIST_METAINFO, 0); + setSettingWord(NULL, DBSETTING_SERVLIST_AVATAR, 0); + setSettingWord(NULL, DBSETTING_SERVLIST_PHOTO, 0); + setSettingWord(NULL, "SrvRecordCount", 0); + deleteSetting(NULL, DBSETTING_SERVLIST_UNHANDLED); + + HANDLE hContact = FindFirstContact(); + + while (hContact) + { + // All these values will be restored during the serv-list receive + setSettingWord(hContact, DBSETTING_SERVLIST_ID, 0); + setSettingWord(hContact, DBSETTING_SERVLIST_GROUP, 0); + setSettingWord(hContact, DBSETTING_SERVLIST_PERMIT, 0); + setSettingWord(hContact, DBSETTING_SERVLIST_DENY, 0); + deleteSetting(hContact, DBSETTING_SERVLIST_IGNORE); + setSettingByte(hContact, "Auth", 0); + deleteSetting(hContact, DBSETTING_SERVLIST_DATA); + + hContact = FindNextContact(hContact); + } + + FlushSrvGroupsCache(); +} + +void CIcqProto::ResetSettingsOnConnect() +{ + // Reset a bunch of session specific settings + setSettingByte(NULL, "SrvVisibility", 0); + setSettingDword(NULL, "IdleTS", 0); + + HANDLE hContact = FindFirstContact(); + + while (hContact) + { + setSettingDword(hContact, "LogonTS", 0); + setSettingDword(hContact, "IdleTS", 0); + setSettingDword(hContact, "TickTS", 0); + setSettingByte(hContact, "TemporaryVisible", 0); + + // All these values will be restored during the login + if (getContactStatus(hContact) != ID_STATUS_OFFLINE) + setSettingWord(hContact, "Status", ID_STATUS_OFFLINE); + + hContact = FindNextContact(hContact); + } +} + +void CIcqProto::ResetSettingsOnLoad() +{ + setSettingDword(NULL, "IdleTS", 0); + setSettingDword(NULL, "LogonTS", 0); + + HANDLE hContact = FindFirstContact(); + + while (hContact) + { + setSettingDword(hContact, "LogonTS", 0); + setSettingDword(hContact, "IdleTS", 0); + setSettingDword(hContact, "TickTS", 0); + if (getContactStatus(hContact) != ID_STATUS_OFFLINE) + { + setSettingWord(hContact, "Status", ID_STATUS_OFFLINE); + + deleteSetting(hContact, DBSETTING_XSTATUS_ID); + deleteSetting(hContact, DBSETTING_XSTATUS_NAME); + deleteSetting(hContact, DBSETTING_XSTATUS_MSG); + } + setSettingByte(hContact, "DCStatus", 0); + + hContact = FindNextContact(hContact); + } +} + +int RandRange(int nLow, int nHigh) +{ + return nLow + (int)((nHigh-nLow+1)*rand()/(RAND_MAX+1.0)); +} + + +BOOL IsStringUIN(const char *pszString) +{ + int i; + int nLen = strlennull(pszString); + + if (nLen > 0 && pszString[0] != '0') + { + for (i=0; i '9')) + return FALSE; + + return TRUE; + } + + return FALSE; +} + + +void __cdecl CIcqProto::ProtocolAckThread(icq_ack_args* pArguments) +{ + Sleep(150); + + if (pArguments->nAckResult == ACKRESULT_SUCCESS) + NetLog_Server("Sent fake message ack"); + else if (pArguments->nAckResult == ACKRESULT_FAILED) + NetLog_Server("Message delivery failed"); + + BroadcastAck(pArguments->hContact, pArguments->nAckType, pArguments->nAckResult, pArguments->hSequence, pArguments->pszMessage); + + SAFE_FREE((void**)(char **)&pArguments->pszMessage); + SAFE_FREE((void**)&pArguments); +} + +void CIcqProto::SendProtoAck(HANDLE hContact, DWORD dwCookie, int nAckResult, int nAckType, char* pszMessage) +{ + icq_ack_args* pArgs = (icq_ack_args*)SAFE_MALLOC(sizeof(icq_ack_args)); // This will be freed in the new thread + pArgs->hContact = hContact; + pArgs->hSequence = (HANDLE)dwCookie; + pArgs->nAckResult = nAckResult; + pArgs->nAckType = nAckType; + pArgs->pszMessage = (LPARAM)null_strdup(pszMessage); + + ForkThread(( IcqThreadFunc )&CIcqProto::ProtocolAckThread, pArgs ); +} + +void CIcqProto::SetCurrentStatus(int nStatus) +{ + int nOldStatus = m_iStatus; + + m_iStatus = nStatus; + BroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)nOldStatus, nStatus); +} + + +int CIcqProto::IsMetaInfoChanged(HANDLE hContact) +{ + DBVARIANT infoToken = {DBVT_DELETED}; + int res = 0; + + if (!getSetting(hContact, DBSETTING_METAINFO_TOKEN, &infoToken)) + { // contact does have info from directory, check if it is not outdated + double dInfoTime = 0; + double dInfoSaved = 0; + + if ((dInfoTime = getSettingDouble(hContact, DBSETTING_METAINFO_TIME, 0)) > 0) + { + if ((dInfoSaved = getSettingDouble(hContact, DBSETTING_METAINFO_SAVED, 0)) > 0) + { + if (dInfoSaved < dInfoTime) + res = 2; // directory info outdated + } + else + res = 1; // directory info not saved at all + } + + ICQFreeVariant(&infoToken); + } + else + { // it cannot be detected if user info was not changed, so use a generic threshold + DBVARIANT infoSaved = {DBVT_DELETED}; + DWORD dwInfoTime = 0; + + if (!getSetting(hContact, DBSETTING_METAINFO_SAVED, &infoSaved)) + { + if (infoSaved.type == DBVT_BLOB && infoSaved.cpbVal == 8) + { + double dwTime = *(double*)infoSaved.pbVal; + + dwInfoTime = (dwTime - 25567) * 86400; + } + else if (infoSaved.type == DBVT_DWORD) + dwInfoTime = infoSaved.dVal; + + ICQFreeVariant(&infoSaved); + + if ((time(NULL) - dwInfoTime) > 14*3600*24) + { + res = 3; // threshold exceeded + } + } + else + res = 4; // no timestamp found + } + + return res; +} + + +void __cdecl CIcqProto::SetStatusNoteThread(void *pDelay) +{ + if (pDelay) + SleepEx((DWORD)pDelay, TRUE); + + cookieMutex->Enter(); + + if (icqOnline() && (setStatusNoteText || setStatusMoodData)) + { // send status note change packets, write status note to database + if (setStatusNoteText) + { // change status note in directory + m_ratesMutex->Enter(); + if (m_rates) + { // rate management + WORD wGroup = m_rates->getGroupFromSNAC(ICQ_EXTENSIONS_FAMILY, ICQ_META_CLI_REQUEST); + + while (m_rates->getNextRateLevel(wGroup) < m_rates->getLimitLevel(wGroup, RML_LIMIT)) + { // we are over rate, need to wait before sending + int nDelay = m_rates->getDelayToLimitLevel(wGroup, RML_IDLE_10); + + m_ratesMutex->Leave(); + cookieMutex->Leave(); +#ifdef _DEBUG + NetLog_Server("Rates: SetStatusNote delayed %dms", nDelay); +#endif + SleepEx(nDelay, TRUE); // do not keep things locked during sleep + cookieMutex->Enter(); + m_ratesMutex->Enter(); + if (!m_rates) // we lost connection when we slept, go away + break; + } + } + m_ratesMutex->Leave(); + + BYTE *pBuffer = NULL; + int cbBuffer = 0; + + ppackTLV(&pBuffer, &cbBuffer, 0x226, strlennull(setStatusNoteText), (BYTE*)setStatusNoteText); + icq_changeUserDirectoryInfoServ(pBuffer, cbBuffer, DIRECTORYREQUEST_UPDATENOTE); + + SAFE_FREE((void**)&pBuffer); + } + + if (setStatusNoteText || setStatusMoodData) + { // change status note and mood in session data + m_ratesMutex->Enter(); + if (m_rates) + { // rate management + WORD wGroup = m_rates->getGroupFromSNAC(ICQ_SERVICE_FAMILY, ICQ_CLIENT_SET_STATUS); + + while (m_rates->getNextRateLevel(wGroup) < m_rates->getLimitLevel(wGroup, RML_LIMIT)) + { // we are over rate, need to wait before sending + int nDelay = m_rates->getDelayToLimitLevel(wGroup, RML_IDLE_10); + + m_ratesMutex->Leave(); + cookieMutex->Leave(); +#ifdef _DEBUG + NetLog_Server("Rates: SetStatusNote delayed %dms", nDelay); +#endif + SleepEx(nDelay, TRUE); // do not keep things locked during sleep + cookieMutex->Enter(); + m_ratesMutex->Enter(); + if (!m_rates) // we lost connection when we slept, go away + break; + } + } + m_ratesMutex->Leave(); + + // check if the session data were not updated already + char *szCurrentStatusNote = getSettingStringUtf(NULL, DBSETTING_STATUS_NOTE, NULL); + char *szCurrentStatusMood = NULL; + DBVARIANT dbv = {DBVT_DELETED}; + + if (m_bMoodsEnabled && !getSettingString(NULL, DBSETTING_STATUS_MOOD, &dbv)) + szCurrentStatusMood = dbv.pszVal; + + if (!setStatusNoteText && szCurrentStatusNote) + setStatusNoteText = null_strdup(szCurrentStatusNote); + if (m_bMoodsEnabled && !setStatusMoodData && szCurrentStatusMood) + setStatusMoodData = null_strdup(szCurrentStatusMood); + + if (strcmpnull(szCurrentStatusNote, setStatusNoteText) || (m_bMoodsEnabled && strcmpnull(szCurrentStatusMood, setStatusMoodData))) + { + setSettingStringUtf(NULL, DBSETTING_STATUS_NOTE, setStatusNoteText); + if (m_bMoodsEnabled) + setSettingString(NULL, DBSETTING_STATUS_MOOD, setStatusMoodData); + + WORD wStatusNoteLen = strlennull(setStatusNoteText); + WORD wStatusMoodLen = m_bMoodsEnabled ? strlennull(setStatusMoodData) : 0; + icq_packet packet; + WORD wDataLen = (wStatusNoteLen ? wStatusNoteLen + 4 : 0) + 4 + wStatusMoodLen + 4; + + serverPacketInit(&packet, wDataLen + 14); + packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_SET_STATUS); + // Change only session data + packWord(&packet, 0x1D); // TLV 1D + packWord(&packet, wDataLen); // TLV length + packWord(&packet, 0x02); // Item Type + if (wStatusNoteLen) + { + packWord(&packet, 0x400 | (WORD)(wStatusNoteLen + 4)); // Flags + Item Length + packWord(&packet, wStatusNoteLen); // Text Length + packBuffer(&packet, (LPBYTE)setStatusNoteText, wStatusNoteLen); + packWord(&packet, 0); // Encoding not specified (utf-8 is default) + } + else + packWord(&packet, 0); // Flags + Item Length + packWord(&packet, 0x0E); // Item Type + packWord(&packet, wStatusMoodLen); // Flags + Item Length + if (wStatusMoodLen) + packBuffer(&packet, (LPBYTE)setStatusMoodData, wStatusMoodLen); // Mood + + sendServPacket(&packet); + } + SAFE_FREE(&szCurrentStatusNote); + ICQFreeVariant(&dbv); + } + } + SAFE_FREE(&setStatusNoteText); + SAFE_FREE(&setStatusMoodData); + + cookieMutex->Leave(); +} + + +int CIcqProto::SetStatusNote(const char *szStatusNote, DWORD dwDelay, int bForce) +{ + int bChanged = FALSE; + + // bForce is intended for login sequence - need to call this earlier than icqOnline() + // the process is delayed and icqOnline() is ready when needed inside SetStatusNoteThread() + if (!bForce && !icqOnline()) return bChanged; + + // reuse generic critical section (used for cookies list and object variables locks) + icq_lock l(cookieMutex); + + if (!setStatusNoteText && (!m_bMoodsEnabled || !setStatusMoodData)) + { // check if the status note was changed and if yes, create thread to change it + char *szCurrentStatusNote = getSettingStringUtf(NULL, DBSETTING_STATUS_NOTE, NULL); + + if (strcmpnull(szCurrentStatusNote, szStatusNote)) + { // status note was changed + // create thread to change status note on existing server connection + setStatusNoteText = null_strdup(szStatusNote); + + if (dwDelay) + ForkThread(&CIcqProto::SetStatusNoteThread, (void*)dwDelay); + else // we cannot afford any delay, so do not run in separate thread + SetStatusNoteThread(NULL); + + bChanged = TRUE; + } + SAFE_FREE(&szCurrentStatusNote); + } + else + { // only alter status note object with new status note, keep the thread waiting for execution + SAFE_FREE(&setStatusNoteText); + setStatusNoteText = null_strdup(szStatusNote); + + bChanged = TRUE; + } + + return bChanged; +} + + +int CIcqProto::SetStatusMood(const char *szMoodData, DWORD dwDelay) +{ + int bChanged = FALSE; + + if (!icqOnline()) return bChanged; + + // reuse generic critical section (used for cookies list and object variables locks) + icq_lock l(cookieMutex); + + if (!setStatusNoteText && !setStatusMoodData) + { // check if the status mood was changed and if yes, create thread to change it + char *szCurrentStatusMood = NULL; + DBVARIANT dbv = {DBVT_DELETED}; + + if (!getSettingString(NULL, DBSETTING_STATUS_MOOD, &dbv)) + szCurrentStatusMood = dbv.pszVal; + + if (strcmpnull(szCurrentStatusMood, szMoodData)) + { // status mood was changed + // create thread to change status mood on existing server connection + setStatusMoodData = null_strdup(szMoodData); + if (dwDelay) + ForkThread(&CIcqProto::SetStatusNoteThread, (void*)dwDelay); + else // we cannot afford any delay, so do not run in separate thread + SetStatusNoteThread(NULL); + + bChanged = TRUE; + } + ICQFreeVariant(&dbv); + } + else + { // only alter status mood object with new status mood, keep the thread waiting for execution + SAFE_FREE(&setStatusMoodData); + setStatusMoodData = null_strdup(szMoodData); + + bChanged = TRUE; + } + + return bChanged; +} + + +void CIcqProto::writeDbInfoSettingTLVStringUtf(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv) +{ + oscar_tlv *pTLV = chain->getTLV(wTlv, 1); + + if (pTLV && pTLV->wLen > 0) + { + char *str = (char*)_alloca(pTLV->wLen + 1); + + memcpy(str, pTLV->pData, pTLV->wLen); + str[pTLV->wLen] = '\0'; + setSettingStringUtf(hContact, szSetting, str); + } + else + deleteSetting(hContact, szSetting); +} + + +void CIcqProto::writeDbInfoSettingTLVString(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv) +{ + oscar_tlv *pTLV = chain->getTLV(wTlv, 1); + + if (pTLV && pTLV->wLen > 0) + { + char *str = (char*)_alloca(pTLV->wLen + 1); + + memcpy(str, pTLV->pData, pTLV->wLen); + str[pTLV->wLen] = '\0'; + setSettingString(hContact, szSetting, str); + } + else + deleteSetting(hContact, szSetting); +} + + +void CIcqProto::writeDbInfoSettingTLVWord(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv) +{ + int num = chain->getNumber(wTlv, 1); + + if (num > 0) + setSettingWord(hContact, szSetting, num); + else + deleteSetting(hContact, szSetting); +} + + +void CIcqProto::writeDbInfoSettingTLVByte(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv) +{ + int num = chain->getNumber(wTlv, 1); + + if (num > 0) + setSettingByte(hContact, szSetting, num); + else + deleteSetting(hContact, szSetting); +} + + +void CIcqProto::writeDbInfoSettingTLVDouble(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv) +{ + double num = chain->getDouble(wTlv, 1); + + if (num > 0) + setSettingDouble(hContact, szSetting, num); + else + deleteSetting(hContact, szSetting); +} + +void CIcqProto::writeDbInfoSettingTLVDate(HANDLE hContact, const char* szSettingYear, const char* szSettingMonth, const char* szSettingDay, oscar_tlv_chain* chain, WORD wTlv) +{ + double time = chain->getDouble(wTlv, 1); + + if (time > 0) + { // date is stored as double with unit equal to a day, incrementing since 1/1/1900 0:00 GMT + SYSTEMTIME sTime = {0}; + if (VariantTimeToSystemTime(time + 2, &sTime)) + { + setSettingWord(hContact, szSettingYear, sTime.wYear); + setSettingByte(hContact, szSettingMonth, (BYTE)sTime.wMonth); + setSettingByte(hContact, szSettingDay, (BYTE)sTime.wDay); + } + else + { + deleteSetting(hContact, szSettingYear); + deleteSetting(hContact, szSettingMonth); + deleteSetting(hContact, szSettingDay); + } + } + else + { + deleteSetting(hContact, szSettingYear); + deleteSetting(hContact, szSettingMonth); + deleteSetting(hContact, szSettingDay); + } +} + + +void CIcqProto::writeDbInfoSettingTLVBlob(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv) +{ + oscar_tlv *pTLV = chain->getTLV(wTlv, 1); + + if (pTLV && pTLV->wLen > 0) + setSettingBlob(hContact, szSetting, pTLV->pData, pTLV->wLen); + else + deleteSetting(hContact, szSetting); +} + + +BOOL CIcqProto::writeDbInfoSettingString(HANDLE hContact, const char* szSetting, char** buf, WORD* pwLength) +{ + WORD wLen; + + if (*pwLength < 2) + return FALSE; + + unpackLEWord((LPBYTE*)buf, &wLen); + *pwLength -= 2; + + if (*pwLength < wLen) + return FALSE; + + if ((wLen > 0) && (**buf) && ((*buf)[wLen-1]==0)) // Make sure we have a proper string + { + WORD wCp = getSettingWord(hContact, "InfoCodePage", getSettingWord(hContact, "InfoCP", CP_ACP)); + + if (wCp != CP_ACP) + { + char *szUtf = ansi_to_utf8_codepage(*buf, wCp); + + if (szUtf) + { + setSettingStringUtf(hContact, szSetting, szUtf); + SAFE_FREE((void**)&szUtf); + } + else + setSettingString(hContact, szSetting, *buf); + } + else + setSettingString(hContact, szSetting, *buf); + } + else + deleteSetting(hContact, szSetting); + + *buf += wLen; + *pwLength -= wLen; + + return TRUE; +} + +BOOL CIcqProto::writeDbInfoSettingWord(HANDLE hContact, const char *szSetting, char **buf, WORD* pwLength) +{ + WORD wVal; + + + if (*pwLength < 2) + return FALSE; + + unpackLEWord((LPBYTE*)buf, &wVal); + *pwLength -= 2; + + if (wVal != 0) + setSettingWord(hContact, szSetting, wVal); + else + deleteSetting(hContact, szSetting); + + return TRUE; +} + +BOOL CIcqProto::writeDbInfoSettingWordWithTable(HANDLE hContact, const char *szSetting, const FieldNamesItem *table, char **buf, WORD* pwLength) +{ + WORD wVal; + char sbuf[MAX_PATH]; + char *text; + + if (*pwLength < 2) + return FALSE; + + unpackLEWord((LPBYTE*)buf, &wVal); + *pwLength -= 2; + + text = LookupFieldNameUtf(table, wVal, sbuf, MAX_PATH); + if (text) + setSettingStringUtf(hContact, szSetting, text); + else + deleteSetting(hContact, szSetting); + + return TRUE; +} + +BOOL CIcqProto::writeDbInfoSettingByte(HANDLE hContact, const char *pszSetting, char **buf, WORD* pwLength) +{ + BYTE byVal; + + if (*pwLength < 1) + return FALSE; + + unpackByte((LPBYTE*)buf, &byVal); + *pwLength -= 1; + + if (byVal != 0) + setSettingByte(hContact, pszSetting, byVal); + else + deleteSetting(hContact, pszSetting); + + return TRUE; +} + +BOOL CIcqProto::writeDbInfoSettingByteWithTable(HANDLE hContact, const char *szSetting, const FieldNamesItem *table, char **buf, WORD* pwLength) +{ + BYTE byVal; + char sbuf[MAX_PATH]; + char *text; + + if (*pwLength < 1) + return FALSE; + + unpackByte((LPBYTE*)buf, &byVal); + *pwLength -= 1; + + text = LookupFieldNameUtf(table, byVal, sbuf, MAX_PATH); + if (text) + setSettingStringUtf(hContact, szSetting, text); + else + deleteSetting(hContact, szSetting); + + return TRUE; +} + +char* time2text(time_t time) +{ + tm *local = localtime(&time); + + if (local) + { + char *str = asctime(local); + str[24] = '\0'; // remove new line + return str; + } + else + return ""; +} + + +BOOL CIcqProto::validateStatusMessageRequest(HANDLE hContact, WORD byMessageType) +{ + // Privacy control + if (getSettingByte(NULL, "StatusMsgReplyCList", 0)) + { + // Don't send statusmessage to unknown contacts + if (hContact == INVALID_HANDLE_VALUE) + return FALSE; + + // Don't send statusmessage to temporary contacts or hidden contacts + if (DBGetContactSettingByte(hContact, "CList", "NotOnList", 0) || + DBGetContactSettingByte(hContact, "CList", "Hidden", 0)) + return FALSE; + + // Don't send statusmessage to invisible contacts + if (getSettingByte(NULL, "StatusMsgReplyVisible", 0)) + { + WORD wStatus = getContactStatus(hContact); + if (wStatus == ID_STATUS_OFFLINE) + return FALSE; + } + } + + // Dont send messages to people you are hiding from + if (hContact != INVALID_HANDLE_VALUE && + getSettingWord(hContact, "ApparentMode", 0) == ID_STATUS_OFFLINE) + { + return FALSE; + } + + // Dont respond to request for other statuses than your current one + if ((byMessageType == MTYPE_AUTOONLINE && m_iStatus != ID_STATUS_ONLINE) || + (byMessageType == MTYPE_AUTOAWAY && m_iStatus != ID_STATUS_AWAY) || + (byMessageType == MTYPE_AUTOBUSY && m_iStatus != ID_STATUS_OCCUPIED) || + (byMessageType == MTYPE_AUTONA && m_iStatus != ID_STATUS_NA) || + (byMessageType == MTYPE_AUTODND && m_iStatus != ID_STATUS_DND) || + (byMessageType == MTYPE_AUTOFFC && m_iStatus != ID_STATUS_FREECHAT)) + { + return FALSE; + } + + if (hContact != INVALID_HANDLE_VALUE && m_iStatus==ID_STATUS_INVISIBLE && + getSettingWord(hContact, "ApparentMode", 0) != ID_STATUS_ONLINE) + { + if (!getSettingByte(hContact, "TemporaryVisible", 0)) + { // Allow request to temporary visible contacts + return FALSE; + } + } + + // All OK! + return TRUE; +} + + +void __fastcall SAFE_DELETE(MZeroedObject **p) +{ + if (*p) + { + delete *p; + *p = NULL; + } +} + + +void __fastcall SAFE_DELETE(lockable_struct **p) +{ + if (*p) + { + (*p)->_Release(); + *p = NULL; + } +} + + +void __fastcall SAFE_FREE(void** p) +{ + if (*p) + { + free(*p); + *p = NULL; + } +} + + +void* __fastcall SAFE_MALLOC(size_t size) +{ + void* p = NULL; + + if (size) + { + p = malloc(size); + + if (p) + ZeroMemory(p, size); + } + return p; +} + + +void* __fastcall SAFE_REALLOC(void* p, size_t size) +{ + if (p) + { + return realloc(p, size); + } + else + return SAFE_MALLOC(size); +} + + +DWORD ICQWaitForSingleObject(HANDLE hObject, DWORD dwMilliseconds, int bWaitAlways) +{ + DWORD dwResult; + + do { // will get WAIT_IO_COMPLETION for QueueUserAPC(), ignore it unless terminating + dwResult = WaitForSingleObjectEx(hObject, dwMilliseconds, TRUE); + } while (dwResult == WAIT_IO_COMPLETION && (bWaitAlways || !Miranda_Terminated())); + + return dwResult; +} + + +HANDLE NetLib_OpenConnection(HANDLE hUser, const char* szIdent, NETLIBOPENCONNECTION* nloc) +{ + Netlib_Logf(hUser, "%sConnecting to %s:%u", szIdent?szIdent:"", nloc->szHost, nloc->wPort); + + nloc->cbSize = sizeof(NETLIBOPENCONNECTION); + nloc->flags |= NLOCF_V2; + + return (HANDLE)CallService(MS_NETLIB_OPENCONNECTION, (WPARAM)hUser, (LPARAM)nloc); +} + + +HANDLE CIcqProto::NetLib_BindPort(NETLIBNEWCONNECTIONPROC_V2 pFunc, void* lParam, WORD* pwPort, DWORD* pdwIntIP) +{ + NETLIBBIND nlb = {0}; + + nlb.cbSize = sizeof(NETLIBBIND); + nlb.pfnNewConnectionV2 = pFunc; + nlb.pExtra = lParam; + SetLastError(ERROR_INVALID_PARAMETER); // this must be here - NetLib does not set any error :(( + + HANDLE hBoundPort = (HANDLE)CallService(MS_NETLIB_BINDPORT, (WPARAM)m_hDirectNetlibUser, (LPARAM)&nlb); + + if (pwPort) *pwPort = nlb.wPort; + if (pdwIntIP) *pdwIntIP = nlb.dwInternalIP; + + return hBoundPort; +} + + +void NetLib_CloseConnection(HANDLE *hConnection, int bServerConn) +{ + if (*hConnection) + { + NetLib_SafeCloseHandle(hConnection); + + if (bServerConn) + FreeGatewayIndex(*hConnection); + } +} + + +void NetLib_SafeCloseHandle(HANDLE *hConnection) +{ + if (*hConnection) + { + Netlib_CloseHandle(*hConnection); + *hConnection = NULL; + } +} + + +int CIcqProto::NetLog_Server(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)m_hServerNetlibUser,(LPARAM)szText); +} + +int CIcqProto::NetLog_Direct(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)m_hDirectNetlibUser,(LPARAM)szText); +} + +int CIcqProto::NetLog_Uni(BOOL bDC, const char *fmt,...) +{ + va_list va; + char szText[1024]; + HANDLE hNetlib; + + va_start(va,fmt); + mir_vsnprintf(szText,sizeof(szText),fmt,va); + va_end(va); + + if (bDC) + hNetlib = m_hDirectNetlibUser; + else + hNetlib = m_hServerNetlibUser; + + return CallService(MS_NETLIB_LOG,(WPARAM)hNetlib,(LPARAM)szText); +} + +int CIcqProto::BroadcastAck(HANDLE hContact,int type,int result,HANDLE hProcess,LPARAM lParam) +{ + ACKDATA ack={0}; + + ack.cbSize = sizeof(ACKDATA); + ack.szModule = m_szModuleName; + ack.hContact = hContact; + ack.type = type; + ack.result = result; + ack.hProcess = hProcess; + ack.lParam = lParam; + return CallService(MS_PROTO_BROADCASTACK,0,(LPARAM)&ack); +} + +char* __fastcall ICQTranslateUtf(const char *src) +{ // this takes UTF-8 strings only!!! + char *szRes = NULL; + + if (!strlennull(src)) + { // for the case of empty strings + return null_strdup(src); + } + + { // we can use unicode translate (0.5+) + WCHAR* usrc = make_unicode_string(src); + + szRes = make_utf8_string(TranslateW(usrc)); + + SAFE_FREE((void**)&usrc); + } + return szRes; +} + +char* __fastcall ICQTranslateUtfStatic(const char *src, char *buf, size_t bufsize) +{ // this takes UTF-8 strings only!!! + if (strlennull(src)) + { // we can use unicode translate (0.5+) + WCHAR *usrc = make_unicode_string(src); + + make_utf8_string_static(TranslateW(usrc), buf, bufsize); + + SAFE_FREE((void**)&usrc); + } + else + buf[0] = '\0'; + + return buf; +} + +void CIcqProto::ForkThread( IcqThreadFunc pFunc, void* arg ) +{ + CloseHandle(( HANDLE )mir_forkthreadowner(( pThreadFuncOwner )*( void** )&pFunc, this, arg, NULL )); +} + +HANDLE CIcqProto::ForkThreadEx( IcqThreadFunc pFunc, void* arg, UINT* threadID ) +{ + return ( HANDLE )mir_forkthreadowner(( pThreadFuncOwner )*( void** )&pFunc, this, arg, threadID ); +} + + +char* CIcqProto::GetUserStoredPassword(char *szBuffer, int cbSize) +{ + if (!getSettingStringStatic(NULL, "Password", szBuffer, cbSize)) + { + CallService(MS_DB_CRYPT_DECODESTRING, strlennull(szBuffer) + 1, (LPARAM)szBuffer); + + if (strlennull(szBuffer)) + return szBuffer; + } + return NULL; +} + + +char* CIcqProto::GetUserPassword(BOOL bAlways) +{ + if (m_szPassword[0] != '\0' && (m_bRememberPwd || bAlways)) + return m_szPassword; + + if (GetUserStoredPassword(m_szPassword, sizeof(m_szPassword))) + { + m_bRememberPwd = TRUE; + + return m_szPassword; + } + + return NULL; +} + + +WORD CIcqProto::GetMyStatusFlags() +{ + WORD wFlags = 0; + + // Webaware setting bit flag + if (getSettingByte(NULL, "WebAware", 0)) + wFlags |= STATUS_WEBAWARE; + + // DC setting bit flag + switch (getSettingByte(NULL, "DCType", 0)) + { + case 0: + break; + + case 1: + wFlags |= STATUS_DCCONT; + break; + + case 2: + wFlags |= STATUS_DCAUTH; + break; + + default: + wFlags |= STATUS_DCDISABLED; + break; + } + return wFlags; +} + + +int IsValidRelativePath(const char *filename) +{ + if (strstrnull(filename, "..\\") || strstrnull(filename, "../") || + strstrnull(filename, ":\\") || strstrnull(filename, ":/") || + filename[0] == '\\' || filename[0] == '/') + return 0; // Contains malicious chars, Failure + + return 1; // Success +} + + +const char* ExtractFileName(const char *fullname) +{ + const char *szFileName; + + // already is only filename + if (((szFileName = strrchr(fullname, '\\')) == NULL) && ((szFileName = strrchr(fullname, '/')) == NULL)) + return fullname; + + return szFileName + 1; // skip backslash +} + + +char* FileNameToUtf(const TCHAR *filename) +{ + // reasonable only on NT systems + HINSTANCE hKernel = GetModuleHandle(_T("KERNEL32")); + DWORD (CALLBACK *RealGetLongPathName)(LPCWSTR, LPWSTR, DWORD); + + *(FARPROC *)&RealGetLongPathName = GetProcAddress(hKernel, "GetLongPathNameW"); + + if (RealGetLongPathName) + { // the function is available (it is not on old NT systems) + WCHAR *usFileName = NULL; + int wchars = RealGetLongPathName(filename, usFileName, 0); + usFileName = (WCHAR*)_alloca((wchars + 1) * sizeof(WCHAR)); + RealGetLongPathName(filename, usFileName, wchars); + + return make_utf8_string(usFileName); + } + return make_utf8_string(filename); +} + + +int FileAccessUtf(const char *path, int mode) +{ + int size = strlennull(path) + 2; + TCHAR *szPath = (TCHAR*)_alloca(size * sizeof(TCHAR)); + + if (utf8_to_tchar_static(path, szPath, size)) + return _taccess(szPath, mode); + + return -1; +} + + +int FileStatUtf(const char *path, struct _stati64 *buffer) +{ + int size = strlennull(path) + 2; + TCHAR *szPath = (TCHAR*)_alloca(size * sizeof(TCHAR)); + + if (utf8_to_tchar_static(path, szPath, size)) + return _tstati64(szPath, buffer); + + return -1; +} + + +int MakeDirUtf(const char *dir) +{ + int wRes = -1; + int size = strlennull(dir) + 2; + TCHAR *szDir = (TCHAR*)_alloca(size * sizeof(TCHAR)); + + if (utf8_to_tchar_static(dir, szDir, size)) + { // _tmkdir can created only one dir at once + wRes = _tmkdir(szDir); + // check if dir not already existed - return success if yes + if (wRes == -1 && errno == 17 /* EEXIST */) + wRes = 0; + else if (wRes && errno == 2 /* ENOENT */) + { // failed, try one directory less first + char *szLast = (char*)strrchr(dir, '\\'); + if (!szLast) szLast = (char*)strrchr(dir, '/'); + if (szLast) + { + char cOld = *szLast; + + *szLast = '\0'; + if (!MakeDirUtf(dir)) + wRes = _tmkdir(szDir); + + *szLast = cOld; + } + } + } + + return wRes; +} + + +int OpenFileUtf(const char *filename, int oflag, int pmode) +{ + int size = strlennull(filename) + 2; + TCHAR *szFile = (TCHAR*)_alloca(size * sizeof(TCHAR)); + + if (utf8_to_tchar_static(filename, szFile, size)) + return _topen(szFile, oflag, pmode); + + return -1; +} + + +WCHAR *GetWindowTextUcs(HWND hWnd) +{ + WCHAR *utext; + int nLen = GetWindowTextLengthW(hWnd); + utext = (WCHAR*)SAFE_MALLOC((nLen+2)*sizeof(WCHAR)); + GetWindowTextW(hWnd, utext, nLen + 1); + return utext; +} + + +void SetWindowTextUcs(HWND hWnd, WCHAR *text) +{ + SetWindowTextW(hWnd, text); +} + + +char* GetWindowTextUtf(HWND hWnd) +{ + int nLen = GetWindowTextLength(hWnd); + TCHAR *szText = (TCHAR*)_alloca((nLen + 2) * sizeof(TCHAR)); + + GetWindowText(hWnd, szText, nLen + 1); + + return tchar_to_utf8(szText); +} + + +char* GetDlgItemTextUtf(HWND hwndDlg, int iItem) +{ + return GetWindowTextUtf(GetDlgItem(hwndDlg, iItem)); +} + + +void SetWindowTextUtf(HWND hWnd, const char *szText) +{ + int size = strlennull(szText) + 2; + TCHAR *tszText = (TCHAR*)_alloca(size * sizeof(TCHAR)); + + if (utf8_to_tchar_static(szText, tszText, size)) + SetWindowText(hWnd, tszText); +} + + +void SetDlgItemTextUtf(HWND hwndDlg, int iItem, const char *szText) +{ + SetWindowTextUtf(GetDlgItem(hwndDlg, iItem), szText); +} + + +static int ControlAddStringUtf(HWND ctrl, DWORD msg, const char *szString) +{ + char str[MAX_PATH]; + char *szItem = ICQTranslateUtfStatic(szString, str, MAX_PATH); + int item = -1; + WCHAR *wItem = make_unicode_string(szItem); + item = SendMessage(ctrl, msg, 0, (LPARAM)wItem); + SAFE_FREE((void**)&wItem); + return item; +} + +int ComboBoxAddStringUtf(HWND hCombo, const char *szString, DWORD data) +{ + int item = ControlAddStringUtf(hCombo, CB_ADDSTRING, szString); + SendMessage(hCombo, CB_SETITEMDATA, item, data); + + return item; +} + +int ListBoxAddStringUtf(HWND hList, const char *szString) +{ + return ControlAddStringUtf(hList, LB_ADDSTRING, szString); +} + +int MessageBoxUtf(HWND hWnd, const char *szText, const char *szCaption, UINT uType) +{ + int res; + char str[1024]; + char cap[MAX_PATH]; + WCHAR *text = make_unicode_string(ICQTranslateUtfStatic(szText, str, 1024)); + WCHAR *caption = make_unicode_string(ICQTranslateUtfStatic(szCaption, cap, MAX_PATH)); + res = MessageBoxW(hWnd, text, caption, uType); + SAFE_FREE((void**)&caption); + SAFE_FREE((void**)&text); + return res; +} + +char* CIcqProto::ConvertMsgToUserSpecificAnsi(HANDLE hContact, const char* szMsg) +{ // this takes utf-8 encoded message + WORD wCP = getSettingWord(hContact, "CodePage", m_wAnsiCodepage); + char* szAnsi = NULL; + + if (wCP != CP_ACP) // convert to proper codepage + if (!utf8_decode_codepage(szMsg, &szAnsi, wCP)) + return NULL; + + return szAnsi; +} + +// just broadcast generic send error with dummy cookie and return that cookie +DWORD CIcqProto::ReportGenericSendError(HANDLE hContact, int nType, const char* szErrorMsg) +{ + DWORD dwCookie = GenerateCookie(0); + SendProtoAck(hContact, dwCookie, ACKRESULT_FAILED, nType, Translate(szErrorMsg)); + return dwCookie; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +void CIcqProto::CreateProtoService(const char* szService, IcqServiceFunc serviceProc) +{ + char temp[MAX_PATH*2]; + + null_snprintf(temp, sizeof(temp), "%s%s", m_szModuleName, szService); + CreateServiceFunctionObj( temp, ( MIRANDASERVICEOBJ )*( void** )&serviceProc, this ); +} + +void CIcqProto::CreateProtoServiceParam(const char* szService, IcqServiceFuncParam serviceProc, LPARAM lParam) +{ + char temp[MAX_PATH*2]; + + null_snprintf(temp, sizeof(temp), "%s%s", m_szModuleName, szService); + CreateServiceFunctionObjParam( temp, ( MIRANDASERVICEOBJPARAM )*( void** )&serviceProc, this, lParam ); +} + + +HANDLE CIcqProto::HookProtoEvent(const char* szEvent, IcqEventFunc pFunc) +{ + return ::HookEventObj(szEvent, (MIRANDAHOOKOBJ)*(void**)&pFunc, this); +} + + +HANDLE CIcqProto::CreateProtoEvent(const char* szEvent) +{ + char str[MAX_PATH + 32]; + strcpy(str, m_szModuleName); + strcat(str, szEvent); + return CreateHookableEvent(str); +} diff --git a/protocols/IcqOscarJ/src/utilities.h b/protocols/IcqOscarJ/src/utilities.h new file mode 100644 index 0000000000..962ecce15f --- /dev/null +++ b/protocols/IcqOscarJ/src/utilities.h @@ -0,0 +1,188 @@ +// ---------------------------------------------------------------------------80 +// ICQ plugin for Miranda Instant Messenger +// ________________________________________ +// +// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede +// Copyright © 2001-2002 Jon Keating, Richard Hughes +// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater +// Copyright © 2004-2010 Joe Kucera +// +// 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +// +// ----------------------------------------------------------------------------- +// DESCRIPTION: +// +// Describe me here please... +// +// ----------------------------------------------------------------------------- +#ifndef __UTILITIES_H +#define __UTILITIES_H + + +struct icq_ack_args +{ + HANDLE hContact; + int nAckType; + int nAckResult; + HANDLE hSequence; + LPARAM pszMessage; +}; + +struct icq_contacts_cache +{ + HANDLE hContact; + DWORD dwUin; + const char *szUid; +}; + + +/*---------* Functions *---------------*/ + +void MoveDlgItem(HWND hwndDlg, int iItem, int left, int top, int width, int height); +void EnableDlgItem(HWND hwndDlg, UINT control, int state); +void ShowDlgItem(HWND hwndDlg, UINT control, int state); +void icq_EnableMultipleControls(HWND hwndDlg, const UINT* controls, int cControls, int state); +void icq_ShowMultipleControls(HWND hwndDlg, const UINT* controls, int cControls, int state); +int IcqStatusToMiranda(WORD wStatus); +WORD MirandaStatusToIcq(int nStatus); +int MirandaStatusToSupported(int nMirandaStatus); +char *MirandaStatusToString(int); +char *MirandaStatusToStringUtf(int); + +int AwayMsgTypeToStatus(int nMsgType); + +void SetGatewayIndex(HANDLE hConn, DWORD dwIndex); +DWORD GetGatewayIndex(HANDLE hConn); +void FreeGatewayIndex(HANDLE hConn); + +char *NickFromHandle(HANDLE hContact); +char *NickFromHandleUtf(HANDLE hContact); +char *strUID(DWORD dwUIN, char *pszUID); + +int __fastcall strlennull(const char *string); +int __fastcall strcmpnull(const char *str1, const char *str2); +int __fastcall stricmpnull(const char *str1, const char *str2); +char* __fastcall strstrnull(const char *str, const char *substr); +int null_snprintf(char *buffer, size_t count, const char *fmt, ...); +char* __fastcall null_strdup(const char *string); +char* __fastcall null_strcpy(char *dest, const char *src, size_t maxlen); +int __fastcall null_strcut(char *string, int maxlen); + +int __fastcall strlennull(const WCHAR *string); +int null_snprintf(WCHAR *buffer, size_t count, const WCHAR *fmt, ...); +WCHAR* __fastcall null_strdup(const WCHAR *string); +WCHAR* __fastcall null_strcpy(WCHAR *dest, const WCHAR *src, size_t maxlen); + +void parseServerAddress(char *szServer, WORD* wPort); + +char *DemangleXml(const char *string, int len); +char *MangleXml(const char *string, int len); +char *EliminateHtml(const char *string, int len); +char *ApplyEncoding(const char *string, const char *pszEncoding); + +int RandRange(int nLow, int nHigh); + +BOOL IsStringUIN(const char *pszString); + +char* time2text(time_t time); + +BOOL validateStatusMessageRequest(HANDLE hContact, WORD byMessageType); + +void __fastcall SAFE_FREE(void** p); +void* __fastcall SAFE_MALLOC(size_t size); +void* __fastcall SAFE_REALLOC(void* p, size_t size); + +__inline static void SAFE_FREE(char** str) { SAFE_FREE((void**)str); } +__inline static void SAFE_FREE(WCHAR** str) { SAFE_FREE((void**)str); } + +struct lockable_struct: public MZeroedObject +{ +private: + int nLockCount; +public: + lockable_struct() { _Lock(); }; + virtual ~lockable_struct() {}; + + void _Lock() { nLockCount++; }; + void _Release() { nLockCount--; if (!nLockCount) delete this; }; + + int getLockCount() { return nLockCount; }; +}; + +void __fastcall SAFE_DELETE(MZeroedObject **p); +void __fastcall SAFE_DELETE(lockable_struct **p); + +DWORD ICQWaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds, int bWaitAlways = FALSE); + + +struct icq_critical_section: public lockable_struct +{ +private: + HANDLE hMutex; + +public: + icq_critical_section() { hMutex = CreateMutex(NULL, FALSE, NULL); } + ~icq_critical_section() { CloseHandle(hMutex); } + + void Enter(void) { ICQWaitForSingleObject(hMutex, INFINITE, TRUE); } + void Leave(void) { ReleaseMutex(hMutex); } +}; + +__inline static void SAFE_DELETE(icq_critical_section **p) { SAFE_DELETE((lockable_struct**)p); } + +struct icq_lock +{ +private: + icq_critical_section *pMutex; +public: + icq_lock(icq_critical_section *mutex) { pMutex = mutex; pMutex->Enter(); }; + ~icq_lock() { pMutex->Leave(); pMutex = NULL; }; +}; + + +HANDLE NetLib_OpenConnection(HANDLE hUser, const char* szIdent, NETLIBOPENCONNECTION* nloc); +void NetLib_CloseConnection(HANDLE *hConnection, int bServerConn); +void NetLib_SafeCloseHandle(HANDLE *hConnection); + +char* __fastcall ICQTranslateUtf(const char *src); +char* __fastcall ICQTranslateUtfStatic(const char *src, char *buf, size_t bufsize); + +WORD GetMyStatusFlags(); + +/* Unicode FS utility functions */ + +int IsValidRelativePath(const char *filename); +const char* ExtractFileName(const char *fullname); +char* FileNameToUtf(const TCHAR *filename); + +int FileAccessUtf(const char *path, int mode); +int FileStatUtf(const char *path, struct _stati64 *buffer); +int MakeDirUtf(const char *dir); +int OpenFileUtf(const char *filename, int oflag, int pmode); + +/* Unicode UI utility functions */ +WCHAR* GetWindowTextUcs(HWND hWnd); +void SetWindowTextUcs(HWND hWnd, WCHAR *text); +char *GetWindowTextUtf(HWND hWnd); +char *GetDlgItemTextUtf(HWND hwndDlg, int iItem); +void SetWindowTextUtf(HWND hWnd, const char *szText); +void SetDlgItemTextUtf(HWND hwndDlg, int iItem, const char *szText); + +int ComboBoxAddStringUtf(HWND hCombo, const char *szString, DWORD data); +int ListBoxAddStringUtf(HWND hList, const char *szString); + +int MessageBoxUtf(HWND hWnd, const char *szText, const char *szCaption, UINT uType); + +#endif /* __UTILITIES_H */ diff --git a/protocols/IcqOscarJ/src/version.h b/protocols/IcqOscarJ/src/version.h new file mode 100644 index 0000000000..0231d7a7ea --- /dev/null +++ b/protocols/IcqOscarJ/src/version.h @@ -0,0 +1,3 @@ +#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