/*
Plugin of Miranda IM for communicating with users of the AIM protocol.
Copyright (c) 2008-2012 Boris Krasnovskiy
Copyright (C) 2005-2006 Aaron Myles Landwehr
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, see .
*/
#include "stdafx.h"
void CAimProto::broadcast_status(int status)
{
debugLogA("Broadcast Status: %d", status);
int old_status = m_iStatus;
m_iStatus = status;
if (m_iStatus == ID_STATUS_OFFLINE) {
shutdown_file_transfers();
shutdown_chat_conn();
if (m_hServerConn) {
aim_sendflap(m_hServerConn, 0x04, 0, nullptr, m_seqno);
Netlib_Shutdown(m_hServerConn);
}
if (m_hMailConn && m_hMailConn != (HANDLE)1) {
aim_sendflap(m_hMailConn, 0x04, 0, nullptr, m_mail_seqno);
Netlib_Shutdown(m_hMailConn);
}
else if (m_hMailConn == (HANDLE)1)
m_hMailConn = nullptr;
if (m_hAvatarConn && m_hAvatarConn != (HANDLE)1) {
aim_sendflap(m_hAvatarConn, 0x04, 0, nullptr, m_avatar_seqno);
Netlib_Shutdown(m_hAvatarConn);
}
else if (m_hAvatarConn == (HANDLE)1)
m_hAvatarConn = nullptr;
if (m_hChatNavConn && m_hChatNavConn != (HANDLE)1) {
aim_sendflap(m_hChatNavConn, 0x04, 0, nullptr, m_chatnav_seqno);
Netlib_Shutdown(m_hChatNavConn);
}
else if (m_hChatNavConn == (HANDLE)1)
m_hChatNavConn = nullptr;
m_idle = false;
m_instantidle = false;
m_list_received = false;
m_state = 0;
m_iDesiredStatus = ID_STATUS_OFFLINE;
replaceStr(m_last_status_msg, nullptr);
m_avatar_id_lg = 0;
m_avatar_id_sm = 0;
replaceStr(m_hash_lg, nullptr);
replaceStr(m_hash_sm, nullptr);
m_pd_flags = 0;
m_pd_info_id = 0;
m_pd_mode = 0;
m_seqno = 0;
m_mail_seqno = 0;
m_avatar_seqno = 0;
m_chatnav_seqno = 0;
m_admin_seqno = 0;
}
ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)old_status, m_iStatus);
}
void CAimProto::start_connection(void*)
{
if (m_iStatus <= ID_STATUS_OFFLINE) {
offline_contacts();
DBVARIANT dbv;
if (!getString(AIM_KEY_SN, &dbv))
db_free(&dbv);
else {
ShowPopup(LPGEN("Please, enter a user name in the options dialog."), 0);
broadcast_status(ID_STATUS_OFFLINE);
return;
}
if (!getString(AIM_KEY_PW, &dbv))
db_free(&dbv);
else {
ShowPopup(LPGEN("Please, enter a password in the options dialog."), 0);
broadcast_status(ID_STATUS_OFFLINE);
return;
}
bool use_clientlogin = getByte(AIM_KEY_CLIENTLOGIN, 1) != 0; //clientlogin should be enabled by default
if (!use_clientlogin)
{
char* login_url = getStringA(AIM_KEY_HN);
//if (login_url == NULL) login_url = mir_strdup(use_ssl ? AIM_DEFAULT_SERVER : AIM_DEFAULT_SERVER_NS);
if (login_url == nullptr) login_url = mir_strdup(AIM_DEFAULT_SERVER);
m_hServerConn = aim_connect(login_url, get_default_port(), false, login_url); //ssl does not work anymore with old authorization algo
mir_free(login_url);
m_pref1_flags = 0x77ffff;
m_pref1_set_flags = 0x77ffff;
mir_free(m_pref2_flags); m_pref2_flags = nullptr; m_pref2_len = 0;
mir_free(m_pref2_set_flags); m_pref2_set_flags = nullptr; m_pref2_set_len = 0;
if (m_hServerConn)
aim_connection_authorization();
else
broadcast_status(ID_STATUS_OFFLINE);
}
else
{
aim_connection_clientlogin();
}
}
}
bool CAimProto::wait_conn(HNETLIBCONN &hConn, HANDLE &hEvent, unsigned short service)
{
if (m_iStatus == ID_STATUS_OFFLINE)
return false;
{
mir_cslock lck(connMutex);
if (hConn == nullptr && m_hServerConn) {
debugLogA("Starting Connection.");
hConn = (HNETLIBCONN)1; //set so no additional service request attempts are made while aim is still processing the request
aim_new_service_request(m_hServerConn, m_seqno, service);//general service connection!
}
}
if (WaitForSingleObjectEx(hEvent, 10000, TRUE) != WAIT_OBJECT_0)
return false;
if (Miranda_IsTerminated() || m_iStatus == ID_STATUS_OFFLINE)
return false;
return true;
}
unsigned short CAimProto::get_default_port(void)
{
//return getWord(AIM_KEY_PN, getByte(AIM_KEY_DSSL, 0) ? AIM_DEFAULT_PORT : AIM_DEFAULT_SSL_PORT);
return AIM_DEFAULT_PORT;
}
bool CAimProto::is_my_contact(MCONTACT hContact)
{
const char* szProto = GetContactProto(hContact);
return szProto != nullptr && mir_strcmp(m_szModuleName, szProto) == 0;
}
MCONTACT CAimProto::find_chat_contact(const char* room)
{
for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) {
DBVARIANT dbv;
if (!getString(hContact, "ChatRoomID", &dbv)) {
bool found = !mir_strcmp(room, dbv.pszVal);
db_free(&dbv);
if (found)
return hContact;
}
}
return NULL;
}
MCONTACT CAimProto::contact_from_sn(const char* sn, bool addIfNeeded, bool temporary)
{
ptrA norm_sn(normalize_name(sn));
for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) {
DBVARIANT dbv;
if (!getString(hContact, AIM_KEY_SN, &dbv)) {
bool found = !mir_strcmp(norm_sn, dbv.pszVal);
db_free(&dbv);
if (found)
return hContact;
}
}
if (addIfNeeded) {
MCONTACT hContact = db_add_contact();
if (hContact) {
if (Proto_AddToContact(hContact, m_szModuleName) == 0) {
setString(hContact, AIM_KEY_SN, norm_sn);
setString(hContact, AIM_KEY_NK, sn);
debugLogA("Adding contact %s to client side list.", norm_sn);
if (temporary)
db_set_b(hContact, "CList", "NotOnList", 1);
return hContact;
}
db_delete_contact(hContact);
}
}
return NULL;
}
void CAimProto::update_server_group(const char* group, unsigned short group_id)
{
unsigned short user_id_array_size;
unsigned short* user_id_array;
if (group_id)
user_id_array = get_members_of_group(group_id, user_id_array_size);
else {
user_id_array_size = (unsigned short)m_group_list.getCount();
user_id_array = (unsigned short*)mir_alloc(user_id_array_size * sizeof(unsigned short));
for (unsigned short i = 0; i < user_id_array_size; ++i)
user_id_array[i] = _htons(m_group_list[i].item_id);
}
debugLogA("Modifying group %s:%u on the serverside list", group, group_id);
aim_mod_group(m_hServerConn, m_seqno, group, group_id, (char*)user_id_array,
user_id_array_size * sizeof(unsigned short));
mir_free(user_id_array);
}
void CAimProto::add_contact_to_group(MCONTACT hContact, const char* new_group)
{
if (new_group == nullptr)
return;
unsigned short old_group_id = getGroupId(hContact, 1);
char *old_group = m_group_list.find_name(old_group_id);
if (old_group && mir_strcmp(new_group, old_group) == 0)
return;
DBVARIANT dbv;
char *nick = nullptr;
if (!db_get_utf(hContact, MOD_KEY_CL, "MyHandle", &dbv)) {
nick = NEWSTR_ALLOCA(dbv.pszVal);
db_free(&dbv);
}
if (getString(hContact, AIM_KEY_SN, &dbv)) return;
unsigned short item_id = getBuddyId(hContact, 1);
unsigned short new_item_id = search_for_free_item_id(hContact);
unsigned short new_group_id = m_group_list.find_id(new_group);
if (!item_id)
debugLogA("Contact %u not on list.", hContact);
setGroupId(hContact, 1, new_group_id);
if (new_group && mir_strcmp(new_group, AIM_DEFAULT_GROUP))
db_set_utf(hContact, MOD_KEY_CL, OTH_KEY_GP, new_group);
else
db_unset(hContact, MOD_KEY_CL, OTH_KEY_GP);
aim_ssi_update(m_hServerConn, m_seqno, true);
if (new_group_id == 0) {
create_group(new_group);
debugLogA("Group %s not on list.", new_group);
new_group_id = m_group_list.add(new_group);
debugLogA("Adding group %s:%u to the serverside list", new_group, new_group_id);
aim_add_contact(m_hServerConn, m_seqno, new_group, 0, new_group_id, 1);//add the group server-side even if it exist
}
debugLogA("Adding buddy %s:%u %s:%u to the serverside list", dbv.pszVal, new_item_id, new_group, new_group_id);
aim_add_contact(m_hServerConn, m_seqno, dbv.pszVal, new_item_id, new_group_id, 0, nick);
update_server_group(new_group, new_group_id);
if (old_group_id && item_id) {
bool is_not_in_list = getBool(hContact, AIM_KEY_NIL, false);
debugLogA("Removing buddy %s:%u %s:%u from the serverside list", dbv.pszVal, item_id, old_group, old_group_id);
aim_delete_contact(m_hServerConn, m_seqno, dbv.pszVal, item_id, old_group_id, 0, is_not_in_list);
update_server_group(old_group, old_group_id);
delSetting(hContact, AIM_KEY_NIL);
}
aim_ssi_update(m_hServerConn, m_seqno, false);
db_free(&dbv);
}
void CAimProto::offline_contact(MCONTACT hContact, bool remove_settings)
{
if (remove_settings) {
//We need some of this stuff if we are still online.
for (int i = 1;; ++i) {
if (deleteBuddyId(hContact, i)) break;
deleteGroupId(hContact, i);
}
db_unset(hContact, MOD_KEY_CL, OTH_KEY_SM);
}
setWord(hContact, AIM_KEY_ST, ID_STATUS_OFFLINE);
}
void CAimProto::offline_contacts(void)
{
for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName))
offline_contact(hContact, true);
m_allow_list.destroy();
m_block_list.destroy();
m_group_list.destroy();
}
char *normalize_name(const char *s)
{
if (s == nullptr) return nullptr;
char* buf = mir_strdup(s);
_strlwr(buf);
/*
char *p = strchr(buf, ' ');
if (p)
{
char *q = p;
while (*p)
{
if (*p != ' ') *(q++) = *p;
++p;
}
*q = '\0';
}
*/
return buf;
}
char* trim_str(char* s)
{
if (s == nullptr) return nullptr;
size_t len = mir_strlen(s);
while (len) {
if (isspace(s[len - 1])) --len;
else break;
}
s[len] = 0;
char* sc = s;
while (isspace(*sc)) ++sc;
memcpy(s, sc, mir_strlen(sc) + 1);
return s;
}
void create_group(const char *group)
{
if (mir_strcmp(group, AIM_DEFAULT_GROUP) == 0) return;
wchar_t* szGroupName = mir_utf8decodeW(group);
Clist_GroupCreate(0, szGroupName);
mir_free(szGroupName);
}
unsigned short CAimProto::search_for_free_item_id(MCONTACT hbuddy)//returns a free item id and links the id to the buddy
{
unsigned short id;
retry:
id = get_random();
for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) {
for (int i = 1; ; ++i) {
unsigned short item_id = getBuddyId(hContact, i);
if (item_id == 0) break;
if (item_id == id) goto retry; //found one no need to look through anymore
}
}
setBuddyId(hbuddy, 1, id);
return id;
}
//returns the size of the list array aquired with data
unsigned short* CAimProto::get_members_of_group(unsigned short group_id, unsigned short &size)
{
unsigned short* list = nullptr;
size = 0;
for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) {
for (int i = 1; ; ++i) {
unsigned short user_group_id = getGroupId(hContact, i);
if (user_group_id == 0)
break;
if (group_id == user_group_id) {
unsigned short buddy_id = getBuddyId(hContact, i);
if (buddy_id) {
list = (unsigned short*)mir_realloc(list, ++size*sizeof(list[0]));
list[size - 1] = _htons(buddy_id);
}
}
}
}
return list;
}
void CAimProto::upload_nicks(void)
{
for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) {
DBVARIANT dbv;
if (!db_get_utf(hContact, MOD_KEY_CL, "MyHandle", &dbv)) {
set_local_nick(hContact, dbv.pszVal, nullptr);
db_free(&dbv);
}
}
}
void CAimProto::set_local_nick(MCONTACT hContact, char* nick, char* note)
{
DBVARIANT dbv;
if (getString(hContact, AIM_KEY_SN, &dbv)) return;
for (int i = 1; ; ++i) {
unsigned short group_id = getGroupId(hContact, i);
if (group_id == 0) break;
unsigned short buddy_id = getBuddyId(hContact, i);
if (buddy_id == 0) break;
aim_mod_buddy(m_hServerConn, m_seqno, dbv.pszVal, buddy_id, group_id, nick, note);
}
db_free(&dbv);
}
/////////////////////////////////////////////////////////////////////////////////////////
unsigned short BdList::get_free_id(void)
{
unsigned short id;
retry:
id = get_random();
for (int i = 0; i < count; ++i)
if (items[i]->item_id == id) goto retry;
return id;
}
unsigned short BdList::find_id(const char* name)
{
for (int i = 0; i < count; ++i) {
if (_stricmp(items[i]->name, name) == 0)
return items[i]->item_id;
}
return 0;
}
char* BdList::find_name(unsigned short id)
{
for (int i = 0; i < count; ++i) {
if (items[i]->item_id == id)
return items[i]->name;
}
return nullptr;
}
void BdList::remove_by_id(unsigned short id)
{
for (int i = 0; i < count; ++i) {
if (items[i]->item_id == id) {
remove(i);
break;
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////
unsigned short CAimProto::getBuddyId(MCONTACT hContact, int i)
{
char item[sizeof(AIM_KEY_BI) + 10];
mir_snprintf(item, AIM_KEY_BI"%d", i);
return getWord(hContact, item, 0);
}
void CAimProto::setBuddyId(MCONTACT hContact, int i, unsigned short id)
{
char item[sizeof(AIM_KEY_BI) + 10];
mir_snprintf(item, AIM_KEY_BI"%d", i);
setWord(hContact, item, id);
}
int CAimProto::deleteBuddyId(MCONTACT hContact, int i)
{
char item[sizeof(AIM_KEY_BI) + 10];
mir_snprintf(item, AIM_KEY_BI"%d", i);
return delSetting(hContact, item);
}
unsigned short CAimProto::getGroupId(MCONTACT hContact, int i)
{
char item[sizeof(AIM_KEY_GI) + 10];
mir_snprintf(item, AIM_KEY_GI"%d", i);
return getWord(hContact, item, 0);
}
void CAimProto::setGroupId(MCONTACT hContact, int i, unsigned short id)
{
char item[sizeof(AIM_KEY_GI) + 10];
mir_snprintf(item, AIM_KEY_GI"%d", i);
setWord(hContact, item, id);
}
int CAimProto::deleteGroupId(MCONTACT hContact, int i)
{
char item[sizeof(AIM_KEY_GI) + 10];
mir_snprintf(item, AIM_KEY_GI"%d", i);
return delSetting(hContact, item);
}
/////////////////////////////////////////////////////////////////////////////////////////
int CAimProto::open_contact_file(const char*, const wchar_t* file, const char*, wchar_t* &path, bool contact_dir)
{
path = (wchar_t*)mir_alloc(MAX_PATH * sizeof(wchar_t));
int pos = mir_snwprintf(path, MAX_PATH, L"%s\\%S", VARSW(L"%miranda_userdata%"), m_szModuleName);
if (contact_dir)
pos += mir_snwprintf(path + pos, MAX_PATH - pos, L"\\%S", m_szModuleName);
if (_waccess(path, 0))
CreateDirectoryTreeW(path);
mir_snwprintf(path + pos, MAX_PATH - pos, L"\\%s", file);
int fid = _wopen(path, _O_CREAT | _O_RDWR | _O_BINARY, _S_IREAD);
if (fid < 0) {
wchar_t errmsg[512];
mir_snwprintf(errmsg, TranslateT("Failed to open file: %s %s"), path, __tcserror(nullptr));
ShowPopup((char*)errmsg, ERROR_POPUP | TCHAR_POPUP);
}
return fid;
}
void CAimProto::write_away_message(const char* sn, const char* msg, bool utf)
{
wchar_t* path;
int fid = open_contact_file(sn, L"away.html", "wb", path, 1);
if (fid >= 0) {
if (utf) _write(fid, "\xEF\xBB\xBF", 3);
char* s_msg = process_status_msg(msg, sn);
_write(fid, "
", 4);
_write(fid, sn, (unsigned)mir_strlen(sn));
_write(fid, "'s Away Message:
", 21);
_write(fid, s_msg, (unsigned)mir_strlen(s_msg));
_close(fid);
ShellExecute(nullptr, L"open", path, nullptr, nullptr, SW_SHOW);
mir_free(path);
mir_free(s_msg);
}
}
void CAimProto::write_profile(const char* sn, const char* msg, bool utf)
{
wchar_t* path;
int fid = open_contact_file(sn, L"profile.html", "wb", path, 1);
if (fid >= 0) {
if (utf) _write(fid, "\xEF\xBB\xBF", 3);
char* s_msg = process_status_msg(msg, sn);
_write(fid, "", 4);
_write(fid, sn, (unsigned)mir_strlen(sn));
_write(fid, "'s Profile:
", 16);
_write(fid, s_msg, (unsigned)mir_strlen(s_msg));
_close(fid);
ShellExecute(nullptr, L"open", path, nullptr, nullptr, SW_SHOW);
mir_free(path);
mir_free(s_msg);
}
}
unsigned long aim_oft_checksum_chunk(unsigned long dwChecksum, const unsigned char *buffer, int len)
{
unsigned long checksum = (dwChecksum >> 16) & 0xffff;
for (int i = 0; i < len; i++) {
unsigned val = buffer[i];
if ((i & 1) == 0)
val <<= 8;
if (checksum < val) ++val;
checksum -= val;
}
checksum = ((checksum & 0x0000ffff) + (checksum >> 16));
checksum = ((checksum & 0x0000ffff) + (checksum >> 16));
return checksum << 16;
}
unsigned int aim_oft_checksum_file(wchar_t *filename, unsigned __int64 size)
{
unsigned long checksum = 0xffff0000;
int fid = _wopen(filename, _O_RDONLY | _O_BINARY, _S_IREAD);
if (fid >= 0) {
unsigned __int64 sz = _filelengthi64(fid);
if (size > sz) size = sz;
while (size) {
unsigned char buffer[8912];
int bytes = (int)min(size, sizeof(buffer));
bytes = _read(fid, buffer, bytes);
size -= bytes;
checksum = aim_oft_checksum_chunk(checksum, buffer, bytes);
}
_close(fid);
}
return checksum;
}
char* long_ip_to_char_ip(unsigned long host, char* ip)
{
host = _htonl(host);
unsigned char* bytes = (unsigned char*)&host;
size_t buf_loc = 0;
for (int i = 0; i < 4; i++) {
char store[16];
_itoa(bytes[i], store, 10);
size_t len = mir_strlen(store);
memcpy(&ip[buf_loc], store, len);
buf_loc += len;
ip[buf_loc++] = '.';
}
ip[buf_loc - 1] = '\0';
return ip;
}
unsigned long char_ip_to_long_ip(char* ip)
{
unsigned char chost[4] = { 0 };
char *c = ip;
for (int i = 0; i < 4; ++i) {
chost[i] = (unsigned char)atoi(c);
c = strchr(c, '.');
if (c) ++c;
else break;
}
return *(unsigned long*)&chost;
}
unsigned short get_random(void)
{
unsigned short id;
Utils_GetRandom(&id, sizeof(id));
id &= 0x7fff;
return id;
}