aboutsummaryrefslogtreecommitdiff
path: root/src/sbprotocol.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sbprotocol.c')
-rw-r--r--src/sbprotocol.c1271
1 files changed, 1271 insertions, 0 deletions
diff --git a/src/sbprotocol.c b/src/sbprotocol.c
new file mode 100644
index 0000000..1d58ac7
--- /dev/null
+++ b/src/sbprotocol.c
@@ -0,0 +1,1271 @@
+/*
+ * sbprotocol.c
+ *
+ * SB protocol handling
+ *
+ * (c) 2002-2006 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 <assert.h>
+#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 <string.h>
+
+#include "sbsessions.h"
+#include "msnprotocol.h"
+#include "error.h"
+#include "debug.h"
+#include "options.h"
+#include "routines.h"
+#include "messagewindow.h"
+#include "mime.h"
+#include "contactlist.h"
+#include "main.h"
+#include "msnp2p.h"
+#include "msninvite.h"
+#include "ink.h"
+#include "sbprotocol.h"
+#include "msngenerics.h"
+
+/* Maximum allowed message size, and chunk size to split large messages into.
+ * MAX_MSG_SIZE needs to be sufficiently bigger than MULTIPACKET_SIZE to
+ * fit the headers in, otherwise you get an infinite loop. */
+#define MULTIPACKET_SIZE 1024
+#define MAX_MSG_SIZE 1280
+
+/* Define this if you want to see what gets written to the socket. */
+#define SBSEND_DEBUG 1
+/* Define if you want to see SB write buffer operations. */
+#undef SBWRITE_DEBUG
+
+/* Some prototypes */
+static void sbprotocol_sendmsg(SbSession *session, char *contenttype, char *extra_headers, char *body, ssize_t length);
+
+/* Called when a socket gets disconnected. Session record vanishes. */
+static void sbprotocol_disconnected(SbSession *session) {
+ debug_print("SB %8p: Disconnected - deleting.\n", session);
+ sbsessions_destroy(session);
+}
+
+/* Write a chunk of the outgoing data queue to an SB socket. */
+static void sbprotocol_writeable(SbSession *session) {
+
+ ssize_t wlen;
+ unsigned int new_wbufsize;
+#ifdef SBSEND_DEBUG
+ char *debug_string;
+ unsigned int i;
+#endif /* SBSEND_DEBUG */
+
+ wlen = write(session->socket, session->wbuffer, session->wbufsize);
+
+#ifdef SBSEND_DEBUG
+ debug_string = malloc(session->wbufsize+1);
+ memcpy(debug_string, session->wbuffer, session->wbufsize);
+ debug_string[wlen] = '\0';
+ for ( i=0; i<session->wbufsize; i++ ) {
+ if ( debug_string[i] == '\r' ) {
+ debug_string[i] = 'r';
+ }
+ if ( debug_string[i] == '\n' ) {
+ debug_string[i] = 'n';
+ }
+ }
+ debug_print("SB %8p: Send:'%s'\n", session, debug_string);
+ free(debug_string);
+#endif /* SBSEND_DEBUG */
+
+ if ( wlen > 0 ) {
+
+#ifdef SBWRITE_DEBUG
+ debug_print("SB %8p: Wrote %i bytes to socket.\n", session, wlen);
+#endif /* SBWRITE_DEBUG */
+
+ /* wlen holds the number of bytes written. Sort the buffer out accordingly... */
+ memmove(session->wbuffer, session->wbuffer + wlen, session->wbufsize - wlen);
+ new_wbufsize = session->wbufsize - wlen;
+
+ session->wbuffer = realloc(session->wbuffer, new_wbufsize);
+ if ( new_wbufsize == 0 ) {
+
+#ifdef SBWRITE_DEBUG
+ debug_print("SB %8p: SB write buffer empty: destroying.\n", session);
+#endif /* SBWRITE_DEBUG */
+ gdk_input_remove(session->wcallback);
+ session->wcallback = 0;
+ session->wbuffer = NULL;
+
+ }
+ session->wbufsize = new_wbufsize;
+ session->woffset -= wlen;
+
+ } else {
+
+ if ( wlen == -1 ) {
+
+ /* Write error! :( */
+ if ( errno != EAGAIN ) { /* EAGAIN should never happen here */
+
+ /* Something bad happened */
+ int closeval;
+ closeval = close(session->socket);
+ if ( closeval != 0 ) {
+ /* Grrr.. now it won't close - not much we can do. */
+ debug_print("SB %8p: Couldn't close socket after write error.\n", session);
+ }
+ sbprotocol_disconnected(session);
+ return;
+
+ }
+
+ }
+
+ }
+
+}
+
+/* Queue data for sending to the server, adding a TrID - returns the TrID used for this transaction. */
+static int sbprotocol_sendtr_internal(SbSession *session, const char *instr, const char *args, size_t arglen, int newline) {
+
+ char *trid_string;
+ size_t len;
+
+ len = strlen(instr);
+ trid_string = malloc(8);
+ assert(session->trid < 999999); /* Sanity check */
+ sprintf(trid_string, "%i", session->trid);
+ len += 1; /* Space before the TrId */
+ len += strlen(trid_string);
+ if ( arglen > 0 ) {
+ len += arglen;
+ len += 1; /* Space after the TrID */
+ }
+ if ( newline ) {
+ len += 2;
+ }
+
+ if ( session->wbufsize == 0 ) {
+
+ /* No buffer space currently exists. Create it. */
+#ifdef SBWRITE_DEBUG
+ debug_print("SB %8p: Creating SB write buffer: %i bytes.\n", session, len);
+#endif /* SBWRITE_DEBUG */
+ assert(session->wbuffer == NULL);
+ session->wbuffer = malloc(len);
+ assert(session->wbuffer != NULL);
+ session->wbufsize = len;
+ session->woffset = 0;
+
+ }
+ if ( (session->wbufsize - session->woffset) < len ) {
+
+ /* Write buffer isn't big enough. Make it bigger. */
+#ifdef SBWRITE_DEBUG
+ debug_print("SB %8p: Extending SB write buffer. Old size=%i (offset %i), Need %i, New size=%i\n", session, session->wbufsize, session->woffset, len, len+session->woffset);
+#endif /* SBWRITE_DEBUG */
+ session->wbuffer = realloc(session->wbuffer, len + session->woffset);
+ assert(session->wbuffer != NULL);
+ session->wbufsize = len + session->woffset;
+ assert(session->wbufsize < 1024*1024); /* Stop the buffer from getting insane */
+
+ }
+
+ /* Do the write (to memory). Deliberately verbose... */
+ memcpy(session->wbuffer + session->woffset, instr, strlen(instr));
+ session->woffset += strlen(instr);
+
+ *(char *)(session->wbuffer + session->woffset) = ' ';
+ session->woffset += 1;
+
+ memcpy(session->wbuffer + session->woffset, trid_string, strlen(trid_string));
+ session->woffset += strlen(trid_string);
+ free(trid_string);
+
+ if ( arglen > 0 ) {
+
+ *(session->wbuffer + session->woffset) = ' ';
+ session->woffset += 1;
+ memcpy(session->wbuffer + session->woffset, args, arglen);
+ session->woffset += arglen;
+
+ }
+
+ if ( newline ) {
+
+ *(session->wbuffer + session->woffset) = '\r';
+ session->woffset += 1;
+ *(session->wbuffer + session->woffset) = '\n';
+ session->woffset += 1;
+
+ }
+ /* Note the lack of a \0 terminator. It's done using records of the buffer data lengths. */
+
+ if ( session->wcallback == 0 ) {
+ session->wcallback = gdk_input_add(session->socket, GDK_INPUT_WRITE, (GdkInputFunction)sbprotocol_writeable, session);
+ }
+
+ session->trid++;
+ return session->trid-1;
+
+}
+
+/* Send a command to the switchboard, adding a TrID and a newline */
+static int sbprotocol_sendtr(SbSession *session, const char *instr, const char *args) {
+ return sbprotocol_sendtr_internal(session, instr, args, strlen(args), 1);
+}
+
+/* Send a command to the switchboard, adding a TrID but no newline. */
+int sbprotocol_sendtr_nonewline(SbSession *session, const char *instr, const char *args, ssize_t length) {
+ return sbprotocol_sendtr_internal(session, instr, args, length, 0);
+}
+
+void sbprotocol_leavesession(SbSession *session) {
+ sbprotocol_sendtr(session, "OUT", "");
+}
+
+/* Split a large message up and throw it back at sbprotocol_sendmsg */
+static int sbprotocol_sendmultipacket(SbSession *session, char *contenttype, char *extra_headers, char *body, ssize_t length) {
+
+ char *messageid = routines_guid();
+ int parts = (length / MULTIPACKET_SIZE) + 1;
+ int i;
+ char *new_extra_headers;
+ char *packet = malloc(MULTIPACKET_SIZE);
+
+ debug_print("SB %8p: Sending multipacket message (%i parts)\n", session, parts);
+ if ( parts > 9999 ) {
+ debug_print("SB %8p: Silly number of packets - aborting.\n", session);
+ return 0;
+ }
+
+ /* Message-ID: Chunks: Chunk: */
+ new_extra_headers = malloc(strlen(extra_headers) +12+40 +7+4 +6+4 +1);
+
+ for ( i=0; i<parts; i++ ) {
+
+ ssize_t size;
+
+ strcpy(new_extra_headers, extra_headers);
+ strcat(new_extra_headers, "\r\nMessage-ID: ");
+ strcat(new_extra_headers, messageid);
+
+ if ( i == 0 ) {
+
+ /* First packet. Modify header to include number of chunks... */
+
+ char *chunks;
+
+ strcat(new_extra_headers, "\r\nChunks: ");
+ chunks = malloc(5);
+ sprintf(chunks, "%i", parts);
+ strcat(new_extra_headers, chunks);
+ free(chunks);
+
+ } else {
+
+ /* Not first packet: include chunk ID */
+
+ char *chunk;
+
+ strcat(new_extra_headers, "\r\nChunk: ");
+ chunk = malloc(5);
+ sprintf(chunk, "%i", i);
+ strcat(new_extra_headers, chunk);
+ free(chunk);
+
+ }
+
+ size = MULTIPACKET_SIZE;
+ if ( i == parts-1 ) {
+ /* Last message */
+ size = length % MULTIPACKET_SIZE;
+ }
+ memcpy(packet, body+(MULTIPACKET_SIZE*i), size);
+
+ debug_print("SB %8p: Sending packet %i of %i (bytes %i-%i of %i)\n", session, i+1, parts, MULTIPACKET_SIZE*i, size+(MULTIPACKET_SIZE*i), length);
+ sbprotocol_sendmsg(session, contenttype, new_extra_headers, packet, size);
+
+ }
+
+ return 1;
+
+}
+
+static void sbprotocol_flushcache(SbSession *session) {
+
+ CachedMsg *cached = session->cached;
+ CachedMsg *next;
+
+ while ( cached != NULL ) {
+
+ debug_print("Sending cached message %8p.\n", cached);
+ sbprotocol_sendtr_nonewline(session, "MSG", cached->message, cached->length);
+ free(cached->message);
+ next = cached->next;
+ free(cached);
+ session->cached = next;
+ cached = next;
+
+ }
+
+}
+
+static void sbprotocol_sendmsg(SbSession *session, char *contenttype, char *extra_headers, char *body, ssize_t mlength) {
+
+ char *serversend;
+ char *length_text;
+ size_t length;
+
+ length = mlength;
+ if ( extra_headers != NULL ) {
+ length += (strlen(extra_headers) + 2);
+ }
+ length += (35 + strlen(contenttype) + 2);
+
+ /* Check if this needs to be multipacketed or not... */
+ if ( length > MAX_MSG_SIZE ) {
+ sbprotocol_sendmultipacket(session, contenttype, extra_headers, body, mlength);
+ return;
+ }
+
+ serversend = malloc(length+15);
+
+ strcpy(serversend, "N ");
+ length_text = malloc(16);
+ sprintf(length_text, "%i", length);
+ strncat(serversend, length_text, 15);
+
+ strcat(serversend, "\r\nMIME-Version: 1.0\r\nContent-Type: ");
+ strcat(serversend, contenttype);
+ strcat(serversend, "\r\n");
+ if ( extra_headers != NULL ) {
+ strcat(serversend, extra_headers);
+ strcat(serversend, "\r\n");
+ }
+ strcat(serversend, "\r\n");
+ memcpy(serversend+strlen(serversend), body, mlength);
+
+ if ( session->ready ) {
+ sbprotocol_sendtr_nonewline(session, "MSG", serversend, length+strlen(length_text)+4);
+ } else {
+
+ CachedMsg *cached = malloc(sizeof(CachedMsg));
+
+ cached->message = malloc(length+strlen(length_text)+5);
+ memcpy(cached->message, serversend, length+strlen(length_text)+5);
+
+ cached->message[length+strlen(length_text)+4] = '\0'; /* NULL-terminate! */
+
+ cached->length = length+strlen(length_text)+4;
+
+ if ( session->cached == NULL ) {
+ session->cached = cached;
+ } else {
+ CachedMsg *find = session->cached;
+ while ( find->next != NULL ) {
+ find = find->next;
+ }
+ find->next = cached;
+ }
+
+ cached->next = NULL;
+
+ debug_print("SB %8p: Cached message for sending later (%8p).\n", session, cached);
+
+ }
+
+ free(length_text);
+ free(serversend);
+
+}
+
+void sbprotocol_sendcaps(SbSession *session) {
+
+ char *caps_body;
+
+ caps_body = malloc(64 + strlen(tuxmessenger_versionstring()));
+ strcpy(caps_body, "Client-Name: ");
+ strcat(caps_body, tuxmessenger_versionstring());
+ strcat(caps_body, "\r\nChat-Logging: N"); /* Logging not implemented yet. */
+
+ sbprotocol_sendmsg(session, "text/x-clientcaps", NULL, caps_body, strlen(caps_body));
+ free(caps_body);
+
+}
+
+void sbprotocol_invite(SbSession *session, const char *username) {
+
+ sbprotocol_sendtr(session, "CAL", username);
+
+}
+
+static void sbprotocol_parseline(SbSession *session, char *line) {
+
+ char *token;
+
+ debug_print("SB %8p: Recv '%s'\n", session, line);
+ assert(session != NULL);
+
+ token = routines_lindex(line, 0);
+
+ if ( strcmp(token, "USR") == 0 ) {
+
+ char *status;
+
+ status = routines_lindex(line, 2);
+
+ if ( strcmp(status, "OK") == 0 ) {
+
+ if ( session->num_users != 1 ) {
+ /* Not quite inconceivable, but would only happen in a *really* screwed-up case. */
+ debug_print("SB %8p: %i users on session record during negotiation!\n", session, session->num_users);
+ }
+
+ assert(session->users != NULL);
+ assert(session->users->username != NULL);
+ debug_print("SB %8p: Inviting %s\n", session, session->users->username);
+ sbprotocol_invite(session, session->users->username);
+ if ( session->threeway_intent != NULL ) {
+ debug_print("SB %8p: Also inviting %s\n", session, session->threeway_intent);
+ sbprotocol_invite(session, session->threeway_intent);
+ }
+
+ } else {
+
+ debug_print("SB %8p: Negotiation failed\n", session);
+
+ }
+
+ free(status);
+
+ }
+
+ if ( strcmp(token, "JOI") == 0 ) {
+
+ char *username;
+ char *friendlyname;
+
+ session->ready = TRUE;
+ sbprotocol_sendcaps(session);
+ sbprotocol_flushcache(session);
+
+ username = routines_lindex(line, 1);
+ friendlyname = routines_lindex(line, 2);
+ sbsessions_joined(session, username, friendlyname);
+ free(username);
+ free(friendlyname);
+
+ }
+
+ if ( strcmp(token, "IRO") == 0 ) {
+
+ char *username;
+ char *friendlyname;
+
+ username = routines_lindex(line, 4);
+ friendlyname = routines_lindex(line, 5);
+ session->ready = TRUE;
+ sbsessions_joined(session, username, friendlyname);
+ free(username);
+ free(friendlyname);
+
+ }
+
+ if ( strcmp(token, "MSG") == 0 ) {
+
+ char *length;
+
+ length = routines_lindex(line, 3);
+ session->expect_length = atoi(length);
+ free(length);
+
+ session->msg_source = routines_lindex(line, 1);
+
+ assert(session->expect_length > 0);
+ session->conmode = SBCONMODE_MSG;
+
+ }
+
+ if ( strcmp(token, "BYE") == 0 ) {
+
+ char *username;
+ username = routines_lindex(line, 1);
+ sbsessions_left(session, username);
+ free(username);
+
+ }
+
+ if ( strcmp(token, "ANS") == 0 ) {
+
+ char *reply;
+
+ reply = routines_lindex(line, 2);
+ if ( strcmp(reply, "OK") == 0 ) {
+ session->ready = TRUE; /* This is probably already the case. */
+ sbprotocol_sendcaps(session);
+ } else {
+ debug_print("SB %8p: ANS reply not OK!\n", session);
+ }
+ free(reply);
+
+ }
+
+ if ( strcmp(token, "NAK") == 0 ) {
+
+ /* Difficult to analyse this in detail... */
+ if ( session->messagewindow != NULL ) {
+ if ( !messagewindow_get_last_was_nak(session->messagewindow) ) {
+ messagewindow_addtext_system(session->messagewindow, "Connection problems at the other end?");
+ messagewindow_set_last_was_nak(session->messagewindow, TRUE);
+ }
+ }
+
+ }
+
+ if ( strcmp(token, "217") == 0 ) {
+
+ /* Hmmm. */
+ if ( !session->ready ) {
+ debug_print("SB %8p: 217 before READY. Leaving...\n", session);
+ sbprotocol_leavesession(session);
+ }
+
+ }
+
+ free(token);
+
+}
+
+/* Deal with an incoming user IM */
+static void sbprotocol_parseim(SbSession *session, const char *msg, size_t length) {
+
+ char *says_text;
+ const char *friendlyname_coded;
+ char *friendlyname;
+ SbUser *user;
+ char *format;
+ char *colour = NULL;
+ char *flipcolour;
+ char *hashcolour = NULL;
+ char *padcolour;
+ size_t i;
+ char *font = NULL;
+
+ if ( session->messagewindow == NULL ) {
+ messagewindow_mitigate(session);
+ }
+
+ friendlyname_coded = contactlist_friendlyname(session->msg_source);
+ friendlyname = routines_urldecode(friendlyname_coded);
+ says_text = malloc(strlen(friendlyname) + 7);
+ strcpy(says_text, friendlyname);
+ free(friendlyname);
+ strcat(says_text, " says:");
+
+ if ( session->messagewindow->ofontoverride ) {
+ hashcolour = strdup(session->messagewindow->ocolour_string);
+ font = session->messagewindow->ofont;
+ } else {
+ format = mime_getfield(msg, "X-MMS-IM-Format");
+ debug_print("SB %8p: Got format: '%s'\n", session, format);
+ if ( strlen(format) > 0 ) {
+
+ colour = strdup("000000");
+ for ( i=0; i<strlen(format); i++ ) {
+
+ if ( (format[i]=='C') && (format[i+1]=='O') && (format[i+2]=='=') ) {
+
+ char c;
+ size_t j = 0;
+
+ free(colour);
+ colour = malloc(7);
+ c = format[i+3+j];
+ while ( (j<7) && (c != ';') && (c != ' ') && (i+3+j < strlen(format)-1) ) {
+ colour[j] = c;
+ j++;
+ c = format[i+3+j];
+ }
+ colour[j] = '\0';
+ break;
+
+ }
+
+ }
+ free(format);
+ assert(colour != NULL);
+ debug_print("SB %8p: Got colour '%s'\n", session, colour);
+
+ if ( strlen(colour) < 6 ) {
+
+ size_t len = strlen(colour);
+ padcolour = strdup("000000");
+ padcolour[6-len] = '\0';
+ strcat(padcolour, colour);
+ free(colour);
+ colour = padcolour;
+ debug_print("SB %8p: Padded: '%s'\n", session, colour);
+
+ }
+
+ flipcolour = routines_flipcolour(colour);
+ hashcolour = malloc(8);
+ strcpy(hashcolour, "#");
+ strncat(hashcolour, flipcolour, 6);
+ hashcolour[7] = '\0';
+ free(flipcolour);
+ free(colour);
+ debug_print("SB %8p: Flipped and hashed: '%s'\n", session, hashcolour);
+ }
+
+ }
+
+ messagewindow_addtext_system(session->messagewindow, says_text);
+ free(says_text);
+ messagewindow_addtext_user_nonewline(session->messagewindow, "\n", 1, NULL, NULL);
+ messagewindow_addtext_user_nonewline(session->messagewindow, mime_getbody(msg), length-mime_headerlength(msg), hashcolour, font);
+ free(hashcolour);
+
+ user = sbsessions_find_username(session, session->msg_source);
+ if ( user == NULL ) {
+ debug_print("SB %8p: IM source user not found on switchboard!\n", session);
+ return;
+ }
+ messagewindow_stoptypingbyusername(session->messagewindow, session->msg_source);
+
+}
+
+/* Handle an "MSMsgsControl" */
+static void sbprotocol_parsemsgcontrol(SbSession *session, const char *msg) {
+
+ char *typinguser;
+
+ /* Check for a typing user.*/
+ typinguser = mime_getfield(msg, "TypingUser");
+ if ( strlen(typinguser) > 0 ) {
+
+ if ( session->messagewindow == NULL ) {
+ messagewindow_mitigate(session);
+ }
+ messagewindow_starttyping(session->messagewindow, typinguser);
+
+ } else {
+ debug_print("SB %8p: Unrecognised TypingUser control!\n", session);
+ }
+
+ free(typinguser);
+
+}
+
+static void sbprotocol_handleclientcaps(SbSession *session, const char *msg) {
+
+ debug_print("SB %8p: clientcaps: '%s'\n", session, msg);
+
+}
+
+static void sbprotocol_handledatacast(SbSession *session, const char *username, const char *data) {
+
+ int id;
+ char *id_string = mime_getfield(data, "ID");
+
+ if ( id_string == NULL ) {
+ debug_print("SB %8p: Got datacast, couldn't find ID.\n", session);
+ return;
+ }
+
+ id = atoi(id_string);
+ free(id_string);
+
+ debug_print("SB %8p: Got datacast: ID %i.\n", session, id);
+
+ if ( id == 1 ) {
+
+ const char *friendlyname;
+ char *friendlyname_decoded;
+ char *text;
+
+ friendlyname = contactlist_friendlyname(username);
+ friendlyname_decoded = routines_urldecode(friendlyname);
+
+ text = malloc(strlen(friendlyname_decoded)+9);
+ strcpy(text, friendlyname_decoded);
+ strcat(text, " nudges.");
+ free(friendlyname_decoded);
+
+ if ( session->messagewindow == NULL ) {
+ messagewindow_mitigate(session);
+ }
+ messagewindow_addtext_system(session->messagewindow, text);
+ free(text);
+
+ }
+
+}
+
+
+static void sbprotocol_handleink_isf(SbSession *session, const char *username, const char *ink) {
+
+ debug_print("SB %8p: Got ISF: '%s'\n", session, ink);
+
+}
+
+static MultiPacketMsg *sbprotocol_findmultipacket(SbSession *session, const char *message_id) {
+
+ MultiPacketMsg *msg = session->multipackets;
+
+ while ( msg != NULL ) {
+ if ( strcmp(msg->message_id, message_id) == 0 ) {
+ return msg;
+ }
+ msg = msg->next;
+ }
+
+ return NULL;
+
+}
+
+/* Currently, this assumes the packets will arrive in the correct order. */
+static int sbprotocol_checkmultipacket(SbSession *session, const char *msgdata, ssize_t msgdatalength) {
+
+ char *chunks;
+ char *chunk;
+
+ chunks = mime_getfield(msgdata, "Chunks");
+ chunk = mime_getfield(msgdata, "Chunk");
+ if ( (strlen(chunks)>0) || (strlen(chunk)>0) ) {
+
+ /* This is part of a multipacketed message. */
+ MultiPacketMsg *msg;
+ char *messageid;
+
+ debug_print("SB %8p: Got a part of a multipacketed message.\n", session);
+
+ messageid = mime_getfield(msgdata, "Message-ID");
+ if ( strlen(messageid) == 0 ) {
+ debug_print("SB %8p: No Message-ID!\n", session);
+ free(messageid);
+ free(chunks);
+ free(chunk);
+ return 0;
+ }
+
+ if ( (atoi(chunk) == 0) && (atoi(chunks) == 0) ) {
+ debug_print("SB %8p: Couldn't find either a sensible 'Chunk' or 'Chunks' value!\n", session);
+ free(messageid);
+ free(chunks);
+ free(chunk);
+ return 0;
+ }
+
+ msg = sbprotocol_findmultipacket(session, messageid);
+ if ( msg == NULL ) {
+
+ /* No previous record of this series, so it had *better* contain the number of chunks to expect... */
+ if ( atoi(chunks) == 0 ) {
+ debug_print("SB %8p: Couldn't find the length of the multipacket series...\n", session);
+ free(messageid);
+ free(chunks);
+ free(chunk);
+ return 0;
+ }
+
+ msg = malloc(sizeof(MultiPacketMsg));
+ msg->chunks_expected = atoi(chunks);
+ msg->chunks_received = 1;
+ msg->data = mime_removeheader(msgdata, msgdatalength, "Chunks");
+ msg->datalength = msgdatalength - strlen(chunks) - strlen("Chunks: rn");
+ msg->message_id = messageid;
+
+ /* Link it into the list for this session. */
+ msg->next = session->multipackets;
+ msg->previous = NULL;
+ if ( session->multipackets != NULL ) {
+ session->multipackets->previous = msg;
+ }
+ session->multipackets = msg;
+
+ } else {
+
+ size_t body_size;
+ const char *body;
+
+ /* Known series, so this had *better* contain a chunk ID... */
+ if ( atoi(chunk) == 0 ) {
+ debug_print("SB %8p: Couldn't find the multipacket chunk ID...\n", session);
+ return 0;
+ }
+
+ body = mime_getbody(msgdata);
+ body_size = msgdatalength - mime_headerlength(msgdata);
+
+ msg->data = realloc(msg->data, body_size+(msg->datalength));
+ memcpy((msg->data)+msg->datalength, body, body_size);
+ msg->datalength += body_size;
+
+ msg->chunks_received++;
+ if ( msg->chunks_received == msg->chunks_expected ) {
+
+ debug_print("SB %8p: Got all parts of a multipacket message...\n", session);
+
+ /* Extend buffer by one byte and fix \0 terminator. */
+ msg->data = realloc(msg->data, (msg->datalength)+1);
+ msg->data[msg->datalength] = '\0';
+
+ sbprotocol_parsemsg(session, msg->data, msg->datalength);
+
+ /* Remove from list */
+ if ( msg->previous != NULL ) {
+ msg->previous->next = msg->next;
+ } else {
+ session->multipackets = msg->next;
+ }
+ if ( msg->next != NULL ) {
+ msg->next->previous = msg->previous;
+ }
+
+ free(msg->data);
+ free(msg);
+
+ return 1; /* It's already been handled. */
+
+ }
+
+ }
+
+ } else {
+
+ free(chunks);
+ free(chunk);
+ return 0;
+
+ }
+
+ free(chunks);
+ free(chunk);
+
+ return 1; /* Cause sbprotocol_parsemsg to ignore this one for now. */
+
+}
+
+/* Decide what to do with a received message.
+ Also called by msnp2p.c to throw us a MIME message sent via MSNP2P (Yuk!). */
+void sbprotocol_parsemsg(SbSession *session, const char *msg, ssize_t msglength) {
+
+ char *content_type;
+
+ debug_print("SB %8p: MSG (%i bytes): '%s'\n", session, msglength, msg);
+
+ if ( sbprotocol_checkmultipacket(session, msg, msglength) ) {
+ /* Soon, my pretties... */
+ return;
+ }
+
+ content_type = mime_getfield(msg, "Content-Type");
+
+ if ( strstr(content_type, "text/plain") != NULL ) {
+ sbprotocol_parseim(session, msg, msglength);
+ } else if ( strstr(content_type, "text/x-msmsgscontrol") != NULL ) {
+ sbprotocol_parsemsgcontrol(session, msg);
+ } else if ( strstr(content_type, "application/x-msnmsgrp2p") != NULL ) {
+ msnp2p_parsemsg(session, session->msg_source, msg, session->expect_length);
+ } else if ( strstr(content_type, "text/x-msmsgsinvite") != NULL ) {
+ msninvite_parsemsg(session, msg);
+ } else if ( strstr(content_type, "text/x-clientcaps") != NULL ) {
+ sbprotocol_handleclientcaps(session, msg);
+ } else if ( strstr(content_type, "application/x-ms-ink") != NULL ) {
+ sbprotocol_handleink_isf(session, session->msg_source, mime_getbody(msg));
+ } else if ( strstr(content_type, "text/x-msnmsgr-datacast") != NULL ) {
+ sbprotocol_handledatacast(session, session->msg_source, mime_getbody(msg));
+ } else {
+ debug_print("SB %8p: No handler for this MSG (%s).\n", session, content_type);
+ }
+
+ free(content_type);
+ free(session->msg_source); /* Can safely be freed even if routines_lindex didn't find anything earlier. */
+
+}
+
+/* Internally called when data arrives from the SB */
+static void sbprotocol_readable(SbSession *session) {
+
+ unsigned int i = 0; /* =0 keeps compiler happy */
+ ssize_t rlen;
+ int no_string = 0;
+
+ assert(session->rbufsize - session->roffset > 0);
+ rlen = read(session->socket, session->rbuffer + session->roffset, session->rbufsize - session->roffset);
+ if ( rlen > 0 ) {
+ session->roffset += rlen;
+ }
+ assert(session->roffset <= session->rbufsize); /* This would indicate a buffer overrun */
+
+ /* First, check this isn't a disconnection. rlen=0 is EOF, rlen=-1 is an error. */
+ if ( (rlen == 0) || (rlen == -1) ) {
+
+ int closeval;
+
+ closeval = close(session->socket);
+
+ if ( closeval != 0 ) {
+ debug_print("SB %8p: Couldn't close socket after read error.\n", session);
+ }
+
+ sbprotocol_disconnected(session);
+ return;
+
+ }
+
+ while ( (!no_string) && (session->roffset > 0) ) {
+
+ int block_ready = 0;
+
+ /* See if there's a full "block" in the buffer yet */
+ if ( session->conmode == SBCONMODE_LINE ) {
+
+ for ( i=0; i<session->roffset-1; i++ ) { /* Means the last value looked at is roffset-2 */
+ if ( (session->rbuffer[i] == '\r') && (session->rbuffer[i+1] == '\n') ) {
+ block_ready = 1;
+ break;
+ }
+ }
+
+ } else if ( session->conmode == SBCONMODE_MSG ) {
+
+ if ( session->roffset >= session->expect_length ) {
+ i = session->expect_length - 2;
+ block_ready = 1;
+ }
+
+ } else {
+ /* "Never happens". */
+ debug_print("SB %8p: Can't determine end of block for SbConMode %i!\n", session, session->conmode);
+ }
+
+ if ( block_ready == 1 ) {
+
+ char *block_buffer = NULL;
+ unsigned int new_rbufsize;
+ unsigned int endbit_length;
+ SbConMode next_mode = session->conmode;
+
+ if ( session->conmode == SBCONMODE_LINE ) {
+ assert(session->rbuffer[i] == '\r');
+ assert(session->rbuffer[i+1] == '\n');
+ }
+
+
+ if ( session->conmode == SBCONMODE_LINE ) {
+
+ block_buffer = malloc(i+1);
+ memcpy(block_buffer, session->rbuffer, i);
+ block_buffer[i] = '\0';
+
+ sbprotocol_parseline(session, block_buffer);
+
+ /* Check to see if sbprotocol_parseline changed the mode. If so, make sure
+ it stays changed. */
+ if ( session->conmode != SBCONMODE_LINE ) {
+ next_mode = session->conmode;
+ }
+
+ } else if ( session->conmode == SBCONMODE_MSG ) {
+
+ block_buffer = malloc(i+3); /* Exactly the number of bytes we were told to expect, plus one for terminator. */
+ memcpy(block_buffer, session->rbuffer, i+2); /* Copy it in */
+ block_buffer[i+2] = '\0'; /* Terminate */
+
+ sbprotocol_parsemsg(session, block_buffer, i+2);
+ next_mode = SBCONMODE_LINE;
+
+ } else {
+ debug_print("SB %8p: No handler for SbConMode %i !\n", session, session->conmode);
+ }
+
+ free(block_buffer);
+
+ /* Now the block's been parsed, it should be forgotten about */
+ if ( session->conmode == SBCONMODE_LINE ) {
+ assert(session->rbuffer[i+1] == '\n'); /* Should still be the case. Paranoid. */
+ }
+ endbit_length = i+2;
+ memmove(session->rbuffer, session->rbuffer + endbit_length, session->rbufsize - endbit_length);
+ session->roffset = session->roffset - endbit_length; /* Subtract the number of bytes removed */
+ new_rbufsize = session->rbufsize - endbit_length;
+ if ( new_rbufsize == 0 ) {
+ new_rbufsize = 32;
+ }
+ session->rbuffer = realloc(session->rbuffer, new_rbufsize);
+ session->rbufsize = new_rbufsize;
+ session->conmode = next_mode;
+
+ } else {
+
+ if ( session->roffset == session->rbufsize ) {
+
+ /* More buffer space is needed */
+ session->rbuffer = realloc(session->rbuffer, session->rbufsize + 32);
+ session->rbufsize = session->rbufsize + 32;
+ /* The new space gets used at the next read, shortly... */
+
+ }
+ no_string = 1;
+
+ }
+
+ }
+
+}
+
+/* Internally called when the socket is connected */
+static void sbprotocol_ready(SbSession *session) {
+
+ /* Remove the "writeable" callback, and add a "readable" callback */
+ gdk_input_remove(session->wcallback);
+ session->wcallback = 0;
+ session->rcallback = gdk_input_add(session->socket, GDK_INPUT_READ, (GdkInputFunction)sbprotocol_readable, session);
+
+ /* Begin the handshake */
+ if ( session->source == SESSION_SOURCE_REMOTE ) {
+
+ char *args;
+ const char *username;
+
+ username = options_username();
+ args = malloc(strlen(username) + strlen(session->cki_key) + strlen(session->sessionid) +3);
+
+ assert(username != NULL);
+ assert(args != NULL);
+ assert(session->cki_key != NULL);
+ assert(session->sessionid != NULL);
+
+ strcpy(args, username);
+ strcat(args, " ");
+ strcat(args, session->cki_key);
+ strcat(args, " ");
+ strcat(args, session->sessionid);
+ free(session->cki_key);
+ free(session->sessionid);
+
+ sbprotocol_sendtr(session, "ANS", args);
+ free(args);
+
+ } else {
+
+ char *args;
+ const char *username;
+
+ username = options_username();
+ args = malloc(strlen(username) + strlen(session->cki_key) + 2);
+
+ assert(username != NULL);
+ assert(args != NULL);
+ assert(session->cki_key != NULL);
+
+ strcpy(args, username);
+ strcat(args, " ");
+ strcat(args, session->cki_key);
+ free(session->cki_key);
+
+ sbprotocol_sendtr(session, "USR", args);
+ free(args);
+
+ }
+
+}
+
+static void sbprotocol_connect(SbSession *session, const char *hostname, unsigned short int port) {
+
+ struct sockaddr_in sa_desc;
+ struct hostent *server;
+ unsigned int sockopts;
+ int conn_error;
+
+ assert(hostname != NULL);
+ if ( port == 0 ) {
+ port = DEFAULT_SB_PORT;
+ debug_print("SB %8p: Fixed obviously wrong port number to %i\n", session, port);
+ }
+ debug_print("SB %8p: Connecting to '%s' port %i\n", session, hostname, port);
+
+ /* Create and configure the socket (but don't connect it yet) */
+ session->socket = socket(PF_INET, SOCK_STREAM, 0);
+ if ( session->socket == -1 ) {
+ error_report("Couldn't create switchboard socket");
+ return;
+ }
+ sockopts = fcntl(session->socket, F_GETFL);
+ fcntl(session->socket, F_SETFL, sockopts | O_NONBLOCK);
+
+ /* Resolve the server name */
+ server = gethostbyname(hostname);
+ if ( server == NULL ) {
+ error_report("Couldn't resolve switchboard hostname.");
+ 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);
+
+ session->rbuffer = malloc(256);
+ assert(session->rbuffer != NULL);
+ session->rbufsize = 256;
+ session->roffset = 0;
+ session->wbufsize = 0;
+ session->wbuffer = NULL;
+ session->woffset = 0;
+
+ conn_error = connect(session->socket, (struct sockaddr *)&sa_desc, sizeof(sa_desc))<0;
+ if ( (conn_error < 0) && (conn_error != EINPROGRESS) ) {
+
+ debug_print("SB %8p: Couldn't connect to server", session);
+ error_report("Couldn't connect to switchboard.");
+ return;
+
+ }
+
+ /* Call back when the socket is connected */
+ session->wcallback = gdk_input_add(session->socket, GDK_INPUT_WRITE, (GdkInputFunction)sbprotocol_ready, session);
+
+ session->trid = 1;
+ session->conmode = SBCONMODE_LINE;
+ session->expect_length = 0;
+
+}
+
+/* Called from src/msnprotocol.c when the XFR SB information is ready. */
+void sbprotocol_initiate_local(unsigned int trid, const char *hostname, unsigned int port, const char *cki_key) {
+
+ SbSession *session;
+
+ session = sbsessions_find_trid(trid);
+ if ( session == NULL ) {
+ debug_print("SB %8p: Couldn't find session! Aborting.\n", NULL);
+ return;
+ }
+
+ assert(session->users != NULL);
+ assert(session->users->username != NULL);
+ debug_print("SB %8p: Matched to user %s\n", session, session->users->username);
+ session->neg_trid = 0; /* Stop this session from being re-matched. */
+
+ session->cki_key = strdup(cki_key);
+ sbprotocol_connect(session, hostname, port);
+
+}
+
+void sbprotocol_initiate_remote(SbSession *session, const char *switchboardaddress, const char *sessionid, const char *authchallenge) {
+
+ char *hostname;
+ unsigned int port;
+
+ session->cki_key = strdup(authchallenge);
+ session->sessionid = strdup(sessionid);
+
+ hostname = routines_hostname(switchboardaddress);
+ port = routines_port(switchboardaddress);
+
+ sbprotocol_connect(session, hostname, port);
+
+ free(hostname);
+
+}
+
+void sbprotocol_send(SbSession *session, char *textblock, int length) {
+
+ if ( strlen(textblock) != 0 ) {
+
+ char *user_header;
+
+ session->am_typing = 0;
+ if ( session->am_typing_callback != 0 ) {
+ gtk_timeout_remove(session->am_typing_callback);
+ }
+
+ /* Send "User-Agent" and text style string with normal messages. */
+ user_header = malloc(256 + strlen(tuxmessenger_versionstring()));
+ strcpy(user_header, "User-Agent: ");
+ strcat(user_header, tuxmessenger_versionstring());
+
+ if ( session->messagewindow->localfont != NULL ) {
+ strcat(user_header, "\r\nX-MMS-IM-Format: ");
+ strcat(user_header, session->messagewindow->localfont);
+ if ( session->messagewindow->localcolour_string != NULL ) {
+ strcat(user_header, "; CO=");
+ strcat(user_header, session->messagewindow->localcolour_string);
+ }
+ } else {
+ if ( session->messagewindow->localcolour_string != NULL ) {
+ strcat(user_header, "\r\nX-MMS-IM-Format: CO=");
+ strcat(user_header, session->messagewindow->localcolour_string);
+ }
+ }
+
+ sbprotocol_sendmsg(session, "text/plain; charset=UTF-8", user_header, textblock, strlen(textblock));
+
+ free(user_header);
+ return;
+
+ } else {
+
+ debug_print("SB %8p: Not sending empty message.\n", session);
+ return;
+
+ }
+
+}
+
+void sbprotocol_sendnudge(SbSession *session) {
+ sbprotocol_sendmsg(session, "text/x-msnmsgr-datacast", NULL, "ID: 1\r\n\r\n", 9);
+}
+
+void sbprotocol_sendtypingcontrol(SbSession *session) {
+
+ char *control_header;
+
+ if ( session->ready != FALSE ) {
+
+ control_header = malloc(strlen(options_username())+16);
+ strcpy(control_header, "TypingUser: ");
+ strcat(control_header, options_username());
+ sbprotocol_sendmsg(session, "text/x-msmsgscontrol", control_header, "", 0);
+ free(control_header);
+
+ } else {
+
+ debug_print("SB %8p: Not sending TypingUser control before session is ready.\n", session);
+
+ }
+
+}
+
+void sbprotocol_close(SbSession *session) {
+ close(session->socket);
+}