diff options
Diffstat (limited to 'src/msnprotocol.c')
-rw-r--r-- | src/msnprotocol.c | 1692 |
1 files changed, 1692 insertions, 0 deletions
diff --git a/src/msnprotocol.c b/src/msnprotocol.c new file mode 100644 index 0000000..fe7635e --- /dev/null +++ b/src/msnprotocol.c @@ -0,0 +1,1692 @@ +/* + * msnprotocol.c + * + * Low-level DS/NS protocol handling + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * 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 <config.h> +#endif + +#include <stdlib.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <unistd.h> +#include <fcntl.h> +#include <netdb.h> +#include <errno.h> +#include <gdk/gdk.h> +#include <assert.h> +#include <string.h> +#include <openssl/md5.h> + +#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<cstate.wbufsize; i++ ) { + if ( debug_string[i] == '\r' ) { + debug_string[i] = 'r'; + } + if ( debug_string[i] == '\n' ) { + debug_string[i] = 'n'; + } + } + debug_print("NS: send: '%s'\n", debug_string); + free(debug_string); + + if ( wlen > 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<strlen(response); i++ ) { + if ( response[i]==' ' ) { + response[i]='0'; + } + } + + } else { + + char *hex; + hex = msnp11chl_response(challenge); + response = malloc(strlen(hex)+strlen(CLIENT_ID)+7); + sprintf(response, CLIENT_ID" 32\n%s", hex); + free(hex); + + } + + free(challenge); + msnprotocol_sendtr_nonewline("QRY", response); + free(response); + + } + + if ( strcmp("PRP", instr) == 0 ) { + + char *prp_field; + int check; + + /* Format #1: from SYN */ + prp_field = routines_lindex(line, 1); + if ( strcmp(prp_field, "MFN") == 0 ) { + + char *new_friendlyname; + new_friendlyname = routines_lindex(line, 2); + mainwindow_setmfn(new_friendlyname); + listcache_setmfn(new_friendlyname); + free(new_friendlyname); + + } + + check = atoi(prp_field); + if ( check > 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) || (rlen == -1) ) { + + 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.roffset-1; i++ ) { /* Means the last value looked at is roffset-2 */ + if ( (cstate.rbuffer[i] == '\r') && (cstate.rbuffer[i+1] == '\n') ) { + block_ready = 1; + break; + } + } + + } else if ( cstate.conmode == NSCONMODE_MSG ) { + + if ( cstate.roffset >= 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 = "<Data><PSM></PSM><CurrentMedia></CurrentMedia></Data>"; + 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; +} |