/* * sbprotocol.c * * SB protocol handling * * (c) 2002-2006 Thomas White * Part of TuxMessenger - GTK+-based MSN Messenger client * * This package is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 dated June, 1991. * * This package is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this package; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include "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; iwbufsize; 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; icached; 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; imessagewindow, 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; iroffset-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); }