diff options
author | Thomas White <taw@bitwiz.org.uk> | 2009-07-17 12:46:27 +0100 |
---|---|---|
committer | Thomas White <taw@bitwiz.org.uk> | 2009-07-17 12:46:27 +0100 |
commit | 9ae0abe3414ea26f83fe3e01a37c3cd4819a82b9 (patch) | |
tree | d9e87a4b4bc035132a7e93b71c97ba90f257faec /src/sbprotocol.c |
Initial import
Diffstat (limited to 'src/sbprotocol.c')
-rw-r--r-- | src/sbprotocol.c | 1271 |
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); +} |