# HG changeset patch # User Elliott Sales de Andrade # Date 1274402981 0 # Node ID fb103111bbd50e2f411595730dd1a545e2b2de80 # Parent d6f9f4320bf16b7d0d02ec1b32fbe75b9b1d7748# Parent 1e9644d6c74aaabe899042cbf9c8f03518d2f522 merge of '53053c3093fabd661045da5b90dd26b37214a73c' and '9c7a00da5da4b9502fdf69a36ddf46f4fa5ec353' diff -r d6f9f4320bf1 -r fb103111bbd5 COPYRIGHT --- a/COPYRIGHT Thu May 20 22:54:26 2010 +0000 +++ b/COPYRIGHT Fri May 21 00:49:41 2010 +0000 @@ -481,6 +481,7 @@ Marcus Sundberg Mårten Svantesson (fursten) Amir Szekely (kichik) +Gábor Szuromi (kukkerman) Robert T. Greg Taeger Rob Taft diff -r d6f9f4320bf1 -r fb103111bbd5 ChangeLog --- a/ChangeLog Thu May 20 22:54:26 2010 +0000 +++ b/ChangeLog Fri May 21 00:49:41 2010 +0000 @@ -17,6 +17,8 @@ MSN: * Fix unnecessary bandwidth consumption for buddy icon requests when buddies have capital letters in their passport addresses. + * Support for direct connections, enabling faster file transfers, + smiley and buddy icon loading. (Gábor Szuromi) version 2.7.0 (05/12/2010): General: diff -r d6f9f4320bf1 -r fb103111bbd5 libpurple/protocols/msn/Makefile.am --- a/libpurple/protocols/msn/Makefile.am Thu May 20 22:54:26 2010 +0000 +++ b/libpurple/protocols/msn/Makefile.am Fri May 21 00:49:41 2010 +0000 @@ -14,6 +14,8 @@ contact.h\ dialog.c \ dialog.h \ + directconn.c \ + directconn.h \ error.c \ error.h \ group.c \ diff -r d6f9f4320bf1 -r fb103111bbd5 libpurple/protocols/msn/Makefile.mingw --- a/libpurple/protocols/msn/Makefile.mingw Thu May 20 22:54:26 2010 +0000 +++ b/libpurple/protocols/msn/Makefile.mingw Fri May 21 00:49:41 2010 +0000 @@ -41,6 +41,7 @@ command.c \ contact.c\ dialog.c \ + directconn.c \ error.c \ group.c \ history.c \ diff -r d6f9f4320bf1 -r fb103111bbd5 libpurple/protocols/msn/directconn.c --- a/libpurple/protocols/msn/directconn.c Thu May 20 22:54:26 2010 +0000 +++ b/libpurple/protocols/msn/directconn.c Fri May 21 00:49:41 2010 +0000 @@ -27,479 +27,976 @@ #include "slp.h" #include "slpmsg.h" -/************************************************************************** - * Directconn Specific - **************************************************************************/ - -void -msn_directconn_send_handshake(MsnDirectConn *directconn) -{ - MsnSlpLink *slplink; - MsnSlpMessage *slpmsg; - - g_return_if_fail(directconn != NULL); - - slplink = directconn->slplink; - - slpmsg = msn_slpmsg_new(slplink); - slpmsg->flags = 0x100; - - if (directconn->nonce != NULL) - { - guint32 t1; - guint16 t2; - guint16 t3; - guint16 t4; - guint64 t5; - - sscanf (directconn->nonce, "%08X-%04hX-%04hX-%04hX-%012" G_GINT64_MODIFIER "X", &t1, &t2, &t3, &t4, &t5); - - t1 = GUINT32_TO_LE(t1); - t2 = GUINT16_TO_LE(t2); - t3 = GUINT16_TO_LE(t3); - t4 = GUINT16_TO_BE(t4); - t5 = GUINT64_TO_BE(t5); - - slpmsg->ack_id = t1; - slpmsg->ack_sub_id = t2 | (t3 << 16); - slpmsg->ack_size = t4 | t5; - } - - g_free(directconn->nonce); - - msn_slplink_send_slpmsg(slplink, slpmsg); - - directconn->acked =TRUE; -} +#pragma pack(push,1) +typedef struct { + guint32 session_id; + guint32 seq_id; + guint64 offset; + guint64 total_size; + guint32 length; + guint32 flags; + guint32 ack_id; + guint32 ack_uid; + guint64 ack_size; +/* guint8 body[1]; */ +} MsnDcContext; +#pragma pack(pop) -/************************************************************************** - * Connection Functions - **************************************************************************/ - -static int -create_listener(int port) -{ - int fd; - int flags; - const int on = 1; - -#if 0 - struct addrinfo hints; - struct addrinfo *c, *res; - char port_str[5]; - - snprintf(port_str, sizeof(port_str), "%d", port); - - memset(&hints, 0, sizeof(hints)); - - hints.ai_flags = AI_PASSIVE; - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; +#define DC_PACKET_HEADER_SIZE sizeof(MsnDcContext) +#define DC_MAX_BODY_SIZE 8*1024 +#define DC_MAX_PACKET_SIZE (DC_PACKET_HEADER_SIZE + DC_MAX_BODY_SIZE) - if (getaddrinfo(NULL, port_str, &hints, &res) != 0) - { - purple_debug_error("msn", "Could not get address info: %s.\n", - port_str); - return -1; - } - - for (c = res; c != NULL; c = c->ai_next) - { - fd = socket(c->ai_family, c->ai_socktype, c->ai_protocol); +static void +msn_dc_calculate_nonce_hash(MsnDirectConnNonceType type, + const guchar nonce[16], gchar nonce_hash[37]) +{ + guchar digest[20]; - if (fd < 0) - continue; - - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); - - if (bind(fd, c->ai_addr, c->ai_addrlen) == 0) - break; - - close(fd); - } - - if (c == NULL) - { - purple_debug_error("msn", "Could not find socket: %s.\n", port_str); - return -1; + if (type == DC_NONCE_SHA1) { + PurpleCipher *cipher = purple_ciphers_find_cipher("sha1"); + PurpleCipherContext *context = purple_cipher_context_new(cipher, NULL); + purple_cipher_context_append(context, nonce, sizeof(nonce)); + purple_cipher_context_digest(context, sizeof(digest), digest, NULL); + purple_cipher_context_destroy(context); + } else if (type == DC_NONCE_PLAIN) { + memcpy(digest, nonce, 16); } - freeaddrinfo(res); -#else - struct sockaddr_in sockin; - - fd = socket(AF_INET, SOCK_STREAM, 0); - - if (fd < 0) - return -1; - - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) != 0) - { - close(fd); - return -1; - } - - memset(&sockin, 0, sizeof(struct sockaddr_in)); - sockin.sin_family = AF_INET; - sockin.sin_port = htons(port); - - if (bind(fd, (struct sockaddr *)&sockin, sizeof(struct sockaddr_in)) != 0) - { - close(fd); - return -1; - } -#endif - - if (listen (fd, 4) != 0) - { - close (fd); - return -1; - } - - flags = fcntl(fd, F_GETFL); - fcntl(fd, F_SETFL, flags | O_NONBLOCK); -#ifndef _WIN32 - fcntl(fd, F_SETFD, FD_CLOEXEC); -#endif - - return fd; + g_sprintf(nonce_hash, + "%08X-%04X-%04X-%04X-%08X%04X", + GUINT32_FROM_LE(*((guint32 *)(digest + 0))), + GUINT16_FROM_LE(*((guint16 *)(digest + 4))), + GUINT16_FROM_LE(*((guint16 *)(digest + 6))), + GUINT16_FROM_BE(*((guint16 *)(digest + 8))), + GUINT32_FROM_BE(*((guint32 *)(digest + 10))), + GUINT16_FROM_BE(*((guint16 *)(digest + 14))) + ); } -static gssize -msn_directconn_write(MsnDirectConn *directconn, - const char *data, size_t len) +static void +msn_dc_generate_nonce(MsnDirectConn *dc) { - char *buffer, *tmp; - size_t buf_size; - gssize ret; - guint32 sent_len; - - g_return_val_if_fail(directconn != NULL, 0); - - buf_size = len + 4; - buffer = tmp = g_malloc(buf_size); - - sent_len = GUINT32_TO_LE(len); + guint32 *nonce; + int i; - memcpy(tmp, &sent_len, 4); - tmp += 4; - memcpy(tmp, data, len); - tmp += len; - - ret = write(directconn->fd, buffer, buf_size); - -#ifdef DEBUG_DC - char *str; - str = g_strdup_printf("%s/msntest/w%.4d.bin", g_get_home_dir(), directconn->c); + nonce = (guint32 *)&dc->nonce; + for (i = 0; i < 4; i++) + nonce[i] = rand(); - FILE *tf = g_fopen(str, "w"); - fwrite(buffer, 1, buf_size, tf); - fclose(tf); + msn_dc_calculate_nonce_hash(dc->nonce_type, dc->nonce, dc->nonce_hash); - g_free(str); -#endif - - g_free(buffer); - - directconn->c++; - - return ret; + if (purple_debug_is_verbose()) + purple_debug_info("msn", "DC %p generated nonce %s\n", dc, dc->nonce_hash); } -#if 0 -void -msn_directconn_parse_nonce(MsnDirectConn *directconn, const char *nonce) +static MsnDirectConnPacket * +msn_dc_new_packet(guint32 length) { - guint32 t1; - guint16 t2; - guint16 t3; - guint16 t4; - guint64 t5; - - g_return_if_fail(directconn != NULL); - g_return_if_fail(nonce != NULL); - - sscanf (nonce, "%08X-%04hX-%04hX-%04hX-%012llX", &t1, &t2, &t3, &t4, &t5); + MsnDirectConnPacket *p; - t1 = GUINT32_TO_LE(t1); - t2 = GUINT16_TO_LE(t2); - t3 = GUINT16_TO_LE(t3); - t4 = GUINT16_TO_BE(t4); - t5 = GUINT64_TO_BE(t5); - - directconn->slpheader = g_new0(MsnSlpHeader, 1); + p = g_new0(MsnDirectConnPacket, 1); + p->length = length; + p->data = g_malloc(length); - directconn->slpheader->ack_id = t1; - directconn->slpheader->ack_sub_id = t2 | (t3 << 16); - directconn->slpheader->ack_size = t4 | t5; -} -#endif - -void -msn_directconn_send_msg(MsnDirectConn *directconn, MsnMessage *msg) -{ - char *body; - size_t body_len; - - body = msn_message_gen_slp_body(msg, &body_len); - - msn_directconn_write(directconn, body, body_len); + return p; } static void -read_cb(gpointer data, gint source, PurpleInputCondition cond) +msn_dc_destroy_packet(MsnDirectConnPacket *p) +{ + g_free(p->data); + + if (p->msg) + msn_message_unref(p->msg); + + g_free(p); +} + +MsnDirectConn * +msn_dc_new(MsnSlpCall *slpcall) { - MsnDirectConn* directconn; - char *body; - size_t body_len; - gssize len; + MsnDirectConn *dc; + + g_return_val_if_fail(slpcall != NULL, NULL); + + dc = g_new0(MsnDirectConn, 1); + + if (purple_debug_is_verbose()) + purple_debug_info("msn", "msn_dc_new %p\n", dc); + + dc->slplink = slpcall->slplink; + dc->slpcall = slpcall; + + if (dc->slplink->dc != NULL) + purple_debug_warning("msn", "msn_dc_new: slplink already has an allocated DC!\n"); + + dc->slplink->dc = dc; - purple_debug_info("msn", "read_cb: %d, %d\n", source, cond); + dc->msg_body = NULL; + dc->prev_ack = NULL; + dc->listen_data = NULL; + dc->connect_data = NULL; + dc->listenfd = -1; + dc->listenfd_handle = 0; + dc->connect_timeout_handle = 0; + dc->fd = -1; + dc->recv_handle = 0; + dc->send_handle = 0; + dc->state = DC_STATE_CLOSED; + dc->in_buffer = NULL; + dc->out_queue = g_queue_new(); + dc->msg_pos = -1; + dc->send_connection_info_msg_cb = NULL; + dc->ext_ip = NULL; + dc->timeout_handle = 0; + dc->progress = FALSE; + //dc->num_calls = 1; - directconn = data; + /* TODO: Probably should set this based on buddy caps */ + dc->nonce_type = DC_NONCE_PLAIN; + msn_dc_generate_nonce(dc); + + return dc; +} + +void +msn_dc_destroy(MsnDirectConn *dc) +{ + MsnSlpLink *slplink; + + if (purple_debug_is_verbose()) + purple_debug_info("msn", "msn_dc_destroy %p\n", dc); - /* Let's read the length of the data. */ -#error This code is broken. See the note below. - /* - * TODO: This has problems! First of all, sizeof(body_len) will be - * different on 32bit systems and on 64bit systems (4 bytes - * vs. 8 bytes). - * Secondly, we're reading from a TCP stream. There is no - * guarantee that we have received the number of bytes we're - * trying to read. We need to read into a buffer. If read - * returns <0 then we need to check errno. If errno is EAGAIN - * then don't destroy anything, just exit and wait for more - * data. See every other function in libpurple that does this - * correctly for an example. - */ - len = read(directconn->fd, &body_len, sizeof(body_len)); + g_return_if_fail(dc != NULL); + + slplink = dc->slplink; + + if (dc->slpcall != NULL) + dc->slpcall->wait_for_socket = FALSE; + + slplink->dc = NULL; + + if (slplink->swboard == NULL) + msn_slplink_destroy(slplink); + + g_free(dc->msg_body); + + if (dc->prev_ack) { + msn_slpmsg_destroy(dc->prev_ack); + } + + if (dc->listen_data != NULL) { + purple_network_listen_cancel(dc->listen_data); + } + + if (dc->connect_data != NULL) { + purple_proxy_connect_cancel(dc->connect_data); + } + + if (dc->listenfd != -1) { + purple_network_remove_port_mapping(dc->listenfd); + close(dc->listenfd); + } - if (len <= 0) - { - /* ERROR */ - purple_debug_error("msn", "error reading\n"); + if (dc->listenfd_handle != 0) { + purple_input_remove(dc->listenfd_handle); + } + + if (dc->connect_timeout_handle != 0) { + purple_timeout_remove(dc->connect_timeout_handle); + } + + if (dc->fd != -1) { + close(dc->fd); + } + + if (dc->send_handle != 0) { + purple_input_remove(dc->send_handle); + } - if (directconn->inpa) - purple_input_remove(directconn->inpa); + if (dc->recv_handle != 0) { + purple_input_remove(dc->recv_handle); + } + + g_free(dc->in_buffer); - close(directconn->fd); + if (dc->out_queue != NULL) { + while (!g_queue_is_empty(dc->out_queue)) + msn_dc_destroy_packet( g_queue_pop_head(dc->out_queue) ); - msn_directconn_destroy(directconn); + g_queue_free(dc->out_queue); + } - return; + g_free(dc->ext_ip); + + if (dc->timeout_handle != 0) { + purple_timeout_remove(dc->timeout_handle); } - body_len = GUINT32_FROM_LE(body_len); + g_free(dc); +} + +/* +void +msn_dc_ref(MsnDirectConn *dc) +{ + g_return_if_fail(dc != NULL); + + dc->num_calls++; +} + +void +msn_dc_unref(MsnDirectConn *dc) +{ + g_return_if_fail(dc != NULL); + + + if (dc->num_calls > 0) { + dc->num_calls--; + } +} +*/ + +void +msn_dc_send_invite(MsnDirectConn *dc) +{ + MsnSlpCall *slpcall; + MsnSlpMessage *msg; + gchar *header; + + if (purple_debug_is_verbose()) + purple_debug_info("msn", "msn_dc_send_invite %p\n", dc); + + g_return_if_fail(dc != NULL); + + slpcall = dc->slpcall; + g_return_if_fail(slpcall != NULL); - purple_debug_info("msn", "body_len=%" G_GSIZE_FORMAT "\n", body_len); + header = g_strdup_printf( + "INVITE MSNMSGR:%s MSNSLP/1.0", + slpcall->slplink->remote_user + ); + + msg = msn_slpmsg_sip_new( + slpcall, + 0, + header, + slpcall->branch, + "application/x-msnmsgr-transrespbody", + dc->msg_body + ); + msg->info = "DC INVITE"; + msg->text_body = TRUE; + g_free(header); + g_free(dc->msg_body); + dc->msg_body = NULL; + + msn_slplink_queue_slpmsg(slpcall->slplink, msg); +} + +void +msn_dc_send_ok(MsnDirectConn *dc) +{ + if (purple_debug_is_verbose()) + purple_debug_info("msn", "msn_dc_send_ok %p\n", dc); + + g_return_if_fail(dc != NULL); + + msn_slp_send_ok(dc->slpcall, dc->slpcall->branch, + "application/x-msnmsgr-transrespbody", dc->msg_body); + g_free(dc->msg_body); + dc->msg_body = NULL; + + msn_slplink_send_slpmsg(dc->slpcall->slplink, dc->prev_ack); + msn_slpmsg_destroy(dc->prev_ack); + dc->prev_ack = NULL; + msn_slplink_send_queued_slpmsgs(dc->slpcall->slplink); +} + +void +msn_dc_fallback_to_p2p(MsnDirectConn *dc) +{ + MsnSlpLink *slplink; + MsnSlpCall *slpcall; + GQueue *queue = NULL; - if (body_len <= 0) - { - /* ERROR */ - purple_debug_error("msn", "error reading\n"); + purple_debug_info("msn", "msn_dc_try_fallback_to_p2p %p\n", dc); + + g_return_if_fail(dc != NULL); + + slpcall = dc->slpcall; + slplink = msn_slplink_ref(dc->slplink); + if (slpcall && !g_queue_is_empty(dc->out_queue)) { + queue = dc->out_queue; + dc->out_queue = NULL; + } + + msn_dc_destroy(dc); + + if (slpcall) { + msn_slpcall_session_init(slpcall); + if (queue) { + while (!g_queue_is_empty(queue)) { + MsnDirectConnPacket *p = g_queue_pop_head(queue); + msn_slplink_send_msg(slplink, p->msg); + msn_dc_destroy_packet(p); + } + g_queue_free(queue); + } + } + msn_slplink_unref(slplink); +} + +static void +msn_dc_parse_binary_header(MsnDirectConn *dc) +{ + MsnSlpHeader *h; + MsnDcContext *context; + + g_return_if_fail(dc != NULL); + + h = &dc->header; + /* Skip packet size */ + context = (MsnDcContext *)(dc->in_buffer + 4); - if (directconn->inpa) - purple_input_remove(directconn->inpa); + h->session_id = GUINT32_FROM_LE(context->session_id); + h->id = GUINT32_FROM_LE(context->seq_id); + h->offset = GUINT64_FROM_LE(context->offset); + h->total_size = GUINT64_FROM_LE(context->total_size); + h->length = GUINT32_FROM_LE(context->length); + h->flags = GUINT32_FROM_LE(context->flags); + h->ack_id = GUINT32_FROM_LE(context->ack_id); + h->ack_sub_id = GUINT32_FROM_LE(context->ack_uid); + h->ack_size = GUINT64_FROM_LE(context->ack_size); +} + +static const gchar * +msn_dc_serialize_binary_header(MsnDirectConn *dc) { + MsnSlpHeader *h; + static MsnDcContext bin_header; + + g_return_val_if_fail(dc != NULL, NULL); + + h = &dc->header; - close(directconn->fd); + bin_header.session_id = GUINT32_TO_LE(h->session_id); + bin_header.seq_id = GUINT32_TO_LE(h->id); + bin_header.offset = GUINT64_TO_LE(h->offset); + bin_header.total_size = GUINT64_TO_LE(h->total_size); + bin_header.length = GUINT32_TO_LE(h->length); + bin_header.flags = GUINT32_TO_LE(h->flags); + bin_header.ack_id = GUINT32_TO_LE(h->ack_id); + bin_header.ack_uid = GUINT32_TO_LE(h->ack_sub_id); + bin_header.ack_size = GUINT64_TO_LE(h->ack_size); + + return (const gchar *)&bin_header; +} - msn_directconn_destroy(directconn); +static void +msn_dc_send_cb(gpointer data, gint fd, PurpleInputCondition cond) +{ + MsnDirectConn *dc = data; + MsnDirectConnPacket *p; + int bytes_to_send; + int bytes_sent; + g_return_if_fail(dc != NULL); + g_return_if_fail(fd != -1); + + if (g_queue_is_empty(dc->out_queue)) { + if (dc->send_handle != 0) { + purple_input_remove(dc->send_handle); + dc->send_handle = 0; + } return; } - body = g_try_malloc(body_len); - - if (body != NULL) - { - /* Let's read the data. */ - len = read(directconn->fd, body, body_len); + p = g_queue_peek_head(dc->out_queue); - purple_debug_info("msn", "len=%" G_GSIZE_FORMAT "\n", len); - } - else - { - purple_debug_error("msn", "Failed to allocate memory for read\n"); - len = 0; + if (dc->msg_pos < 0) { + /* First we send the length of the packet */ + guint32 len = GUINT32_TO_LE(p->length); + bytes_sent = send(fd, &len, 4, 0); + if (bytes_sent < 0) { + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) + return; + + purple_debug_warning("msn", "msn_dc_send_cb: send error\n"); + msn_dc_destroy(dc); + return; + } + dc->msg_pos = 0; } - if (len > 0) - { - MsnMessage *msg; - -#ifdef DEBUG_DC - str = g_strdup_printf("%s/msntest/r%.4d.bin", g_get_home_dir(), directconn->c); - - FILE *tf = g_fopen(str, "w"); - fwrite(body, 1, len, tf); - fclose(tf); - - g_free(str); -#endif - - directconn->c++; + bytes_to_send = p->length - dc->msg_pos; + bytes_sent = send(fd, p->data + dc->msg_pos, bytes_to_send, 0); + if (bytes_sent < 0) { + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) + return; - msg = msn_message_new_msnslp(); - msn_message_parse_slp_body(msg, body, body_len); - - purple_debug_info("msn", "directconn: process_msg\n"); - msn_slplink_process_msg(directconn->slplink, msg); - } - else - { - /* ERROR */ - purple_debug_error("msn", "error reading\n"); - - if (directconn->inpa) - purple_input_remove(directconn->inpa); - - close(directconn->fd); - - msn_directconn_destroy(directconn); + purple_debug_warning("msn", "msn_dc_send_cb: send error\n"); + msn_dc_destroy(dc); + return; } - g_free(body); + dc->progress = TRUE; + + dc->msg_pos += bytes_sent; + if (dc->msg_pos == p->length) { + if (p->sent_cb != NULL) + p->sent_cb(p); + + g_queue_pop_head(dc->out_queue); + msn_dc_destroy_packet(p); + + dc->msg_pos = -1; + } } static void -connect_cb(gpointer data, gint source, PurpleInputCondition cond) +msn_dc_enqueue_packet(MsnDirectConn *dc, MsnDirectConnPacket *p) { - MsnDirectConn* directconn; - int fd; - - purple_debug_misc("msn", "directconn: connect_cb: %d, %d.\n", source, cond); - - directconn = data; - directconn->connect_data = NULL; - - if (TRUE) - { - fd = source; - } - else - { - struct sockaddr_in client_addr; - socklen_t client; - fd = accept (source, (struct sockaddr *)&client_addr, &client); - } - - directconn->fd = fd; + gboolean was_empty; - if (fd > 0) - { - directconn->inpa = purple_input_add(fd, PURPLE_INPUT_READ, read_cb, - directconn); - - if (TRUE) - { - /* Send foo. */ - msn_directconn_write(directconn, "foo", strlen("foo") + 1); + was_empty = g_queue_is_empty(dc->out_queue); + g_queue_push_tail(dc->out_queue, p); - /* Send Handshake */ - msn_directconn_send_handshake(directconn); - } - else - { - } - } - else - { - /* ERROR */ - purple_debug_error("msn", "could not add input\n"); - - if (directconn->inpa) - purple_input_remove(directconn->inpa); - - close(directconn->fd); + if (was_empty && dc->send_handle == 0) { + dc->send_handle = purple_input_add(dc->fd, PURPLE_INPUT_WRITE, msn_dc_send_cb, dc); + msn_dc_send_cb(dc, dc->fd, PURPLE_INPUT_WRITE); } } static void -directconn_connect_cb(gpointer data, gint source, const gchar *error_message) +msn_dc_send_foo(MsnDirectConn *dc) +{ + MsnDirectConnPacket *p; + + if (purple_debug_is_verbose()) + purple_debug_info("msn", "msn_dc_send_foo %p\n", dc); + + p = msn_dc_new_packet(4); + + memcpy(p->data, "foo\0", 4); + + msn_dc_enqueue_packet(dc, p); +} + +static void +msn_dc_send_handshake_with_nonce(MsnDirectConn *dc, MsnDirectConnPacket *p) { - if (error_message) - purple_debug_error("msn", "Error making direct connection: %s\n", error_message); + const gchar *h; + + h = msn_dc_serialize_binary_header(dc); + memcpy(p->data, h, DC_PACKET_HEADER_SIZE); + + memcpy(p->data + offsetof(MsnDcContext, ack_id), dc->nonce, 16); + + msn_dc_enqueue_packet(dc, p); +} - connect_cb(data, source, PURPLE_INPUT_READ); +static void +msn_dc_send_handshake(MsnDirectConn *dc) +{ + MsnDirectConnPacket *p; + + p = msn_dc_new_packet(DC_PACKET_HEADER_SIZE); + + dc->header.session_id = 0; + dc->header.id = dc->slpcall->slplink->slp_seq_id++; + dc->header.offset = 0; + dc->header.total_size = 0; + dc->header.length = 0; + dc->header.flags = 0x100; + + msn_dc_send_handshake_with_nonce(dc, p); } -gboolean -msn_directconn_connect(MsnDirectConn *directconn, const char *host, int port) +static void +msn_dc_send_handshake_reply(MsnDirectConn *dc) { - MsnSession *session; + MsnDirectConnPacket *p; + + p = msn_dc_new_packet(DC_PACKET_HEADER_SIZE); + + dc->header.id = dc->slpcall->slplink->slp_seq_id++; + dc->header.length = 0; - g_return_val_if_fail(directconn != NULL, FALSE); - g_return_val_if_fail(host != NULL, TRUE); - g_return_val_if_fail(port > 0, FALSE); + msn_dc_send_handshake_with_nonce(dc, p); +} - session = directconn->slplink->session; +static gboolean +msn_dc_verify_handshake(MsnDirectConn *dc, guint32 packet_length) +{ + guchar nonce[16]; + gchar nonce_hash[37]; + + if (packet_length != DC_PACKET_HEADER_SIZE) + return FALSE; + + memcpy(nonce, dc->in_buffer + 4 + offsetof(MsnDcContext, ack_id), 16); -#if 0 - if (session->http_method) - { - servconn->http_data->gateway_host = g_strdup(host); - } -#endif + if (dc->nonce_type == DC_NONCE_PLAIN) { + if (memcmp(dc->nonce, nonce, 16) == 0) { + purple_debug_info("msn", + "Nonce from buddy request and nonce from DC attempt match, " + "allowing direct connection\n"); + return TRUE; + } else { + purple_debug_warning("msn", + "Nonce from buddy request and nonce from DC attempt " + "don't match, ignoring direct connection\n"); + return FALSE; + } + + } else if (dc->nonce_type == DC_NONCE_SHA1) { + msn_dc_calculate_nonce_hash(dc->nonce_type, nonce, nonce_hash); - directconn->connect_data = purple_proxy_connect(NULL, session->account, - host, port, directconn_connect_cb, directconn); + if (g_str_equal(dc->remote_nonce, nonce_hash)) { + purple_debug_info("msn", + "Received nonce %s from buddy request " + "and calculated nonce %s from DC attempt. " + "Nonces match, allowing direct connection\n", + dc->remote_nonce, nonce_hash); + return TRUE; + } else { + purple_debug_warning("msn", + "Received nonce %s from buddy request " + "and calculated nonce %s from DC attempt. " + "Nonces don't match, ignoring direct connection\n", + dc->remote_nonce, nonce_hash); + return FALSE; + } + } else + return FALSE; +} - return (directconn->connect_data != NULL); +static void +msn_dc_send_packet_cb(MsnDirectConnPacket *p) +{ + if (p->msg != NULL && p->msg->ack_cb != NULL) + p->msg->ack_cb(p->msg, p->msg->ack_data); } void -msn_directconn_listen(MsnDirectConn *directconn) +msn_dc_enqueue_msg(MsnDirectConn *dc, MsnMessage *msg) { - int port; - int fd; + MsnDirectConnPacket *p; + guint32 length; + + length = msg->body_len + DC_PACKET_HEADER_SIZE; + p = msn_dc_new_packet(length); + + memcpy(p->data, &msg->msnslp_header, DC_PACKET_HEADER_SIZE); + memcpy(p->data + DC_PACKET_HEADER_SIZE, msg->body, msg->body_len); + + p->sent_cb = msn_dc_send_packet_cb; + p->msg = msn_message_ref(msg); + + msn_dc_enqueue_packet(dc, p); +} - port = 7000; +static int +msn_dc_process_packet(MsnDirectConn *dc, guint32 packet_length) +{ + g_return_val_if_fail(dc != NULL, DC_PROCESS_ERROR); + + switch (dc->state) { + case DC_STATE_CLOSED: + break; + + case DC_STATE_FOO: + /* FOO message is always 4 bytes long */ + if (packet_length != 4 || memcmp(dc->in_buffer, "\4\0\0\0foo", 8) != 0) + return DC_PROCESS_FALLBACK; + + dc->state = DC_STATE_HANDSHAKE; + break; - for (fd = -1; fd < 0;) - fd = create_listener(++port); + case DC_STATE_HANDSHAKE: + if (!msn_dc_verify_handshake(dc, packet_length)) + return DC_PROCESS_FALLBACK; + + msn_dc_send_handshake_reply(dc); + dc->state = DC_STATE_ESTABLISHED; - directconn->fd = fd; + msn_slpcall_session_init(dc->slpcall); + dc->slpcall = NULL; + break; + + case DC_STATE_HANDSHAKE_REPLY: + if (!msn_dc_verify_handshake(dc, packet_length)) + return DC_PROCESS_FALLBACK; + + dc->state = DC_STATE_ESTABLISHED; - directconn->inpa = purple_input_add(fd, PURPLE_INPUT_READ, connect_cb, - directconn); + msn_slpcall_session_init(dc->slpcall); + dc->slpcall = NULL; + break; + + case DC_STATE_ESTABLISHED: + msn_slplink_process_msg( + dc->slplink, + &dc->header, + dc->in_buffer + 4 + DC_PACKET_HEADER_SIZE, + dc->header.length + ); - directconn->port = port; - directconn->c = 0; + /* + if (dc->num_calls == 0) { + msn_dc_destroy(dc); + + return DC_PROCESS_CLOSE; + } + */ + break; + } + + return DC_PROCESS_OK; } -MsnDirectConn* -msn_directconn_new(MsnSlpLink *slplink) +static void +msn_dc_recv_cb(gpointer data, gint fd, PurpleInputCondition cond) { - MsnDirectConn *directconn; + MsnDirectConn *dc; + int free_buf_space; + int bytes_received; + guint32 packet_length; + + g_return_if_fail(data != NULL); + g_return_if_fail(fd != -1); + + dc = data; + free_buf_space = dc->in_size - dc->in_pos; + + bytes_received = recv(fd, dc->in_buffer + dc->in_pos, free_buf_space, 0); + if (bytes_received < 0) { + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) + return; + + purple_debug_warning("msn", "msn_dc_recv_cb: recv error\n"); + + if(dc->state != DC_STATE_ESTABLISHED) + msn_dc_fallback_to_p2p(dc); + else + msn_dc_destroy(dc); + return; - directconn = g_new0(MsnDirectConn, 1); + } else if (bytes_received == 0) { + /* EOF. Remote side closed connection. */ + purple_debug_info("msn", "msn_dc_recv_cb: recv EOF\n"); + + if(dc->state != DC_STATE_ESTABLISHED) + msn_dc_fallback_to_p2p(dc); + else + msn_dc_destroy(dc); + return; + } + + dc->progress = TRUE; + + dc->in_pos += bytes_received; + + /* Wait for packet length */ + while (dc->in_pos >= 4) { + packet_length = GUINT32_FROM_LE(*((guint32*)dc->in_buffer)); + + if (packet_length > DC_MAX_PACKET_SIZE) { + /* Oversized packet */ + purple_debug_warning("msn", "msn_dc_recv_cb: oversized packet received\n"); + return; + } - directconn->slplink = slplink; + /* Wait for the whole packet to arrive */ + if (dc->in_pos < 4 + packet_length) + return; + + if (dc->state != DC_STATE_FOO) { + msn_dc_parse_binary_header(dc); + } + + switch (msn_dc_process_packet(dc, packet_length)) { + case DC_PROCESS_CLOSE: + return; + + case DC_PROCESS_FALLBACK: + purple_debug_warning("msn", "msn_dc_recv_cb: packet processing error, fall back to p2p\n"); + msn_dc_fallback_to_p2p(dc); + return; + + } + + if (dc->in_pos > packet_length + 4) { + g_memmove(dc->in_buffer, dc->in_buffer + 4 + packet_length, dc->in_pos - packet_length - 4); + } + + dc->in_pos -= packet_length + 4; + } +} - if (slplink->directconn != NULL) - purple_debug_info("msn", "got_transresp: LEAK\n"); +static gboolean +msn_dc_timeout(gpointer data) +{ + MsnDirectConn *dc = data; + + g_return_val_if_fail(dc != NULL, FALSE); - slplink->directconn = directconn; + if (dc->progress) { + dc->progress = FALSE; + return TRUE; + } else { + dc->timeout_handle = 0; + msn_dc_destroy(dc); + return FALSE; + } +} - return directconn; +static void +msn_dc_init(MsnDirectConn *dc) +{ + g_return_if_fail(dc != NULL); + + dc->in_size = DC_MAX_PACKET_SIZE + 4; + dc->in_pos = 0; + dc->in_buffer = g_malloc(dc->in_size); + + dc->recv_handle = purple_input_add(dc->fd, PURPLE_INPUT_READ, msn_dc_recv_cb, dc); + dc->send_handle = purple_input_add(dc->fd, PURPLE_INPUT_WRITE, msn_dc_send_cb, dc); + + dc->timeout_handle = purple_timeout_add_seconds(DC_TIMEOUT, msn_dc_timeout, dc); } void -msn_directconn_destroy(MsnDirectConn *directconn) +msn_dc_connected_to_peer_cb(gpointer data, gint fd, const gchar *error_msg) { - if (directconn->connect_data != NULL) - purple_proxy_connect_cancel(directconn->connect_data); + MsnDirectConn *dc = data; + + if (purple_debug_is_verbose()) + purple_debug_info("msn", "msn_dc_connected_to_peer_cb %p\n", dc); + + g_return_if_fail(dc != NULL); + + dc->connect_data = NULL; + purple_timeout_remove(dc->connect_timeout_handle); + dc->connect_timeout_handle = 0; + + dc->fd = fd; + if (dc->fd != -1) { + msn_dc_init(dc); + msn_dc_send_foo(dc); + msn_dc_send_handshake(dc); + dc->state = DC_STATE_HANDSHAKE_REPLY; + } +} + +/* + * This callback will be called when we're the server + * and nobody has connected us in DC_CONNECT_TIMEOUT seconds + */ +static gboolean +msn_dc_incoming_connection_timeout_cb(gpointer data) { + MsnDirectConn *dc = data; + MsnSlpCall *slpcall; + + if (purple_debug_is_verbose()) + purple_debug_info("msn", "msn_dc_incoming_connection_timeout_cb %p\n", dc); + + g_return_val_if_fail(dc != NULL, FALSE); + + slpcall = dc->slpcall; + + if (dc->listen_data != NULL) { + purple_network_listen_cancel(dc->listen_data); + dc->listen_data = NULL; + } + + if (dc->listenfd_handle != 0) { + purple_input_remove(dc->listenfd_handle); + dc->listenfd_handle = 0; + } + + if (dc->listenfd != -1) { + purple_network_remove_port_mapping(dc->listenfd); + close(dc->listenfd); + dc->listenfd = -1; + } + + dc->connect_timeout_handle = 0; + msn_dc_fallback_to_p2p(dc); - if (directconn->inpa != 0) - purple_input_remove(directconn->inpa); + return FALSE; +} + +/* + * This callback will be called when we're unable to connect to + * the remote host in DC_CONNECT_TIMEOUT seconds. + */ +gboolean +msn_dc_outgoing_connection_timeout_cb(gpointer data) +{ + MsnDirectConn *dc = data; + + purple_debug_info("msn", "msn_dc_outgoing_connection_timeout_cb %p\n", dc); + + g_return_val_if_fail(dc != NULL, FALSE); + + dc->connect_timeout_handle = 0; + + if (dc->connect_data != NULL) { + purple_proxy_connect_cancel(dc->connect_data); + dc->connect_data = NULL; + } + + if (dc->ext_ip && dc->ext_port) { + /* Try external IP/port if available. */ + dc->connect_data = purple_proxy_connect( + NULL, + dc->slpcall->slplink->session->account, + dc->ext_ip, + dc->ext_port, + msn_dc_connected_to_peer_cb, + dc + ); + + g_free(dc->ext_ip); + dc->ext_ip = NULL; + + if (dc->connect_data) { + dc->connect_timeout_handle = purple_timeout_add_seconds( + DC_CONNECT_TIMEOUT, + msn_dc_outgoing_connection_timeout_cb, + dc + ); + } else { + /* + * Connection failed + * Fall back to P2P transfer + */ + msn_dc_outgoing_connection_timeout_cb(dc); + } + + } else { + /* + * Both internal and external connection attempts failed. + * Fall back to p2p transfer. + */ + msn_dc_fallback_to_p2p(dc); + } - if (directconn->fd >= 0) - close(directconn->fd); + return FALSE; +} + +/* + * This callback will be called when we're the server + * and somebody has connected to us in DC_CONNECT_TIMEOUT seconds. + */ +static void +msn_dc_incoming_connection_cb(gpointer data, gint listenfd, PurpleInputCondition cond) +{ + MsnDirectConn *dc = data; + + if (purple_debug_is_verbose()) + purple_debug_info("msn", "msn_dc_incoming_connection_cb %p\n", dc); + + g_return_if_fail(dc != NULL); + + if (dc->connect_timeout_handle != 0) { + purple_timeout_remove(dc->connect_timeout_handle); + dc->connect_timeout_handle = 0; + } + + if (dc->listenfd_handle != 0) { + purple_input_remove(dc->listenfd_handle); + dc->listenfd_handle = 0; + } + + dc->fd = accept(listenfd, NULL, 0); + + purple_network_remove_port_mapping(dc->listenfd); + close(dc->listenfd); + dc->listenfd = -1; + + if (dc->fd != -1) { + msn_dc_init(dc); + dc->state = DC_STATE_FOO; + } +} + +void +msn_dc_listen_socket_created_cb(int listenfd, gpointer data) +{ + MsnDirectConn *dc = data; + + if (purple_debug_is_verbose()) + purple_debug_info("msn", "msn_dc_listen_socket_created_cb %p\n", dc); + + g_return_if_fail(dc != NULL); + + dc->listen_data = NULL; + + if (listenfd != -1) { + const char *ext_ip; + const char *int_ip; + int port; - if (directconn->nonce != NULL) - g_free(directconn->nonce); + ext_ip = purple_network_get_my_ip(listenfd); + int_ip = purple_network_get_local_system_ip(listenfd); + port = purple_network_get_port_from_fd(listenfd); + + dc->listenfd = listenfd; + dc->listenfd_handle = purple_input_add( + listenfd, + PURPLE_INPUT_READ, + msn_dc_incoming_connection_cb, + dc + ); + dc->connect_timeout_handle = purple_timeout_add_seconds( + DC_CONNECT_TIMEOUT * 2, /* Internal + external connection attempts */ + msn_dc_incoming_connection_timeout_cb, + dc + ); + + if (strcmp(int_ip, ext_ip) != 0) { + dc->msg_body = g_strdup_printf( + "Bridge: TCPv1\r\n" + "Listening: true\r\n" + "%sNonce: {%s}\r\n" + "IPv4External-Addrs: %s\r\n" + "IPv4External-Port: %d\r\n" + "IPv4Internal-Addrs: %s\r\n" + "IPv4Internal-Port: %d\r\n" + "\r\n", - directconn->slplink->directconn = NULL; + dc->nonce_type != DC_NONCE_PLAIN ? "Hashed-" : "", + dc->nonce_hash, + ext_ip, + port, + int_ip, + port + ); + + } else { + dc->msg_body = g_strdup_printf( + "Bridge: TCPv1\r\n" + "Listening: true\r\n" + "%sNonce: {%s}\r\n" + "IPv4External-Addrs: %s\r\n" + "IPv4External-Port: %d\r\n" + "\r\n", - g_free(directconn); + dc->nonce_type != DC_NONCE_PLAIN ? "Hashed-" : "", + dc->nonce_hash, + ext_ip, + port + ); + } + + if (dc->slpcall->wait_for_socket) { + if (dc->send_connection_info_msg_cb != NULL) + dc->send_connection_info_msg_cb(dc); + + dc->slpcall->wait_for_socket = FALSE; + } + } } + diff -r d6f9f4320bf1 -r fb103111bbd5 libpurple/protocols/msn/directconn.h --- a/libpurple/protocols/msn/directconn.h Thu May 20 22:54:26 2010 +0000 +++ b/libpurple/protocols/msn/directconn.h Fri May 21 00:49:41 2010 +0000 @@ -26,36 +26,170 @@ typedef struct _MsnDirectConn MsnDirectConn; +#include "network.h" +#include "proxy.h" +#include "circbuffer.h" + #include "msg.h" #include "slp.h" #include "slplink.h" +#include "slpmsg.h" + +typedef enum +{ + DC_STATE_CLOSED, /*< No socket opened yet */ + DC_STATE_FOO, /*< Waiting for FOO message */ + DC_STATE_HANDSHAKE, /*< Waiting for handshake message */ + DC_STATE_HANDSHAKE_REPLY, /*< Waiting for handshake reply message */ + DC_STATE_ESTABLISHED /*< Handshake complete */ +} MsnDirectConnState; + +typedef enum +{ + DC_PROCESS_OK = 0, + DC_PROCESS_ERROR, + DC_PROCESS_FALLBACK, + DC_PROCESS_CLOSE + +} MsnDirectConnProcessResult; + +typedef enum +{ + DC_NONCE_UNKNOWN, /**< Invalid scheme */ + DC_NONCE_PLAIN, /**< No hashing */ + DC_NONCE_SHA1 /**< First 16 bytes of SHA1 of nonce */ + +} MsnDirectConnNonceType; + +typedef struct _MsnDirectConnPacket MsnDirectConnPacket; + +struct _MsnDirectConnPacket { + guint32 length; + guchar *data; + + void (*sent_cb)(struct _MsnDirectConnPacket*); + MsnMessage *msg; +}; struct _MsnDirectConn { - MsnSlpLink *slplink; - MsnSlpCall *initial_call; + MsnDirectConnState state; /**< Direct connection status */ + MsnSlpLink *slplink; /**< The slplink using this direct connection */ + MsnSlpCall *slpcall; /**< The slpcall which initiated the direct connection */ + char *msg_body; /**< The body of message sent by send_connection_info_msg_cb */ + MsnSlpMessage *prev_ack; /**< The saved SLP ACK message */ - PurpleProxyConnectData *connect_data; + MsnDirectConnNonceType nonce_type; /**< The type of nonce hashing */ + guchar nonce[16]; /**< The nonce used for handshake */ + gchar nonce_hash[37]; /**< The hash of nonce */ + gchar remote_nonce[37]; /**< The remote side's nonce */ - gboolean acked; + PurpleNetworkListenData *listen_data; /**< The pending socket creation request */ + PurpleProxyConnectData *connect_data; /**< The pending connection attempt */ + int listenfd; /**< The socket we're listening for incoming connections */ + guint listenfd_handle; /**< The timeout handle for incoming connection */ + guint connect_timeout_handle; /**< The timeout handle for outgoing connection */ - char *nonce; + int fd; /**< The direct connection socket */ + guint recv_handle; /**< The incoming data callback handle */ + guint send_handle; /**< The outgoing data callback handle */ - int fd; + gchar *in_buffer; /**< The receive buffer */ + int in_size; /**< The receive buffer size */ + int in_pos; /**< The first free position in receive buffer */ + GQueue *out_queue; /**< The outgoing packet queue */ + int msg_pos; /**< The position of next byte to be sent in the actual packet */ - int port; - int inpa; + MsnSlpHeader header; /**< SLP header for parsing / serializing */ + + /** The callback used for sending information to the peer about the opened socket */ + void (*send_connection_info_msg_cb)(MsnDirectConn *); - int c; + gchar *ext_ip; /**< Our external IP address */ + int ext_port; /**< Our external port */ + + guint timeout_handle; + gboolean progress; + + //int num_calls; /**< The number of slpcalls using this direct connection */ }; -MsnDirectConn *msn_directconn_new(MsnSlpLink *slplink); -gboolean msn_directconn_connect(MsnDirectConn *directconn, - const char *host, int port); -void msn_directconn_listen(MsnDirectConn *directconn); -void msn_directconn_send_msg(MsnDirectConn *directconn, MsnMessage *msg); -void msn_directconn_parse_nonce(MsnDirectConn *directconn, const char *nonce); -void msn_directconn_destroy(MsnDirectConn *directconn); -void msn_directconn_send_handshake(MsnDirectConn *directconn); +#define DC_CONNECT_TIMEOUT 5 +#define DC_TIMEOUT 60 + +/* + * Queues an MSN message to be sent via direct connection. + */ +void +msn_dc_enqueue_msg(MsnDirectConn *dc, MsnMessage *msg); + +/* + * Creates, initializes, and returns a new MsnDirectConn structure. + */ +MsnDirectConn * +msn_dc_new(MsnSlpCall *slplink); + +/* + * Destroys an MsnDirectConn structure. Frees every buffer allocated earlier + * restores saved callbacks, etc. + */ +void +msn_dc_destroy(MsnDirectConn *dc); + +/* + * Fallback to switchboard connection. Used when neither side is able to + * create a listening socket. + */ +void +msn_dc_fallback_to_p2p(MsnDirectConn *dc); + +/* + * Increases the slpcall counter in DC. The direct connection remains open + * until all slpcalls using it are destroyed. + */ +void +msn_dc_ref(MsnDirectConn *dc); + +/* + * Decrease the slpcall counter in DC. The direct connection remains open + * until all slpcalls using it are destroyed. + */ +void +msn_dc_unref(MsnDirectConn *dc); + +/* + * Sends a direct connect INVITE message on the associated slplink + * with the corresponding connection type and information. + */ +void +msn_dc_send_invite(MsnDirectConn *dc); + +/* + * Sends a direct connect OK message as a response to an INVITE received earliaer + * on the corresponding slplink. + */ +void +msn_dc_send_ok(MsnDirectConn *dc); + +/* + * This callback will be called when we're successfully connected to + * the remote host. + */ +void +msn_dc_connected_to_peer_cb(gpointer data, gint fd, const gchar *error_msg); + +/* + * This callback will be called when we're unable to connect to + * the remote host in DC_CONNECT_TIMEOUT seconds. + */ +gboolean +msn_dc_outgoing_connection_timeout_cb(gpointer data); + +/* + * This callback will be called when the listening socket is successfully + * created and its parameters (IP/port) are available. + */ +void +msn_dc_listen_socket_created_cb(int listenfd, gpointer data); #endif /* MSN_DIRECTCONN_H */ diff -r d6f9f4320bf1 -r fb103111bbd5 libpurple/protocols/msn/msg.c --- a/libpurple/protocols/msn/msg.c Thu May 20 22:54:26 2010 +0000 +++ b/libpurple/protocols/msn/msg.c Fri May 21 00:49:41 2010 +0000 @@ -1117,7 +1117,8 @@ msn_invite_msg(MsnCmdProc *cmdproc, MsnMessage *msg) { GHashTable *body; - const gchar *guid; + const gchar *command; + const gchar *cookie; gboolean accepted = FALSE; g_return_if_fail(cmdproc != NULL); @@ -1130,59 +1131,64 @@ "Unable to parse invite msg body.\n"); return; } - - guid = g_hash_table_lookup(body, "Application-GUID"); - - if (guid == NULL) { - const gchar *cmd = g_hash_table_lookup( - body, "Invitation-Command"); + + /* + * GUID is NOT always present but Invitation-Command and Invitation-Cookie + * are mandatory. + */ + command = g_hash_table_lookup(body, "Invitation-Command"); + cookie = g_hash_table_lookup(body, "Invitation-Cookie"); - if (cmd && !strcmp(cmd, "CANCEL")) { - const gchar *code = g_hash_table_lookup( - body, "Cancel-Code"); - purple_debug_info("msn", - "MSMSGS invitation cancelled: %s.\n", - code ? code : "no reason given"); - } else - purple_debug_warning("msn", "Invite msg missing " - "Application-GUID.\n"); + if (command == NULL || cookie == NULL) { + purple_debug_warning("msn", + "Invalid invitation message: either Invitation-Command " + "or Invitation-Cookie is missing or invalid.\n" + ); + return; - accepted = TRUE; - - } else if (!strcmp(guid, "{02D3C01F-BF30-4825-A83A-DE7AF41648AA}")) { - purple_debug_info("msn", "Computer call\n"); + } else if (!strcmp(command, "INVITE")) { + const gchar *guid = g_hash_table_lookup(body, "Application-GUID"); + + if (guid == NULL) { + purple_debug_warning("msn", + "Invite msg missing Application-GUID.\n"); - if (cmdproc->session) { - PurpleConversation *conv = NULL; - gchar *from = msg->remote_user; - gchar *buf = NULL; + accepted = TRUE; + + } else if (!strcmp(guid, MSN_FT_GUID)) { + + } else if (!strcmp(guid, "{02D3C01F-BF30-4825-A83A-DE7AF41648AA}")) { + purple_debug_info("msn", "Computer call\n"); - if (from) - conv = purple_find_conversation_with_account( - PURPLE_CONV_TYPE_IM, from, - cmdproc->session->account); - if (conv) - buf = g_strdup_printf( - _("%s sent you a voice chat " - "invite, which is not yet " - "supported."), from); - if (buf) { - purple_conversation_write(conv, NULL, buf, - PURPLE_MESSAGE_SYSTEM | - PURPLE_MESSAGE_NOTIFY, - time(NULL)); - g_free(buf); + if (cmdproc->session) { + PurpleConversation *conv = NULL; + gchar *from = msg->remote_user; + gchar *buf = NULL; + + if (from) + conv = purple_find_conversation_with_account( + PURPLE_CONV_TYPE_IM, from, + cmdproc->session->account); + if (conv) + buf = g_strdup_printf( + _("%s sent you a voice chat " + "invite, which is not yet " + "supported."), from); + if (buf) { + purple_conversation_write(conv, NULL, buf, + PURPLE_MESSAGE_SYSTEM | + PURPLE_MESSAGE_NOTIFY, + time(NULL)); + g_free(buf); + } } + } else { + const gchar *application = g_hash_table_lookup(body, "Application-Name"); + purple_debug_warning("msn", "Unhandled invite msg with GUID %s: %s.\n", + guid, application ? application : "(null)"); } - } else { - const gchar *application = g_hash_table_lookup(body, "Application-Name"); - purple_debug_warning("msn", "Unhandled invite msg with GUID %s: %s.\n", - guid, application ? application : "(null)"); - } - - if (!accepted) { - const gchar *cookie = g_hash_table_lookup(body, "Invitation-Cookie"); - if (cookie) { + + if (!accepted) { MsnSwitchBoard *swboard = cmdproc->data; char *text; MsnMessage *cancel; @@ -1202,6 +1208,17 @@ msn_switchboard_send_msg(swboard, cancel, TRUE); msn_message_destroy(cancel); } + + } else if (!strcmp(command, "CANCEL")) { + const gchar *code = g_hash_table_lookup(body, "Cancel-Code"); + purple_debug_info("msn", "MSMSGS invitation cancelled: %s.\n", + code ? code : "no reason given"); + + } else { + /* + * Some other already established invitation session. + * Can be retrieved by Invitation-Cookie. + */ } g_hash_table_destroy(body); diff -r d6f9f4320bf1 -r fb103111bbd5 libpurple/protocols/msn/slp.c --- a/libpurple/protocols/msn/slp.c Thu May 20 22:54:26 2010 +0000 +++ b/libpurple/protocols/msn/slp.c Fri May 21 00:49:41 2010 +0000 @@ -25,23 +25,20 @@ #include "slp.h" #include "slpcall.h" #include "slpmsg.h" +#include "msnutils.h" #include "object.h" #include "user.h" #include "switchboard.h" +#include "directconn.h" #include "smiley.h" /* ms to delay between sending buddy icon requests to the server. */ #define BUDDY_ICON_DELAY 20 -static void send_ok(MsnSlpCall *slpcall, const char *branch, - const char *type, const char *content); +static void request_user_display(MsnUser *user); -static void send_decline(MsnSlpCall *slpcall, const char *branch, - const char *type, const char *content); - -static void request_user_display(MsnUser *user); /************************************************************************** * Util @@ -91,7 +88,7 @@ content = g_strdup_printf("SessionID: %lu\r\n\r\n", slpcall->session_id); - send_ok(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody", + msn_slp_send_ok(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody", content); g_free(content); @@ -120,7 +117,7 @@ content = g_strdup_printf("SessionID: %lu\r\n\r\n", slpcall->session_id); - send_decline(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody", + msn_slp_send_decline(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody", content); g_free(content); @@ -199,6 +196,7 @@ gsize size) { PurpleXfer *xfer = slpcall->xfer; + purple_xfer_set_completed(xfer, TRUE); purple_xfer_end(xfer); } @@ -207,36 +205,8 @@ * SLP Control **************************************************************************/ -#if 0 -static void -got_transresp(MsnSlpCall *slpcall, const char *nonce, - const char *ips_str, int port) -{ - MsnDirectConn *directconn; - char **ip_addrs, **c; - - directconn = msn_directconn_new(slpcall->slplink); - - directconn->initial_call = slpcall; - - /* msn_directconn_parse_nonce(directconn, nonce); */ - directconn->nonce = g_strdup(nonce); - - ip_addrs = g_strsplit(ips_str, " ", -1); - - for (c = ip_addrs; *c != NULL; c++) - { - purple_debug_info("msn", "ip_addr = %s\n", *c); - if (msn_directconn_connect(directconn, *c, port)) - break; - } - - g_strfreev(ip_addrs); -} -#endif - -static void -send_ok(MsnSlpCall *slpcall, const char *branch, +void +msn_slp_send_ok(MsnSlpCall *slpcall, const char *branch, const char *type, const char *content) { MsnSlpLink *slplink; @@ -253,12 +223,10 @@ slpmsg->text_body = TRUE; msn_slplink_queue_slpmsg(slplink, slpmsg); - - msn_slpcall_session_init(slpcall); } -static void -send_decline(MsnSlpCall *slpcall, const char *branch, +void +msn_slp_send_decline(MsnSlpCall *slpcall, const char *branch, const char *type, const char *content) { MsnSlpLink *slplink; @@ -309,6 +277,188 @@ return NULL; } +static char * +parse_dc_nonce(const char *content, MsnDirectConnNonceType *ntype) +{ + char *nonce; + + *ntype = DC_NONCE_UNKNOWN; + + nonce = get_token(content, "Hashed-Nonce: {", "}\r\n"); + if (nonce) { + *ntype = DC_NONCE_SHA1; + } else { + guint32 n1, n5; + guint16 n2, n3, n4, n6; + nonce = get_token(content, "Nonce: {", "}\r\n"); + *ntype = DC_NONCE_PLAIN; + if (sscanf(nonce, "%08x-%04hx-%04hx-%04hx-%08x%04hx", + &n1, &n2, &n3, &n4, &n5, &n6) == 6) { + g_free(nonce); + nonce = g_malloc(16); + *(guint32 *)(nonce + 0) = GUINT32_TO_LE(n1); + *(guint16 *)(nonce + 4) = GUINT16_TO_LE(n2); + *(guint16 *)(nonce + 6) = GUINT16_TO_LE(n3); + *(guint16 *)(nonce + 8) = GUINT16_TO_BE(n4); + *(guint32 *)(nonce + 10) = GUINT32_TO_BE(n5); + *(guint16 *)(nonce + 14) = GUINT16_TO_BE(n6); + } else { + /* Invalid nonce, so ignore request */ + g_free(nonce); + nonce = NULL; + } + } + + return nonce; +} + +static gboolean +msn_slp_process_transresp(MsnSlpCall *slpcall, const char *content) +{ + /* A direct connection negotiation response */ + char *bridge; + char *nonce; + char *listening; + MsnDirectConn *dc = slpcall->slplink->dc; + MsnDirectConnNonceType ntype; + gboolean result = FALSE; + + purple_debug_info("msn", "process_transresp\n"); + + g_return_val_if_fail(dc != NULL, FALSE); + g_return_val_if_fail(dc->state == DC_STATE_CLOSED, FALSE); + + bridge = get_token(content, "Bridge: ", "\r\n"); + nonce = parse_dc_nonce(content, &ntype); + listening = get_token(content, "Listening: ", "\r\n"); + if (listening && bridge && !strcmp(bridge, "TCPv1")) { + /* Ok, the client supports direct TCP connection */ + + /* We always need this. */ + if (ntype == DC_NONCE_SHA1) { + strncpy(dc->remote_nonce, nonce, 36); + dc->remote_nonce[36] = '\0'; + } + + if (!strcasecmp(listening, "false")) { + if (dc->listen_data != NULL) { + /* + * We'll listen for incoming connections but + * the listening socket isn't ready yet so we cannot + * send the INVITE packet now. Put the slpcall into waiting mode + * and let the callback send the invite. + */ + slpcall->wait_for_socket = TRUE; + + } else if (dc->listenfd != -1) { + /* The listening socket is ready. Send the INVITE here. */ + msn_dc_send_invite(dc); + + } else { + /* We weren't able to create a listener either. Use SB. */ + msn_dc_fallback_to_p2p(dc); + } + + } else { + /* + * We should connect to the client so parse + * IP/port from response. + */ + char *ip, *port_str; + int port = 0; + + if (ntype == DC_NONCE_PLAIN) { + /* Only needed for listening side. */ + memcpy(dc->nonce, nonce, 16); + } + + /* Cancel any listen attempts because we don't need them. */ + if (dc->listenfd_handle != 0) { + purple_input_remove(dc->listenfd_handle); + dc->listenfd_handle = 0; + } + if (dc->connect_timeout_handle != 0) { + purple_timeout_remove(dc->connect_timeout_handle); + dc->connect_timeout_handle = 0; + } + if (dc->listenfd != -1) { + purple_network_remove_port_mapping(dc->listenfd); + close(dc->listenfd); + dc->listenfd = -1; + } + if (dc->listen_data != NULL) { + purple_network_listen_cancel(dc->listen_data); + dc->listen_data = NULL; + } + + /* Save external IP/port for later use. We'll try local connection first. */ + dc->ext_ip = get_token(content, "IPv4External-Addrs: ", "\r\n"); + port_str = get_token(content, "IPv4External-Port: ", "\r\n"); + if (port_str) { + dc->ext_port = atoi(port_str); + g_free(port_str); + } + + ip = get_token(content, "IPv4Internal-Addrs: ", "\r\n"); + port_str = get_token(content, "IPv4Internal-Port: ", "\r\n"); + if (port_str) { + port = atoi(port_str); + g_free(port_str); + } + + if (ip && port) { + /* Try internal address first */ + dc->connect_data = purple_proxy_connect( + NULL, + slpcall->slplink->session->account, + ip, + port, + msn_dc_connected_to_peer_cb, + dc + ); + + if (dc->connect_data) { + /* Add connect timeout handle */ + dc->connect_timeout_handle = purple_timeout_add_seconds( + DC_CONNECT_TIMEOUT, + msn_dc_outgoing_connection_timeout_cb, + dc + ); + } else { + /* + * Connection failed + * Try external IP/port (if specified) + */ + msn_dc_outgoing_connection_timeout_cb(dc); + } + + } else { + /* + * Omitted or invalid internal IP address / port + * Try external IP/port (if specified) + */ + msn_dc_outgoing_connection_timeout_cb(dc); + } + + g_free(ip); + } + + result = TRUE; + + } else { + /* + * Invalid direct connect invitation or + * TCP connection is not supported + */ + } + + g_free(listening); + g_free(nonce); + g_free(bridge); + + return result; +} + static void got_sessionreq(MsnSlpCall *slpcall, const char *branch, const char *euf_guid, const char *context) @@ -331,7 +481,7 @@ content = g_strdup_printf("SessionID: %lu\r\n\r\n", slpcall->session_id); - send_ok(slpcall, branch, "application/x-msnmsgr-sessionreqbody", + msn_slp_send_ok(slpcall, branch, "application/x-msnmsgr-sessionreqbody", content); g_free(content); @@ -486,7 +636,7 @@ if (!accepted) { char *content = g_strdup_printf("SessionID: %lu\r\n\r\n", slpcall->session_id); - send_decline(slpcall, branch, "application/x-msnmsgr-sessionreqbody", content); + msn_slp_send_decline(slpcall, branch, "application/x-msnmsgr-sessionreqbody", content); g_free(content); } } @@ -555,92 +705,92 @@ } else if (!strcmp(type, "application/x-msnmsgr-transreqbody")) { - /* A direct connection? */ + /* A direct connection negotiation request */ + char *bridges; + char *nonce; + MsnDirectConnNonceType ntype; - char *listening, *nonce; - char *content; + purple_debug_info("msn", "got_invite: transreqbody received\n"); + + /* Don't do anything if we already have a direct connection */ + if (slpcall->slplink->dc != NULL) + return; - if (FALSE) - { -#if 0 - MsnDirectConn *directconn; - /* const char *ip_addr; */ - char *ip_port; - int port; + bridges = get_token(content, "Bridges: ", "\r\n"); + nonce = parse_dc_nonce(content, &ntype); + if (nonce && bridges && strstr(bridges, "TCPv1") != NULL) { + /* + * Ok, the client supports direct TCP connection + * Try to create a listening port + */ + MsnDirectConn *dc; - /* ip_addr = purple_prefs_get_string("/purple/ft/public_ip"); */ - ip_port = "5190"; - listening = "true"; - nonce = rand_guid(); + dc = msn_dc_new(slpcall); + if (ntype == DC_NONCE_PLAIN) { + /* There is only one nonce for plain auth. */ + dc->nonce_type = ntype; + memcpy(dc->nonce, nonce, 16); + } else if (ntype == DC_NONCE_SHA1) { + /* Each side has a nonce in SHA1 auth. */ + dc->nonce_type = ntype; + strncpy(dc->remote_nonce, nonce, 36); + dc->remote_nonce[36] = '\0'; + } - directconn = msn_directconn_new(slplink); - - /* msn_directconn_parse_nonce(directconn, nonce); */ - directconn->nonce = g_strdup(nonce); - - msn_directconn_listen(directconn); + dc->listen_data = purple_network_listen_range( + 0, 0, + SOCK_STREAM, + msn_dc_listen_socket_created_cb, + dc + ); - port = directconn->port; + if (dc->listen_data == NULL) { + /* Listen socket creation failed */ + + purple_debug_info("msn", "got_invite: listening failed\n"); - content = g_strdup_printf( - "Bridge: TCPv1\r\n" - "Listening: %s\r\n" - "Nonce: {%s}\r\n" - "Ipv4Internal-Addrs: 192.168.0.82\r\n" - "Ipv4Internal-Port: %d\r\n" - "\r\n", - listening, - nonce, - port); -#endif - } - else - { - listening = "false"; - nonce = g_strdup("00000000-0000-0000-0000-000000000000"); + if (dc->nonce_type != DC_NONCE_PLAIN) + msn_slp_send_ok(slpcall, branch, + "application/x-msnmsgr-transrespbody", + "Bridge: TCPv1\r\n" + "Listening: false\r\n" + "Hashed-Nonce: {00000000-0000-0000-0000-000000000000}\r\n" + "\r\n"); + else + msn_slp_send_ok(slpcall, branch, + "application/x-msnmsgr-transrespbody", + "Bridge: TCPv1\r\n" + "Listening: false\r\n" + "Nonce: {00000000-0000-0000-0000-000000000000}\r\n" + "\r\n"); - content = g_strdup_printf( - "Bridge: TCPv1\r\n" - "Listening: %s\r\n" - "Nonce: {%s}\r\n" - "\r\n", - listening, - nonce); + } else { + /* + * Listen socket created successfully. + * Don't send anything here because we don't know the parameters + * of the created socket yet. msn_dc_send_ok will be called from + * the callback function: dc_listen_socket_created_cb + */ + purple_debug_info("msn", "got_invite: listening socket created\n"); + + dc->send_connection_info_msg_cb = msn_dc_send_ok; + slpcall->wait_for_socket = TRUE; + } + + } else { + /* + * Invalid direct connect invitation or + * TCP connection is not supported. + */ } - send_ok(slpcall, branch, - "application/x-msnmsgr-transrespbody", content); - - g_free(content); g_free(nonce); + g_free(bridges); } else if (!strcmp(type, "application/x-msnmsgr-transrespbody")) { -#if 0 - char *ip_addrs; - char *temp; - char *nonce; - int port; - - nonce = get_token(content, "Nonce: {", "}\r\n"); - if (ip_addrs == NULL) - return; - - ip_addrs = get_token(content, "IPv4Internal-Addrs: ", "\r\n"); - - temp = get_token(content, "IPv4Internal-Port: ", "\r\n"); - if (temp != NULL) - port = atoi(temp); - else - port = -1; - g_free(temp); - - if (port > 0) - got_transresp(slpcall, nonce, ip_addrs, port); - - g_free(nonce); - g_free(ip_addrs); -#endif + /* A direct connection negotiation response */ + msn_slp_process_transresp(slpcall, content); } } @@ -653,52 +803,97 @@ if (!strcmp(type, "application/x-msnmsgr-sessionreqbody")) { -#if 0 - if (slpcall->type == MSN_SLPCALL_DC) - { - /* First let's try a DirectConnection. */ + char *content; + char *header; + char *nonce = NULL; + MsnSlpMessage *msg; + MsnDirectConn *dc; + MsnUser *user; + + if (slpcall->slplink->dc != NULL) { + /* If we already have an established direct connection + * then just start the transfer. + */ + msn_slpcall_session_init(slpcall); + return; + } + + user = msn_userlist_find_user(slpcall->slplink->session->userlist, + slpcall->slplink->remote_user); + if (!user || !(user->clientid & 0xF0000000)) { + /* Just start a normal SB transfer. */ + msn_slpcall_session_init(slpcall); + return; + } - MsnSlpLink *slplink; - MsnSlpMessage *slpmsg; - char *header; - char *content; - char *branch; + /* Try direct file transfer by sending a second INVITE */ + dc = msn_dc_new(slpcall); + slpcall->branch = rand_guid(); + + dc->listen_data = purple_network_listen_range( + 0, 0, + SOCK_STREAM, + msn_dc_listen_socket_created_cb, + dc + ); - slplink = slpcall->slplink; + header = g_strdup_printf( + "INVITE MSNMSGR:%s MSNSLP/1.0", + slpcall->slplink->remote_user + ); - branch = rand_guid(); + if (dc->nonce_type == DC_NONCE_SHA1) + nonce = g_strdup_printf("Hashed-Nonce: {%s}\r\n", dc->nonce_hash); + + if (dc->listen_data == NULL) { + /* Listen socket creation failed */ + purple_debug_info("msn", "got_ok: listening failed\n"); content = g_strdup_printf( - "Bridges: TRUDPv1 TCPv1\r\n" + "Bridges: TCPv1\r\n" + "NetID: %u\r\n" + "Conn-Type: IP-Restrict-NAT\r\n" + "UPnPNat: false\r\n" + "ICF: false\r\n" + "%s" + "\r\n", + + rand() % G_MAXUINT32, + nonce ? nonce : "" + ); + + } else { + /* Listen socket created successfully. */ + purple_debug_info("msn", "got_ok: listening socket created\n"); + + content = g_strdup_printf( + "Bridges: TCPv1\r\n" "NetID: 0\r\n" "Conn-Type: Direct-Connect\r\n" "UPnPNat: false\r\n" "ICF: false\r\n" - ); - - header = g_strdup_printf("INVITE MSNMSGR:%s MSNSLP/1.0", - slplink->remote_user); + "%s" + "\r\n", - slpmsg = msn_slp_sipmsg_new(slpcall, 0, header, branch, - "application/x-msnmsgr-transreqbody", - content); - - slpmsg->info = "SLP INVITE"; - slpmsg->text_body = TRUE; - msn_slplink_send_slpmsg(slplink, slpmsg); + nonce ? nonce : "" + ); + } - g_free(header); - g_free(content); + msg = msn_slpmsg_sip_new( + slpcall, + 0, + header, + slpcall->branch, + "application/x-msnmsgr-transreqbody", + content + ); + msg->info = "DC INVITE"; + msg->text_body = TRUE; + g_free(nonce); + g_free(header); + g_free(content); - g_free(branch); - } - else - { - msn_slpcall_session_init(slpcall); - } -#else - msn_slpcall_session_init(slpcall); -#endif + msn_slplink_queue_slpmsg(slpcall->slplink, msg); } else if (!strcmp(type, "application/x-msnmsgr-transreqbody")) { @@ -707,31 +902,7 @@ } else if (!strcmp(type, "application/x-msnmsgr-transrespbody")) { -#if 0 - char *ip_addrs; - char *temp; - char *nonce; - int port; - - nonce = get_token(content, "Nonce: {", "}\r\n"); - if (ip_addrs == NULL) - return; - - ip_addrs = get_token(content, "IPv4Internal-Addrs: ", "\r\n"); - - temp = get_token(content, "IPv4Internal-Port: ", "\r\n"); - if (temp != NULL) - port = atoi(temp); - else - port = -1; - g_free(temp); - - if (port > 0) - got_transresp(slpcall, nonce, ip_addrs, port); - - g_free(nonce); - g_free(ip_addrs); -#endif + msn_slp_process_transresp(slpcall, content); } } @@ -774,18 +945,24 @@ content = get_token(body, "\r\n\r\n", NULL); - if (branch && call_id && content_type && content) + slpcall = NULL; + if (branch && call_id) { - slpcall = msn_slpcall_new(slplink); - slpcall->id = call_id; - got_invite(slpcall, branch, content_type, content); - } - else - { - g_free(call_id); - slpcall = NULL; + slpcall = msn_slplink_find_slp_call(slplink, call_id); + if (slpcall) + { + g_free(slpcall->branch); + slpcall->branch = g_strdup(branch); + } + else if (content_type && content) + { + slpcall = msn_slpcall_new(slplink); + slpcall->id = g_strdup(call_id); + got_invite(slpcall, branch, content_type, content); + } } + g_free(call_id); g_free(branch); g_free(content_type); g_free(content); @@ -867,6 +1044,8 @@ { MsnSession *session; MsnSlpLink *slplink; + const char *data; + gsize len; session = cmdproc->servconn->session; slplink = msn_session_get_slplink(session, msg->remote_user); @@ -889,7 +1068,9 @@ } } - msn_slplink_process_msg(slplink, msg); + data = msn_message_get_bin_data(msg, &len); + + msn_slplink_process_msg(slplink, &msg->msnslp_header, data, len); } static void diff -r d6f9f4320bf1 -r fb103111bbd5 libpurple/protocols/msn/slp.h --- a/libpurple/protocols/msn/slp.h Thu May 20 22:54:26 2010 +0000 +++ b/libpurple/protocols/msn/slp.h Fri May 21 00:49:41 2010 +0000 @@ -51,6 +51,14 @@ MsnSlpCall * msn_slp_sip_recv(MsnSlpLink *slplink, const char *body); +void +msn_slp_send_ok(MsnSlpCall *slpcall, const char *branch, + const char *type, const char *content); + +void +msn_slp_send_decline(MsnSlpCall *slpcall, const char *branch, + const char *type, const char *content); + void send_bye(MsnSlpCall *slpcall, const char *type); diff -r d6f9f4320bf1 -r fb103111bbd5 libpurple/protocols/msn/slpcall.h --- a/libpurple/protocols/msn/slpcall.h Thu May 20 22:54:26 2010 +0000 +++ b/libpurple/protocols/msn/slpcall.h Fri May 21 00:49:41 2010 +0000 @@ -64,6 +64,8 @@ gboolean started; /**< A flag that states if this slpcall's session has been initiated. */ + gboolean wait_for_socket; + void (*progress_cb)(MsnSlpCall *slpcall, gsize total_length, gsize len, gsize offset); void (*session_init_cb)(MsnSlpCall *slpcall); diff -r d6f9f4320bf1 -r fb103111bbd5 libpurple/protocols/msn/slplink.c --- a/libpurple/protocols/msn/slplink.c Thu May 20 22:54:26 2010 +0000 +++ b/libpurple/protocols/msn/slplink.c Fri May 21 00:49:41 2010 +0000 @@ -101,10 +101,8 @@ session = slplink->session; -#if 0 - if (slplink->directconn != NULL) - msn_directconn_destroy(slplink->directconn); -#endif + if (slplink->dc != NULL) + msn_dc_destroy(slplink->dc); while (slplink->slp_calls != NULL) msn_slpcall_destroy(slplink->slp_calls->data); @@ -185,11 +183,21 @@ slplink->swboard->flag |= MSN_SB_FLAG_FT; slplink->slp_calls = g_list_append(slplink->slp_calls, slpcall); + + /* + if (slplink->dc != NULL && slplink->dc->state == DC_STATE_ESTABLISHED) + msn_dc_ref(slplink->dc); + */ } void msn_slplink_remove_slpcall(MsnSlpLink *slplink, MsnSlpCall *slpcall) { + /* + if (slplink->dc != NULL && slplink->dc->state == DC_STATE_ESTABLISHED) + msn_dc_unref(slplink->dc); + */ + slplink->slp_calls = g_list_remove(slplink->slp_calls, slpcall); /* The slplink has no slpcalls in it, release it from MSN_SB_FLAG_FT. @@ -197,6 +205,10 @@ * destroyed. */ if (slplink->slp_calls == NULL && slplink->swboard != NULL) msn_switchboard_release(slplink->swboard, MSN_SB_FLAG_FT); + + /* The slplink has no slpcalls in it, release it from the DC. */ + if (slplink->slp_calls == NULL && slplink->dc != NULL) + msn_dc_destroy(slplink->dc); } MsnSlpCall * @@ -239,13 +251,11 @@ static void msn_slplink_send_msg(MsnSlpLink *slplink, MsnMessage *msg) { -#if 0 - if (slplink->directconn != NULL) + if (slplink->dc != NULL && slplink->dc->state == DC_STATE_ESTABLISHED) { - msn_directconn_send_msg(slplink->directconn, msg); + msn_dc_enqueue_msg(slplink->dc, msg); } else -#endif { if (slplink->swboard == NULL) { @@ -464,21 +474,29 @@ } } -static void -msn_slplink_send_ack(MsnSlpLink *slplink, MsnMessage *msg) +static MsnSlpMessage * +msn_slplink_create_ack(MsnSlpLink *slplink, MsnSlpHeader *header) { MsnSlpMessage *slpmsg; slpmsg = msn_slpmsg_new(slplink); - slpmsg->session_id = msg->msnslp_header.session_id; - slpmsg->size = msg->msnslp_header.total_size; + slpmsg->session_id = header->session_id; + slpmsg->size = header->total_size; slpmsg->flags = 0x02; - slpmsg->ack_id = msg->msnslp_header.id; - slpmsg->ack_sub_id = msg->msnslp_header.ack_id; - slpmsg->ack_size = msg->msnslp_header.total_size; + slpmsg->ack_id = header->id; + slpmsg->ack_sub_id = header->ack_id; + slpmsg->ack_size = header->total_size; slpmsg->info = "SLP ACK"; + return slpmsg; +} + +static void +msn_slplink_send_ack(MsnSlpLink *slplink, MsnSlpHeader *header) +{ + MsnSlpMessage *slpmsg = msn_slplink_create_ack(slplink, header); + msn_slplink_send_slpmsg(slplink, slpmsg); msn_slpmsg_destroy(slpmsg); } @@ -490,6 +508,9 @@ PurpleXfer *xfer; xfer = (PurpleXfer *)slpcall->xfer; + if (purple_xfer_get_status(xfer) >= PURPLE_XFER_STATUS_STARTED) + return; + purple_xfer_ref(xfer); purple_xfer_start(xfer, -1, NULL, 0); if (purple_xfer_get_status(xfer) != PURPLE_XFER_STATUS_STARTED) { @@ -524,38 +545,27 @@ } void -msn_slplink_process_msg(MsnSlpLink *slplink, MsnMessage *msg) +msn_slplink_process_msg(MsnSlpLink *slplink, MsnSlpHeader *header, const char *data, gsize len) { MsnSlpMessage *slpmsg; - const char *data; guint64 offset; - gsize len; PurpleXfer *xfer = NULL; - if (purple_debug_is_verbose()) - msn_slpmsg_show(msg); - -#ifdef MSN_DEBUG_SLP_FILES - debug_msg_to_file(msg, FALSE); -#endif - - if (msg->msnslp_header.total_size < msg->msnslp_header.length) + if (header->total_size < header->length) { purple_debug_error("msn", "This can't be good\n"); g_return_if_reached(); } - data = msn_message_get_bin_data(msg, &len); - - offset = msg->msnslp_header.offset; + offset = header->offset; if (offset == 0) { slpmsg = msn_slpmsg_new(slplink); - slpmsg->id = msg->msnslp_header.id; - slpmsg->session_id = msg->msnslp_header.session_id; - slpmsg->size = msg->msnslp_header.total_size; - slpmsg->flags = msg->msnslp_header.flags; + slpmsg->id = header->id; + slpmsg->session_id = header->session_id; + slpmsg->size = header->total_size; + slpmsg->flags = header->flags; if (slpmsg->session_id) { @@ -600,7 +610,7 @@ } else { - slpmsg = msn_slplink_message_find(slplink, msg->msnslp_header.session_id, msg->msnslp_header.id); + slpmsg = msn_slplink_message_find(slplink, header->session_id, header->id); if (slpmsg == NULL) { /* Probably the transfer was canceled */ @@ -648,8 +658,7 @@ return; #endif - if (msg->msnslp_header.offset + msg->msnslp_header.length - >= msg->msnslp_header.total_size) + if (header->offset + header->length >= header->total_size) { /* All the pieces of the slpmsg have been received */ MsnSlpCall *slpcall; @@ -661,32 +670,45 @@ return; } - if (!slpcall->wasted) { - if (slpmsg->flags == 0x100) - { - MsnDirectConn *directconn; + purple_debug_info("msn", "msn_slplink_process_msg: slpmsg complete\n"); - directconn = slplink->directconn; + if (/* !slpcall->wasted && */ slpmsg->flags == 0x100) + { #if 0 - if (!directconn->acked) - msn_directconn_send_handshake(directconn); + MsnDirectConn *directconn; + + directconn = slplink->directconn; + if (!directconn->acked) + msn_directconn_send_handshake(directconn); #endif - } - else if (slpmsg->flags == 0x00 || slpmsg->flags == 0x1000000 || - slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 || - slpmsg->flags == 0x1000030) - { - /* Release all the messages and send the ACK */ + } + else if (slpmsg->flags == 0x00 || slpmsg->flags == 0x1000000 || + slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 || + slpmsg->flags == 0x1000030) + { + /* Release all the messages and send the ACK */ - msn_slplink_send_ack(slplink, msg); + if (slpcall->wait_for_socket) { + /* + * Save ack for later because we have to send + * a 200 OK message to the previous direct connect + * invitation before ACK but the listening socket isn't + * created yet. + */ + purple_debug_info("msn", "msn_slplink_process_msg: save ACK\n"); + + slpcall->slplink->dc->prev_ack = msn_slplink_create_ack(slplink, header); + } else if (!slpcall->wasted) { + purple_debug_info("msn", "msn_slplink_process_msg: send ACK\n"); + + msn_slplink_send_ack(slplink, header); msn_slplink_send_queued_slpmsgs(slplink); } - } msn_slpmsg_destroy(slpmsg); - if (slpcall->wasted) + if (!slpcall->wait_for_socket && slpcall->wasted) msn_slpcall_destroy(slpcall); } } diff -r d6f9f4320bf1 -r fb103111bbd5 libpurple/protocols/msn/slplink.h --- a/libpurple/protocols/msn/slplink.h Thu May 20 22:54:26 2010 +0000 +++ b/libpurple/protocols/msn/slplink.h Fri May 21 00:49:41 2010 +0000 @@ -42,6 +42,7 @@ { MsnSession *session; MsnSwitchBoard *swboard; + MsnDirectConn *dc; int refs; @@ -49,8 +50,6 @@ int slp_seq_id; - MsnDirectConn *directconn; - GList *slp_calls; GList *slp_msgs; @@ -84,7 +83,7 @@ void msn_slplink_send_slpmsg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg); void msn_slplink_send_queued_slpmsgs(MsnSlpLink *slplink); -void msn_slplink_process_msg(MsnSlpLink *slplink, MsnMessage *msg); +void msn_slplink_process_msg(MsnSlpLink *slplink, MsnSlpHeader *header, const char *data, gsize len); void msn_slplink_request_ft(MsnSlpLink *slplink, PurpleXfer *xfer); /* Only exported for msn_xfer_write */ diff -r d6f9f4320bf1 -r fb103111bbd5 libpurple/protocols/msn/state.c --- a/libpurple/protocols/msn/state.c Thu May 20 22:54:26 2010 +0000 +++ b/libpurple/protocols/msn/state.c Fri May 21 00:49:41 2010 +0000 @@ -260,7 +260,7 @@ if (msnobj == NULL) { - msn_cmdproc_send(cmdproc, "CHG", "%s %d", state_text, caps); + msn_cmdproc_send(cmdproc, "CHG", "%s %u", state_text, caps); } else { @@ -268,7 +268,7 @@ msnobj_str = msn_object_to_string(msnobj); - msn_cmdproc_send(cmdproc, "CHG", "%s %d %s", state_text, + msn_cmdproc_send(cmdproc, "CHG", "%s %u %s", state_text, caps, purple_url_encode(msnobj_str)); g_free(msnobj_str); diff -r d6f9f4320bf1 -r fb103111bbd5 libpurple/protocols/msn/switchboard.c --- a/libpurple/protocols/msn/switchboard.c Thu May 20 22:54:26 2010 +0000 +++ b/libpurple/protocols/msn/switchboard.c Fri May 21 00:49:41 2010 +0000 @@ -87,8 +87,17 @@ purple_timeout_remove(swboard->reconn_timeout_h); /* If it linked us is because its looking for trouble */ - while (swboard->slplinks != NULL) - msn_slplink_destroy(swboard->slplinks->data); + while (swboard->slplinks != NULL) { + MsnSlpLink *slplink = swboard->slplinks->data; + + /* Destroy only those slplinks which use the switchboard */ + if (slplink->dc == NULL) + msn_slplink_destroy(slplink); + else { + swboard->slplinks = g_list_remove(swboard->slplinks, slplink); + slplink->swboard = NULL; + } + } /* Destroy the message queue */ while ((msg = g_queue_pop_head(swboard->msg_queue)) != NULL)