/* * msnp2p.c * * Wizb Technologies' Combined MSNP2P/MSNSLP layer * * (c) 2002-2005 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 "sbsessions.h" #include "mime.h" #include "options.h" #include "debug.h" #include "sbprotocol.h" #include "avatars.h" #include "routines.h" #include "xml.h" #include "contactlist.h" #define MSNP2P_DEBUG static GList *msnp2p_list = NULL; struct _mpsession { SbSession *parent; char *username; enum { MSNP2P_DIR_RECV, MSNP2P_DIR_SEND } direction; unsigned int baseidentifier; unsigned int id_increment; enum { MSNP2P_TYPE_DP, MSNP2P_TYPE_INK, MSNP2P_TYPE_FT } type; unsigned int session_id; FILE *fh; char *save_filename; char *sha1d; /* The SHA1D field from the MSNObject, after xml_killillegalchars. */ char *via; char *call_id; size_t offset; void *data; guint delete_timeout; }; typedef struct _mpsession MpSession; struct _mpheader { uint32_t session_id; uint32_t identifier; uint64_t offset; uint64_t total_size; uint32_t size; uint32_t flags; uint32_t ack_id; uint32_t ack_sess; uint64_t ack_size; }; typedef struct _mpheader MpHeader; static MpSession *msnp2p_new() { MpSession *mpsession = malloc(sizeof(MpSession)); mpsession->save_filename = NULL; mpsession->sha1d = NULL; mpsession->via = NULL; mpsession->call_id = NULL; mpsession->username = NULL; mpsession->data = NULL; mpsession->delete_timeout = 0; return mpsession; } static gboolean msnp2p_closesession(MpSession *mpsession) { assert(mpsession != NULL); if ( mpsession->via != NULL ) { free(mpsession->via); } if ( mpsession->call_id != NULL ) { free(mpsession->call_id); } if ( mpsession->username != NULL ) { debug_print("MP %8p: Deleting an MSNP2P session to '%s'\n", mpsession, mpsession->username); free(mpsession->username); } else { debug_print("MP %8p: Deleting an MSNP2P session.\n", mpsession); } if ( mpsession->save_filename != NULL ) { free(mpsession->save_filename); } if ( mpsession->sha1d != NULL ) { free(mpsession->sha1d); } if ( mpsession->delete_timeout != 0 ) { g_source_remove(mpsession->delete_timeout); } if ( mpsession->data != NULL ) { free(mpsession->data); } msnp2p_list = g_list_remove(msnp2p_list, mpsession); free(mpsession); return FALSE; } /* Yes. Not Pretty. */ static void msnp2p_send(SbSession *session, MpHeader mpheader, const char *username, const char *payload, int payload_length, int app_id) { int total_message_length; char *msnp2p_text; char *message_stub; int stub_length; int sf; char *final; /* Create the overall header for the message. */ msnp2p_text = malloc(74 + strlen(username)); if ( msnp2p_text == NULL ) { debug_print("MP: Not enough memory to send MSNP2P message.\n"); return; } strcpy(msnp2p_text, "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: "); strcat(msnp2p_text, username); strcat(msnp2p_text, "\r\n\r\n"); /* Calculate the total message length. */ total_message_length = strlen(msnp2p_text) + 48 + payload_length + 4; /* Snippet */ message_stub = malloc(9); if ( message_stub == NULL ) { debug_print("MP: Not enough memory to send MSNP2P message.\n"); free(msnp2p_text); return; } assert(total_message_length < 10000); /* i.e. fits into four digits. */ sprintf(message_stub, "D %i\r\n", total_message_length); stub_length = strlen(message_stub); final = malloc(stub_length + strlen(msnp2p_text) + 48 + payload_length + 4); if ( final == NULL ) { debug_print("MP: Not enough memory to send MSNP2P message.\n"); free(message_stub); free(msnp2p_text); return; } strcpy(final, message_stub); free(message_stub); strcat(final, msnp2p_text); free(msnp2p_text); sf = strlen(final); memcpy (final+sf, &mpheader, 48); memcpy (final+sf+48, payload, payload_length); *((int *)(final+sf+48+payload_length)) = htonl(app_id); sbprotocol_sendtr_nonewline(session, "MSG", final, total_message_length+stub_length); free(final); } static gint msnp2p_compare_identifier(MpSession *mpsession, unsigned int *identifier) { if ( mpsession->baseidentifier == *identifier ) { return 0; } return 1; } static gint msnp2p_compare_parent(MpSession *mpsession, SbSession **parent) { if ( mpsession->parent == *parent ) { return 0; } return 1; } static gint msnp2p_compare_sessionid(MpSession *mpsession, unsigned int *sessionid) { if ( mpsession->session_id == *sessionid ) { return 0; } return 1; } static gint msnp2p_compare_callid(MpSession *mpsession, char *call_id) { if ( mpsession->call_id != NULL ) { if ( strcmp(mpsession->call_id, call_id) == 0 ) { return 0; } } return 1; } static gint msnp2p_compare_savefilename(MpSession *mpsession, char *save_filename) { if ( mpsession->save_filename != NULL ) { if ( strcmp(mpsession->save_filename, save_filename) == 0 ) { return 0; } } return 1; } /*static gint msnp2p_compare_sha1d(MpSession *mpsession, char *sha1d) { if ( mpsession->sha1d != NULL ) { if ( strcmp(mpsession->sha1d, sha1d) == 0 ) { return 0; } } return 1; }*/ static MpSession *msnp2p_find_identifier(unsigned long int identifier) { GList *result; result = g_list_find_custom(msnp2p_list, &identifier, (GCompareFunc)msnp2p_compare_identifier); if ( result != NULL ) { return (MpSession *)result->data; } return NULL; } static MpSession *msnp2p_find_sessionid(unsigned long int sessionid) { GList *result; result = g_list_find_custom(msnp2p_list, &sessionid, (GCompareFunc)msnp2p_compare_sessionid); if ( result != NULL ) { return (MpSession *)result->data; } return NULL; } static MpSession *msnp2p_find_callid(char *callid) { GList *result; result = g_list_find_custom(msnp2p_list, callid, (GCompareFunc)msnp2p_compare_callid); if ( result != NULL ) { return (MpSession *)result->data; } return NULL; } /* Who invented this cr*p? */ static void msnp2p_inc(MpSession *mpsession) { mpsession->id_increment++; if ( mpsession->id_increment == -1 ) { mpsession->id_increment = 1; } } static MpSession *msnp2p_check_ok(const char *msnslp) { char *session_id_string; MpSession *mpsession; session_id_string = mime_getfield_anywhere(msnslp, "SessionID"); if ( strlen(session_id_string) == 0 ) { debug_print("MP: Couldn't find SessionID field.\n"); free(session_id_string); return NULL; } mpsession = msnp2p_find_sessionid(atoi(session_id_string)); free(session_id_string); if ( strncmp(msnslp, "MSNSLP/1.0 200 OK", 17) == 0 ) { debug_print("MP: Got MSNSLP/1.0 200 OK - "); if ( mpsession == NULL ) { debug_print("but session not found.\n"); } else { debug_print("and session found.\n"); } } return mpsession; } static int msnp2p_check_bye(SbSession *session, const char *msnslp, MpHeader *mpheader) { char *callid_string; MpSession *mpsession; callid_string = mime_getfield_anywhere(msnslp, "Call-ID"); if ( callid_string == NULL ) { debug_print("MP: Couldn't find Call-ID field.\n"); return 0; } mpsession = msnp2p_find_callid(callid_string); free(callid_string); if ( strncmp(msnslp, "BYE MSNMSGR", 11) == 0 ) { debug_print("MP: Got BYE MSNMSGR - "); if ( mpsession == NULL ) { debug_print("but session not found.\n"); } else { MpHeader acknowledgement; debug_print("and session found.\n"); /* Send BYE ACK message. */ acknowledgement.session_id = 0; acknowledgement.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment); /* msnp2p_inc(mpsession); */ acknowledgement.offset = 0; acknowledgement.total_size = 0; acknowledgement.size = 0; acknowledgement.flags = GINT32_TO_LE(0x42); acknowledgement.ack_sess = mpheader->identifier; acknowledgement.ack_id = mpheader->ack_sess; acknowledgement.ack_size = mpheader->size; debug_print("MP: Sending BYE ACK\n"); msnp2p_send(session, acknowledgement, mpsession->username, "", 0, 0); /* Delay before deletion allows straggling packets to be collected. */ debug_print("MP: Closing MSNP2P session in ten seconds...\n"); mpsession->delete_timeout = g_timeout_add(10000, (GSourceFunc)msnp2p_closesession, mpsession); return 1; } } return 0; } static unsigned int msnp2p_cookie() { FILE *fh; unsigned int seed; float num; unsigned int val; fh = fopen("/dev/urandom", "r"); fread(&seed, sizeof(seed), 1, fh); fclose(fh); srand(seed); num = (float)rand() / (float)RAND_MAX; val = (num * 16777216)+8; return val; } static MpSession *msnp2p_send_dp(MpSession *mpsession, SbSession *session, const char *msnslp) { MpHeader msnp2p_binary; char *msnslp_block; size_t msnslp_length; glob_t glob_result; struct stat *statbuf; void *picture_data; unsigned int file_offs; int zero; struct stat stat_buffer; FILE *fh; unsigned int size; int readval; char *request_details; size_t request_details_length; char *local_avatar; mpsession->via = mime_getfield_anywhere(msnslp, "Via:"); if ( mpsession->via == NULL ) { debug_print("MP: Couldn't find Via field.\n"); free(mpsession); return NULL; } mpsession->call_id = mime_getfield_anywhere(msnslp, "Call-ID:"); if ( mpsession->call_id == NULL ) { debug_print("MP: Couldn't find Call-ID field.\n"); free(mpsession); return NULL; } request_details = malloc(26); debug_print("MP: New session ID=%i\n", mpsession->session_id); strcpy(request_details, "SessionID: "); sprintf(request_details+strlen(request_details), "%i\r\n\r\n", mpsession->session_id); request_details_length = strlen(request_details); msnslp_block = malloc( 176 + strlen(options_username()) + strlen(mpsession->username) + strlen(mpsession->via) + strlen(mpsession->call_id) + strlen(request_details) ); strcpy(msnslp_block, "MSNSLP/1.0 200 OK\r\nTo: username, 64); strcat(msnslp_block, ">\r\nFrom: \r\nVia: "); strncat(msnslp_block, mpsession->via, 256); strcat(msnslp_block, "\r\nCSeq: 1 \r\nCall-ID: "); strncat(msnslp_block, mpsession->call_id, 256); strcat(msnslp_block, "\r\nMax-Forwards: 0\r\nContent-Type: application/x-msnmsgr-sessionreqbody\r\nContent-Length: "); assert(request_details_length + 1 < 10000); sprintf(msnslp_block+strlen(msnslp_block), "%i", request_details_length+1); strcat(msnslp_block, "\r\n\r\n"); strcat(msnslp_block, request_details); free(request_details); msnslp_length = strlen(msnslp_block); msnp2p_binary.session_id = 0; msnp2p_binary.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment); msnp2p_inc(mpsession); msnp2p_binary.offset = 0; msnp2p_binary.total_size = GINT64_TO_LE(msnslp_length+1); msnp2p_binary.size = GINT64_TO_LE(msnslp_length+1); msnp2p_binary.flags = 0; msnp2p_binary.ack_sess = GINT32_TO_LE(mpsession->session_id); msnp2p_binary.ack_id = 0; msnp2p_binary.ack_size = 0; debug_print("MP: Sending 200 OK message\n"); msnp2p_send(session, msnp2p_binary, mpsession->username, msnslp_block, strlen(msnslp_block)+1, 0); free(msnslp_block); local_avatar = avatars_local(); glob(local_avatar, GLOB_TILDE, NULL, &glob_result); free(local_avatar); statbuf = &stat_buffer; if ( stat(glob_result.gl_pathv[0], statbuf) == -1 ) { debug_print("MP: Couldn't find avatar file :(\n"); free(mpsession); return NULL; } size = (int)statbuf->st_size; assert(size > 0); fh = fopen(glob_result.gl_pathv[0], "r"); if ( fh == NULL ) { debug_print("MP: Couldn't open avatar file.\n"); free(mpsession); return NULL; } picture_data = malloc(size); readval = fread(picture_data, 1, size, fh); if ( readval < 0 ) { debug_print("MP: Couldn't read avatar file.\n"); free(mpsession); free(picture_data); return NULL; } debug_print("MP: Read %i bytes of avatar file\n", readval); fclose(fh); globfree(&glob_result); msnp2p_binary.session_id = GINT32_TO_LE(mpsession->session_id); msnp2p_binary.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment); msnp2p_inc(mpsession); msnp2p_binary.offset = 0; msnp2p_binary.total_size = GINT64_TO_LE(4); msnp2p_binary.size = GINT64_TO_LE(4); msnp2p_binary.flags = 0; msnp2p_binary.ack_sess = GINT32_TO_LE(123456); msnp2p_binary.ack_id = 0; msnp2p_binary.ack_size = 0; zero = 0; debug_print("MP: Sending data preparation message\n"); msnp2p_send(session, msnp2p_binary, mpsession->username, (char *)&zero, 4, 1); msnp2p_binary.session_id = GINT32_TO_LE(mpsession->session_id); msnp2p_binary.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment); msnp2p_inc(mpsession); msnp2p_binary.total_size = GINT64_TO_LE(size); msnp2p_binary.flags = GINT32_TO_LE(0x20); msnp2p_binary.ack_id = 0; msnp2p_binary.ack_size = 0; for ( file_offs=0; file_offs 1201 ) { msnp2p_binary.size = GINT64_TO_LE(1202); } else { msnp2p_binary.size = GINT64_TO_LE((size-file_offs) % 1202); } msnp2p_binary.ack_sess = msnp2p_cookie(); debug_print("MP: Sending %i bytes: %i-%i of %i\n", msnp2p_binary.size, (int)msnp2p_binary.offset, (int)(msnp2p_binary.offset+msnp2p_binary.size)-1, (int)msnp2p_binary.total_size); msnp2p_send(session, msnp2p_binary, mpsession->username, picture_data+file_offs, msnp2p_binary.size, 1); } free(picture_data); return mpsession; } static MpSession *msnp2p_incomingsession(SbSession *session, const char *msnslp, MpHeader *mpheader) { MpSession *mpsession; char *temp_name; char *session_id_string; unsigned int session_id; MpHeader acknowledgement; char *euf_guid; char *content_type; if ( strncmp(msnslp, "INVITE MSNMSGR:", 15) != 0 ) { return NULL; } content_type = mime_getfield(msnslp, "Content-Type"); if ( strcmp(content_type, "application/x-msnmsgr-transreqbody") == 0 ) { } free(content_type); session_id_string = mime_getfield_anywhere(msnslp, "SessionID:"); if ( strlen(session_id_string) == 0 ) { debug_print("MP: Couldn't find SessionID field.\n"); free(session_id_string); return NULL; } session_id = atoi(session_id_string); free(session_id_string); /* Return if this packet isn't to be dealt with here. */ if ( session_id == 0 ) { return NULL; } euf_guid = mime_getfield_anywhere(msnslp, "EUF-GUID:"); if ( strlen(euf_guid) == 0 ) { debug_print("MP: Couldn't find EUF-GUID field.\n"); return NULL; } mpsession = msnp2p_new(); if ( mpsession == NULL ) { debug_print("MP: Not enough memory to start MSNP2P transfer.\n"); return NULL; } if ( strcmp(euf_guid, "{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}") == 0 ) { mpsession->type = MSNP2P_TYPE_DP; } else if ( strcmp(euf_guid, "{5D3E02AB-6190-11D3-BBBB-00C04F795683}") == 0 ) { mpsession->type = MSNP2P_TYPE_FT; } else { debug_print("MP: Unrecognised MSNSLP INVITE.\n"); free(euf_guid); return NULL; } free(euf_guid); mpsession->parent = session; mpsession->baseidentifier = msnp2p_cookie(); mpsession->id_increment = -3; mpsession->session_id = session_id; mpsession->direction = MSNP2P_DIR_SEND; mpsession->save_filename = NULL; mpsession->sha1d = NULL; /* Get actual username. */ temp_name = mime_getfield(msnslp, "From:"); if ( temp_name == NULL ) { debug_print("MP: Couldn't find From field.\n"); free(mpsession); return NULL; } if ( strncmp(temp_name, "\" - giving up.\n"); free(mpsession); return NULL; } mpsession->username = strdup(temp_name+9); free(temp_name); if ( mpsession->username == NULL ) { debug_print("MP: Couldn't find From field.\n"); free(mpsession); return NULL; } mpsession->username[strlen(mpsession->username)-1] = '\0'; switch ( mpsession->type ) { case MSNP2P_TYPE_DP : debug_print("MP: Got Display Picture Invite from %s\n", mpsession->username); break; case MSNP2P_TYPE_FT : debug_print("MP: Got File Transfer Invite from %s\n", mpsession->username); break; case MSNP2P_TYPE_INK : debug_print("MP: This doesn't happen.\n"); break; } debug_print("MSNSLP: '%s'\n", msnslp); acknowledgement.session_id = 0; acknowledgement.identifier = GINT32_TO_LE(mpsession->baseidentifier); acknowledgement.offset = 0; acknowledgement.total_size = mpheader->total_size; acknowledgement.size = 0; acknowledgement.flags = GINT32_TO_LE(2); acknowledgement.ack_sess = mpheader->identifier; acknowledgement.ack_id = mpheader->ack_sess; acknowledgement.ack_size = mpheader->total_size; debug_print("MP: Sending BaseIdentifier message\n"); msnp2p_send(session, acknowledgement, mpsession->username, "", 0, 0); if ( mpsession->type == MSNP2P_TYPE_DP ) { mpsession = msnp2p_send_dp(mpsession, session, msnslp); } if ( mpsession != NULL ) { msnp2p_list = g_list_append(msnp2p_list, mpsession); } return mpsession; } static void msnp2p_sendbye(SbSession *session, MpSession *mpsession) { char *msnslp_block; MpHeader mpheader; int msnslp_length; msnslp_block = malloc( 294 + 2*strlen(mpsession->username) + strlen(options_username()) ); strcpy(msnslp_block, "BYE MSNMSGR:"); strncat(msnslp_block, mpsession->username, 64); strcat(msnslp_block, " MSNSLP/1.0\r\nTo: username, 64); strcat(msnslp_block, ">\r\nFrom: \r\nVia: MSNSLP/1.0/TLP ;branch={33517CE4-02FC-4428-B6F4-39927229B722}\r\nCSeq: 0 \r\nCall-ID: "); strncat(msnslp_block, mpsession->call_id, 38); strcat(msnslp_block, "\r\nMax-Forwards: 0\r\nContent-Type: application/x-msnmsgr-sessionclosebody\r\nContent-Length: 3"); strcat(msnslp_block, "\r\n\r\n"); msnslp_length = strlen(msnslp_block); mpheader.session_id = 0; mpheader.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment); msnp2p_inc(mpsession); mpheader.offset = 0; mpheader.total_size = GINT64_TO_LE(msnslp_length+1); mpheader.size = GINT64_TO_LE(msnslp_length+1); mpheader.flags = 0; mpheader.ack_sess = GINT32_TO_LE(mpsession->session_id); mpheader.ack_id = 0; mpheader.ack_size = 0; debug_print("MP: Sending MSNSLP BYE for display picture.\n"); msnp2p_send(session, mpheader, mpsession->username, msnslp_block, msnslp_length+1, 0); free(msnslp_block); } static void msnp2p_handledatapacket(SbSession *session, MpHeader *mpheader, MpSession *mpsession, const char *msnslp) { FILE *fh; debug_print("MP: Got data packet: %i-%i of %i\n", (int)mpheader->offset, (int)(mpheader->offset+mpheader->size)-1, (int)mpheader->total_size); fh = fopen(mpsession->save_filename, "a"); if ( fh == NULL ) { debug_print("MP: Couldn't open avatar file to write data.\n"); return; } fwrite(msnslp, 1, mpheader->size, fh); fclose(fh); /* Got all of data? */ if ( mpheader->offset+mpheader->size >= mpheader->total_size ) { /* Send an acknowledgement for the data */ MpHeader acknowledgement; acknowledgement.session_id = mpheader->session_id; acknowledgement.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment); msnp2p_inc(mpsession); acknowledgement.offset = 0; acknowledgement.total_size = 0; acknowledgement.size = 0; acknowledgement.flags = GINT32_TO_LE(2); acknowledgement.ack_sess = mpheader->session_id; acknowledgement.ack_id = mpheader->identifier; acknowledgement.ack_size = mpheader->total_size; msnp2p_send(session, acknowledgement, mpsession->username, "", 0, 0); msnp2p_sendbye(session, mpsession); contactlist_picturekick_sha1d(mpsession->sha1d); /* Delay before deletion allows straggling packets to be collected. */ debug_print("MP: Closing MSNP2P session in ten seconds...\n"); mpsession->delete_timeout = g_timeout_add(10000, (GSourceFunc)msnp2p_closesession, mpsession); } } static MpSession *msnp2p_check_ink(SbSession *session, const char *username, MpHeader *header, const void *data, unsigned long int app_id) { MpSession *mpsession; size_t datalength = GINT32_TO_LE(header->size); if ( !(header->session_id == 64) || !(app_id == 3) ) { return NULL; } debug_print("MP: Ink packet from '%s'\n", username); if ( header->offset == 0 ) { debug_print("MP: First in a sequence of Ink packets.\n"); mpsession = msnp2p_new(); mpsession->baseidentifier = GINT32_TO_LE(header->identifier); mpsession->type = MSNP2P_TYPE_INK; mpsession->direction = MSNP2P_DIR_RECV; mpsession->parent = session; mpsession->username = strdup(username); mpsession->session_id = 0; mpsession->via = NULL; mpsession->save_filename = NULL; mpsession->sha1d = NULL; mpsession->call_id = NULL; if ( datalength <= 2000 ) { /* Sanity check */ mpsession->data = malloc(datalength); memcpy(mpsession->data, data, datalength); mpsession->offset = datalength; } else { debug_print("MP: Ink packet too big - binning.\n"); free(mpsession); return NULL; } msnp2p_list = g_list_append(msnp2p_list, mpsession); } else { mpsession = msnp2p_find_identifier(GINT32_TO_LE(header->identifier)); if ( mpsession == NULL ) { debug_print("MP: Unrecognised Ink packet.\n"); return NULL; } if ( datalength <= 2000 ) { mpsession->data = realloc(mpsession->data, (mpsession->offset) + datalength); if ( mpsession->data == NULL ) { debug_print("MP: Whoops! Couldn't allocate memory.\n"); return NULL; } memcpy((mpsession->data)+(mpsession->offset), data, datalength); mpsession->offset += datalength; debug_print("MP: Got some continuing Ink data.\n"); } else { debug_print("MP: Ink packet too big - binning.\n"); return NULL; } } assert(mpsession != NULL); /* NB It's possible for a single message to be both the first and last in a sequence. */ debug_print("MP: %i of %i bytes.\n", GINT64_TO_LE(header->offset), GINT64_TO_LE(header->total_size)); if ( GINT64_TO_LE(header->offset) >= GINT64_TO_LE(header->total_size) ) { gchar *body; glong new_length; glong items_in; GError *error; debug_print("MP: Last in a sequence of Ink packets.\n"); debug_print("MP: Decoding UTF-16: %i bytes in.\n", mpsession->offset); body = g_utf16_to_utf8(mpsession->data, mpsession->offset, &items_in, &new_length, &error); debug_print("MP: %i items in, %i items out.\n", items_in, new_length); if ( error == NULL ) { debug_print("MP: No error.\n"); } else { debug_print("MP: Error!\n"); debug_print("%s'\n", error->message); } if ( body == NULL ) { debug_print("MP: Conversion to UTF-8 failed: '%s'\n", error->message); } else { sbprotocol_parsemsg(session, body, new_length); debug_print("MP: Ink (%i bytes): '%s'\n", new_length, body); g_free(body); } debug_print("MP: Closing MSNP2P session in ten seconds...\n"); mpsession->delete_timeout = g_timeout_add(10000, (GSourceFunc)msnp2p_closesession, mpsession); } return mpsession; } void msnp2p_parsemsg(SbSession *session, const char *username, const char *whole_msg, size_t len) { char *p2p_dest; MpSession *mpsession; MpHeader *mpheader; const char *msnslp; const char *msg; unsigned long int app_id; /* First check the destination of the message and ignore if appropriate. */ p2p_dest = mime_getfield(whole_msg, "P2P-Dest"); if ( p2p_dest == NULL ) { debug_print("MP: Couldn't find P2P-Dest field.\n"); return; } if ( strcasecmp(options_username(), p2p_dest) != 0 ) { free(p2p_dest); debug_print("MP: Ignoring misaddressed MSNP2P message.\n"); return; } free(p2p_dest); /* Now forget about the MIME header. */ len -= mime_headerlength(whole_msg); msg = mime_getbody(whole_msg); mpheader = (MpHeader *)msg; /* First bytes of "msg" are the header. Obviously. */ msnslp = msg+sizeof(MpHeader); /* Contents */ /* Check the provided packet size is sane. */ if ( len < GINT64_TO_LE(mpheader->size) ) { debug_print("MP: 'size' field in header too big - rejecting packet.\n"); return; } app_id = ntohl(*(int *)(msg+(mpheader->size)+48)); #ifdef MSNP2P_DEBUG debug_print("MP: ---- MSNP2P packet ----\n"); debug_print("MP: sessionid = 0x%lx\n", mpheader->session_id); debug_print("MP: identifier = 0x%lx\n", mpheader->identifier); debug_print("MP: offset = 0x%llx\n", mpheader->offset); debug_print("MP: total_size = 0x%llx\n", mpheader->total_size); debug_print("MP: size = 0x%lx\n", mpheader->size); debug_print("MP: flags = 0x%lx\n", mpheader->flags); debug_print("MP: ack_id = 0x%lx\n", mpheader->ack_id); debug_print("MP: ack_sess = 0x%lx\n", mpheader->ack_sess); debug_print("MP: ack_size = 0x%llx\n", mpheader->ack_size); debug_print("MP: app_id = 0x%lx\n", app_id); #endif /* MSNP2P_DEBUG */ /* Arrrggggggggggghhhhhhhhhhhhhhhhhhhhhh. */ mpsession = msnp2p_find_identifier(GINT32_TO_LE(mpheader->ack_sess)); if ( mpsession == NULL ) { mpsession = msnp2p_find_sessionid(GINT32_TO_LE(mpheader->session_id)); } if ( mpsession == NULL ) { mpsession = msnp2p_check_ok(msnslp); } if ( mpsession == NULL ) { mpsession = msnp2p_find_sessionid(GINT32_TO_LE(mpheader->ack_id)); } if ( (mpsession == NULL) && (mpheader->session_id == 0) ) { mpsession = msnp2p_incomingsession(session, msnslp, mpheader); } if ( msnp2p_check_bye(session, msnslp, mpheader) != 0 ) { debug_print("MP: Got BYE.\n"); return; } if ( mpsession == NULL ) { mpsession = msnp2p_check_ink(session, username, mpheader, msnslp, app_id); } /* Past this point, if the message hasn't been matched to a session then it's unrecognised. */ if ( mpsession == NULL ) { debug_print("MP: Unrecognised MSNP2P "); if ( (GINT32_TO_LE(mpheader->flags) & 2) != 0 ) { debug_print("ack.\n"); } else { debug_print("packet:\n"); debug_print("MP: Flags = %x\n", GINT32_TO_LE(mpheader->flags)); debug_print("MP: Data: '%s'\n", msnslp); } return; } if ( ( GINT32_TO_LE(mpheader->flags) & 2) != 0 ) { debug_print("MP: Got ACK.\n"); } if ( ( (GINT32_TO_LE(mpheader->flags) & 2) == 0) && (mpheader->size == mpheader->total_size) ) { /* Message wasn't an ACK, so probably needs an ACK sending */ MpHeader acknowledgement; acknowledgement.session_id = GINT32_TO_LE(mpheader->session_id); acknowledgement.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment); /* msnp2p_inc(mpsession); */ acknowledgement.offset = 0; acknowledgement.total_size = 0; acknowledgement.size = 0; if ( GINT32_TO_LE(mpheader->flags) == 0x40 ) { acknowledgement.flags = GINT32_TO_LE(0x80); } else { acknowledgement.flags = GINT32_TO_LE(2); } /* Copying directly from received header: don't touch endianness. */ acknowledgement.ack_sess = mpheader->session_id; acknowledgement.ack_id = mpheader->identifier; acknowledgement.ack_size = mpheader->size; debug_print("MP: Sending ACK.\n"); msnp2p_send(session, acknowledgement, mpsession->username, "", 0, 0); } if ( (app_id == 1) && (GINT64_TO_LE(mpheader->total_size) != 4) ) { msnp2p_handledatapacket(session, mpheader, mpsession, msnslp); } } /* Check for a receive session for the DP SHA1D given. */ int msnp2p_retrieving(const char *save_filename) { GList *result = NULL; result = g_list_find_custom(msnp2p_list, save_filename, (GCompareFunc)msnp2p_compare_savefilename); if ( result != NULL ) { /* At least one session exists. Don't care what it is... */ return 1; } /* Nothing found. */ return 0; } /* Initiate the downloading of a picture. */ void msnp2p_getpicture(SbSession *session, const char *username, const char *dpobject) { MpSession *mpsession; MpHeader mpheader; char *dpobject_decoded; char *context; char *data_hash; char *filename; char *request_details; int request_details_length; char *request_details_length_string; char *msnslp_block; int msnslp_length; glob_t glob_result; struct stat stat_buffer; struct stat *statbuf; debug_print("MP: Beginning download for DP from '%s'\n", username); assert(username != NULL); assert(session != NULL); if ( dpobject == NULL ) { debug_print("MP: Attempt to download avatar for user who doesn't have one.\n"); return; } if ( sbsessions_sessionready(session) == 0 ) { debug_print("MP: Session isn't ready to download picture - not trying.\n"); return; } mpsession = msnp2p_new(); if ( mpsession == NULL ) { debug_print("MP: Not enough memory to do picture request - giving up.\n"); return; } mpsession->parent = session; mpsession->baseidentifier = msnp2p_cookie(); mpsession->session_id = msnp2p_cookie(); mpsession->direction = MSNP2P_DIR_RECV; mpsession->id_increment = -3; mpsession->call_id = routines_guid(); mpsession->via = NULL; debug_print("MP: Generated: Session ID '%i', BaseIdentifier '%i', Call-ID '%s'.\n", mpsession->session_id, mpsession->baseidentifier, mpsession->call_id); /* urldecode() the MSNObject and then base64 it to get the "Context" field, then retrieve the "SHA1D" field from the (urldecoded) MSNObject to generate the filename to save to. */ dpobject_decoded = routines_urldecode(dpobject); context = routines_base64(dpobject_decoded); data_hash = xml_getfield(dpobject_decoded, "SHA1D"); debug_print("MP: Got data hash: %s\n", data_hash); free(dpobject_decoded); filename = xml_killillegalchars(data_hash); mpsession->sha1d = data_hash; /* Don't free this yet. */ /* Determine the full filename to save to. */ glob("~", GLOB_TILDE, NULL, &glob_result); mpsession->save_filename = malloc(strlen(filename)+strlen("/.tuxmessenger/avatars/")+strlen(glob_result.gl_pathv[0]) + 1); if ( mpsession->save_filename == NULL ) { debug_print("MP: Not enough memory to do picture request - giving up.\n"); free(mpsession->call_id); free(mpsession); return; } strcpy(mpsession->save_filename, glob_result.gl_pathv[0]); strcat(mpsession->save_filename, "/.tuxmessenger/avatars/"); strcat(mpsession->save_filename, filename); globfree(&glob_result); free(filename); /* Check to see there isn't already a receive session going on for this user. */ if ( msnp2p_retrieving(mpsession->save_filename) != 0 ) { debug_print("MP: A DP receive session is already in progress to '%s' - not starting a new one.\n", mpsession->save_filename); free(mpsession->call_id); free(mpsession); return; } /* Check if we already have this picture or not */ statbuf = &stat_buffer; if ( stat(mpsession->save_filename, statbuf) != -1 ) { /* messagewindow_trypicture should have worked this out before. */ debug_print("MP: Already have this picture: %s\n", mpsession->save_filename); free(mpsession->save_filename); free(mpsession->call_id); free(mpsession); return; } /* Silly preliminaries out of the way. Time to do the request... */ /* Create the payload for the MSNSLP request */ request_details = malloc( 96 + strlen(context) + 9 ); if ( request_details == NULL ) { debug_print("MP: Not enough memory to do picture request - giving up.\n"); free(mpsession->save_filename); free(mpsession->call_id); free(mpsession); return; } strcpy(request_details, "EUF-GUID: {A4268EEC-FEC5-49E5-95C3-F126696BDBF6}\r\nSessionID: "); /* Check we're not about to screw ourselves over. 8 bytes were allowed above to fit the sessionid into. */ if ( mpsession->session_id > 99999999 ) { debug_print("MP: SessionID is too big - giving up.\n"); free(request_details); free(mpsession->save_filename); free(mpsession->call_id); free(mpsession); return; } sprintf(request_details+strlen(request_details), "%i", mpsession->session_id); strcat(request_details, "\r\nAppID: 1\r\nContext: "); strcat(request_details, context); free(context); strcat(request_details, "\r\n\r\n"); request_details_length = strlen(request_details); request_details_length_string = malloc(5); assert(request_details_length < 9998); /* Sanity check. */ sprintf(request_details_length_string, "%i", request_details_length+1); /* Now create the MSNSLP request itself */ msnslp_block = malloc( 291 + (2*strlen(username)) + strlen(options_username()) + strlen(request_details) + strlen(request_details_length_string) ); strcpy(msnslp_block, "INVITE MSNMSGR:"); strncat(msnslp_block, username, 64); strcat(msnslp_block, " MSNSLP/1.0\r\nTo: \r\nFrom: \r\nVia: MSNSLP/1.0/TLP ;branch={33517CE4-02FC-4428-B6F4-39927229B722}\r\nCSeq: 0 \r\nCall-ID: "); strncat(msnslp_block, mpsession->call_id, 38); strcat(msnslp_block, "\r\nMax-Forwards: 0\r\nContent-Type: application/x-msnmsgr-sessionreqbody\r\nContent-Length: "); strncat(msnslp_block, request_details_length_string, 30); free(request_details_length_string); strcat(msnslp_block, "\r\n\r\n"); strcat(msnslp_block, request_details); free(request_details); msnslp_length = strlen(msnslp_block); debug_print("MP: Sending MSNSLP INVITE for display picture.\n"); /* Now wrap it up with the MSNP2P rubbish */ mpheader.session_id = 0; mpheader.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment); msnp2p_inc(mpsession); mpheader.offset = 0; mpheader.total_size = GINT64_TO_LE(msnslp_length+1); mpheader.size = GINT64_TO_LE(msnslp_length+1); mpheader.flags = 0; mpheader.ack_sess = GINT32_TO_LE(mpsession->session_id); mpheader.ack_id = 0; mpheader.ack_size = 0; msnp2p_send(session, mpheader, username, msnslp_block, strlen(msnslp_block)+1, 0); free(msnslp_block); mpsession->username = strdup(username); msnp2p_list = g_list_append(msnp2p_list, mpsession); } /* Bin all the MSNP2P/MSNSLP sessions in a given switchboard session. */ void msnp2p_abortall(SbSession *session) { GList *result; result = g_list_find_custom(msnp2p_list, &session, (GCompareFunc)msnp2p_compare_parent); while ( result != NULL ) { MpSession *mpsession; mpsession = result->data; msnp2p_closesession(mpsession); result = g_list_find_custom(msnp2p_list, &session, (GCompareFunc)msnp2p_compare_parent); } } void msnp2p_offerfile(SbSession *session, const char *username, const char *filename) { MpSession *mpsession; MpHeader mpheader; char *context; char *request_details; size_t request_details_length; char *request_details_length_string; char *msnslp_block; size_t msnslp_length; assert(session != NULL); assert(username != NULL); assert(filename != NULL); mpsession = msnp2p_new(); if ( mpsession == NULL ) { debug_print("MP: Not enough memory to send file - giving up.\n"); return; } mpsession->parent = session; mpsession->baseidentifier = msnp2p_cookie(); mpsession->session_id = msnp2p_cookie(); mpsession->direction = MSNP2P_DIR_SEND; mpsession->id_increment = 0; mpsession->call_id = routines_guid(); mpsession->via = NULL; context = strdup("WIBBLE!!!112"); /* File preview data... */ debug_print("MP: Generated: Session ID '%i', BaseIdentifier '%i', Call-ID '%s'.\n", mpsession->session_id, mpsession->baseidentifier, mpsession->call_id); /* Create the payload for the MSNSLP request */ request_details = malloc( 96 + strlen(context) + 9 ); if ( request_details == NULL ) { debug_print("MP: Not enough memory to do picture request - giving up.\n"); free(mpsession->save_filename); free(mpsession->call_id); free(mpsession); return; } strcpy(request_details, "EUF-GUID: {5D3E02AB-6190-11D3-BBBB-00C04F795683}\r\nSessionID: "); /* Check we're not about to screw ourselves over. 8 bytes were allowed above to fit the sessionid into. */ if ( mpsession->session_id > 99999999 ) { debug_print("MP: SessionID is too big - giving up.\n"); free(request_details); free(mpsession->call_id); free(mpsession); return; } sprintf(request_details+strlen(request_details), "%i", mpsession->session_id); strcat(request_details, "\r\nAppID: 1\r\nContext: "); strcat(request_details, context); free(context); strcat(request_details, "\r\n\r\n"); request_details_length = strlen(request_details); request_details_length_string = malloc(5); assert(request_details_length < 9998); /* Sanity check. */ sprintf(request_details_length_string, "%i", request_details_length+1); /* Now create the MSNSLP request itself */ msnslp_block = malloc( 291 + (2*strlen(username)) + strlen(options_username()) + strlen(request_details) + strlen(request_details_length_string) ); strcpy(msnslp_block, "INVITE MSNMSGR:"); strncat(msnslp_block, username, 64); strcat(msnslp_block, " MSNSLP/1.0\r\nTo: \r\nFrom: \r\nVia: MSNSLP/1.0/TLP ;branch={33517CE4-02FC-4428-B6F4-39927229B722}\r\nCSeq: 0 \r\nCall-ID: "); strncat(msnslp_block, mpsession->call_id, 38); strcat(msnslp_block, "\r\nMax-Forwards: 0\r\nContent-Type: application/x-msnmsgr-sessionreqbody\r\nContent-Length: "); strncat(msnslp_block, request_details_length_string, 30); free(request_details_length_string); strcat(msnslp_block, "\r\n\r\n"); strcat(msnslp_block, request_details); free(request_details); msnslp_length = strlen(msnslp_block); debug_print("MP: Sending MSNSLP INVITE for file transfer...\n"); /* Now wrap it up with the MSNP2P rubbish */ mpheader.session_id = 0; mpheader.identifier = GINT32_TO_LE(mpsession->baseidentifier + mpsession->id_increment); msnp2p_inc(mpsession); mpheader.offset = 0; mpheader.total_size = GINT64_TO_LE(msnslp_length+1); mpheader.size = GINT64_TO_LE(msnslp_length+1); mpheader.flags = 0; mpheader.ack_sess = GINT32_TO_LE(mpsession->session_id); mpheader.ack_id = 0; mpheader.ack_size = 0; msnp2p_send(session, mpheader, username, msnslp_block, strlen(msnslp_block)+1, 0); free(msnslp_block); mpsession->username = strdup(username); msnp2p_list = g_list_append(msnp2p_list, mpsession); }