# HG changeset patch # User kukkerman@gmail.com # Date 1268797507 0 # Node ID 119bd7b072eb411e733b89b93a4399cc3651f5eb # Parent bfaf039aed87406490b060f90e72f7118470b06a Initial support for direct connections. Preliminary patch from ticket #247 by GŹ«ˇbor Szuromi. Still needs lots of testing and fixes. References #247. committer: Elliott Sales de Andrade diff -r bfaf039aed87 -r 119bd7b072eb COPYRIGHT --- a/COPYRIGHT Tue Mar 16 06:20:05 2010 +0000 +++ b/COPYRIGHT Wed Mar 17 03:45:07 2010 +0000 @@ -468,6 +468,7 @@ Marcus Sundberg MĂĄrten Svantesson (fursten) Amir Szekely (kichik) +Gábor Szuromi (kukkerman) Robert T. Greg Taeger Rob Taft diff -r bfaf039aed87 -r 119bd7b072eb ChangeLog --- a/ChangeLog Tue Mar 16 06:20:05 2010 +0000 +++ b/ChangeLog Wed Mar 17 03:45:07 2010 +0000 @@ -42,6 +42,8 @@ MSN: * Support for version 9 of the MSN protocol has been removed. This version is no longer supported on the servers. + * Support for direct connections, enabling faster file transfers, + smiley and buddy icon loading. (Gábor Szuromi) XMPP: * Direct messages to a specific resource only upon receipt of a message diff -r bfaf039aed87 -r 119bd7b072eb libpurple/protocols/msn/Makefile.am --- a/libpurple/protocols/msn/Makefile.am Tue Mar 16 06:20:05 2010 +0000 +++ b/libpurple/protocols/msn/Makefile.am Wed Mar 17 03:45:07 2010 +0000 @@ -14,6 +14,8 @@ contact.h\ dialog.c \ dialog.h \ + directconn.c \ + directconn.h \ error.c \ error.h \ group.c \ diff -r bfaf039aed87 -r 119bd7b072eb libpurple/protocols/msn/Makefile.mingw --- a/libpurple/protocols/msn/Makefile.mingw Tue Mar 16 06:20:05 2010 +0000 +++ b/libpurple/protocols/msn/Makefile.mingw Wed Mar 17 03:45:07 2010 +0000 @@ -41,6 +41,7 @@ command.c \ contact.c\ dialog.c \ + directconn.c \ error.c \ group.c \ history.c \ diff -r bfaf039aed87 -r 119bd7b072eb libpurple/protocols/msn/directconn.c --- a/libpurple/protocols/msn/directconn.c Tue Mar 16 06:20:05 2010 +0000 +++ b/libpurple/protocols/msn/directconn.c Wed Mar 17 03:45:07 2010 +0000 @@ -27,479 +27,1379 @@ #include "slp.h" #include "slpmsg.h" -/************************************************************************** - * Directconn Specific - **************************************************************************/ +#define DC_SESSION_ID_OFFS 0 +#define DC_SEQ_ID_OFFS 4 +#define DC_DATA_OFFSET_OFFS 8 +#define DC_TOTAL_DATA_SIZE_OFFS 16 +#define DC_MESSAGE_LENGTH_OFFS 24 +#define DC_FLAGS_OFFS 28 +#define DC_ACK_ID_OFFS 32 +#define DC_ACK_UID_OFFS 36 +#define DC_ACK_DATA_SIZE_OFFS 40 +#define DC_MESSAGE_BODY_OFFS 48 -void -msn_directconn_send_handshake(MsnDirectConn *directconn) +#define DC_PACKET_HEADER_SIZE 48 +#define DC_MAX_BODY_SIZE 1352 +#define DC_MAX_PACKET_SIZE (DC_PACKET_HEADER_SIZE + DC_MAX_BODY_SIZE) + +static void +msn_dc_generate_nonce(MsnDirectConn *dc) { - MsnSlpLink *slplink; - MsnSlpMessage *slpmsg; - - g_return_if_fail(directconn != NULL); + PurpleCipher *cipher = NULL; + PurpleCipherContext *context = NULL; + static guchar digest[20]; + int i; + + guint32 g1; + guint16 g2; + guint16 g3; + guint64 g4; - slplink = directconn->slplink; - - slpmsg = msn_slpmsg_new(slplink); - slpmsg->flags = 0x100; + cipher = purple_ciphers_find_cipher("sha1"); + g_return_if_fail(cipher != NULL); + + for (i = 0; i < 16; i++) + dc->nonce[i] = rand() & 0xff; - 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); + context = purple_cipher_context_new(cipher, NULL); + purple_cipher_context_append(context, dc->nonce, 16); + purple_cipher_context_digest(context, 20, digest, NULL); + purple_cipher_context_destroy(context); - 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); + g1 = *((guint32*)(digest + 0)); + g1 = GUINT32_FROM_LE(g1); + + g2 = *((guint16*)(digest + 4)); + g2 = GUINT16_FROM_LE(g2); + + g3 = *((guint16*)(digest + 6)); + g3 = GUINT32_FROM_LE(g3); + + g4 = *((guint64*)(digest + 8)); + g4 = GUINT64_FROM_BE(g4); + + g_sprintf( + dc->nonce_hash, + "%08X-%04X-%04X-%04X-%08X%04X", + g1, + g2, + g3, + (guint16)(g4 >> 48), + (guint32)((g4 >> 16) & 0xffffffff), + (guint16)(g4 & 0xffff) + ); +} - slpmsg->ack_id = t1; - slpmsg->ack_sub_id = t2 | (t3 << 16); - slpmsg->ack_size = t4 | t5; - } +static MsnDirectConnPacket* +msn_dc_new_packet() +{ + MsnDirectConnPacket *p; - g_free(directconn->nonce); + p = g_new0(MsnDirectConnPacket, 1); + p->data = NULL; + p->sent_cb = NULL; + p->msg = NULL; - msn_slplink_send_slpmsg(slplink, slpmsg); - - directconn->acked =TRUE; + return p; } -/************************************************************************** - * Connection Functions - **************************************************************************/ +static void +msn_dc_destroy_packet(MsnDirectConnPacket *p) +{ + if (p->data) + g_free(p->data); + + if (p->msg) + msn_message_unref(p->msg); -static int -create_listener(int port) -{ - int fd; - int flags; - const int on = 1; + g_free(p); +} -#if 0 - struct addrinfo hints; - struct addrinfo *c, *res; - char port_str[5]; +MsnDirectConn* +msn_dc_new(MsnSlpCall *slpcall) +{ + MsnDirectConn *dc; + + purple_debug_info("msn", "msn_dc_new\n"); - snprintf(port_str, sizeof(port_str), "%d", port); - - memset(&hints, 0, sizeof(hints)); + g_return_val_if_fail(slpcall != NULL, NULL); + + dc = g_new0(MsnDirectConn, 1); - hints.ai_flags = AI_PASSIVE; - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; + 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; - if (getaddrinfo(NULL, port_str, &hints, &res) != 0) - { - purple_debug_error("msn", "Could not get address info: %s.\n", - port_str); - return -1; - } + 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 = 0; + dc->send_connection_info_msg_cb = NULL; + dc->ext_ip = NULL; + dc->timeout_handle = 0; + dc->progress = FALSE; + //dc->num_calls = 1; - for (c = res; c != NULL; c = c->ai_next) - { - fd = socket(c->ai_family, c->ai_socktype, c->ai_protocol); + msn_dc_generate_nonce(dc); + + return dc; +} - if (fd < 0) - continue; +void +msn_dc_destroy(MsnDirectConn *dc) +{ + MsnSlpLink *slplink; + + purple_debug_info("msn", "msn_dc_destroy\n"); + + g_return_if_fail(dc != NULL); - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); - - if (bind(fd, c->ai_addr, c->ai_addrlen) == 0) - break; + slplink = dc->slplink; - close(fd); - } + if (dc->slpcall != NULL) + dc->slpcall->wait_for_socket = FALSE; + + slplink->dc = NULL; + + if (slplink->swboard == NULL) + msn_slplink_destroy(slplink); - if (c == NULL) - { - purple_debug_error("msn", "Could not find socket: %s.\n", port_str); - return -1; + if (dc->msg_body != NULL) { + g_free(dc->msg_body); + dc->msg_body = NULL; + } + + if (dc->prev_ack) { + msn_slpmsg_destroy(dc->prev_ack); + dc->prev_ack = NULL; } - 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; + if (dc->listen_data != NULL) { + purple_network_listen_cancel(dc->listen_data); + dc->listen_data = NULL; + } + + if (dc->connect_data != NULL) { + purple_proxy_connect_cancel(dc->connect_data); + dc->connect_data = NULL; + } + + if (dc->listenfd != -1) { + purple_network_remove_port_mapping(dc->listenfd); + close(dc->listenfd); + dc->listenfd = -1; + } + + if (dc->listenfd_handle != 0) { + purple_timeout_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->fd != -1) { + close(dc->fd); + dc->fd = -1; + } + + if (dc->send_handle != 0) { + purple_input_remove(dc->send_handle); + dc->send_handle = 0; + } + + if (dc->recv_handle != 0) { + purple_input_remove(dc->recv_handle); + dc->recv_handle = 0; } - memset(&sockin, 0, sizeof(struct sockaddr_in)); - sockin.sin_family = AF_INET; - sockin.sin_port = htons(port); + if (dc->in_buffer != NULL) { + g_free(dc->in_buffer); + dc->in_buffer = NULL; + } + + if (dc->out_queue != NULL) { + while (!g_queue_is_empty(dc->out_queue)) + msn_dc_destroy_packet( g_queue_pop_head(dc->out_queue) ); - 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; + g_queue_free(dc->out_queue); } - flags = fcntl(fd, F_GETFL); - fcntl(fd, F_SETFL, flags | O_NONBLOCK); -#ifndef _WIN32 - fcntl(fd, F_SETFD, FD_CLOEXEC); -#endif + if (dc->ext_ip != NULL) { + g_free(dc->ext_ip); + dc->ext_ip = NULL; + } - return fd; + if (dc->timeout_handle != 0) { + purple_timeout_remove(dc->timeout_handle); + dc->timeout_handle = 0; + } + + g_free(dc); } -static gssize -msn_directconn_write(MsnDirectConn *directconn, - const char *data, size_t len) +/* +void +msn_dc_ref(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); + g_return_if_fail(dc != NULL); - 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); - - FILE *tf = g_fopen(str, "w"); - fwrite(buffer, 1, buf_size, tf); - fclose(tf); - - g_free(str); -#endif - - g_free(buffer); - - directconn->c++; - - return ret; + dc->num_calls++; } -#if 0 void -msn_directconn_parse_nonce(MsnDirectConn *directconn, const char *nonce) +msn_dc_unref(MsnDirectConn *dc) { - guint32 t1; - guint16 t2; - guint16 t3; - guint16 t4; - guint64 t5; - - g_return_if_fail(directconn != NULL); - g_return_if_fail(nonce != NULL); + g_return_if_fail(dc != NULL); - sscanf (nonce, "%08X-%04hX-%04hX-%04hX-%012llX", &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); - - directconn->slpheader = g_new0(MsnSlpHeader, 1); - - directconn->slpheader->ack_id = t1; - directconn->slpheader->ack_sub_id = t2 | (t3 << 16); - directconn->slpheader->ack_size = t4 | t5; + + if (dc->num_calls > 0) { + dc->num_calls--; + } } -#endif +*/ void -msn_directconn_send_msg(MsnDirectConn *directconn, MsnMessage *msg) +msn_dc_send_invite(MsnDirectConn *dc) { - char *body; - size_t body_len; + MsnSlpCall *slpcall; + MsnSlpMessage *msg; + gchar *header; + + purple_debug_info("msn", "msn_dc_send_invite\n"); + + g_return_if_fail(dc != NULL); + + slpcall = dc->slpcall; + g_return_if_fail(slpcall != NULL); - body = msn_message_gen_slp_body(msg, &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 + ); + g_free(header); + g_free(dc->msg_body); + dc->msg_body = NULL; - msn_directconn_write(directconn, body, body_len); + msn_slplink_queue_slpmsg(slpcall->slplink, msg); +} + +void +msn_dc_send_ok(MsnDirectConn *dc) +{ + purple_debug_info("msn", "msn_dc_send_ok\n"); + + 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); } static void -read_cb(gpointer data, gint source, PurpleInputCondition cond) +msn_dc_fallback_to_p2p(MsnDirectConn *dc) { - MsnDirectConn* directconn; - char *body; - size_t body_len; - gssize len; + MsnSlpCall *slpcall; + PurpleXfer *xfer; + + purple_debug_info("msn", "msn_dc_try_fallback_to_p2p\n"); + + g_return_if_fail(dc != NULL); + + slpcall = dc->slpcall; + g_return_if_fail(slpcall != NULL); + + xfer = slpcall->xfer; + g_return_if_fail(xfer != NULL); + + msn_dc_destroy(dc); + + msn_slpcall_session_init(slpcall); + + /* + switch (purple_xfer_get_status(xfer)) { + case PURPLE_XFER_STATUS_NOT_STARTED: + case PURPLE_XFER_STATUS_ACCEPTED: + msn_slpcall_session_init(slpcall); + break; - purple_debug_info("msn", "read_cb: %d, %d\n", source, cond); + case PURPLE_XFER_STATUS_STARTED: + slpcall->session_init_cb = NULL; + slpcall->end_cb = NULL; + slpcall->progress_cb = NULL; + slpcall->cb = NULL; + + if (fail_local) + purple_xfer_cancel_local(xfer); + else + purple_xfer_cancel_remote(xfer); + break; - directconn = data; + default: + slpcall->session_init_cb = NULL; + slpcall->end_cb = NULL; + slpcall->progress_cb = NULL; + slpcall->cb = NULL; + + if (fail_local) + purple_xfer_cancel_local(xfer); + else + purple_xfer_cancel_remote(xfer); + + break; + } + */ +} + +static void +msn_dc_parse_binary_header(MsnDirectConn *dc) +{ + MsnSlpHeader *h; + gchar *buffer; + + g_return_if_fail(dc != NULL); + + h = &dc->header; + /* Skip packet size */ + buffer = dc->in_buffer + 4; - /* 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)); + memcpy(&h->session_id, buffer + DC_SESSION_ID_OFFS, sizeof(h->session_id)); + h->session_id = GUINT32_FROM_LE(h->session_id); + + memcpy(&h->id, buffer + DC_SEQ_ID_OFFS, sizeof(h->id)); + h->id = GUINT32_FROM_LE(h->id); + + memcpy(&h->offset, buffer + DC_DATA_OFFSET_OFFS, sizeof(h->offset)); + h->offset = GUINT64_FROM_LE(h->offset); + + memcpy(&h->total_size, buffer + DC_TOTAL_DATA_SIZE_OFFS, sizeof(h->total_size)); + h->total_size = GUINT64_FROM_LE(h->total_size); + + memcpy(&h->length, buffer + DC_MESSAGE_LENGTH_OFFS, sizeof(h->length)); + h->length = GUINT32_FROM_LE(h->length); + + memcpy(&h->flags, buffer + DC_FLAGS_OFFS, sizeof(h->flags)); + h->flags = GUINT32_FROM_LE(h->flags); + + memcpy(&h->ack_id, buffer + DC_ACK_ID_OFFS, sizeof(h->ack_id)); + h->ack_id = GUINT32_FROM_LE(h->ack_id); + + memcpy(&h->ack_sub_id, buffer + DC_ACK_UID_OFFS, sizeof(h->ack_sub_id)); + h->ack_sub_id = GUINT32_FROM_LE(h->ack_sub_id); + + memcpy(&h->ack_size, buffer + DC_ACK_DATA_SIZE_OFFS, sizeof(h->ack_size)); + h->ack_size = GUINT64_FROM_LE(h->ack_size); +} + +static gchar* +msn_dc_serialize_binary_header(MsnDirectConn *dc) { + static MsnSlpHeader h; + static gchar bin_header[DC_PACKET_HEADER_SIZE]; + + g_return_val_if_fail(dc != NULL, NULL); + + memcpy(&h, &dc->header, sizeof(h)); + + h.session_id = GUINT32_TO_LE(h.session_id); + memcpy(bin_header + DC_SESSION_ID_OFFS, &h.session_id, sizeof(h.session_id)); + + h.id = GUINT32_TO_LE(h.id); + memcpy(bin_header + DC_SEQ_ID_OFFS, &h.id, sizeof(h.id)); + + h.offset = GUINT64_TO_LE(h.offset); + memcpy(bin_header + DC_DATA_OFFSET_OFFS, &h.offset, sizeof(h.offset)); + + h.total_size = GUINT64_TO_LE(h.total_size); + memcpy(bin_header + DC_TOTAL_DATA_SIZE_OFFS, &h.total_size, sizeof(h.total_size)); + + h.length = GUINT32_TO_LE(h.length); + memcpy(bin_header + DC_MESSAGE_LENGTH_OFFS, &h.length, sizeof(h.length)); + + h.flags = GUINT32_TO_LE(h.flags); + memcpy(bin_header + DC_FLAGS_OFFS, &h.flags, sizeof(h.flags)); + + h.ack_id = GUINT32_TO_LE(h.ack_id); + memcpy(bin_header + DC_ACK_ID_OFFS, &h.ack_id, sizeof(h.ack_id)); + + h.ack_sub_id = GUINT32_TO_LE(h.ack_sub_id); + memcpy(bin_header + DC_ACK_UID_OFFS, &h.ack_sub_id, sizeof(h.ack_sub_id)); + + h.ack_size = GUINT64_TO_LE(h.ack_size); + memcpy(bin_header + DC_ACK_DATA_SIZE_OFFS, &h.ack_size, sizeof(h.ack_size)); + + return bin_header; +} - if (len <= 0) - { - /* ERROR */ - purple_debug_error("msn", "error reading\n"); +/* +static void +msn_dc_send_bye(MsnDirectConn *dc) +{ + MsnSlpLink *slplink; + PurpleAccount *account; + char *body; + int body_len; + + purple_debug_info("msn", "msn_dc_send_bye\n"); + + g_return_if_fail(dc != NULL); + g_return_if_fail(dc->slpcall != NULL); + + slplink = dc->slpcall->slplink; + account = slplink->session->account; + + dc->header.session_id = 0; + dc->header.id = dc->slpcall->slplink->slp_seq_id++; + dc->header.offset = 0; + + body = g_strdup_printf( + "BYE MSNMSGR:%s MSNSLP/1.0\r\n" + "To: \r\n" + "From: \r\n" + "Via: MSNSLP/1.0/TLP ;branch={%s}\r\n" + "CSeq: 0\r\n" + "Call-ID: {%s}\r\n" + "Max-Forwards: 0\r\n" + "Content-Type: application/x-msnmsgr-sessionclosebody\r\n" + "Content-Length: 3\r\n" + "\r\n\r\n", - if (directconn->inpa) - purple_input_remove(directconn->inpa); + slplink->remote_user, + slplink->remote_user, + purple_account_get_username(account), + dc->slpcall->branch, + dc->slpcall->id + ); + body_len = strlen(body) + 1; + memcpy(dc->buffer, body, body_len); + g_free(body); - close(directconn->fd); + dc->header.total_size = body_len; + dc->header.length = body_len; + dc->header.flags = 0; + dc->header.ack_sub_id = 0; + dc->header.ack_size = 0; - msn_directconn_destroy(directconn); + msn_dc_send_packet(dc); +} - return; - } +static void +msn_dc_send_ack(MsnDirectConn *dc) +{ + g_return_if_fail(dc != NULL); + + dc->header.session_id = 0; + dc->header.ack_sub_id = dc->header.ack_id; + dc->header.ack_id = dc->header.id; + dc->header.id = dc->slpcall->slplink->slp_seq_id++; + dc->header.offset = 0; + dc->header.length = 0; + dc->header.flags = 0x02; + dc->header.ack_size = dc->header.total_size; + + msn_dc_send_packet(dc); +} - body_len = GUINT32_FROM_LE(body_len); +static void +msn_dc_send_data_ack(MsnDirectConn *dc) +{ + g_return_if_fail(dc != NULL); + + dc->header.session_id = dc->slpcall->session_id; + dc->header.ack_sub_id = dc->header.ack_id; + dc->header.ack_id = dc->header.id; + dc->header.id = dc->slpcall->slplink->slp_seq_id++; + dc->header.offset = 0; + dc->header.length = 0; + dc->header.flags = 0x02; + dc->header.ack_size = dc->header.total_size; + + msn_dc_send_packet(dc); +} - purple_debug_info("msn", "body_len=%" G_GSIZE_FORMAT "\n", body_len); +static void +msn_dc_xfer_send_cancel(PurpleXfer *xfer) +{ + MsnSlpCall *slpcall; + MsnDirectConn *dc; + + purple_debug_info("msn", "msn_dc_xfer_send_cancel\n"); + + g_return_if_fail(xfer != NULL); + + slpcall = xfer->data; + g_return_if_fail(slpcall != NULL); + + dc = slpcall->dc; + g_return_if_fail(dc != NULL); + + switch (dc->state) { + case DC_STATE_TRANSFER: + msn_dc_send_bye(dc); + dc->state = DC_STATE_CANCELLED; + break; + + default: + msn_dc_destroy(dc); + break; + } +} - if (body_len <= 0) - { - /* ERROR */ - purple_debug_error("msn", "error reading\n"); +static void +msn_dc_xfer_recv_cancel(PurpleXfer *xfer) +{ + MsnSlpCall *slpcall; + MsnDirectConn *dc; + + purple_debug_info("msn", "msn_dc_xfer_recv_cancel\n"); + + g_return_if_fail(xfer != NULL); + + slpcall = xfer->data; + g_return_if_fail(slpcall != NULL); + + dc = slpcall->dc; + g_return_if_fail(dc != NULL); + + switch (dc->state) { + case DC_STATE_TRANSFER: + msn_dc_send_bye(dc); + dc->state = DC_STATE_CANCELLED; + break; - if (directconn->inpa) - purple_input_remove(directconn->inpa); + default: + msn_dc_destroy(dc); + break; + } +} +*/ - close(directconn->fd); +static void +msn_dc_send_cb(gpointer data, gint fd, PurpleInputCondition cond) +{ + MsnDirectConn *dc = data; + MsnDirectConnPacket *p; + int bytes_to_send; + int bytes_sent; - msn_directconn_destroy(directconn); - + 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); + bytes_to_send = p->length - dc->msg_pos; - purple_debug_info("msn", "len=%" G_GSIZE_FORMAT "\n", len); - } - else - { - purple_debug_error("msn", "Failed to allocate memory for read\n"); - len = 0; + bytes_sent = send(fd, p->data, bytes_to_send, 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; } - 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++; + dc->progress = TRUE; - msg = msn_message_new_msnslp(); - msn_message_parse_slp_body(msg, body, body_len); + dc->msg_pos += bytes_sent; + if (dc->msg_pos == p->length) { + if (p->sent_cb != NULL) + p->sent_cb(p); - purple_debug_info("msn", "directconn: process_msg\n"); - msn_slplink_process_msg(directconn->slplink, msg); + g_queue_pop_head(dc->out_queue); + msn_dc_destroy_packet(p); + + dc->msg_pos = 0; } - else - { - /* ERROR */ - purple_debug_error("msn", "error reading\n"); - - if (directconn->inpa) - purple_input_remove(directconn->inpa); - - close(directconn->fd); - - msn_directconn_destroy(directconn); - } - - g_free(body); } 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; + + was_empty = g_queue_is_empty(dc->out_queue); + g_queue_push_tail(dc->out_queue, p); - 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); - - /* 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) { - if (error_message) - purple_debug_error("msn", "Error making direct connection: %s\n", error_message); + MsnDirectConnPacket *p; + + purple_debug_info("msn", "msn_dc_send_foo\n"); + + g_return_if_fail(dc != NULL); - connect_cb(data, source, PURPLE_INPUT_READ); + p = msn_dc_new_packet(); + + p->length = 8; + p->data = (guchar*)g_strdup("\4\0\0\0foo"); + p->sent_cb = NULL; + + msn_dc_enqueue_packet(dc, p); } -gboolean -msn_directconn_connect(MsnDirectConn *directconn, const char *host, int port) +static void +msn_dc_send_handshake(MsnDirectConn *dc) { - MsnSession *session; + MsnDirectConnPacket *p; + gchar *h; + guint32 l; + + g_return_if_fail(dc != NULL); + + p = msn_dc_new_packet(); + + p->length = 4 + DC_PACKET_HEADER_SIZE; + p->data = g_malloc(p->length); - g_return_val_if_fail(directconn != NULL, FALSE); - g_return_val_if_fail(host != NULL, TRUE); - g_return_val_if_fail(port > 0, FALSE); + l = DC_PACKET_HEADER_SIZE; + l = GUINT32_TO_LE(l); + memcpy(p->data, &l, 4); + + 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; - session = directconn->slplink->session; + h = msn_dc_serialize_binary_header(dc); + memcpy(p->data + 4, h, DC_PACKET_HEADER_SIZE); + memcpy(p->data + 4 + DC_ACK_ID_OFFS, dc->nonce, 16); + + msn_dc_enqueue_packet(dc, p); +} -#if 0 - if (session->http_method) - { - servconn->http_data->gateway_host = g_strdup(host); - } -#endif +static void +msn_dc_send_handshake_reply(MsnDirectConn *dc) +{ + MsnDirectConnPacket *p; + gchar *h; + guint32 l; + + g_return_if_fail(dc != NULL); + + p = msn_dc_new_packet(); + + p->length = 4 + DC_PACKET_HEADER_SIZE; + p->data = g_malloc(p->length); - directconn->connect_data = purple_proxy_connect(NULL, session->account, - host, port, directconn_connect_cb, directconn); + l = DC_PACKET_HEADER_SIZE; + l = GUINT32_TO_LE(l); + memcpy(p->data, &l, 4); + + dc->header.id = dc->slpcall->slplink->slp_seq_id++; + dc->header.length = 0; - return (directconn->connect_data != NULL); + h = msn_dc_serialize_binary_header(dc); + memcpy(p->data + 4, h, DC_PACKET_HEADER_SIZE); + memcpy(p->data + 4 + DC_ACK_ID_OFFS, dc->nonce, 16); + + msn_dc_enqueue_packet(dc, p); +} + +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) +{ + MsnDirectConnPacket *p = msn_dc_new_packet(); + guint32 length = msg->body_len + DC_PACKET_HEADER_SIZE; + + p->length = 4 + length; + p->data = g_malloc(p->length); + + length = GUINT32_TO_LE(length); + memcpy(p->data, &length, 4); + memcpy(p->data + 4, &msg->msnslp_header, DC_PACKET_HEADER_SIZE); + memcpy(p->data + 4 + DC_PACKET_HEADER_SIZE, msg->body, msg->body_len); + + p->sent_cb = msn_dc_send_packet_cb; + p->msg = msg; + msn_message_ref(msg); + + msn_dc_enqueue_packet(dc, p); +} + +static int +msn_dc_process_packet(MsnDirectConn *dc, guint32 packet_length) { - int port; - int fd; + 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; + } + + case DC_STATE_HANDSHAKE: { + if (packet_length != DC_PACKET_HEADER_SIZE) + return DC_PROCESS_FALLBACK; + + /* TODO: Check! */ + msn_dc_send_handshake_reply(dc); + dc->state = DC_STATE_ESTABILISHED; - port = 7000; + msn_slpcall_session_init(dc->slpcall); + dc->slpcall = NULL; + break; + } + + case DC_STATE_HANDSHAKE_REPLY: + /* TODO: Check! */ + dc->state = DC_STATE_ESTABILISHED; + + msn_slpcall_session_init(dc->slpcall); + dc->slpcall = NULL; + break; + + case DC_STATE_ESTABILISHED: + msn_slplink_process_msg( + dc->slplink, + &dc->header, + dc->in_buffer + 4 + DC_PACKET_HEADER_SIZE, + dc->header.length + ); + + /* + if (dc->num_calls == 0) { + msn_dc_destroy(dc); + + return DC_PROCESS_CLOSE; + } + */ + break; +#if 0 + { + guint64 file_size; + int bytes_written; + PurpleXfer *xfer; + MsnSlpHeader *h = &dc->header; + + if (packet_length < DC_PACKET_HEADER_SIZE) + return DC_TRANSFER_FALLBACK; - for (fd = -1; fd < 0;) - fd = create_listener(++port); + /* + * TODO: MSN Messenger 7.0 sends BYE with flags 0x0000000 so we'll get rid of + * 0x1000000 bit but file data is always sent with flags 0x1000030 in both + * MSN Messenger and Live.*/ + switch (h->flags) { + case 0x0000000: + case 0x1000000: + msn_dc_send_ack(dc); + if (strncmp(dc->buffer, "BYE", 3) == 0) { + /* Remote side cancelled the transfer. */ + purple_xfer_cancel_remote(dc->slpcall->xfer); + return DC_TRANSFER_CANCELLED; + } + break; + + case 0x1000030: + /* File data */ + xfer = dc->slpcall->xfer; + file_size = purple_xfer_get_size(xfer); - directconn->fd = fd; + /* Packet sanity checks */ + if ( h->session_id != dc->slpcall->session_id || + h->offset >= file_size || + h->total_size != file_size || + h->length != packet_length - DC_PACKET_HEADER_SIZE || + h->offset + h->length > file_size) { + + purple_debug_warning("msn", "msn_dc_recv_process_packet_cb: packet range check error!\n"); + purple_xfer_cancel_local(dc->slpcall->xfer); + return DC_TRANSFER_CANCELLED; + } + + bytes_written = fwrite(dc->buffer, 1, h->length, xfer->dest_fp); + if (bytes_written != h->length) { + purple_debug_warning("msn", "msn_dc_recv_process_packet_cb: cannot write whole packet to file!\n"); + purple_xfer_cancel_local(dc->slpcall->xfer); + return DC_TRANSFER_CANCELLED; + } + + xfer->bytes_sent = (h->offset + h->length); + xfer->bytes_remaining = h->total_size - xfer->bytes_sent; + + purple_xfer_update_progress(xfer); - directconn->inpa = purple_input_add(fd, PURPLE_INPUT_READ, connect_cb, - directconn); + if (xfer->bytes_remaining == 0) { + /* ACK only the last data packet */ + msn_dc_send_data_ack(dc); + purple_xfer_set_completed(xfer, TRUE); + dc->state = DC_STATE_BYE; + } + break; + default: + /* + * TODO: Packet with unknown flags. Should we ACK these? + */ + msn_dc_send_ack(dc); + + purple_debug_warning( + "msn", + "msn_dc_recv_process_packet_cb: received packet with unknown flags: 0x%08x\n", + dc->header.flags + ); + } + break; + } - directconn->port = port; - directconn->c = 0; + case DC_STATE_BYE: + /* TODO: Check! */ + switch (dc->header.flags) { + case 0x0000000: + case 0x1000000: + msn_dc_send_ack(dc); + if (strncmp(dc->buffer, "BYE", 3) == 0) { + dc->state = DC_STATE_COMPLETED; + return DC_TRANSFER_COMPLETED; + } + break; + + default: + /* + * TODO: Packet with unknown flags. Should we ACK these? + */ + msn_dc_send_ack(dc); + purple_debug_warning( + "msn", + "msn_dc_recv_process_packet_cb: received packet with unknown flags: 0x%08x\n", + dc->header.flags + ); + } + break; +#endif + } + + 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_ESTABILISHED) + msn_dc_fallback_to_p2p(dc); + else + msn_dc_destroy(dc); + return; + + } 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_ESTABILISHED) + 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*)dc->in_buffer); + packet_length = GUINT32_FROM_LE(packet_length); + + if (packet_length > DC_MAX_PACKET_SIZE) { + /* Oversized packet */ + purple_debug_warning("msn", "msn_dc_recv_cb: oversized packet received\n"); + return; + } - directconn = g_new0(MsnDirectConn, 1); + /* 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) { + memcpy(dc->in_buffer, dc->in_buffer + 4 + packet_length, dc->in_pos - packet_length - 4); + } + + dc->in_pos -= packet_length + 4; + } +} + +#if 0 +static gboolean +msn_dc_send_next_packet(MsnDirectConn *dc) +{ + MsnSlpMessage *msg; + + if(g_queue_is_empty(dc->out_queue)) + return TRUE; + + msg = g_queue_peek_head(dc->out_queue); + msn_slplink_send_msgpart(dc->slplink, msg); + + + + PurpleXfer *xfer; + int bytes_read; + + g_return_val_if_fail(dc != NULL, FALSE); + g_return_val_if_fail(dc->slpcall != NULL, FALSE); + + xfer = dc->slpcall->xfer; + + bytes_read = fread(dc->buffer, 1, DC_MAX_BODY_SIZE, xfer->dest_fp); + + if (bytes_read > 0) { + dc->header.session_id = dc->slpcall->session_id; + /* Only increment seq. ID before sending BYE */ + dc->header.id = dc->slpcall->slplink->slp_seq_id; + dc->header.offset = xfer->bytes_sent; + dc->header.total_size = xfer->size; + dc->header.length = bytes_read; + dc->header.flags = 0x1000030; + dc->header.ack_id = rand() % G_MAXUINT32; + dc->header.ack_sub_id = 0; + dc->header.ack_size = 0; + + msn_dc_send_packet(dc); - directconn->slplink = slplink; + xfer->bytes_sent += bytes_read; + xfer->bytes_remaining -= bytes_read; + purple_xfer_update_progress(xfer); + + if (xfer->bytes_remaining == 0) { + purple_xfer_set_completed(xfer, TRUE); + + /* Increment seq. ID for the next BYE message */ + dc->slpcall->slplink->slp_seq_id++; + dc->state = DC_STATE_DATA_ACK; + } + + } else { + /* File read error */ + purple_xfer_cancel_local(xfer); + return FALSE; + } + + return TRUE; +} + +static int +msn_dc_send_process_packet_cb(MsnDirectConn *dc, guint32 packet_length) +{ + g_return_val_if_fail(dc != NULL, DC_TRANSFER_CANCELLED); + + switch (dc->state) { + case DC_STATE_FOO: { + if (packet_length != 4) + return DC_TRANSFER_FALLBACK; + + if (memcmp(dc->in_buffer, "\4\0\0\0foo", 8) != 0) + return DC_TRANSFER_FALLBACK; + + dc->state = DC_STATE_HANDSHAKE; + break; + } + + case DC_STATE_HANDSHAKE: { + if (packet_length != DC_PACKET_HEADER_SIZE) + return DC_TRANSFER_FALLBACK; + + /* TODO: Check! */ + msn_dc_send_handshake_reply(dc); + dc->state = DC_STATE_TRANSFER; + + purple_xfer_set_request_denied_fnc(dc->slpcall->xfer, msn_dc_xfer_send_cancel); + purple_xfer_set_cancel_send_fnc(dc->slpcall->xfer, msn_dc_xfer_send_cancel); + purple_xfer_set_end_fnc(dc->slpcall->xfer, msn_dc_xfer_end); + purple_xfer_start(dc->slpcall->xfer, -1, NULL, 0); + break; + } - if (slplink->directconn != NULL) - purple_debug_info("msn", "got_transresp: LEAK\n"); + case DC_STATE_HANDSHAKE_REPLY: + /* TODO: Check! */ + dc->state = DC_STATE_TRANSFER; + break; + + case DC_STATE_TRANSFER: { + switch (dc->header.flags) { + case 0x0000000: + case 0x1000000: + msn_dc_send_ack(dc); + if (strncmp(dc->buffer, "BYE", 3) == 0) { + /* Remote side cancelled the transfer. */ + purple_xfer_cancel_remote(dc->slpcall->xfer); + return DC_TRANSFER_CANCELLED; + } + break; + } + break; + } + + case DC_STATE_DATA_ACK: { + /* TODO: Check! */ + msn_dc_send_bye(dc); + dc->state = DC_STATE_BYE_ACK; + break; + } + + case DC_STATE_BYE_ACK: + /* TODO: Check! */ + dc->state = DC_STATE_COMPLETED; + return DC_TRANSFER_COMPLETED; + } - slplink->directconn = directconn; + return DC_TRANSFER_OK; +} +#endif + +static gboolean +msn_dc_timeout(gpointer data) +{ + MsnDirectConn *dc = data; + + g_return_val_if_fail(dc != NULL, FALSE); + + if (dc->progress) + dc->progress = FALSE; + else + msn_dc_destroy(dc); - return directconn; + return TRUE; +} + +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; + + purple_debug_info("msn", "msn_dc_connected_to_peer_cb\n"); + + g_return_if_fail(data != NULL); + + dc = data; + + 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 = dc->slpcall; + + purple_debug_info("msn", "msn_dc_incoming_connection_timeout_cb\n"); + + dc = data; + g_return_val_if_fail(dc != NULL, FALSE); + + slpcall = dc->slpcall; + g_return_val_if_fail(slpcall != NULL, FALSE); + + if (dc->listen_data != NULL) { + purple_network_listen_cancel(dc->listen_data); + dc->listen_data = NULL; + } + + if (dc->listenfd_handle != 0) { + purple_timeout_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; + } - if (directconn->inpa != 0) - purple_input_remove(directconn->inpa); + msn_dc_destroy(dc); + /* Start p2p file transfer */ + msn_slpcall_session_init(slpcall); + + 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\n"); + + g_return_val_if_fail(dc != NULL, FALSE); + + if (dc->connect_timeout_handle != 0) { + purple_timeout_remove(dc->connect_timeout_handle); + 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 { + /* + * Both internal and external connection attempts are failed. + * Fall back to p2p transfer. + */ + MsnSlpCall *slpcall = dc->slpcall; - if (directconn->fd >= 0) - close(directconn->fd); + msn_dc_destroy(dc); + /* Start p2p file transfer */ + msn_slpcall_session_init(slpcall); + } + + 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; + + purple_debug_info("msn", "msn_dc_incoming_connection_cb\n"); + + 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; + + purple_debug_info("msn", "msn_dc_listen_socket_created_cb\n"); + + 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" + "Hashed-Nonce: {%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_hash, + ext_ip, + port, + int_ip, + port + ); + + } else { + dc->msg_body = g_strdup_printf( + "Bridge: TCPv1\r\n" + "Listening: true\r\n" + "Hashed-Nonce: {%s}\r\n" + "IPv4External-Addrs: %s\r\n" + "IPv4External-Port: %d\r\n" + "\r\n", - g_free(directconn); + 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 bfaf039aed87 -r 119bd7b072eb libpurple/protocols/msn/directconn.h --- a/libpurple/protocols/msn/directconn.h Tue Mar 16 06:20:05 2010 +0000 +++ b/libpurple/protocols/msn/directconn.h Wed Mar 17 03:45:07 2010 +0000 @@ -26,36 +26,153 @@ 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_ESTABILISHED /*< Handshake complete */ +} MsnDirectConnState; + +typedef enum +{ + DC_PROCESS_OK = 0, + DC_PROCESS_ERROR, + DC_PROCESS_FALLBACK, + DC_PROCESS_CLOSE + +} MsnDirectConnProcessResult; + +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 */ + + guchar nonce[16]; /**< The nonce used for direct connection handshake */ + gchar nonce_hash[37]; /**< The hash of nonce */ - PurpleProxyConnectData *connect_data; + 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 */ - gboolean acked; + int fd; /**< The direct connection socket */ + guint recv_handle; /**< The incoming data callback handle */ + guint send_handle; /**< The outgoing data callback handle */ - char *nonce; - - 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 scoket */ + void (*send_connection_info_msg_cb)(struct _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); + +/* + * 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 it's parameters (IP/port) are available. + */ +void +msn_dc_listen_socket_created_cb(int listenfd, gpointer data); #endif /* MSN_DIRECTCONN_H */ diff -r bfaf039aed87 -r 119bd7b072eb libpurple/protocols/msn/msg.c --- a/libpurple/protocols/msn/msg.c Tue Mar 16 06:20:05 2010 +0000 +++ b/libpurple/protocols/msn/msg.c Wed Mar 17 03:45:07 2010 +0000 @@ -1100,7 +1100,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); @@ -1113,59 +1114,75 @@ "Unable to parse invite msg body.\n"); return; } - - guid = g_hash_table_lookup(body, "Application-GUID"); + + /* + * 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 (guid == NULL) { - const gchar *cmd = g_hash_table_lookup( - body, "Invitation-Command"); + if (command == NULL || cookie == NULL) { + purple_debug_warning("msn", + "Invalid invitation message: " + "either Invitation-Command or Invitation-Cookie is missing or invaild" + ); + return; + + } else if (!strcmp(command, "INVITE")) { - 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"); + const gchar *guid = g_hash_table_lookup(body, "Application-GUID"); + + if (guid == NULL) { + const gchar *cmd = g_hash_table_lookup( + body, "Invitation-Command"); - accepted = TRUE; - - } else if (!strcmp(guid, "{02D3C01F-BF30-4825-A83A-DE7AF41648AA}")) { - purple_debug_info("msn", "Computer call\n"); + 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 (cmdproc->session) { - PurpleConversation *conv = NULL; - gchar *from = msg->remote_user; - gchar *buf = NULL; + accepted = TRUE; - 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 if (!strcmp(guid, MSN_FT_GUID)) { + + } else if (!strcmp(guid, "{02D3C01F-BF30-4825-A83A-DE7AF41648AA}")) { + purple_debug_info("msn", "Computer call\n"); + + 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; @@ -1176,15 +1193,21 @@ msn_message_set_flag(cancel, 'U'); text = g_strdup_printf("Invitation-Command: CANCEL\r\n" - "Invitation-Cookie: %s\r\n" - "Cancel-Code: REJECT_NOT_INSTALLED\r\n", - cookie); + "Invitation-Cookie: %s\r\n" + "Cancel-Code: REJECT_NOT_INSTALLED\r\n", + cookie); msn_message_set_bin_data(cancel, text, strlen(text)); g_free(text); msn_switchboard_send_msg(swboard, cancel, TRUE); msn_message_destroy(cancel); } + + } else { + /* + * Some other already estabilished invitation session. + * Can be retrieved by Invitation-Cookie. + */ } g_hash_table_destroy(body); diff -r bfaf039aed87 -r 119bd7b072eb libpurple/protocols/msn/slp.c --- a/libpurple/protocols/msn/slp.c Tue Mar 16 06:20:05 2010 +0000 +++ b/libpurple/protocols/msn/slp.c Wed Mar 17 03:45:07 2010 +0000 @@ -25,23 +25,27 @@ #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, +/* +static void msn_slp_send_ok(MsnSlpCall *slpcall, const char *branch, const char *type, const char *content); -static void send_decline(MsnSlpCall *slpcall, const char *branch, +static void msn_slp_send_decline(MsnSlpCall *slpcall, const char *branch, const char *type, const char *content); +*/ +static void request_user_display(MsnUser *user); -static void request_user_display(MsnUser *user); /************************************************************************** * Util @@ -91,7 +95,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); @@ -109,6 +113,7 @@ slpcall = xfer->data; + if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL) { if (slpcall->started) @@ -120,7 +125,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); @@ -185,6 +190,8 @@ void msn_xfer_end_cb(MsnSlpCall *slpcall, MsnSession *session) { + purple_debug_info("msn", "msn_xfer_end_cb\n"); + if ((purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_DONE) && (purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_CANCEL_REMOTE) && (purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_CANCEL_LOCAL)) @@ -198,6 +205,7 @@ gsize size) { PurpleXfer *xfer = slpcall->xfer; + purple_xfer_set_completed(xfer, TRUE); purple_xfer_end(xfer); } @@ -234,8 +242,8 @@ } #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,11 +261,18 @@ msn_slplink_queue_slpmsg(slplink, slpmsg); - msn_slpcall_session_init(slpcall); + /* + * TODO: Removed because it interferes with + * direct file transfer. If we're sending some file + * then this call initiates a p2p file transfer which is + * undesirable. + */ + + /* 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; @@ -308,6 +323,119 @@ return NULL; } +static gboolean +msn_slp_process_transresp(MsnSlpCall *slpcall, const char *content) +{ + /* A direct connection negotiation response */ + char *bridge; + MsnDirectConn *dc = slpcall->slplink->dc; + + 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"); + if(bridge && strcmp(bridge, "TCPv1") == 0) { + /* Ok, the client supports direct TCP connection */ + + if (dc->listen_data != NULL || dc->listenfd != -1) { + 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 { + /* The listening socket is ready. Send the INVITE here. */ + msn_dc_send_invite(dc); + } + + return TRUE; + + } else { + /* + * We should connect to the client so parse + * IP/port from response. + */ + char *ip, *port_str; + int port = 0; + + /* 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 + ); + return TRUE; + + } else { + /* + * Connection failed + * Try external IP/port (if specified) + */ + msn_dc_outgoing_connection_timeout_cb(dc); + return TRUE; + } + + g_free(ip); + + } else { + /* + * Omitted or invalid internal IP address / port + * Try external IP/port (if specified) + */ + msn_dc_outgoing_connection_timeout_cb(dc); + return TRUE; + } + + if (ip) + g_free(ip); + } + + } else { + /* + * Invalid direct connect invitation or + * TCP connection is not supported + */ + } + + if (bridge) + g_free(bridge); + + return FALSE; +} + static void got_sessionreq(MsnSlpCall *slpcall, const char *branch, const char *euf_guid, const char *context) @@ -330,7 +458,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); @@ -479,7 +607,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); } } @@ -548,67 +676,75 @@ } else if (!strcmp(type, "application/x-msnmsgr-transreqbody")) { - /* A direct connection? */ - - char *listening, *nonce; - char *content; - - if (FALSE) - { -#if 0 - MsnDirectConn *directconn; - /* const char *ip_addr; */ - char *ip_port; - int port; + /* A direct connection negotiation request */ + char *bridges; + + purple_debug_info("msn", "got_invite: transreqbody received\n"); + + g_return_if_fail(slpcall->xfer != NULL); - /* ip_addr = purple_prefs_get_string("/purple/ft/public_ip"); */ - ip_port = "5190"; - listening = "true"; - nonce = rand_guid(); - - directconn = msn_directconn_new(slplink); + /* Don't do anything if we already have a direct connection */ + g_return_if_fail(slpcall->slplink->dc == NULL); + + bridges = get_token(content, "Bridges: ", "\r\n"); + if(bridges && strstr(bridges, "TCPv1") != NULL) { + /* + * Ok, the client supports direct TCP connection + * Try to create a listening port + */ + char *content; + MsnDirectConn *dc; - /* msn_directconn_parse_nonce(directconn, nonce); */ - directconn->nonce = g_strdup(nonce); + dc = msn_dc_new(slpcall); - msn_directconn_listen(directconn); - - port = directconn->port; + dc->listen_data = purple_network_listen_range( + 0, 0, + SOCK_STREAM, + msn_dc_listen_socket_created_cb, + dc + ); - 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 + if (dc->listen_data == NULL) { + /* Listen socket creation failed */ + + purple_debug_info("msn", "got_invite: listening failed\n"); + + content = g_strdup( + "Bridge: TCPv1\r\n" + "Listening: false\r\n" + "Hashed-Nonce: {00000000-0000-0000-0000-000000000000}\r\n" + "\r\n" + ); + msn_slp_send_ok(slpcall, branch, + "application/x-msnmsgr-transrespbody", content); + g_free(content); + + } 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. + */ } - else - { - listening = "false"; - nonce = g_strdup("00000000-0000-0000-0000-000000000000"); - - content = g_strdup_printf( - "Bridge: TCPv1\r\n" - "Listening: %s\r\n" - "Nonce: {%s}\r\n" - "\r\n", - listening, - nonce); - } - - send_ok(slpcall, branch, - "application/x-msnmsgr-transrespbody", content); - - g_free(content); - g_free(nonce); } else if (!strcmp(type, "application/x-msnmsgr-transrespbody")) { + /* A direct connection negotiation response */ + g_return_if_fail(slpcall->xfer != NULL); + + msn_slp_process_transresp(slpcall, content); #if 0 char *ip_addrs; char *temp; @@ -646,6 +782,86 @@ if (!strcmp(type, "application/x-msnmsgr-sessionreqbody")) { + char *content; + char *header; + MsnSlpMessage *msg; + MsnDirectConn *dc; + + g_return_if_fail(slpcall->xfer != NULL); + + if(slpcall->slplink->dc != NULL) { + /* + * If we already have an estabilished direct connection + * then just start the transfer. + */ + msn_slpcall_session_init(slpcall); + return; + } + + /* 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 + ); + + header = g_strdup_printf( + "INVITE MSNMSGR:%s MSNSLP/1.0", + slpcall->slplink->remote_user + ); + + if (dc->listen_data == NULL) { + /* Listen socket creation failed */ + purple_debug_info("msn", "got_ok: listening failed\n"); + + content = g_strdup_printf( + "Bridges: TCPv1\r\n" + "NetID: %u\r\n" + "Conn-Type: IP-Restrict-NAT\r\n" + "UPnPNat: false\r\n" + "ICF: false\r\n" + "Hashed-Nonce: {%s}\r\n" + "\r\n", + + rand() % G_MAXUINT32, + dc->nonce_hash + ); + + } 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" + "Hashed-Nonce: {%s}\r\n" + "\r\n", + + dc->nonce_hash + ); + } + + msg = msn_slpmsg_sip_new( + slpcall, + 0, + header, + slpcall->branch, + "application/x-msnmsgr-transreqbody", + content + ); + g_free(header); + g_free(content); + + msn_slplink_queue_slpmsg(slpcall->slplink, msg); #if 0 if (slpcall->type == MSN_SLPCALL_DC) { @@ -690,7 +906,12 @@ msn_slpcall_session_init(slpcall); } #else - msn_slpcall_session_init(slpcall); + /* + * Removed because it messes up direct connection by + * starting p2p transfer + */ + + /* msn_slpcall_session_init(slpcall); */ #endif } else if (!strcmp(type, "application/x-msnmsgr-transreqbody")) @@ -700,6 +921,7 @@ } else if (!strcmp(type, "application/x-msnmsgr-transrespbody")) { + msn_slp_process_transresp(slpcall, content); #if 0 char *ip_addrs; char *temp; @@ -767,11 +989,25 @@ content = get_token(body, "\r\n\r\n", NULL); - if (branch && call_id && content_type && content) + if (branch && call_id) { - slpcall = msn_slpcall_new(slplink); - slpcall->id = call_id; - got_invite(slpcall, branch, content_type, content); + 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 = call_id; + got_invite(slpcall, branch, content_type, content); + } + else + { + g_free(call_id); + slpcall = NULL; + } } else { @@ -860,6 +1096,8 @@ { MsnSession *session; MsnSlpLink *slplink; + const char *data; + gsize len; session = cmdproc->servconn->session; slplink = msn_session_get_slplink(session, msg->remote_user); @@ -882,7 +1120,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 bfaf039aed87 -r 119bd7b072eb libpurple/protocols/msn/slp.h --- a/libpurple/protocols/msn/slp.h Tue Mar 16 06:20:05 2010 +0000 +++ b/libpurple/protocols/msn/slp.h Wed Mar 17 03:45:07 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 bfaf039aed87 -r 119bd7b072eb libpurple/protocols/msn/slpcall.c --- a/libpurple/protocols/msn/slpcall.c Tue Mar 16 06:20:05 2010 +0000 +++ b/libpurple/protocols/msn/slpcall.c Wed Mar 17 03:45:07 2010 +0000 @@ -66,6 +66,10 @@ slpcall->slplink = slplink; + slpcall->wait_for_socket = FALSE; + slpcall->xfer = NULL; + slpcall->branch = NULL; + msn_slplink_add_slpcall(slplink, slpcall); slpcall->timer = purple_timeout_add_seconds(MSN_SLPCALL_TIMEOUT, msn_slpcall_timeout, slpcall); diff -r bfaf039aed87 -r 119bd7b072eb libpurple/protocols/msn/slpcall.h --- a/libpurple/protocols/msn/slpcall.h Tue Mar 16 06:20:05 2010 +0000 +++ b/libpurple/protocols/msn/slpcall.h Wed Mar 17 03:45:07 2010 +0000 @@ -64,13 +64,15 @@ 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); /* Can be checksum, or smile */ char *data_info; - + PurpleXfer *xfer; union { GByteArray *incoming_data; diff -r bfaf039aed87 -r 119bd7b072eb libpurple/protocols/msn/slplink.c --- a/libpurple/protocols/msn/slplink.c Tue Mar 16 06:20:05 2010 +0000 +++ b/libpurple/protocols/msn/slplink.c Wed Mar 17 03:45:07 2010 +0000 @@ -155,11 +155,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_ESTABILISHED) + msn_dc_ref(slplink->dc); + */ } void msn_slplink_remove_slpcall(MsnSlpLink *slplink, MsnSlpCall *slpcall) { + /* + if (slplink->dc != NULL && slplink->dc->state == DC_STATE_ESTABILISHED) + 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. @@ -209,13 +219,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_ESTABILISHED) { - msn_directconn_send_msg(slplink->directconn, msg); + msn_dc_enqueue_msg(slplink->dc, msg); } else -#endif { if (slplink->swboard == NULL) { @@ -431,11 +439,30 @@ } } -static void -msn_slplink_send_ack(MsnSlpLink *slplink, MsnMessage *msg) +static MsnSlpMessage* +msn_slplink_create_ack(MsnSlpLink *slplink, MsnSlpHeader *header) { - MsnSlpMessage *slpmsg; + MsnSlpMessage *slpmsg; + + slpmsg = msn_slpmsg_new(slplink); + slpmsg->session_id = header->session_id; + slpmsg->size = header->total_size; + slpmsg->flags = 0x02; + 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); + + /* slpmsg = msn_slpmsg_new(slplink); slpmsg->session_id = msg->msnslp_header.session_id; @@ -445,6 +472,7 @@ slpmsg->ack_sub_id = msg->msnslp_header.ack_id; slpmsg->ack_size = msg->msnslp_header.total_size; slpmsg->info = "SLP ACK"; + */ msn_slplink_send_slpmsg(slplink, slpmsg); msn_slpmsg_destroy(slpmsg); @@ -491,38 +519,36 @@ } 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); + /* 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) { @@ -567,7 +593,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 */ @@ -615,45 +641,60 @@ 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; - + slpcall = msn_slp_process_msg(slplink, slpmsg); if (slpcall == NULL) { msn_slpmsg_destroy(slpmsg); return; } + + purple_debug_info("msn", "msn_slplink_process_msg: slpmsg complete\n"); - if (!slpcall->wasted) { + /*if (!slpcall->wasted) {*/ if (slpmsg->flags == 0x100) { +#if 0 MsnDirectConn *directconn; directconn = slplink->directconn; -#if 0 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) + slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 || + slpmsg->flags == 0x1000030) { /* Release all the messages and send the ACK */ - - msn_slplink_send_ack(slplink, msg); - msn_slplink_send_queued_slpmsgs(slplink); + + if (slpcall != NULL && 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 { + 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 != NULL && !slpcall->wait_for_socket && slpcall->wasted) msn_slpcall_destroy(slpcall); } } diff -r bfaf039aed87 -r 119bd7b072eb libpurple/protocols/msn/slplink.h --- a/libpurple/protocols/msn/slplink.h Tue Mar 16 06:20:05 2010 +0000 +++ b/libpurple/protocols/msn/slplink.h Wed Mar 17 03:45:07 2010 +0000 @@ -42,13 +42,12 @@ { MsnSession *session; MsnSwitchBoard *swboard; + MsnDirectConn *dc; char *remote_user; int slp_seq_id; - MsnDirectConn *directconn; - GList *slp_calls; GList *slp_msgs; @@ -79,7 +78,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 bfaf039aed87 -r 119bd7b072eb libpurple/protocols/msn/switchboard.c --- a/libpurple/protocols/msn/switchboard.c Tue Mar 16 06:20:05 2010 +0000 +++ b/libpurple/protocols/msn/switchboard.c Wed Mar 17 03:45:07 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) { + /* Destroy only those slplinks which use the switchboard */ + MsnSlpLink *slplink = swboard->slplinks->data; + + 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)