/* * msnprotocol.c * * Low-level DS/NS protocol handling * * (c) 2002-2005 Thomas White * Part of TuxMessenger - GTK+-based MSN Messenger client * * This package 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; version 2 dated June, 1991. * * This package 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 package; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "mainwindow.h" #include "error.h" #include "options.h" #include "debug.h" #include "routines.h" #include "msnprotocol.h" #include "listcache.h" #include "twnauth.h" #include "contactlist.h" #include "msngenerics.h" #include "listcache.h" #include "sbsessions.h" #include "sbprotocol.h" #include "avatars.h" #include "msnp11chl.h" #include "xml.h" #include "addcontact.h" typedef enum { CSTATE_DISCONNECTED, /* Offline, disconnected, mainwindow in dispatch mode */ CSTATE_SIGNIN, /* Connected and in the process of signing in */ CSTATE_LIST, /* Receiving list data */ CSTATE_CONNECTED /* Fully logged in */ } CState; /* Part of something messy to avoid duplicating code */ typedef enum { NLN_NLN, NLN_ILN } nln_t; typedef enum { NSCONMODE_LINE, NSCONMODE_MSG, NSCONMODE_UBX, NSCONMODE_NOT, NSCONMODE_GCF_SHIELDS } NSConMode; static struct { int connect_lock; /* Prevent a second connection attempt from starting */ int socket; /* The DS or NS connection socket (only one exists at a time */ gint wcallback; /* GDK "writeable" callback for the socket */ gint rcallback; /* GDK "readable" callback for the socket */ unsigned int trid; /* TrID to be used for the next transaction sent */ CState connected; /* Indicator of progress into the login procedure */ int disconnect_expected; /* Suppress "Read error" message if the disconnection was anticipated */ gint disconnect_callback; /* Callback tag for forcing cleanup if disconnection fails. */ OnlineState status; /* Current online status */ int lst_num; /* Number of LSTs so far received */ int lst_num_max; /* Expected number of LSTs (from SYN) */ gint ping_callback; /* Callback tag for sending pings */ int protocol_version; /* MSNP version in use */ NSConMode conmode; /* What's being read at the moment? */ size_t expect_length; /* Amount of MSG/UBX etc expected. */ char *msg_source; /* Source of MSG/UBX etc */ /* Read/write buffers */ char *wbuffer; /* Buffer for outgoing data */ unsigned int wbufsize; /* Current size of the write buffer */ unsigned int woffset; /* Current offset into the write buffer */ char *rbuffer; /* Buffer for incoming data */ unsigned int rbufsize; /* Current size of the read buffer */ unsigned int roffset; /* Current offset into the read buffer */ } cstate = { 0, -1, 0, 0, 1, CSTATE_DISCONNECTED, 0, 0, ONLINE_FLN, 0, 0, 0, 12, NSCONMODE_LINE, 0, NULL, NULL, 0, 0, NULL, 0, 0 }; /* Free buffers, remove callbacks etc. */ static void msnprotocol_cleanup_internal() { close(cstate.socket); if ( cstate.rbuffer != NULL ) { free(cstate.rbuffer); } cstate.rbufsize = 0; cstate.rbuffer = NULL; cstate.roffset = 0; if ( cstate.wbuffer != NULL ) { free(cstate.wbuffer); } cstate.wbufsize = 0; cstate.wbuffer = NULL; cstate.woffset = 0; if ( cstate.rcallback != 0 ) { gdk_input_remove(cstate.rcallback); } cstate.rcallback = 0; if ( cstate.wcallback != 0 ) { gdk_input_remove(cstate.wcallback); } cstate.wcallback = 0; if ( cstate.disconnect_callback != 0 ) { gtk_timeout_remove(cstate.disconnect_callback); cstate.disconnect_callback = 0; } if ( cstate.ping_callback != 0 ) { gtk_timeout_remove(cstate.ping_callback); cstate.ping_callback = 0; } cstate.connect_lock = 0; cstate.trid = 1; cstate.disconnect_expected = 0; cstate.connected = CSTATE_DISCONNECTED; sbsessions_destroy_all(); messagewindow_disable_all(); contactlist_clear(); } /* Free buffers, remove callbacks, return main window to dispatch mode, etc. */ static void msnprotocol_cleanup() { msnprotocol_cleanup_internal(); mainwindow_setdispatch(); } /* If a disconnection hasn't happened ten seconds after requesting it, pull the plug. */ static gint msnprotocol_forcedisconnect() { int closeval; debug_print("NS: Waited too long for disconnection... pulling the plug.\n"); cstate.disconnect_callback = 0; closeval = close(cstate.socket); if ( closeval != 0 ) { /* Grrr.. now it won't close - not much we can do. */ debug_print("NS: Couldn't close socket after write error.\n"); } msnprotocol_cleanup(); return FALSE; } /* Write a chunk of the outgoing data queue to the socket. */ static void msnprotocol_writeable() { ssize_t wlen; int new_wbufsize; char *debug_string; unsigned int i; wlen = write(cstate.socket, cstate.wbuffer, cstate.wbufsize); debug_string = malloc(cstate.wbufsize+1); memcpy(debug_string, cstate.wbuffer, cstate.wbufsize); debug_string[cstate.wbufsize] = '\0'; for ( i=0; i 0 ) { /* wlen holds the number of bytes written. Sort the buffer out accordingly... */ memmove(cstate.wbuffer, cstate.wbuffer + wlen, cstate.wbufsize - wlen); new_wbufsize = cstate.wbufsize - wlen; assert(new_wbufsize >= 0); cstate.wbuffer = realloc(cstate.wbuffer, new_wbufsize); if ( new_wbufsize == 0 ) { gdk_input_remove(cstate.wcallback); cstate.wcallback = 0; cstate.wbuffer = NULL; } cstate.wbufsize = new_wbufsize; cstate.woffset -= wlen; } else { if ( wlen == -1 ) { /* Write error! :( */ if ( errno != EAGAIN ) { /* EAGAIN should never happen here */ /* Something bad happened */ int closeval; closeval = close(cstate.socket); if ( closeval != 0 ) { /* Grrr.. now it won't close - not much we can do. */ debug_print("NS: Couldn't close socket after write error.\n"); } error_report("Write error! Signed out."); msnprotocol_cleanup(); return; } } } } /* Queue a command for sending to the server, adding a TrID - returns the TrID used for this transaction. */ static int msnprotocol_sendtr_internal(char *instr, char *args, int newline, int send_trid) { char *trid_string = NULL; unsigned int len; len = strlen(instr); if ( send_trid ) { trid_string = malloc(8); assert(cstate.trid < 999999); /* Sanity check */ sprintf(trid_string, "%i", cstate.trid); len += 1; /* Space before the TrId */ len += strlen(trid_string); } if ( strlen(args) > 0 ) { len += strlen(args); len += 1; /* Space after the TrID */ } if ( newline ) { len += 2; } if ( cstate.wbufsize == 0 ) { /* No buffer space currently exists. Create it. */ assert(cstate.wbuffer == NULL); cstate.wbuffer = malloc(len); assert(cstate.wbuffer != NULL); cstate.wbufsize = len; cstate.woffset = 0; } if ( (cstate.wbufsize - cstate.woffset) < len ) { /* Write buffer isn't big enough. Make it bigger. */ cstate.wbuffer = realloc(cstate.wbuffer, len + cstate.woffset); assert(cstate.wbuffer != NULL); cstate.wbufsize = len + cstate.woffset; assert(cstate.wbufsize < 1024*1024); /* Stop the buffer from getting insane */ } /* Do the write (to memory). Deliberately verbose... */ memcpy(cstate.wbuffer + cstate.woffset, instr, strlen(instr)); cstate.woffset += strlen(instr); if ( send_trid ) { *(cstate.wbuffer + cstate.woffset) = ' '; cstate.woffset += 1; memcpy(cstate.wbuffer + cstate.woffset, trid_string, strlen(trid_string)); cstate.woffset += strlen(trid_string); free(trid_string); } if ( strlen(args) > 0 ) { *(cstate.wbuffer + cstate.woffset) = ' '; cstate.woffset += 1; memcpy(cstate.wbuffer + cstate.woffset, args, strlen(args)); cstate.woffset += strlen(args); } if ( newline ) { *(cstate.wbuffer + cstate.woffset) = '\r'; cstate.woffset += 1; *(cstate.wbuffer + cstate.woffset) = '\n'; cstate.woffset += 1; } /* Note the lack of a \0 terminator. It's done using records of the buffer data lengths. */ if ( cstate.wcallback == 0 ) { cstate.wcallback = gdk_input_add(cstate.socket, GDK_INPUT_WRITE, (GdkInputFunction) msnprotocol_writeable, NULL); } cstate.trid++; return cstate.trid-1; } /* Send a command to the server, adding a TrID and a newline */ static int msnprotocol_sendtr(char *instr, char *args) { return msnprotocol_sendtr_internal(instr, args, 1, 1); } static gint msnprotocol_sendping() { msnprotocol_sendtr_internal("PNG", "", 1, 0); cstate.ping_callback = 0; return FALSE; } /* Send a command to the server, adding a TrID but no newline */ static int msnprotocol_sendtr_nonewline(char *instr, char *args) { return msnprotocol_sendtr_internal(instr, args, 0, 1); } static void msnprotocol_handle_lst(char *line) { char *username = NULL; char *friendlyname = NULL; char *guid = NULL; char *list = NULL; unsigned int list_num; int finished = 0; int field_num = 1; int list_coming = 0; /* Step through the LST data and look for friendlyname, username and list number */ while ( !finished ) { char *field; int field_used = 0; field = routines_lindex(line, field_num); field_num++; if ( strlen(field) == 0 ) { finished = 1; } else { if ( strlen(field) > 2 ) { if ( (field[0] == 'F') && (field[1] == '=') ) { friendlyname = strdup(field+2); list_coming = 1; field_used = 1; } else if ( (field[0] == 'N') && (field[1] == '=') ) { username = strdup(field+2); list_coming = 1; field_used = 1; } else if ( (field[0] == 'C') && (field[1] == '=') ) { guid = strdup(field+2); list_coming = 1; field_used = 1; } } /* "list" is the first field without "X=" */ if ( list_coming && !field_used ) { list = strdup(field); finished = 1; /* Otherwise the next field will get used instead. */ } } free(field); } /* Need at least a username and list to continue */ if ( (username == NULL) || (list == NULL) ) { debug_print("NS: Didn't get enough information from LST: missing"); if ( username == NULL ) { debug_print(" username"); } else { free(username); } if ( list == NULL ) { debug_print(" list"); } else { free(list); } if ( friendlyname != NULL ) { free(friendlyname); /* Don't leak memory */ } debug_print("\n"); } else { list_num = atoi(list); free(list); if ( list_num > 31 ) { debug_print("NS: Warning: contact is on unrecognised list(s).\n"); } if ( list_num & 1 ) { contactlist_fldetails(CONTACT_SOURCE_LST, username, friendlyname, 0, NULL, ONLINE_FLN, guid); } if ( list_num & 2 ) { contactlist_aldetails(CONTACT_SOURCE_LST, username, friendlyname, ONLINE_FLN); } if ( list_num & 4 ) { contactlist_bldetails(CONTACT_SOURCE_LST, username, friendlyname, ONLINE_FLN); } if ( list_num & 8 ) { contactlist_rldetails(CONTACT_SOURCE_LST, username, friendlyname, ONLINE_FLN); } if ( list_num & 16 ) { contactlist_pldetails(CONTACT_SOURCE_LST, username, friendlyname, ONLINE_FLN); } if ( list_num == 8 ) { /* If contact is ONLY on the RL, add them to the PL as well. This should never happen with MSNP11+, but ensures that caching takes place properly. */ contactlist_pldetails(CONTACT_SOURCE_LST, username, friendlyname, ONLINE_FLN); } if ( (list_num == 8) || (list_num & 16) ) { /* On RL but not FL, AL or BL indicates new user. On PL also indicates new user. */ addcontact_added(username, friendlyname); } } } /* Despite the name, handles ILN and NLN */ static void msnprotocol_handle_nln(nln_t nln, char *line) { OnlineState new_status; char *username; char *friendlyname; char *features_string; int features; char *dpobject; char *status_string; int lindex_sub; /* Subtract one from all the list indices if this isn't ILN, since there's no TrID */ if ( nln == NLN_ILN ) { lindex_sub = 0; } else { lindex_sub = 1; } status_string = routines_lindex(line, 2-lindex_sub); new_status = msngenerics_decodestatus(status_string); assert(new_status != ONLINE_FLN); /* This would be handled elsewhere. */ assert(new_status != ONLINE_HDN); /* Nonsense */ free(status_string); username = routines_lindex(line, 3-lindex_sub); friendlyname = routines_lindex(line, 4-lindex_sub); features_string = routines_lindex(line, 5-lindex_sub); features = atoi(features_string); free(features_string); dpobject = routines_lindex(line, 6-lindex_sub); /* Pass NULL if there's no "dpobject" */ if ( strlen(dpobject) == 0 ) { free(dpobject); dpobject = NULL; } if ( (dpobject != NULL) && (strcmp(dpobject, "0") == 0) ) { free(dpobject); dpobject = NULL; } assert(strlen(username) > 0); assert(strlen(friendlyname) > 0); /* contactlist_fldetails won't overwrite the GUID with NULL. */ contactlist_fldetails(CONTACT_SOURCE_NLN, username, friendlyname, features, dpobject, new_status, NULL); /* contactlist_fldetails copies anything it's interested in */ free(username); free(friendlyname); if ( dpobject != NULL ) { free(dpobject); } } /* Enter main "Connected" mode. */ static void msnprotocol_doneconnect() { cstate.connected = CSTATE_CONNECTED; mainwindow_forcestatus(ONLINE_HDN); /* Start pinging. */ if ( cstate.ping_callback == 0 ) { cstate.ping_callback = gtk_timeout_add(50000, (GtkFunction)msnprotocol_sendping, NULL); } if ( cstate.protocol_version >= 11 ) { msnprotocol_sendtr("GCF", "Shields.xml"); } } /* Internally called to parse a line of text from the DS or NS */ static int msnprotocol_parseline(char *line) { char *instr; char *err_str; int err_num; char *trid_string; int trid; debug_print("NS: recv: '%s'\n", line); /* Prevent blank or short lines from being a problem */ if ( strlen(line) < 4 ) { return 0; } instr = malloc(4); strncpy(instr, line, 3); instr[3] = '\0'; if ( line[3] != ' ' ) { line[0] = ' '; line[1] = ' '; line[2] = ' '; /* Prevent bogus interpretations of things that aren't three letters */ } err_num = atoi(instr); err_str = malloc(4); assert(err_num < 1000); sprintf(err_str, "%i", err_num); if ( strcmp(err_str, instr) == 0 ) { error_report_server(err_num); if ( cstate.connected == CSTATE_SIGNIN ) { cstate.disconnect_expected = 1; } if ( err_num == 540 ) { cstate.disconnect_expected = 1; } } free(err_str); trid_string = routines_lindex(line, 1); trid = atoi(trid_string); free(trid_string); if ( strcmp("OUT", instr) == 0 ) { char *sit = routines_lindex(line, 1); if ( strcmp(sit, "OTH") == 0 ) { cstate.disconnect_expected = 1; error_report("Signed in somewhere else."); } free(sit); } if ( strcmp("VER", instr) == 0 ) { char *string; char *protocol; int got_cvr = FALSE; int highest_msnp = 0; int pos = 1; protocol = routines_lindex(line, pos++); while ( strlen(protocol) ) { if ( strncmp(protocol, "MSNP", 4) == 0 ) { int vnum = atoi(protocol + 4); if ( vnum > highest_msnp ) { highest_msnp = vnum; } } if ( strcmp(protocol, "CVR0") == 0 ) { got_cvr = TRUE; debug_print("Got CVR0.\n"); } free(protocol); protocol = routines_lindex(line, pos++); } debug_print("Negotiated protocol version %i\n", highest_msnp); if ( highest_msnp < 11 ) { debug_print("Too low. Bad luck.\n"); error_report("Couldn't negotiate protocol version.\n"); msnprotocol_cleanup_internal(); return -1; } cstate.protocol_version = highest_msnp; string = malloc(128); strcpy(string, "0x0409 winnt 5.1 i386 MSNMSGR 7.5.0311 msmsgs "); strncat(string, options_username(), 64); string[127] = '\0'; msnprotocol_sendtr("CVR", string); free(string); } if ( strcmp("CVR", instr) == 0 ) { char *string; string = malloc(128); strcpy(string, "TWN I "); strncat(string, options_username(), 64); string[127] = '\0'; msnprotocol_sendtr("USR", string); free(string); } if ( strcmp("XFR", instr) == 0 ) { char *mbuffer_type; mbuffer_type = routines_lindex(line, 2); if ( strcmp("NS", mbuffer_type) == 0 ) { /* got transferred to a different NS */ unsigned int new_port; char *new_hostname; char *target; target = routines_lindex(line, 3); new_hostname = routines_hostname(target); new_port = routines_port(target); debug_print("NS: Redirected to '%s' port '%i'\n", new_hostname, new_port); if ( new_port == 0 ) { new_port = DEFAULT_NS_PORT; debug_print("NS: Corrected to %i\n", new_port); } msnprotocol_cleanup_internal(); msnprotocol_connect(new_hostname, new_port); /* ...and now for some "tedious mucking-about in hyperspace"... */ free(target); free(new_hostname); free(mbuffer_type); free(instr); return -1; } else if ( strcmp("SB", mbuffer_type) == 0 ) { /* SB transfer - part of an SbSession negotiation. */ unsigned int port; char *hostname; char *target; char *cki_key; char *trid_char; char *authtype; target = routines_lindex(line, 3); hostname = routines_hostname(target); port = routines_port(target); debug_print("NS: SB session at '%s' port '%i'\n", hostname, port); if ( port == 0 ) { port = DEFAULT_SB_PORT; debug_print("NS: Corrected to %i\n", port); } authtype = routines_lindex(line, 4); if ( strcmp(authtype, "CKI") != 0 ) { /* How depressing */ debug_print("NS: Incompatible SB authentication method (%s)\n", authtype); free(authtype); free(instr); free(mbuffer_type); return 0; } free(authtype); cki_key = routines_lindex(line, 5); trid_char = routines_lindex(line, 1); sbprotocol_initiate_local(atoi(trid_char), hostname, port, cki_key); free(trid_char); free(cki_key); free(hostname); free(target); } free(mbuffer_type); } if ( strcmp("USR", instr) == 0 ) { if ( line[10] == 'S' ) { char *auth_string; char *auth_data; char *auth_ticket; auth_string = malloc(1024); strcpy(auth_string, "TWN S "); auth_data = routines_lindex(line, 4); auth_ticket = twnauth_ticket(auth_data); if ( auth_ticket != NULL ) { strncat(auth_string, auth_ticket, 1000); auth_string[1023] = '\0'; free(auth_ticket); msnprotocol_sendtr("USR", auth_string); } else { /* Deal with TWN auth failure */ msnprotocol_cleanup(); error_report("TWN authentication failed."); cstate.disconnect_expected = 1; free(instr); return -1; } free(auth_data); free(auth_string); } else { if ( ( line[6] == 'O' ) && ( line[7] == 'K' ) ) { /* Success! */ char *syn_max; cstate.connected = CSTATE_LIST; syn_max = listcache_loadtag(); msnprotocol_sendtr("SYN", syn_max); } } } if ( strcmp("LST", instr) == 0 ) { msnprotocol_handle_lst(line); cstate.lst_num++; if ( cstate.lst_num == cstate.lst_num_max ) { msnprotocol_doneconnect(); } } if ( strcmp("REM", instr) == 0 ) { char *list = routines_lindex(line, 2); char *usernameguid = routines_lindex(line, 3); /* Could be either! */ if ( strcmp(list, "FL") == 0 ) { contactlist_removecontactguid(list, usernameguid); } else { contactlist_removecontact(list, usernameguid); } free(list); free(usernameguid); } if ( strcmp("ADC", instr) == 0 ) { char *list; unsigned int field_num = 2; int finished = 0; char *friendlyname = NULL; char *username = NULL; char *guid = NULL; while ( !finished ) { char *field; field = routines_lindex(line, field_num); field_num++; if ( strlen(field) == 0 ) { finished = 1; } else { if ( strlen(field) > 2 ) { if ( (field[0] == 'F') && (field[1] == '=') ) { friendlyname = strdup(field+2); } else if ( (field[0] == 'N') && (field[1] == '=') ) { username = strdup(field+2); } else if ( (field[0] == 'C') && (field[1] == '=') ) { guid = strdup(field+2); } } } free(field); } list = routines_lindex(line, 2); if ( guid != NULL ) { if ( strlen(guid) == 0 ) { free(guid); guid = NULL; } } /* This is a new user if trid=0, otherwise it's a reponse to moving them PL->RL. */ if ( (strcmp(list, "RL") == 0) && (username != NULL) ) { /* This actually means "PL", unless the contact is already on the AL or the BL. This is logical. Promise. */ if ( (trid == 0) && (contactlist_isonlist("AL", username) || contactlist_isonlist("BL", username)) ) { contactlist_pldetails(CONTACT_SOURCE_ADC, username, friendlyname, ONLINE_FLN); addcontact_added(username, friendlyname); } else { contactlist_rldetails(CONTACT_SOURCE_ADC, username, friendlyname, ONLINE_FLN); } } /* Update contact list as appropriate. */ if ( strcmp(list, "FL") == 0 ) { contactlist_fldetails(CONTACT_SOURCE_ADC, username, friendlyname, 0, NULL, ONLINE_FLN, guid); } if ( strcmp(list, "AL") == 0 ) { contactlist_aldetails(CONTACT_SOURCE_ADC, username, friendlyname, ONLINE_FLN); } if ( strcmp(list, "BL") == 0 ) { contactlist_bldetails(CONTACT_SOURCE_ADC, username, friendlyname, ONLINE_FLN); } free(list); if ( username != NULL ) { free(username); } if ( friendlyname != NULL ) { free(friendlyname); } if ( guid != NULL ) { free(guid); } } if ( strcmp("NLN", instr) == 0 ) { msnprotocol_handle_nln(NLN_NLN, line); } if ( strcmp("ILN", instr) == 0 ) { msnprotocol_handle_nln(NLN_ILN, line); } if ( strcmp("FLN", instr) == 0 ) { char *username = routines_lindex(line, 1); /* NULL values won't overwrite anything since CONTACT_SOURCE_FLN has extremely low priority */ contactlist_fldetails(CONTACT_SOURCE_FLN, username, NULL, 0, NULL, ONLINE_FLN, NULL); messagewindow_notifyoffline(username); free(username); } if ( strcmp("CHL", instr) == 0 ) { char *response; char *challenge; challenge = routines_lindex(line, 2); if ( cstate.protocol_version < 11 ) { unsigned char *md5; unsigned int i; response = malloc(1024); strncpy(response, challenge, 64); response[1023] = '\0'; free(challenge); /* No reverse engineering of any other software took place to provide the CHL key. * This information is freely available on the web */ strcat(response, "Q1P7W2E4J9R8U3S5"); md5 = MD5(response, strlen(response), NULL); sprintf(response, "msmsgs@msnmsgr.com 32\r\n%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx", md5[0], md5[1], md5[2], md5[3], md5[4], md5[5], md5[6], md5[7], md5[8], md5[9], md5[10], md5[11], md5[12], md5[13], md5[14], md5[15]); for ( i=23; i 0 ) { char *prp_field2; /* Format #2: from PRP - only attempt if TrID is present. */ prp_field2 = routines_lindex(line, 2); if ( strcmp(prp_field2, "MFN") == 0 ) { char *new_friendlyname; new_friendlyname = routines_lindex(line, 3); mainwindow_setmfn(new_friendlyname); listcache_setmfn(new_friendlyname); free(new_friendlyname); } free(prp_field2); } free(prp_field); } if ( strcmp("SYN", instr) == 0 ) { char *newest_tag_1; char *newest_tag_2; char *full_tag; char *num_contacts; newest_tag_1 = routines_lindex(line, 2); newest_tag_2 = routines_lindex(line, 3); num_contacts = routines_lindex(line, 4); /* num_groups = routines_lindex(line, 5); */ assert(num_contacts != NULL); cstate.lst_num_max = atoi(num_contacts); free(num_contacts); if ( cstate.lst_num_max == 0 ) { /* This indicates an empty list OR an up-to-date list */ listcache_load(); msnprotocol_doneconnect(); } else { cstate.connected = CSTATE_LIST; } cstate.lst_num = 0; assert(newest_tag_1 != NULL); assert(newest_tag_2 != NULL); full_tag = malloc(strlen(newest_tag_1) + strlen(newest_tag_2) + 2); assert(full_tag != NULL); strcpy(full_tag, newest_tag_1); strcat(full_tag, " "); strcat(full_tag, newest_tag_2); listcache_settag(full_tag); free(full_tag); free(newest_tag_1); free(newest_tag_2); } if ( strcmp("QNG", instr) == 0 ) { /* The time interval given by the QNG is ignored to keep things under program control. Let me know if this causes any problems for you... */ if ( cstate.ping_callback == 0 ) { cstate.ping_callback = gtk_timeout_add(50000, (GtkFunction)msnprotocol_sendping, NULL); } } if ( strcmp("RNG", instr) == 0 ) { /* Yay! */ char *sessionid; char *callinguser; char *switchboardaddress; char *authchallenge; char *authtype; sessionid = routines_lindex(line, 1); callinguser = routines_lindex(line, 5); switchboardaddress = routines_lindex(line, 2); authchallenge = routines_lindex(line, 4); debug_print("NS: Received SB invitation from %s (ID=%s, Key=%s, Addr=%s)\n", callinguser, sessionid, authchallenge, switchboardaddress); /* Check if the friendly name is available for this user. If not, TL them. This could actually happen if the user is on another list but has no friendlyname. This situation doesn't matter. */ if ( contactlist_friendlyname(callinguser) == NULL ) { char *friendlyname = routines_lindex(line, 6); debug_print("NS: Creating TL record: '%s'/'%s'\n", callinguser, friendlyname); contactlist_tldetails(CONTACT_SOURCE_RNG, callinguser, friendlyname, ONLINE_NLN); free(friendlyname); } authtype = routines_lindex(line, 3); if ( strcmp(authtype, "CKI") != 0 ) { /* How depressing */ debug_print("NS: Incompatible SB authentication method (%s)\n", authtype); free(authtype); free(instr); return 0; } free(authtype); sbsessions_create_remote(callinguser, switchboardaddress, sessionid, authchallenge); free(callinguser); free(switchboardaddress); free(authchallenge); free(sessionid); } if ( strcmp("MSG", instr) == 0 ) { char *length; length = routines_lindex(line, 3); cstate.expect_length = atoi(length); free(length); if ( cstate.expect_length > 0 ) { cstate.msg_source = routines_lindex(line, 1); cstate.conmode = NSCONMODE_MSG; } else { debug_print("NS: Zero-sized MSG??\n"); } } if ( strcmp("UBX", instr) == 0 ) { char *length; length = routines_lindex(line, 2); cstate.expect_length = atoi(length); free(length); if ( cstate.expect_length > 0 ) { cstate.msg_source = routines_lindex(line, 1); cstate.conmode = NSCONMODE_UBX; } else { debug_print("NS: Zero-sized UBX??\n"); /* This seems to happen sometimes. */ } } if ( strcmp("NOT", instr) == 0 ) { char *length; length = routines_lindex(line, 1); cstate.expect_length = atoi(length); free(length); if ( cstate.expect_length > 0 ) { cstate.conmode = NSCONMODE_NOT; } else { debug_print("NS: Zero-sized NOT??\n"); } } if ( strcmp("GCF", instr) == 0 ) { char *length; char *file; length = routines_lindex(line, 3); file = routines_lindex(line, 2); cstate.expect_length = atoi(length); free(length); if ( cstate.expect_length > 0 ) { if ( strcmp(file, "Shields.xml") == 0 ) { cstate.conmode = NSCONMODE_GCF_SHIELDS; } else { debug_print("NS: Unrecognised GCF!\n"); } } else { debug_print("NS: Zero-sized GCF??\n"); } free(file); } if ( strcmp("CHG", instr) == 0 ) { OnlineState old_state = cstate.status; char *state = routines_lindex(line, 2); cstate.status = msngenerics_decodestatus(state); free(state); if ( (old_state == ONLINE_HDN) && (cstate.status != ONLINE_HDN) ) { messagewindow_enable_all(); } } free(instr); return 0; } static void msnprotocol_parsemsg(const char *msg, size_t msglen) { debug_print("NS: Got NS MSG from '%s' (%i bytes) '%s'\n", cstate.msg_source, msglen, msg); free(cstate.msg_source); } static void msnprotocol_parseubx(const char *msg, size_t msglen) { debug_print("NS: Got UBX data for '%s' (%i bytes) '%s'\n", cstate.msg_source, msglen, msg); contactlist_setubx(cstate.msg_source, msg, msglen); free(cstate.msg_source); } static void msnprotocol_parsenotification(const char *msg, size_t msglen) { debug_print("NS: Got notification (%i bytes) '%s'\n", msglen, msg); } static void msnprotocol_parseshields(const char *msg, size_t msglen) { debug_print("NS: Got Shields.xml file (%i bytes) '%s'\n", msglen, msg); } /* Internally called when data arrives from the DS/NS */ static void msnprotocol_readable() { ssize_t rlen; int no_string = 0; assert(cstate.rbufsize - cstate.roffset > 0); rlen = read(cstate.socket, cstate.rbuffer + cstate.roffset, cstate.rbufsize - cstate.roffset); if ( rlen > 0 ) { cstate.roffset += rlen; assert(cstate.roffset <= cstate.rbufsize); /* This would indicate a buffer overrun */ } /* First, check this isn't a disconnection */ if ( rlen <= 0 ) { int closeval; closeval = close(cstate.socket); if ( closeval != 0 ) { debug_print("NS: Couldn't close socket after read error.\n"); } /* A variety of ways to express this to the user... */ if ( cstate.connected == CSTATE_CONNECTED ) { if ( !cstate.disconnect_expected ) { error_report("Read error! Signed out."); listcache_save(); } /* Else ignore. */ } else if ( (cstate.connected == CSTATE_SIGNIN) || (cstate.connected == CSTATE_LIST) ) { if ( !cstate.disconnect_expected ) { error_report("Read error! Sign-in failed."); } } else { error_report("Read error in unrecognised connection state. This never happens :P"); debug_print("NS: Connection state %i\n", cstate.connected); } msnprotocol_cleanup(); return; } while ( (!no_string) && (cstate.roffset > 0) ) { int block_ready = 0; size_t i = 0; /* See if there's a full "block" in the buffer yet */ if ( cstate.conmode == NSCONMODE_LINE ) { for ( i=0; i= cstate.expect_length ) { i = cstate.expect_length - 2; block_ready = 1; } } else if ( cstate.conmode == NSCONMODE_UBX ) { if ( cstate.roffset >= cstate.expect_length ) { i = cstate.expect_length - 2; block_ready = 1; } } else if ( cstate.conmode == NSCONMODE_NOT ) { if ( cstate.roffset >= cstate.expect_length ) { i = cstate.expect_length - 2; block_ready = 1; } } else if ( cstate.conmode == NSCONMODE_GCF_SHIELDS ) { if ( cstate.roffset >= cstate.expect_length ) { i = cstate.expect_length - 2; block_ready = 1; } } else { /* "Never happens". */ debug_print("NS: Can't determine end of block for NsConMode %i!\n", cstate.conmode); } if ( block_ready == 1 ) { char *block_buffer = NULL; unsigned int new_rbufsize; unsigned int endbit_length; NSConMode next_mode = cstate.conmode; if ( cstate.conmode == NSCONMODE_LINE ) { assert(cstate.rbuffer[i] == '\r'); assert(cstate.rbuffer[i+1] == '\n'); } if ( cstate.conmode == NSCONMODE_LINE ) { block_buffer = malloc(i+1); memcpy(block_buffer, cstate.rbuffer, i); block_buffer[i] = '\0'; if ( msnprotocol_parseline(block_buffer) == -1 ) { /* Eeek. Redirected... */ free(block_buffer); return; } /* Check to see if sbprotocol_parseline changed the mode. If so, make sure it stays changed. */ if ( cstate.conmode != NSCONMODE_LINE ) { next_mode = cstate.conmode; } } else if ( cstate.conmode == NSCONMODE_MSG ) { block_buffer = malloc(i+3); /* Exactly the number of bytes we were told to expect, plus one for terminator. */ memcpy(block_buffer, cstate.rbuffer, i+2); /* Copy it in */ block_buffer[i+2] = '\0'; /* Terminate */ msnprotocol_parsemsg(block_buffer, i+2); next_mode = NSCONMODE_LINE; } else if ( cstate.conmode == NSCONMODE_UBX ) { block_buffer = malloc(i+3); /* Exactly the number of bytes we were told to expect, plus one for terminator. */ memcpy(block_buffer, cstate.rbuffer, i+2); /* Copy it in */ block_buffer[i+2] = '\0'; /* Terminate */ msnprotocol_parseubx(block_buffer, i+2); next_mode = NSCONMODE_LINE; } else if ( cstate.conmode == NSCONMODE_NOT ) { block_buffer = malloc(i+3); /* Exactly the number of bytes we were told to expect, plus one for terminator. */ memcpy(block_buffer, cstate.rbuffer, i+2); /* Copy it in */ block_buffer[i+2] = '\0'; /* Terminate */ msnprotocol_parsenotification(block_buffer, i+2); next_mode = NSCONMODE_LINE; } else if ( cstate.conmode == NSCONMODE_GCF_SHIELDS ) { block_buffer = malloc(i+3); /* Exactly the number of bytes we were told to expect, plus one for terminator. */ memcpy(block_buffer, cstate.rbuffer, i+2); /* Copy it in */ block_buffer[i+2] = '\0'; /* Terminate */ msnprotocol_parseshields(block_buffer, i+2); next_mode = NSCONMODE_LINE; } else { debug_print("NS: No handler for NsConMode %i !\n", cstate.conmode); } free(block_buffer); /* Now the block's been parsed, it should be forgotten about */ if ( cstate.conmode == NSCONMODE_LINE ) { assert(cstate.rbuffer[i+1] == '\n'); /* Should still be the case. Paranoid. */ } endbit_length = i+2; memmove(cstate.rbuffer, cstate.rbuffer + endbit_length, cstate.rbufsize - endbit_length); cstate.roffset = cstate.roffset - endbit_length; /* Subtract the number of bytes removed */ new_rbufsize = cstate.rbufsize - endbit_length; if ( new_rbufsize == 0 ) { new_rbufsize = 32; } cstate.rbuffer = realloc(cstate.rbuffer, new_rbufsize); cstate.rbufsize = new_rbufsize; cstate.conmode = next_mode; } else { if ( cstate.roffset == cstate.rbufsize ) { /* More buffer space is needed */ cstate.rbuffer = realloc(cstate.rbuffer, cstate.rbufsize + 32); cstate.rbufsize = cstate.rbufsize + 32; /* The new space gets used at the next read, shortly... */ } no_string = 1; } } } /* Return nonzero if we're signed in and have the lists */ int msnprotocol_signedin() { if ( cstate.connected == CSTATE_CONNECTED ) { return 1; } return 0; } /* Return nonzero if we're completely disconnected */ int msnprotocol_disconnected() { if ( cstate.connected == CSTATE_DISCONNECTED ) { return 1; } return 0; } /* Internally called when the socket is connected */ static void msnprotocol_ready() { /* Remove the "writeable" callback, and add a "readable" callback */ gdk_input_remove(cstate.wcallback); cstate.wcallback = 0; cstate.rcallback = gdk_input_add(cstate.socket, GDK_INPUT_READ, (GdkInputFunction) msnprotocol_readable, NULL); /* Begin the handshake */ msnprotocol_sendtr("VER", "MSNP12 MSNP11 CVR0"); } /* Called from src/mainwindow.c to start the sign-in process */ void msnprotocol_connect(const char *hostname, unsigned short int port) { struct sockaddr_in sa_desc; struct hostent *server; unsigned int sockopts; int conn_error; /* Empty values means use the configured value. Other values are provided on an NS redirect */ if ( strcmp(hostname, "") == 0 ) { hostname = options_hostname(); } if ( port == 0 ) { port = options_port(); } assert(hostname != NULL); assert(port > 0); /* Check if a connection attempt is already in progress. Abort if it is */ if ( cstate.connect_lock != 0 ) { debug_print("NS: Aborting connection: locked.\n"); return; } cstate.connect_lock = 1; /* Create and configure the socket (but don't connect it yet) */ cstate.socket = socket(PF_INET, SOCK_STREAM, 0); if ( cstate.socket == -1 ) { error_report("Couldn't create socket"); msnprotocol_cleanup(); return; } sockopts = fcntl(cstate.socket, F_GETFL); fcntl(cstate.socket, F_SETFL, sockopts | O_NONBLOCK); /* Resolve the server name */ server = gethostbyname(hostname); if ( server == NULL ) { error_report("No such host"); msnprotocol_cleanup(); return; } memset(&sa_desc, 0, sizeof(sa_desc)); sa_desc.sin_family = AF_INET; memcpy(&sa_desc.sin_addr.s_addr, server->h_addr, server->h_length); sa_desc.sin_port = htons(port); cstate.rbuffer = malloc(256); assert(cstate.rbuffer != NULL); cstate.rbufsize = 256; /* wbuffer starts at NULL and gets created when data is written */ conn_error = connect(cstate.socket, (struct sockaddr *)&sa_desc, sizeof(sa_desc)); if ( (conn_error < 0) && (errno != EINPROGRESS) ) { debug_print("NS: Couldn't connect to server - %i\n", errno); error_report("Couldn't connect to server"); msnprotocol_cleanup(); return; } else { cstate.wcallback = gdk_input_add(cstate.socket, GDK_INPUT_WRITE, (GdkInputFunction) msnprotocol_ready, NULL); } cstate.connected = CSTATE_SIGNIN; } /* Called by src/statusmenu.c to change online status. * Passes "newstatus" gets cast into a gpointer to do this, which is a bit nasty */ void msnprotocol_setstatus(OnlineState newstatus) { char *mode; char *capabilities; char *dpobj; char *string; unsigned int clientid; /* Don't change status too early (MSN server bug) unless signing out */ if ( (cstate.connected != CSTATE_CONNECTED) && (newstatus != ONLINE_FLN) ) { debug_print("NS: Not ready for status change - aborting.\n"); return; } debug_print("NS: Setting new status: %i\n", newstatus); mode = "NLN"; /* Fallback */ switch ( newstatus ) { case ONLINE_NLN : mode = "NLN"; break; case ONLINE_AWY : mode = "AWY"; break; case ONLINE_BSY : mode = "BSY"; break; case ONLINE_BRB : mode = "BRB"; break; case ONLINE_PHN : mode = "PHN"; break; case ONLINE_LUN : mode = "LUN"; break; case ONLINE_HDN : { mode = "HDN"; /* sbsessions_destroy_all(); messagewindow_disable_all(); */ break; } /* The following two shouldn't happen: keep the compiler happy. */ case ONLINE_ERR : mode = "NLN"; break; case ONLINE_IDL : mode = "AWY"; break; case ONLINE_FLN : { msnprotocol_sendtr("OUT", ""); if ( cstate.connected == CSTATE_CONNECTED ) { listcache_save(); } cstate.disconnect_expected = 1; if ( cstate.disconnect_callback == 0 ) { cstate.disconnect_callback = gtk_timeout_add(10000, (GtkFunction)msnprotocol_forcedisconnect, NULL); } return; } } clientid = 0x50000000 + (1<<5) + (1<<3) + (1<<2); /* MSNC5, Multipacketing (bit 5), Ink (bits 2 and 3) */ capabilities = malloc(12); sprintf(capabilities, "%i", clientid); dpobj = avatars_localobject(); string = malloc(strlen(mode) + strlen(capabilities) + strlen(dpobj) + 3); strcpy(string, mode); strcat(string, " "); strcat(string, capabilities); strcat(string, " "); strcat(string, dpobj); free(dpobj); msnprotocol_sendtr("CHG", string); free(string); free(capabilities); messagewindow_picturekick(NULL); } /* No information from the session record is needed at this stage. */ int msnprotocol_initiatesb() { return msnprotocol_sendtr("XFR", "SB"); } void msnprotocol_requestsignout() { msnprotocol_setstatus(ONLINE_FLN); } void msnprotocol_setmfn(const char *new_mfn) { char *mfn_string; char *mfn_code; mfn_code = routines_urlencode(new_mfn); mfn_string = malloc(strlen(mfn_code)+5); strcpy(mfn_string, "MFN "); strcat(mfn_string, mfn_code); msnprotocol_sendtr("PRP", mfn_string); free(mfn_string); } void msnprotocol_setcsm(const char *new_csm) { const char *template; char *uuxblock; char *sendage; if ( cstate.protocol_version >= 11 ) { template = ""; uuxblock = xml_setblock(template, strlen(template), "Data", "PSM", new_csm); sendage = malloc(strlen(uuxblock)+8); assert(strlen(uuxblock)<=99999); /* 5 chars max. */ sprintf(sendage, "%i\r\n%s", strlen(uuxblock), uuxblock); msnprotocol_sendtr_nonewline("UUX", sendage); free(uuxblock); free(sendage); } listcache_setcsm(new_csm); } void msnprotocol_adduser(const char *username, const char *list) { char *line = malloc(strlen(list) + 6 + strlen(username)); strcpy(line, list); strcat(line, " N="); strcat(line, username); msnprotocol_sendtr("ADC", line); free(line); } void msnprotocol_adduserfriendly(const char *username, const char *friendlyname, const char *list) { char *line = malloc(strlen(list) + 9 + strlen(username) + strlen(friendlyname)); strcpy(line, list); strcat(line, " N="); strcat(line, username); strcat(line, " F="); strcat(line, friendlyname); msnprotocol_sendtr("ADC", line); free(line); } void msnprotocol_remuser(const char *username, const char *list) { char *line = malloc(strlen(list) + 2 + strlen(username)); strcpy(line, list); strcat(line, " "); strcat(line, username); msnprotocol_sendtr("REM", line); free(line); } OnlineState msnprotocol_status() { return cstate.status; }