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/msnp2p.c |
Initial import
Diffstat (limited to 'src/msnp2p.c')
-rw-r--r-- | src/msnp2p.c | 1325 |
1 files changed, 1325 insertions, 0 deletions
diff --git a/src/msnp2p.c b/src/msnp2p.c new file mode 100644 index 0000000..5cd2473 --- /dev/null +++ b/src/msnp2p.c @@ -0,0 +1,1325 @@ +/* + * msnp2p.c + * + * Wizb Technologies' Combined MSNP2P/MSNSLP layer + * + * (c) 2002-2005 Thomas White <taw27@srcf.ucam.org> + * Part of TuxMessenger - GTK+-based MSN Messenger client + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 dated June, 1991. + * + * This package is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this package; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <glob.h> +#include <sys/stat.h> +#include <assert.h> + +#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); + assert(mpsession->session_id <= 2147483647); + 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: <msnmsgr:"); + strncat(msnslp_block, mpsession->username, 64); + strcat(msnslp_block, ">\r\nFrom: <msnmsgr:"); + strncat(msnslp_block, options_username(), 64); + strcat(msnslp_block, ">\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<size; file_offs+=1202 ) { + + msnp2p_binary.offset = file_offs; + if ( (size-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, "<msnmsgr:", 9) != 0 ) { + debug_print("MP: MSNSLP request not from \"<msnmsgr:*>\" - 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: <msnmsgr:"); + strncat(msnslp_block, mpsession->username, 64); + strcat(msnslp_block, ">\r\nFrom: <msnmsgr:"); + strncat(msnslp_block, options_username(), 64); + strcat(msnslp_block, ">\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: <msnmsgr:"); + strncat(msnslp_block, username, 64); + strcat(msnslp_block, ">\r\nFrom: <msnmsgr:"); + strncat(msnslp_block, options_username(), 64); + strcat(msnslp_block, ">\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: <msnmsgr:"); + strncat(msnslp_block, username, 64); + strcat(msnslp_block, ">\r\nFrom: <msnmsgr:"); + strncat(msnslp_block, options_username(), 64); + strcat(msnslp_block, ">\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); + +} |