#include "common.h" #include "server_con.h" #include "net.h" #include "arc4.h" #include "options.h" #include "nick_dialog.h" #include "formatting.h" #include #include #define SERVER_READ_BUFFER_SIZE (1024 * 32) /* TODO: obtain IPs of network interfaces from user's machine, instead of * hardcoding these values below (used in msim_compute_login_response). * This is not immediately * important because you can still connect and perform basic * functions of the protocol. There is also a high chance that the addreses * are RFC1918 private, so the servers couldn't do anything with them * anyways except make note of that fact. Probably important for any * kind of direct connection, or file transfer functionality. */ #define LOGIN_IP_LIST "\x00\x00\x00\x00\x05\x7f\x00\x00\x01\x00\x00\x00\x00\x0a\x00\x00\x40\xc0\xa8\x58\x01\xc0\xa8\x3c\x01" #define LOGIN_IP_LIST_LEN 25 #define NONCE_SIZE 0x20 int status = ID_STATUS_OFFLINE; bool myspace_server_running = false, server_stop = false; HANDLE server_connection = 0; int sesskey = 0, req_id = 1, my_uid = 0; int signon_status = ID_STATUS_ONLINE; char signon_status_msg[512] = {0}; int stat_mir_to_myspace(int mir_status) { switch(mir_status) { case ID_STATUS_INVISIBLE: return 0; case ID_STATUS_AWAY: return 5; case ID_STATUS_ONLINE: return 1; } return 0; } int stat_myspace_to_mir(int myspace_status) { switch(myspace_status) { case 0: return ID_STATUS_OFFLINE; case 1: return ID_STATUS_ONLINE; case 5: return ID_STATUS_AWAY; } return 0; } HANDLE FindContact(int uid) { char *proto; int cuid; HANDLE hContact = ( HANDLE )CallService( MS_DB_CONTACT_FINDFIRST, 0, 0 ); while ( hContact != NULL ) { proto = ( char* )CallService( MS_PROTO_GETCONTACTBASEPROTO, ( WPARAM )hContact,0 ); if ( proto && !strcmp( MODULE, proto)) { cuid = DBGetContactSettingDword(hContact, MODULE, "UID", (DWORD)0); if(cuid == uid) { return hContact; } } hContact = ( HANDLE )CallService( MS_DB_CONTACT_FINDNEXT,( WPARAM )hContact, 0 ); } return 0; } int LookupUID(int uid) { int ret; // lookup user ClientNetMessage msg_lookup; msg_lookup.add_int("persist", 1); msg_lookup.add_int("sesskey", sesskey); msg_lookup.add_int("uid", my_uid); msg_lookup.add_int("cmd", 1); msg_lookup.add_int("dsn", 4); msg_lookup.add_int("lid", 3); msg_lookup.add_int("rid", ret = req_id++); char body[512]; mir_snprintf(body, 512, "UserID=%d", uid); msg_lookup.add_string("body", body); SendMessage(msg_lookup); return ret; } HANDLE CreateContact(int uid, char *nick, char *email, bool add_buddy) { HANDLE hContact = (HANDLE)CallService(MS_DB_CONTACT_ADD, 0, 0); if(hContact) { DBWriteContactSettingDword(hContact, MODULE, "UID", uid); if(nick && strlen(nick)) { DBWriteContactSettingStringUtf(hContact, MODULE, "Nick", nick); } if(email && strlen(email)) { DBWriteContactSettingStringUtf(hContact, MODULE, "email", email); } if(add_buddy) { ClientNetMessage msg_add; msg_add.add_string("addbuddy", ""); msg_add.add_int("sesskey", sesskey); msg_add.add_int("newprofileid", uid); msg_add.add_string("reason", ""); SendMessage(msg_add); } /* ClientNetMessage msg_block; msg_block.add_string("blocklist", ""); msg_block.add_int("sesskey", sesskey); char idlist[1024]; mir_snprintf(idlist, 1024, "b-|%d|a+|%d", mpsr->uid, mpsr->uid); msg_block.add_string("idlist", idlist); SendMessage(msg_block); */ /* // update contact details? ClientNetMessage msg_persist; msg_persist.add_int("persist", 1); msg_persist.add_int("sesskey", sesskey); msg_persist.add_int("cmd", 514); msg_persist.add_int("dsn", 0); msg_persist.add_int("uid", DBGetContactSettingDword(0, MODULE, "UID", 0)); msg_persist.add_int("lid", 9); msg_persist.add_int("rid", req_id++); char body[4096]; mir_snprintf(body, 4096, "ContactID=%d\x1cGroupName=\x1cPosition=1000\x1cVisibility=2\x1cNickName=\x1cNameSelect=0", mpsr->uid); msg_persist.add_string("body", body); SendMessage(msg_persist); */ CallService(MS_PROTO_ADDTOCONTACT, (WPARAM)hContact, (LPARAM)MODULE); } return hContact; } void SendMessage(ClientNetMessage &msg) { char packet[4096]; int packet_size = msg.make_packet(packet, 4096); if(server_connection) Netlib_Send(server_connection, packet, packet_size, MSG_DUMPASTEXT); } void CALLBACK sttMainThreadStatusCallback( ULONG dwParam ) { if(status != dwParam) { int previous_status = status; status = dwParam; ProtoBroadcastAck(MODULE,NULL,ACKTYPE_STATUS,ACKRESULT_SUCCESS, (HANDLE)previous_status, status); } } int waitcallback(unsigned int *timeout) { //PUShowMessage("waitcallback", SM_NOTIFY); return server_stop ? 0 : 1; } int try_ports[9] = {1863, 6660,6661,6662,6665,6668,6669,80,0443}; void try_login(NetMessage &msg, HANDLE connection) { char nonce[NONCE_SIZE * 2 + 2], *nc1 = nonce, *nc2 = nonce + NONCE_SIZE; int size = NONCE_SIZE * 2 + 2; if(msg.get_data("nc", nonce, &size) && size == NONCE_SIZE * 2) { SHA1_INTERFACE sha1; mir_sha1_ctx sha1_ctx; mir_getSHA1I(&sha1); ARC4_INTERFACE arc4; mir_arc4_ctx arc4_ctx; mir_getARC4I(&arc4); char *ch_resp; mir_sha1_byte_t pw_hash[20]; int ch_resp_size; wchar_t wpw[256]; mir_sha1_byte_t key[20]; char email[256]; #ifdef _UNICODE _tcscpy(wpw, options.pw); WideCharToMultiByte(CP_UTF8, 0, options.email, -1, email, 256, 0, 0); #else strcpy(email, options.email); MultiByteToWideChar(code_page, 0, options.pw, -1, wpw, 256); #endif sha1.sha1_hash((mir_sha1_byte_t*)wpw, wcslen(wpw) * sizeof(wchar_t), pw_hash); sha1.sha1_init(&sha1_ctx); sha1.sha1_append(&sha1_ctx, (mir_sha1_byte_t*)pw_hash, 20); sha1.sha1_append(&sha1_ctx, (mir_sha1_byte_t*)nc2, NONCE_SIZE); sha1.sha1_finish(&sha1_ctx, key); arc4.arc4_init(&arc4_ctx, (char *)key, 0x10); ch_resp_size = NONCE_SIZE + strlen(email) + LOGIN_IP_LIST_LEN; ch_resp = new char[ch_resp_size]; memcpy(ch_resp, nc1, NONCE_SIZE); memcpy(ch_resp + NONCE_SIZE, email, strlen(email)); memcpy(ch_resp + NONCE_SIZE + strlen(email), LOGIN_IP_LIST, LOGIN_IP_LIST_LEN); arc4.arc4_crypt(&arc4_ctx, ch_resp, ch_resp, ch_resp_size); ClientNetMessage reply; reply.add_int("login2", 196610); reply.add_string("username", email); reply.add_data("response", ch_resp, ch_resp_size); reply.add_int("clientver", CLIENT_VER); reply.add_int("reconn", 0); reply.add_int("status", 100); reply.add_int("id", 1); delete[] ch_resp; SendMessage(reply); } else { PUShowMessage("Nonce format error", SM_NOTIFY); } } void ParseStatusMessage(HANDLE hContact, char *smsg) { DBWriteContactSettingWord(hContact, MODULE, "Status", stat_myspace_to_mir(smsg[3] - '0')); smsg += 8; char *end = strstr(smsg, "|"); if(end) *end = 0; DBWriteContactSettingStringUtf(hContact, MODULE, "StatusMsg", smsg); } void __cdecl ServerThreadFunc(void*) { NETLIBOPENCONNECTION conn_data = {0}; conn_data.cbSize = sizeof(NETLIBOPENCONNECTION); conn_data.flags = NLOCF_V2; conn_data.szHost = "im.myspace.akadns.net"; conn_data.wPort = DBGetContactSettingDword(0, MODULE, "LastPort", try_ports[0]); conn_data.timeout = 10; conn_data.waitcallback = waitcallback; int conn_stat = ID_STATUS_CONNECTING; QueueUserAPC(sttMainThreadStatusCallback, mainThread, conn_stat); myspace_server_running = true; char *recv_buffer = new char[SERVER_READ_BUFFER_SIZE + 1]; int bytes = 0; char mt[256]; int tries = 0; bool login = true; int bytes_read; HANDLE connection = 0; char *pbuff = recv_buffer, *end; int buffer_bytes = 0; recv_buffer[SERVER_READ_BUFFER_SIZE] = 0; while(!Miranda_Terminated() && !server_stop) { if(login) { if(connection) Netlib_CloseHandle(connection); QueueUserAPC(sttMainThreadStatusCallback, mainThread, conn_stat++); connection = (HANDLE)CallService(MS_NETLIB_OPENCONNECTION, (WPARAM)hNetlibUser, (LPARAM)&conn_data); if(!connection) { if(tries < 9) { conn_data.wPort = try_ports[tries++]; } else { ProtoBroadcastAck(MODULE, 0, ACKTYPE_LOGIN, ACKRESULT_FAILED, (HANDLE)0, (LPARAM)LOGINERR_NOSERVER); break; } } } bytes = Netlib_Recv(connection, (char *)pbuff, SERVER_READ_BUFFER_SIZE - (pbuff - recv_buffer), MSG_DUMPASTEXT); buffer_bytes += bytes; pbuff[bytes] = 0; if(bytes == 0) { //PUShowMessage("Connection closed", SM_NOTIFY); if(login && tries < 9) { conn_data.wPort = try_ports[tries++]; } else { if(login) ProtoBroadcastAck(MODULE, 0, ACKTYPE_LOGIN, ACKRESULT_FAILED, (HANDLE)0, (LPARAM)LOGINERR_NOSERVER); break; } } else if(bytes == SOCKET_ERROR) { //PUShowMessage("Socket ERROR", SM_NOTIFY); if(login && tries < 9) { conn_data.wPort = try_ports[tries++]; } else { if(login) ProtoBroadcastAck(MODULE, 0, ACKTYPE_LOGIN, ACKRESULT_FAILED, (HANDLE)0, (LPARAM)LOGINERR_NOSERVER); break; } } else { if(login) { QueueUserAPC(sttMainThreadStatusCallback, mainThread, conn_stat++); login = false; server_connection = connection; DBWriteContactSettingDword(0, MODULE, "LastPort", conn_data.wPort); } while((end = strstr(pbuff, "\\final\\")) != 0) { //mir_snprintf(mt, 256, "recvd %d bytes", bytes); //PUShowMessage(mt, SM_NOTIFY); end += 7; if(end - pbuff > bytes) { //PUShowMessage("More than one packet", SM_NOTIFY); } if(pbuff != recv_buffer) { //PUShowMessage("Not first packet", SM_NOTIFY); } NetMessage msg; msg.parse(pbuff, bytes); if(msg.exists(NMString("error"))) { char errmsg[256]; if(msg.get_string("errmsg", errmsg, 256)) { PUShowMessage(errmsg, SM_WARNING); int code = msg.get_int("err"); if(code == 6) ProtoBroadcastAck(MODULE, 0, ACKTYPE_LOGIN, ACKRESULT_FAILED, (HANDLE)0, (LPARAM)LOGINERR_OTHERLOCATION); if(code == 259) ProtoBroadcastAck(MODULE, 0, ACKTYPE_LOGIN, ACKRESULT_FAILED, (HANDLE)0, (LPARAM)LOGINERR_BADUSERID); if(code == 260) ProtoBroadcastAck(MODULE, 0, ACKTYPE_LOGIN, ACKRESULT_FAILED, (HANDLE)0, (LPARAM)LOGINERR_WRONGPASSWORD); } } else if(msg.get_int("lc") == 1) { QueueUserAPC(sttMainThreadStatusCallback, mainThread, conn_stat++); try_login(msg, server_connection); } else if(msg.get_int("lc") == 2) { sesskey = msg.get_int("sesskey"); DBWriteContactSettingDword(0, MODULE, "UID", my_uid = msg.get_int("userid")); char nick[256]; if(msg.get_string("uniquenick", nick, 256)) DBWriteContactSettingStringUtf(0, MODULE, "Nick", nick); QueueUserAPC(sttMainThreadStatusCallback, mainThread, signon_status); if(my_uid == msg.get_int("uniquenick")) { // need to pick a nick ShowNickDialog(); } // update our options on server Dictionary d; d.add_string("Sound", options.sound ? "True" : "False"); d.add_int("PrivacyMode", options.privacy_mode); d.add_string("ShowOnlyToList", options.show_only_to_list ? "True" : "False"); d.add_int("OfflineMessageMode", options.offline_message_mode); d.add_string("Headline", ""); d.add_int("Alert", 1); d.add_string("ShowAvatar", options.show_avatar ? "True" : "False"); d.add_string("IMName", options.im_name); ClientNetMessage cmsg; cmsg.add_int("persist", 1); cmsg.add_int("sesskey", sesskey); cmsg.add_int("uid", DBGetContactSettingDword(0, MODULE, "UID", 0)); cmsg.add_int("cmd", 514); cmsg.add_int("dsn", 1); cmsg.add_int("lid", 10); cmsg.add_int("rid", req_id++); cmsg.add_dict("body", d); SendMessage(cmsg); // set blocklist ClientNetMessage msg_block; msg_block.add_string("blocklist", ""); msg_block.add_int("sesskey", sesskey); msg_block.add_string("idlist", "w0|c0|a-|*|b-|*"); SendMessage(msg_block); // set status ClientNetMessage msg_status; msg_status.add_int("status", stat_mir_to_myspace(signon_status)); msg_status.add_int("sesskey", sesskey); msg_status.add_string("statstring", signon_status_msg); msg_status.add_string("locstring", ""); SendMessage(msg_status); /* // set login time? Dictionary ld; ld.add_int("ContactType", 1); ld.add_int("LastLogin", timestamp); ClientNetMessage msg_setinfo; msg_setinfo.add_string("setinfo", ""); msg_block.add_int("sesskey", sesskey); msg_setinfo.add_dict("info", ld); SendMessage(msg_setinfo); */ } else if(msg.get_int("bm") == 100) { // status message int uid = msg.get_int("f"); if(uid) { HANDLE hContact = FindContact(uid); if(!hContact) { hContact = CreateContact(uid, 0, 0, false); LookupUID(uid); } char smsg[1024]; if(msg.get_string("msg", smsg, 1024)) { ParseStatusMessage(hContact, smsg); } } } else if(msg.get_int("bm") == 121) { // action message int uid = msg.get_int("f"); if(uid) { HANDLE hContact = FindContact(uid); if(!hContact) { hContact = CreateContact(uid, 0, 0, false); LookupUID(uid); } char smsg[1024]; if(msg.get_string("msg", smsg, 1024)) { if(strcmp(smsg, "%typing%") == 0) CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hContact, (LPARAM)5); else if(strcmp(smsg, "%stoptyping%") == 0) { CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hContact, (LPARAM)0); } } } } else if(msg.get_int("bm") == 1) { // instant message int uid = msg.get_int("f"); if(uid) { HANDLE hContact = FindContact(uid); if(!hContact) { hContact = CreateContact(uid, 0, 0, true); LookupUID(uid); } char text[MAX_MESSAGE_SIZE]; if(msg.get_string("msg", text, MAX_MESSAGE_SIZE)) { strip_tags(text); decode_smileys(text); unentitize(text); PROTORECVEVENT pre = {0}; pre.flags = PREF_UTF; pre.szMessage = text; pre.timestamp = (DWORD)time(0); CCSDATA css = {0}; css.hContact = hContact; css.lParam = (LPARAM)⪯ css.szProtoService = PSR_MESSAGE; CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&css); } } } else if(msg.exists(NMString("persistr"))) { int cmd, dsn, lid, req; cmd = msg.get_int("cmd") & 255; dsn = msg.get_int("dsn"); lid = msg.get_int("lid"); req = msg.get_int("rid"); //mir_snprintf(mt, 256, "Peristr message: type is %d,%d,%d", cmd, dsn, lid); //PUShowMessage(mt, SM_NOTIFY); if(cmd == 1 && dsn == 5 && lid == 7) { // userinfo (lookup by username/email) Dictionary body = msg.get_dict("body"); char email[256], nick[256]; int uid = body.get_int("UserID"); if(nick_dialog) PostMessage(nick_dialog, WMU_NICKEXISTS, (WPARAM)(uid != 0), msg.get_int("rid")); if(uid != 0) { MYPROTOSEARCHRESULT mpsr = {sizeof(mpsr)}; if(body.get_string("UserName", nick, 256)) { unentitize(nick); mpsr.psr.nick = nick; } else if(body.get_string("DisplayName", nick, 256)) { unentitize(nick); mpsr.psr.nick = nick; } if(body.get_string("Email", email, 256)) mpsr.psr.email = email; mpsr.uid = uid; ProtoBroadcastAck(MODULE, 0, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)req, (LPARAM)&mpsr); } ProtoBroadcastAck(MODULE, 0, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)req, 0); } else if(cmd == 1 && dsn == 4 && lid == 3) { // userinfo(lookup by userid) Dictionary body = msg.get_dict("body"); char errmsg[256]; if(body.get_string("ErrorMessage", errmsg, 256)) { PUShowMessage(errmsg, SM_WARNING); ProtoBroadcastAck(MODULE, 0, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)req, 0); ProtoBroadcastAck(MODULE, 0, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)0, 0); } else { char email[256], nick[256]; int uid = body.get_int("UserID"); if(uid != 0) { MYPROTOSEARCHRESULT mpsr = {sizeof(mpsr)}; if(body.get_string("DisplayName", nick, 256)) { unentitize(nick); mpsr.psr.nick = nick; } else if(body.get_string("UserName", nick, 256)) { unentitize(nick); mpsr.psr.nick = nick; } if(body.get_string("Email", email, 256)) mpsr.psr.email = email; mpsr.uid = uid; HANDLE hContact = FindContact(uid); if(hContact) { if(mpsr.psr.nick) DBWriteContactSettingStringUtf(hContact, MODULE, "Nick", mpsr.psr.nick); if(mpsr.psr.email) DBWriteContactSettingStringUtf(hContact, MODULE, "email", mpsr.psr.nick); ProtoBroadcastAck(MODULE, hContact, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, (HANDLE)1, 0); } ProtoBroadcastAck(MODULE, 0, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)req, (LPARAM)&mpsr); } ProtoBroadcastAck(MODULE, 0, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)req, 0); } } else if(cmd == 2 && dsn == 0 && lid == 9) { Dictionary body = msg.get_dict("body"); char errmsg[256]; if(body.get_string("ErrorMessage", errmsg, 256)) { PUShowMessage(errmsg, SM_WARNING); } else { char nick[256]; int uid = body.get_int("ContactID"); if(uid != 0) { if(body.get_string("NickName", nick, 256)) { unentitize(nick); DBWriteContactSettingStringUtf(0, MODULE, "Nick", nick); } } } } else if(cmd == 2 && dsn == 9 && lid == 14) { Dictionary body = msg.get_dict("body"); char errmsg[256]; if(body.get_string("ErrorMessage", errmsg, 256)) { PUShowMessage(errmsg, SM_WARNING); } else { int code = body.get_int("Code"); if(nick_dialog) PostMessage(nick_dialog, WMU_CHANGEOK, (WPARAM)(code == 0), msg.get_int("rid")); } } } buffer_bytes -= (end - pbuff); memmove(recv_buffer, end, buffer_bytes); pbuff = recv_buffer; } } } if(server_connection) { ClientNetMessage msg; msg.add_string("logout", ""); msg.add_int("sesskey", sesskey); sesskey = 0; SendMessage(msg); Netlib_CloseHandle(server_connection); server_connection = 0; } delete recv_buffer; QueueUserAPC(sttMainThreadStatusCallback, mainThread, ID_STATUS_OFFLINE); myspace_server_running = false; SetAllOffline(); } void StartThread() { if(!myspace_server_running) { server_stop = false; mir_forkthread(ServerThreadFunc, 0); } } void StopThread() { if(myspace_server_running) { if(sesskey) { ClientNetMessage msg; msg.add_string("logout", ""); msg.add_int("sesskey", sesskey); SendMessage(msg); } else { if(server_connection) { Netlib_CloseHandle(server_connection); server_connection = 0; } } } } void SetServerStatus(int st) { if(st == ID_STATUS_OFFLINE) { StopThread(); } else { if(myspace_server_running && sesskey) { // set status ClientNetMessage msg_status; msg_status.add_int("status", stat_mir_to_myspace(st)); msg_status.add_int("sesskey", sesskey); msg_status.add_string("statstring", ""); msg_status.add_string("locstring", ""); SendMessage(msg_status); QueueUserAPC(sttMainThreadStatusCallback, mainThread, st); } else { signon_status = st; StartThread(); } } } void CALLBACK sttMainThreadStatusMessageCallback( ULONG dwParam ) { char *msg = (char *)dwParam; if(status != ID_STATUS_OFFLINE) { if(myspace_server_running && sesskey) { // set status ClientNetMessage msg_status; msg_status.add_int("status", stat_mir_to_myspace(status)); msg_status.add_int("sesskey", sesskey); msg_status.add_string("statstring", msg ? msg : ""); msg_status.add_string("locstring", ""); SendMessage(msg_status); } else { if(msg) strncpy(signon_status_msg, msg, 512); else signon_status_msg[0] = 0; } } if(msg) free(msg); } void SetServerStatusMessage(char *msg) { QueueUserAPC(sttMainThreadStatusMessageCallback, mainThread, (ULONG_PTR)(msg ? strdup(msg): 0)); }