paulo@0: /* paulo@0: * $Id: gt_accept.c,v 1.64 2005/01/04 15:03:40 mkern Exp $ paulo@0: * paulo@0: * Copyright (C) 2001-2003 giFT project (gift.sourceforge.net) paulo@0: * paulo@0: * This program is free software; you can redistribute it and/or modify it paulo@0: * under the terms of the GNU General Public License as published by the paulo@0: * Free Software Foundation; either version 2, or (at your option) any paulo@0: * later version. paulo@0: * paulo@0: * This program is distributed in the hope that it will be useful, but paulo@0: * WITHOUT ANY WARRANTY; without even the implied warranty of paulo@0: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU paulo@0: * General Public License for more details. paulo@0: */ paulo@0: paulo@0: #include "gt_gnutella.h" paulo@0: paulo@0: #include "gt_packet.h" paulo@0: #include "gt_utils.h" paulo@0: paulo@0: #include "gt_connect.h" paulo@0: paulo@0: #include "gt_node.h" paulo@0: #include "gt_node_cache.h" paulo@0: #include "gt_netorg.h" paulo@0: paulo@0: #include "gt_xfer_obj.h" paulo@0: #include "gt_xfer.h" paulo@0: #include "gt_http_server.h" paulo@0: paulo@0: #include "gt_accept.h" paulo@0: #include "gt_bind.h" paulo@0: #include "gt_ban.h" paulo@0: #include "gt_version.h" paulo@0: paulo@0: #include "message/gt_message.h" /* gnutella_start_connection */ paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: #define MAX_FDBUF_SIZE 16384 paulo@0: paulo@0: #define INCOMING_TIMEOUT (1 * MINUTES) paulo@0: paulo@0: /*****************************************************************************/ paulo@0: /* Handle incoming connections to this node */ paulo@0: paulo@0: /* paulo@0: * Wrap TCPC and associated timeout for incoming connections in order to limit paulo@0: * the number of incoming connections. paulo@0: */ paulo@0: struct incoming_conn paulo@0: { paulo@0: TCPC *c; paulo@0: timer_id timer; paulo@0: }; paulo@0: paulo@0: #define GT_METHOD(func) void func (int fd, input_id id, \ paulo@0: struct incoming_conn *conn) paulo@0: typedef void (*GtAcceptFunc) (int fd, input_id id, struct incoming_conn *conn); paulo@0: paulo@0: GT_METHOD(gt_server_accept); paulo@0: GT_METHOD(gt_server_get); paulo@0: GT_METHOD(gt_server_giv); paulo@0: paulo@0: static struct server_cmd paulo@0: { paulo@0: char *name; paulo@0: GtAcceptFunc callback; paulo@0: } paulo@0: server_commands[] = paulo@0: { paulo@0: { "GNUTELLA", gt_server_accept }, paulo@0: { "GET", gt_server_get }, paulo@0: { "HEAD", gt_server_get }, paulo@0: { "GIV", gt_server_giv }, paulo@0: { NULL, NULL } paulo@0: }; paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: static void send_node_headers (int fd, input_id id, TCPC *c); paulo@0: static void recv_final_handshake (int fd, input_id id, TCPC *c); paulo@0: static void determine_method (int fd, input_id id, paulo@0: struct incoming_conn *conn); paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: static void incoming_conn_free (struct incoming_conn *conn) paulo@0: { paulo@0: timer_remove (conn->timer); paulo@0: free (conn); paulo@0: } paulo@0: paulo@0: static void incoming_conn_close (struct incoming_conn *conn) paulo@0: { paulo@0: tcp_close (conn->c); paulo@0: incoming_conn_free (conn); paulo@0: } paulo@0: paulo@0: static BOOL conn_timeout (struct incoming_conn *conn) paulo@0: { paulo@0: incoming_conn_close (conn); paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: static struct incoming_conn *incoming_conn_alloc (TCPC *c) paulo@0: { paulo@0: struct incoming_conn *conn; paulo@0: paulo@0: conn = malloc (sizeof (struct incoming_conn)); paulo@0: if (!conn) paulo@0: return NULL; paulo@0: paulo@0: conn->c = c; paulo@0: conn->timer = timer_add (INCOMING_TIMEOUT, (TimerCallback)conn_timeout, paulo@0: conn); paulo@0: paulo@0: return conn; paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: BOOL gt_fdbuf_full (FDBuf *buf) paulo@0: { paulo@0: size_t len = MAX_FDBUF_SIZE; paulo@0: paulo@0: if (fdbuf_data (buf, &len) == NULL) paulo@0: return TRUE; paulo@0: paulo@0: return len >= MAX_FDBUF_SIZE; paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: /* paulo@0: * Receive an incoming connection. paulo@0: */ paulo@0: void gnutella_handle_incoming (int fd, input_id id, TCPC *listen) paulo@0: { paulo@0: TCPC *c; paulo@0: paulo@0: if (!(c = tcp_accept (listen, FALSE))) paulo@0: return; paulo@0: paulo@0: if (HANDSHAKE_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "got a new connection"); paulo@0: paulo@0: id = INPUT_NONE; paulo@0: gt_handshake_dispatch_incoming (fd, id, c); paulo@0: } paulo@0: paulo@0: /* paulo@0: * Mark not firewalled if the other end isn't local. paulo@0: */ paulo@0: static void fw_status_update (TCPC *c) paulo@0: { paulo@0: if (!c->outgoing && !net_match_host (c->host, "LOCAL")) paulo@0: { paulo@0: if (GT_SELF->firewalled) paulo@0: GT->DBGSOCK (GT, c, "connected, clearing firewalled status"); paulo@0: paulo@0: gt_bind_clear_firewalled (); paulo@0: } paulo@0: } paulo@0: paulo@0: /* paulo@0: * Allocate a structure to track this connection and continue the dispatching paulo@0: * process. paulo@0: */ paulo@0: void gt_handshake_dispatch_incoming (int fd, input_id id, TCPC *c) paulo@0: { paulo@0: struct incoming_conn *conn; paulo@0: in_addr_t peer_ip; paulo@0: paulo@0: /* paulo@0: * This will trigger if the connection was closed on the remote end paulo@0: * or if there's a input timer setup to timeout this connection. In the paulo@0: * latter case, we have to avoid adding any inputs for the connection. paulo@0: */ paulo@0: if (net_sock_error (c->fd)) paulo@0: { paulo@0: tcp_close (c); paulo@0: return; paulo@0: } paulo@0: paulo@0: peer_ip = net_peer (c->fd); paulo@0: paulo@0: /* paulo@0: * While this might be nice for the banner, it's not nice to the ban-ee, paulo@0: * and I'm sure there's some bugs in the banning code, so we should paulo@0: * send back an error instead. paulo@0: */ paulo@0: #if 0 paulo@0: if (gt_ban_ipv4_is_banned (peer_ip)) paulo@0: { paulo@0: if (HANDSHAKE_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "not accepting connection [address banned]"); paulo@0: paulo@0: tcp_close (c); paulo@0: return; paulo@0: } paulo@0: #endif paulo@0: paulo@0: /* paulo@0: * If there are too many HTTP connections for this IP, close the paulo@0: * connection now before investing any more resources servicing it. paulo@0: */ paulo@0: if (gt_http_connection_length (GT_TRANSFER_UPLOAD, peer_ip) >= paulo@0: HTTP_MAX_PERUSER_UPLOAD_CONNS) paulo@0: { paulo@0: if (HTTP_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "too many connections for this user, closing"); paulo@0: paulo@0: tcp_close (c); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* local hosts_allow may need to be evaluated to keep outside sources paulo@0: * away */ paulo@0: if (GNUTELLA_LOCAL_MODE) paulo@0: { paulo@0: if (!net_match_host (peer_ip, GNUTELLA_LOCAL_ALLOW)) paulo@0: { paulo@0: if (HANDSHAKE_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "non-local connection, closing"); paulo@0: paulo@0: tcp_close (c); paulo@0: return; paulo@0: } paulo@0: } paulo@0: paulo@0: if (!(conn = incoming_conn_alloc (c))) paulo@0: { paulo@0: tcp_close (c); paulo@0: return; paulo@0: } paulo@0: paulo@0: input_remove (id); paulo@0: input_add (c->fd, conn, INPUT_READ, paulo@0: (InputCallback)determine_method, 0); paulo@0: } paulo@0: paulo@0: /* paulo@0: * Dispatch incoming connections to the proper subsystem. paulo@0: */ paulo@0: static void determine_method (int fd, input_id id, struct incoming_conn *conn) paulo@0: { paulo@0: struct server_cmd *command; paulo@0: FDBuf *fdbuf; paulo@0: int ret; paulo@0: char *request; paulo@0: TCPC *c = conn->c; paulo@0: paulo@0: fdbuf = tcp_readbuf (c); paulo@0: paulo@0: if ((ret = fdbuf_delim (fdbuf, "\n")) < 0) paulo@0: { paulo@0: if (HANDSHAKE_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "error: %s", GIFT_NETERROR ()); paulo@0: paulo@0: incoming_conn_close (conn); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* some data was read: update fw status */ paulo@0: fw_status_update (c); paulo@0: paulo@0: if (gt_fdbuf_full (fdbuf)) paulo@0: { paulo@0: incoming_conn_close (conn); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (ret > 0) paulo@0: return; paulo@0: paulo@0: /* paulo@0: * NOTE: fdbuf_release() is not called here, so the first line is left on paulo@0: * the FDBuf. paulo@0: */ paulo@0: request = fdbuf_data (fdbuf, NULL); paulo@0: paulo@0: for (command = server_commands; command->name != NULL; command++) paulo@0: { paulo@0: if (!strncasecmp (command->name, request, strlen (command->name))) paulo@0: { paulo@0: input_remove (id); paulo@0: input_add (fd, conn, INPUT_READ, paulo@0: (InputCallback)command->callback, 0); paulo@0: paulo@0: return; paulo@0: } paulo@0: } paulo@0: paulo@0: if (HANDSHAKE_DEBUG) paulo@0: GT->DBGFN (GT, "bad command: %s", request); paulo@0: paulo@0: incoming_conn_close (conn); paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: /* paulo@0: * Main connection acceptance routine. This begins the connection's paulo@0: * journey through the rest of the handshaking code. paulo@0: */ paulo@0: GT_METHOD(gt_server_accept) paulo@0: { paulo@0: char *request; paulo@0: char *version_str; paulo@0: size_t request_len = 0; paulo@0: int n; paulo@0: GtNode *node; paulo@0: TCPC *c = conn->c; paulo@0: FDBuf *buf; paulo@0: paulo@0: if (HANDSHAKE_DEBUG) paulo@0: GT->DBGFN (GT, "entered"); paulo@0: paulo@0: buf = tcp_readbuf (c); paulo@0: paulo@0: if ((n = fdbuf_delim (buf, "\n")) < 0) paulo@0: { paulo@0: if (HANDSHAKE_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "error on recv: %s", GIFT_NETERROR ()); paulo@0: paulo@0: incoming_conn_close (conn); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (gt_fdbuf_full (buf)) paulo@0: { paulo@0: incoming_conn_close (conn); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (n > 0) paulo@0: return; paulo@0: paulo@0: request = fdbuf_data (buf, &request_len); paulo@0: paulo@0: if (!gt_http_header_terminated (request, request_len)) paulo@0: return; paulo@0: paulo@0: fdbuf_release (buf); paulo@0: paulo@0: if (HANDSHAKE_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "accepted headers:\n%s", request); paulo@0: paulo@0: version_str = strchr (request, '/'); paulo@0: if (version_str) paulo@0: version_str++; paulo@0: paulo@0: if (strncasecmp ("GNUTELLA CONNECT/", request, paulo@0: sizeof ("GNUTELLA CONNECT/") - 1) != 0) paulo@0: { paulo@0: if (HANDSHAKE_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "bad handshake header"); paulo@0: paulo@0: incoming_conn_close (conn); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* deny 0.4 connections */ paulo@0: if (!version_str || strncasecmp (version_str, "0.4", 3) == 0) paulo@0: { paulo@0: if (HANDSHAKE_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "closing 0.4 connection"); paulo@0: paulo@0: incoming_conn_close (conn); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* make a node out of this connection */ paulo@0: if (!(node = gt_node_instantiate (c))) paulo@0: { paulo@0: if (HANDSHAKE_DEBUG) paulo@0: GT->DBGFN (GT, "node_instantiate failed"); paulo@0: paulo@0: incoming_conn_close (conn); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* paulo@0: * Update the start_connect_time so the node will stick around for a while paulo@0: * even when the node list gets trimmed. paulo@0: */ paulo@0: node->start_connect_time = time (NULL); paulo@0: paulo@0: gt_node_state_set (node, GT_NODE_CONNECTING_1); paulo@0: node->incoming = TRUE; paulo@0: paulo@0: /* paulo@0: * Store the http header. paulo@0: * paulo@0: * NOTE: We don't check the return code here. This function expects a paulo@0: * properly formatted HTTP response, but we are handing it a paulo@0: * Gnutella connection request instead, so it will return failure. paulo@0: */ paulo@0: gnutella_parse_response_headers (request, &node->hdr); paulo@0: paulo@0: /* paulo@0: * Use the node handshake timeout timer now, and get rid of the paulo@0: * generic incoming connection timeout timer. paulo@0: */ paulo@0: gnutella_set_handshake_timeout (c, TIMEOUT_2 * SECONDS); paulo@0: incoming_conn_free (conn); paulo@0: paulo@0: input_remove (id); paulo@0: input_add (fd, c, INPUT_WRITE, paulo@0: (InputCallback)send_node_headers, TIMEOUT_DEF); paulo@0: } paulo@0: paulo@0: GT_METHOD(gt_server_giv) paulo@0: { paulo@0: FDBuf *buf; paulo@0: int n; paulo@0: in_addr_t peer_ip; paulo@0: char *response; paulo@0: size_t response_len = 0; paulo@0: char *client_id; paulo@0: gt_guid_t *guid; paulo@0: TCPC *c = conn->c; paulo@0: paulo@0: if (HTTP_DEBUG || HANDSHAKE_DEBUG) paulo@0: GT->DBGFN (GT, "entered"); paulo@0: paulo@0: buf = tcp_readbuf (c); paulo@0: paulo@0: if ((n = fdbuf_delim (buf, "\n")) < 0) paulo@0: { paulo@0: incoming_conn_close (conn); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (gt_fdbuf_full (buf)) paulo@0: { paulo@0: incoming_conn_close (conn); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (n > 0) paulo@0: return; paulo@0: paulo@0: response = fdbuf_data (buf, &response_len); paulo@0: paulo@0: if (!gt_http_header_terminated (response, response_len)) paulo@0: return; paulo@0: paulo@0: fdbuf_release (buf); paulo@0: paulo@0: if (HTTP_DEBUG || HANDSHAKE_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "giv response=%s", response); paulo@0: paulo@0: string_sep (&response, " "); /* Skip "GIV " */ paulo@0: string_sep (&response, ":"); /* Skip the file index */ paulo@0: paulo@0: client_id = string_sep (&response, "/"); paulo@0: string_lower (client_id); paulo@0: paulo@0: if (!(guid = gt_guid_bin (client_id))) paulo@0: { paulo@0: incoming_conn_close (conn); paulo@0: return; paulo@0: } paulo@0: paulo@0: peer_ip = net_peer (c->fd); paulo@0: paulo@0: /* paulo@0: * This will continue the GtTransfer if one is found, and store paulo@0: * the connection if one is not. The connection will be resumed paulo@0: * when a chunk for this source is reissued. paulo@0: */ paulo@0: gt_push_source_add_conn (guid, peer_ip, c); paulo@0: paulo@0: incoming_conn_free (conn); paulo@0: free (guid); paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: paulo@0: GT_METHOD(gt_server_get) paulo@0: { paulo@0: if (HTTP_DEBUG) paulo@0: GT->DBGFN (GT, "entered"); paulo@0: paulo@0: /* dispatch to the HTTP server code */ paulo@0: gt_http_server_dispatch (fd, id, conn->c); paulo@0: paulo@0: /* get rid of the tracking information for the connection in this paulo@0: * subsystem */ paulo@0: incoming_conn_free (conn); paulo@0: } paulo@0: paulo@0: /*****************************************************************************/ paulo@0: /* GNUTELLA/0.6 CONNECTIONS */ paulo@0: paulo@0: BOOL gt_http_header_terminated (char *data, size_t len) paulo@0: { paulo@0: int cnt; paulo@0: paulo@0: assert (len > 0); paulo@0: len--; paulo@0: paulo@0: for (cnt = 0; len > 0 && cnt < 2; cnt++) paulo@0: { paulo@0: if (data[len--] != '\n') paulo@0: break; paulo@0: paulo@0: /* treat CRLF as LF */ paulo@0: if (data[len] == '\r') paulo@0: len--; paulo@0: } paulo@0: paulo@0: return (cnt == 2); paulo@0: } paulo@0: paulo@0: /* TODO: header continuation and joining of multiple occurrences */ paulo@0: void gt_http_header_parse (char *headers, Dataset **d) paulo@0: { paulo@0: char *line, *key; paulo@0: paulo@0: while ((line = string_sep_set (&headers, "\r\n"))) paulo@0: { paulo@0: key = string_sep (&line, ":"); paulo@0: paulo@0: if (!key || !line) paulo@0: continue; paulo@0: paulo@0: string_trim (key); paulo@0: string_trim (line); paulo@0: paulo@0: /* dont allow empty key-values, need to check this too */ paulo@0: if (string_isempty (line)) paulo@0: continue; paulo@0: paulo@0: dataset_insertstr (d, string_lower (key), line); paulo@0: } paulo@0: } paulo@0: paulo@0: static void send_nodes (struct cached_node *node, String *s) paulo@0: { paulo@0: if (s->str[s->len - 1] != ':') paulo@0: string_append (s, ","); paulo@0: else paulo@0: string_append (s, " "); paulo@0: paulo@0: string_appendf (s, "%s:%hu", net_ip_str (node->addr.ip), node->addr.port); paulo@0: } paulo@0: paulo@0: static void deny_connection (TCPC *c, int code, char *msg) paulo@0: { paulo@0: String *s; paulo@0: List *nodes; paulo@0: in_addr_t ip; paulo@0: paulo@0: if (!(s = string_new (NULL, 0, 0, TRUE))) paulo@0: return; paulo@0: paulo@0: string_appendf (s, "GNUTELLA/0.6 %d %s\r\n", code, msg); paulo@0: string_appendf (s, "User-Agent: %s\r\n", gt_version()); paulo@0: paulo@0: ip = net_peer (c->fd); paulo@0: if (!gt_is_local_ip (ip, 0)) paulo@0: string_appendf (s, "Remote-IP: %s\r\n", net_ip_str (ip)); paulo@0: paulo@0: /* append some different nodes to try */ paulo@0: nodes = gt_node_cache_get (10); paulo@0: paulo@0: if (nodes) paulo@0: { paulo@0: string_append (s, "X-Try-Ultrapeers:"); paulo@0: paulo@0: list_foreach (nodes, (ListForeachFunc)send_nodes, s); paulo@0: list_free (nodes); paulo@0: paulo@0: string_append (s, "\r\n"); paulo@0: } paulo@0: paulo@0: /* append message terminator */ paulo@0: string_append (s, "\r\n"); paulo@0: paulo@0: /* we will be closing the connection after this, so we don't paulo@0: * care if the send fails or not. */ paulo@0: tcp_send (c, s->str, s->len); paulo@0: paulo@0: if (HANDSHAKE_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "connection denied response:\n%s", s->str); paulo@0: paulo@0: string_free (s); paulo@0: } paulo@0: paulo@0: static void setup_node_class (GtNode *node) paulo@0: { paulo@0: char *ultrapeer; paulo@0: char *qrp; paulo@0: paulo@0: ultrapeer = dataset_lookupstr (node->hdr, "x-ultrapeer"); paulo@0: qrp = dataset_lookupstr (node->hdr, "x-query-routing"); paulo@0: paulo@0: if (ultrapeer && !strcasecmp (ultrapeer, "true") && qrp != NULL) paulo@0: gt_node_class_set (node, GT_NODE_ULTRA); paulo@0: else paulo@0: gt_node_class_set (node, GT_NODE_LEAF); paulo@0: } paulo@0: paulo@0: BOOL gnutella_auth_connection (TCPC *c) paulo@0: { paulo@0: GtNode *node; paulo@0: paulo@0: node = GT_NODE(c); paulo@0: assert (GT_NODE(c) == node && GT_CONN(node) == c); paulo@0: paulo@0: /* set the class of this node based on the headers sent */ paulo@0: setup_node_class (node); paulo@0: paulo@0: /* paulo@0: * If the remote node is only crawling us, accept the connection paulo@0: * no matter what. paulo@0: */ paulo@0: if (dataset_lookupstr (node->hdr, "crawler")) paulo@0: return TRUE; paulo@0: paulo@0: /* paulo@0: * If we are a leaf node, and so is this node, deny the connection, paulo@0: * but send 'X-Try-Ultrapeer:' headers with the ultrapeers we paulo@0: * are connected to in order to try to bootstrap the remote client. paulo@0: */ paulo@0: if (!(GT_SELF->klass & GT_NODE_ULTRA) && (node->klass & GT_NODE_LEAF)) paulo@0: { paulo@0: deny_connection (c, 503, "I am a shielded leaf node"); paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: if (gt_conn_need_connections (node->klass) <= 0) paulo@0: { paulo@0: deny_connection (c, 503, "Too many connections"); paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: if (gt_ban_ipv4_is_banned (node->ip)) paulo@0: { paulo@0: deny_connection (c, 403, "Unauthorized"); paulo@0: return FALSE; paulo@0: } paulo@0: paulo@0: return TRUE; paulo@0: } paulo@0: paulo@0: static void send_node_headers (int fd, input_id id, TCPC *c) paulo@0: { paulo@0: if (net_sock_error (c->fd)) paulo@0: { paulo@0: gt_node_error (c, NULL); paulo@0: gt_node_disconnect (c); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (!gnutella_auth_connection (c)) paulo@0: { paulo@0: gt_node_error (c, "[incoming] connection not authorized"); paulo@0: gt_node_disconnect (c); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* send OK response to the peer and send headers for this node also */ paulo@0: if (!gnutella_send_connection_headers (c, "GNUTELLA/0.6 200 OK")) paulo@0: { paulo@0: gt_node_error (c, NULL); paulo@0: gt_node_disconnect (c); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* reset the timeout for this connection */ paulo@0: gnutella_set_handshake_timeout (c, TIMEOUT_3 * SECONDS); paulo@0: paulo@0: input_remove (id); paulo@0: input_add (fd, c, INPUT_READ, paulo@0: (InputCallback)recv_final_handshake, 0); paulo@0: } paulo@0: paulo@0: /* TODO: this should be abstracted */ paulo@0: static char *field_has_value (Dataset *d, const char *key, const char *val) paulo@0: { paulo@0: char *value; paulo@0: char *str; paulo@0: paulo@0: if (!(value = dataset_lookupstr (d, key))) paulo@0: return NULL; paulo@0: paulo@0: if ((str = strstr (value, val))) paulo@0: return str; paulo@0: paulo@0: return NULL; paulo@0: } paulo@0: paulo@0: static BOOL need_inflate (GtNode *node) paulo@0: { paulo@0: if (!(field_has_value (node->hdr, "content-encoding", "deflate"))) paulo@0: return FALSE; paulo@0: paulo@0: return TRUE; paulo@0: } paulo@0: paulo@0: static BOOL need_deflate (GtNode *node) paulo@0: { paulo@0: if (!(field_has_value (node->hdr, "accept-encoding", "deflate"))) paulo@0: return FALSE; paulo@0: paulo@0: return TRUE; paulo@0: } paulo@0: paulo@0: void gnutella_mark_compression (GtNode *node) paulo@0: { paulo@0: if (need_inflate (node)) paulo@0: node->rx_inflated = TRUE; paulo@0: paulo@0: if (need_deflate (node)) paulo@0: node->tx_deflated = TRUE; paulo@0: } paulo@0: paulo@0: static void add_key (ds_data_t *key, ds_data_t *value, Dataset **d) paulo@0: { paulo@0: char *hdr = key->data; paulo@0: char *val = value->data; paulo@0: paulo@0: dataset_insertstr (d, hdr, val); paulo@0: } paulo@0: paulo@0: static void recv_final_handshake (int fd, input_id id, TCPC *c) paulo@0: { paulo@0: FDBuf *buf; paulo@0: int n; paulo@0: char *response; paulo@0: size_t response_len = 0; paulo@0: Dataset *additional = NULL; paulo@0: paulo@0: buf = tcp_readbuf (c); paulo@0: paulo@0: if ((n = fdbuf_delim (buf, "\n")) < 0) paulo@0: { paulo@0: if (HANDSHAKE_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "fdbuf_delim: error %s", GIFT_NETERROR ()); paulo@0: paulo@0: gt_node_disconnect (c); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (gt_fdbuf_full (buf)) paulo@0: { paulo@0: gt_node_disconnect (c); paulo@0: return; paulo@0: } paulo@0: paulo@0: if (n > 0) paulo@0: return; paulo@0: paulo@0: response = fdbuf_data (buf, &response_len); paulo@0: paulo@0: if (!gt_http_header_terminated (response, response_len)) paulo@0: return; paulo@0: paulo@0: fdbuf_release (buf); paulo@0: paulo@0: if (HANDSHAKE_DEBUG) paulo@0: GT->DBGSOCK (GT, c, "stage3 response:\n%s", response); paulo@0: paulo@0: if (!gnutella_parse_response_headers (response, &additional)) paulo@0: { paulo@0: if (HANDSHAKE_DEBUG) paulo@0: gt_node_error (c, "node denied us in stage3 of handshake"); paulo@0: paulo@0: gt_node_disconnect (c); paulo@0: dataset_clear (additional); paulo@0: return; paulo@0: } paulo@0: paulo@0: /* paulo@0: * Append or replace the fields the node sent into the header for this paulo@0: * node. paulo@0: * paulo@0: * TODO: should probably change the interface to parse_response_headers so paulo@0: * we can just pass in the header list of the node. paulo@0: */ paulo@0: dataset_foreach (additional, DS_FOREACH(add_key), >_NODE(c)->hdr); paulo@0: dataset_clear (additional); paulo@0: paulo@0: /* mark the compression flags on this GtNode */ paulo@0: gnutella_mark_compression (GT_NODE(c)); paulo@0: paulo@0: input_remove (id); paulo@0: input_add (fd, c, INPUT_WRITE, paulo@0: (InputCallback)gnutella_start_connection, TIMEOUT_DEF); paulo@0: }