/** * This is the watcher callback for any listening socket that is * waiting for a peer to connect. When a peer connects we set the * input watcher to start reading data from the peer. * * To make sure that the connection is with the intended person and * not with a malicious middle man, we don't send anything until we've * received a peer frame from the remote user and have verified that * the cookie in the peer frame matches the cookie that was exchanged * in the channel 2 ICBM. */ void peer_connection_listen_cb(gpointer data, gint source, PurpleInputCondition cond) { PeerConnection *conn; struct sockaddr addr; socklen_t addrlen = sizeof(addr); conn = data; purple_debug_info("oscar", "Accepting connection on listener socket.\n"); conn->fd = accept(conn->listenerfd, &addr, &addrlen); if (conn->fd < 0) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) /* No connection yet--no worries */ /* TODO: Hmm, but they SHOULD be connected if we're here, right? */ return; peer_connection_trynext(conn); return; } if ((addr.sa_family != PF_INET) && (addr.sa_family != PF_INET6)) { /* Invalid connection type?! Continue waiting. */ close(conn->fd); return; } _purple_network_set_common_socket_flags(conn->fd); purple_input_remove(conn->watcher_incoming); peer_connection_finalize_connection(conn); }
/** * Someone else wants to establish a peer connection with us. */ void peer_connection_got_proposition(OscarData *od, const gchar *bn, const gchar *message, IcbmArgsCh2 *args) { PurpleConnection *gc; PurpleAccount *account; PeerConnection *conn; gchar *buf; gc = od->gc; account = purple_connection_get_account(gc); /* * If we have a connection with this same cookie then they are * probably just telling us they weren't able to connect to us * and we should try connecting to them, instead. Or they want * to go through a proxy. */ conn = peer_connection_find_by_cookie(od, bn, args->cookie); if ((conn != NULL) && (conn->type == args->type)) { purple_debug_info("oscar", "Remote user wants to try a " "different connection method\n"); g_free(conn->proxyip); g_free(conn->clientip); g_free(conn->verifiedip); if (args->use_proxy) conn->proxyip = g_strdup(args->proxyip); else conn->proxyip = NULL; conn->verifiedip = g_strdup(args->verifiedip); conn->clientip = g_strdup(args->clientip); conn->port = args->port; conn->use_proxy |= args->use_proxy; conn->lastrequestnumber++; peer_connection_trynext(conn); return; } /* If this is a direct IM, then close any existing session */ if (args->type == OSCAR_CAPABILITY_DIRECTIM) { conn = peer_connection_find_by_type(od, bn, args->type); if (conn != NULL) { /* Close the old direct IM and start a new one */ purple_debug_info("oscar", "Received new direct IM request " "from %s. Destroying old connection.\n", bn); peer_connection_destroy(conn, OSCAR_DISCONNECT_REMOTE_CLOSED, NULL); } } /* Check for proper arguments */ if (args->type == OSCAR_CAPABILITY_SENDFILE) { if ((args->info.sendfile.filename == NULL) || (args->info.sendfile.totsize == 0) || (args->info.sendfile.totfiles == 0)) { purple_debug_warning("oscar", "%s tried to send you a file with incomplete " "information.\n", bn); return; } } conn = peer_connection_new(od, args->type, bn); memcpy(conn->cookie, args->cookie, 8); if (args->use_proxy) conn->proxyip = g_strdup(args->proxyip); conn->clientip = g_strdup(args->clientip); conn->verifiedip = g_strdup(args->verifiedip); conn->port = args->port; conn->use_proxy |= args->use_proxy; conn->lastrequestnumber++; if (args->type == OSCAR_CAPABILITY_DIRECTIM) { buf = g_strdup_printf(_("%s has just asked to directly connect to %s"), bn, purple_account_get_username(account)); purple_request_action(conn, NULL, buf, _("This requires a direct connection between " "the two computers and is necessary for IM " "Images. Because your IP address will be " "revealed, this may be considered a privacy " "risk."), PURPLE_DEFAULT_ACTION_NONE, account, bn, NULL, conn, 2, _("C_onnect"), G_CALLBACK(peer_connection_got_proposition_yes_cb), _("Cancel"), G_CALLBACK(peer_connection_got_proposition_no_cb)); } else if (args->type == OSCAR_CAPABILITY_SENDFILE) { gchar *filename; conn->xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE, bn); if (conn->xfer) { conn->xfer->data = conn; purple_xfer_ref(conn->xfer); purple_xfer_set_size(conn->xfer, args->info.sendfile.totsize); /* Set the file name */ if (g_utf8_validate(args->info.sendfile.filename, -1, NULL)) filename = g_strdup(args->info.sendfile.filename); else filename = purple_utf8_salvage(args->info.sendfile.filename); if (args->info.sendfile.subtype == AIM_OFT_SUBTYPE_SEND_DIR) { /* * If they are sending us a directory then the last character * of the file name will be an asterisk. We don't want to * save stuff to a directory named "*" so we remove the * asterisk from the file name. */ char *tmp = strrchr(filename, '\\'); if ((tmp != NULL) && (tmp[1] == '*')) tmp[0] = '\0'; } purple_xfer_set_filename(conn->xfer, filename); g_free(filename); /* * Set the message, unless this is the dummy message from an * ICQ client or an empty message from an AIM client. * TODO: Maybe we should strip HTML and then see if strlen>0? */ if ((message != NULL) && (g_ascii_strncasecmp(message, "<ICQ_COOL_FT>", 13) != 0) && (g_ascii_strcasecmp(message, "<HTML>") != 0)) { purple_xfer_set_message(conn->xfer, message); } /* Setup our I/O op functions */ purple_xfer_set_init_fnc(conn->xfer, peer_oft_recvcb_init); purple_xfer_set_end_fnc(conn->xfer, peer_oft_recvcb_end); purple_xfer_set_request_denied_fnc(conn->xfer, peer_oft_cb_generic_cancel); purple_xfer_set_cancel_recv_fnc(conn->xfer, peer_oft_cb_generic_cancel); purple_xfer_set_ack_fnc(conn->xfer, peer_oft_recvcb_ack_recv); /* Now perform the request */ purple_xfer_request(conn->xfer); } } }
/** * We've just opened a listener socket, so we send the remote * user an ICBM and ask them to connect to us. */ static void peer_connection_establish_listener_cb(int listenerfd, gpointer data) { PeerConnection *conn; OscarData *od; PurpleConnection *gc; PurpleAccount *account; PurpleConversation *conv; char *tmp; FlapConnection *bos_conn; const char *listener_ip; const guchar *ip_atoi; unsigned short listener_port; conn = data; conn->listen_data = NULL; if (listenerfd < 0) { /* Could not open listener socket */ peer_connection_trynext(conn); return; } od = conn->od; gc = od->gc; account = purple_connection_get_account(gc); conn->listenerfd = listenerfd; /* Watch for new connections on our listener socket */ conn->watcher_incoming = purple_input_add(conn->listenerfd, PURPLE_INPUT_READ, peer_connection_listen_cb, conn); /* Send the "please connect to me!" ICBM */ bos_conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICBM); if (bos_conn == NULL) { /* Not good */ peer_connection_trynext(conn); return; } if (bos_conn->gsc) listener_ip = purple_network_get_my_ip(bos_conn->gsc->fd); else listener_ip = purple_network_get_my_ip(bos_conn->fd); ip_atoi = purple_network_ip_atoi(listener_ip); if (ip_atoi == NULL) { /* Could not convert IP to 4 byte array--weird, but this does happen for some users (#4829, Adium #15839). Maybe they're connecting with IPv6...? Maybe through a proxy? */ purple_debug_error("oscar", "Can't ask peer to connect to us " "because purple_network_ip_atoi(%s) returned NULL. " "fd=%d. is_ssl=%d\n", listener_ip ? listener_ip : "(null)", bos_conn->gsc ? bos_conn->gsc->fd : bos_conn->fd, bos_conn->gsc ? 1 : 0); peer_connection_trynext(conn); return; } listener_port = purple_network_get_port_from_fd(conn->listenerfd); if (conn->type == OSCAR_CAPABILITY_DIRECTIM) { aim_im_sendch2_odc_requestdirect(od, conn->cookie, conn->bn, ip_atoi, listener_port, ++conn->lastrequestnumber); /* Print a message to a local conversation window */ conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, conn->bn); tmp = g_strdup_printf(_("Asking %s to connect to us at %s:%hu for " "Direct IM."), conn->bn, listener_ip, listener_port); purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL)); g_free(tmp); } else if (conn->type == OSCAR_CAPABILITY_SENDFILE) { aim_im_sendch2_sendfile_requestdirect(od, conn->cookie, conn->bn, ip_atoi, listener_port, ++conn->lastrequestnumber, (const gchar *)conn->xferdata.name, conn->xferdata.size, conn->xferdata.totfiles); } }
static void send_cb(gpointer data, gint source, PurpleInputCondition cond) { PeerConnection *conn; gsize writelen; gssize wrotelen; conn = data; writelen = purple_circ_buffer_get_max_read(conn->buffer_outgoing); if (writelen == 0) { purple_input_remove(conn->watcher_outgoing); conn->watcher_outgoing = 0; /* * The buffer is currently empty, so reset the current input * and output positions to the start of the buffer. We do * this so that the next chunk of data that we put into the * buffer can be read back out of the buffer in one fell swoop. * Otherwise it gets fragmented and we have to read from the * second half of the buffer than go back and read the rest of * the chunk from the first half. * * We're using TCP, which is a stream based protocol, so this * isn't supposed to matter. However, experience has shown * that at least the proxy file transfer code in AIM 6.1.41.2 * requires that the entire OFT frame arrive all at once. If * the frame is fragmented then AIM freaks out and aborts the * file transfer. Somebody should teach those guys how to * write good TCP code. */ conn->buffer_outgoing->inptr = conn->buffer_outgoing->buffer; conn->buffer_outgoing->outptr = conn->buffer_outgoing->buffer; return; } wrotelen = send(conn->fd, conn->buffer_outgoing->outptr, writelen, 0); if (wrotelen <= 0) { if (wrotelen < 0 && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) /* No worries */ return; if (conn->ready) { purple_input_remove(conn->watcher_outgoing); conn->watcher_outgoing = 0; close(conn->fd); conn->fd = -1; peer_connection_schedule_destroy(conn, OSCAR_DISCONNECT_LOST_CONNECTION, NULL); } else { /* * This could happen when unable to send a negotiation * frame to a peer proxy server. */ peer_connection_trynext(conn); } return; } purple_circ_buffer_mark_read(conn->buffer_outgoing, wrotelen); conn->lastactivity = time(NULL); }
static void peer_proxy_connection_recv_cb(gpointer data, gint source, PurpleInputCondition cond) { PeerConnection *conn; gssize read; ProxyFrame *frame; conn = data; frame = conn->frame; /* Start reading a new proxy frame */ if (frame == NULL) { /* Read the first 12 bytes (frame length and header) */ read = recv(conn->fd, conn->proxy_header + conn->proxy_header_received, 12 - conn->proxy_header_received, 0); /* Check if the proxy server closed the connection */ if (read == 0) { purple_debug_info("oscar", "Peer proxy server closed connection\n"); peer_connection_trynext(conn); return; } /* If there was an error then close the connection */ if (read < 0) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) /* No worries */ return; purple_debug_info("oscar", "Lost connection with peer proxy server\n"); peer_connection_trynext(conn); return; } conn->lastactivity = time(NULL); /* If we don't even have the first 12 bytes then do nothing */ conn->proxy_header_received += read; if (conn->proxy_header_received < 12) return; /* We only support a specific version of the proxy protocol */ if (aimutil_get16(&conn->proxy_header[2]) != PEER_PROXY_PACKET_VERSION) { purple_debug_warning("oscar", "Expected peer proxy protocol " "version %u but received version %u. Closing " "connection.\n", PEER_PROXY_PACKET_VERSION, aimutil_get16(&conn->proxy_header[2])); peer_connection_trynext(conn); return; } /* Initialize a new temporary ProxyFrame for incoming data */ frame = g_new0(ProxyFrame, 1); frame->payload.len = aimutil_get16(&conn->proxy_header[0]) - 10; frame->version = aimutil_get16(&conn->proxy_header[2]); frame->type = aimutil_get16(&conn->proxy_header[4]); frame->unknown = aimutil_get16(&conn->proxy_header[6]); frame->flags = aimutil_get16(&conn->proxy_header[10]); if (frame->payload.len > 0) frame->payload.data = g_new(guint8, frame->payload.len); conn->frame = frame; } /* If this frame has a payload then attempt to read it */ if (frame->payload.len - frame->payload.offset > 0) { /* Read data into the temporary buffer until it is complete */ read = recv(conn->fd, &frame->payload.data[frame->payload.offset], frame->payload.len - frame->payload.offset, 0); /* Check if the proxy server closed the connection */ if (read == 0) { purple_debug_info("oscar", "Peer proxy server closed connection\n"); g_free(frame->payload.data); g_free(frame); conn->frame = NULL; peer_connection_trynext(conn); return; } /* If there was an error then close the connection */ if (read < 0) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) /* No worries */ return; purple_debug_info("oscar", "Lost connection with peer proxy server\n"); g_free(frame->payload.data); g_free(frame); conn->frame = NULL; peer_connection_trynext(conn); return; } frame->payload.offset += read; } conn->lastactivity = time(NULL); if (frame->payload.offset < frame->payload.len) /* Waiting for more data to arrive */ return; /* We have a complete proxy frame! Handle it and continue reading */ conn->frame = NULL; byte_stream_rewind(&frame->payload); peer_proxy_recv_frame(conn, frame); g_free(frame->payload.data); g_free(frame); conn->proxy_header_received = 0; }
/** * Handle an incoming peer proxy negotiation frame. */ static void peer_proxy_recv_frame(PeerConnection *conn, ProxyFrame *frame) { purple_debug_info("oscar", "Incoming peer proxy frame with " "type=0x%04hx, unknown=0x%08x, " "flags=0x%04hx, and payload length=%hd\n", frame->type, frame->unknown, frame->flags, frame->payload.len); if (frame->type == PEER_PROXY_TYPE_CREATED) { /* * Read in 2 byte port then 4 byte IP and tell the * remote user to connect to it by sending an ICBM. */ guint16 pin; int i; guint8 ip[4]; pin = byte_stream_get16(&frame->payload); for (i = 0; i < 4; i++) ip[i] = byte_stream_get8(&frame->payload); if (conn->type == OSCAR_CAPABILITY_DIRECTIM) aim_im_sendch2_odc_requestproxy(conn->od, conn->cookie, conn->bn, ip, pin, ++conn->lastrequestnumber); else if (conn->type == OSCAR_CAPABILITY_SENDFILE) { aim_im_sendch2_sendfile_requestproxy(conn->od, conn->cookie, conn->bn, ip, pin, ++conn->lastrequestnumber, (const gchar *)conn->xferdata.name, conn->xferdata.size, conn->xferdata.totfiles); } } else if (frame->type == PEER_PROXY_TYPE_READY) { purple_input_remove(conn->watcher_incoming); conn->watcher_incoming = 0; peer_connection_finalize_connection(conn); } else if (frame->type == PEER_PROXY_TYPE_ERROR) { if (byte_stream_empty(&frame->payload) >= 2) { guint16 error; const char *msg; error = byte_stream_get16(&frame->payload); if (error == 0x000d) msg = "bad request"; else if (error == 0x0010) msg = "initial request timed out"; else if (error == 0x001a) msg ="accept period timed out"; else msg = "unknown reason"; purple_debug_info("oscar", "Proxy negotiation failed with " "error 0x%04hx: %s\n", error, msg); } else { purple_debug_warning("oscar", "Proxy negotiation failed with " "an unknown error\n"); } peer_connection_trynext(conn); } else { purple_debug_warning("oscar", "Unknown peer proxy frame type 0x%04hx.\n", frame->type); } }
/** * We've just opened a listener socket, so we send the remote * user an ICBM and ask them to connect to us. */ static void peer_connection_establish_listener_cb(int listenerfd, gpointer data) { PeerConnection *conn; OscarData *od; PurpleConnection *gc; PurpleAccount *account; PurpleConversation *conv; char *tmp; FlapConnection *bos_conn; const char *listener_ip; unsigned short listener_port; conn = data; conn->listen_data = NULL; if (listenerfd < 0) { /* Could not open listener socket */ peer_connection_trynext(conn); return; } od = conn->od; gc = od->gc; account = purple_connection_get_account(gc); conn->listenerfd = listenerfd; /* Watch for new connections on our listener socket */ conn->watcher_incoming = purple_input_add(conn->listenerfd, PURPLE_INPUT_READ, peer_connection_listen_cb, conn); /* Send the "please connect to me!" ICBM */ bos_conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICBM); if (bos_conn == NULL) { /* Not good */ peer_connection_trynext(conn); return; } if (bos_conn->gsc) listener_ip = purple_network_get_my_ip(bos_conn->gsc->fd); else listener_ip = purple_network_get_my_ip(bos_conn->fd); listener_port = purple_network_get_port_from_fd(conn->listenerfd); if (conn->type == OSCAR_CAPABILITY_DIRECTIM) { aim_im_sendch2_odc_requestdirect(od, conn->cookie, conn->sn, purple_network_ip_atoi(listener_ip), listener_port, ++conn->lastrequestnumber); /* Print a message to a local conversation window */ conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, conn->sn); tmp = g_strdup_printf(_("Asking %s to connect to us at %s:%hu for " "Direct IM."), conn->sn, listener_ip, listener_port); purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL)); g_free(tmp); } else if (conn->type == OSCAR_CAPABILITY_SENDFILE) { aim_im_sendch2_sendfile_requestdirect(od, conn->cookie, conn->sn, purple_network_ip_atoi(listener_ip), listener_port, ++conn->lastrequestnumber, (const gchar *)conn->xferdata.name, conn->xferdata.size, conn->xferdata.totfiles); } }