aboutsummaryrefslogtreecommitdiff
path: root/src/msnprotocol.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/msnprotocol.c')
-rw-r--r--src/msnprotocol.c1692
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;
+}