annotate src/gt_accept.c @ 0:d39e1d0d75b6

initial add
author paulo@hit-nxdomain.opendns.com
date Sat, 20 Feb 2010 21:18:28 -0800
parents
children
rev   line source
paulo@0 1 /*
paulo@0 2 * $Id: gt_accept.c,v 1.64 2005/01/04 15:03:40 mkern Exp $
paulo@0 3 *
paulo@0 4 * Copyright (C) 2001-2003 giFT project (gift.sourceforge.net)
paulo@0 5 *
paulo@0 6 * This program is free software; you can redistribute it and/or modify it
paulo@0 7 * under the terms of the GNU General Public License as published by the
paulo@0 8 * Free Software Foundation; either version 2, or (at your option) any
paulo@0 9 * later version.
paulo@0 10 *
paulo@0 11 * This program is distributed in the hope that it will be useful, but
paulo@0 12 * WITHOUT ANY WARRANTY; without even the implied warranty of
paulo@0 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
paulo@0 14 * General Public License for more details.
paulo@0 15 */
paulo@0 16
paulo@0 17 #include "gt_gnutella.h"
paulo@0 18
paulo@0 19 #include "gt_packet.h"
paulo@0 20 #include "gt_utils.h"
paulo@0 21
paulo@0 22 #include "gt_connect.h"
paulo@0 23
paulo@0 24 #include "gt_node.h"
paulo@0 25 #include "gt_node_cache.h"
paulo@0 26 #include "gt_netorg.h"
paulo@0 27
paulo@0 28 #include "gt_xfer_obj.h"
paulo@0 29 #include "gt_xfer.h"
paulo@0 30 #include "gt_http_server.h"
paulo@0 31
paulo@0 32 #include "gt_accept.h"
paulo@0 33 #include "gt_bind.h"
paulo@0 34 #include "gt_ban.h"
paulo@0 35 #include "gt_version.h"
paulo@0 36
paulo@0 37 #include "message/gt_message.h" /* gnutella_start_connection */
paulo@0 38
paulo@0 39 /*****************************************************************************/
paulo@0 40
paulo@0 41 #define MAX_FDBUF_SIZE 16384
paulo@0 42
paulo@0 43 #define INCOMING_TIMEOUT (1 * MINUTES)
paulo@0 44
paulo@0 45 /*****************************************************************************/
paulo@0 46 /* Handle incoming connections to this node */
paulo@0 47
paulo@0 48 /*
paulo@0 49 * Wrap TCPC and associated timeout for incoming connections in order to limit
paulo@0 50 * the number of incoming connections.
paulo@0 51 */
paulo@0 52 struct incoming_conn
paulo@0 53 {
paulo@0 54 TCPC *c;
paulo@0 55 timer_id timer;
paulo@0 56 };
paulo@0 57
paulo@0 58 #define GT_METHOD(func) void func (int fd, input_id id, \
paulo@0 59 struct incoming_conn *conn)
paulo@0 60 typedef void (*GtAcceptFunc) (int fd, input_id id, struct incoming_conn *conn);
paulo@0 61
paulo@0 62 GT_METHOD(gt_server_accept);
paulo@0 63 GT_METHOD(gt_server_get);
paulo@0 64 GT_METHOD(gt_server_giv);
paulo@0 65
paulo@0 66 static struct server_cmd
paulo@0 67 {
paulo@0 68 char *name;
paulo@0 69 GtAcceptFunc callback;
paulo@0 70 }
paulo@0 71 server_commands[] =
paulo@0 72 {
paulo@0 73 { "GNUTELLA", gt_server_accept },
paulo@0 74 { "GET", gt_server_get },
paulo@0 75 { "HEAD", gt_server_get },
paulo@0 76 { "GIV", gt_server_giv },
paulo@0 77 { NULL, NULL }
paulo@0 78 };
paulo@0 79
paulo@0 80 /*****************************************************************************/
paulo@0 81
paulo@0 82 static void send_node_headers (int fd, input_id id, TCPC *c);
paulo@0 83 static void recv_final_handshake (int fd, input_id id, TCPC *c);
paulo@0 84 static void determine_method (int fd, input_id id,
paulo@0 85 struct incoming_conn *conn);
paulo@0 86
paulo@0 87 /*****************************************************************************/
paulo@0 88
paulo@0 89 static void incoming_conn_free (struct incoming_conn *conn)
paulo@0 90 {
paulo@0 91 timer_remove (conn->timer);
paulo@0 92 free (conn);
paulo@0 93 }
paulo@0 94
paulo@0 95 static void incoming_conn_close (struct incoming_conn *conn)
paulo@0 96 {
paulo@0 97 tcp_close (conn->c);
paulo@0 98 incoming_conn_free (conn);
paulo@0 99 }
paulo@0 100
paulo@0 101 static BOOL conn_timeout (struct incoming_conn *conn)
paulo@0 102 {
paulo@0 103 incoming_conn_close (conn);
paulo@0 104 return FALSE;
paulo@0 105 }
paulo@0 106
paulo@0 107 static struct incoming_conn *incoming_conn_alloc (TCPC *c)
paulo@0 108 {
paulo@0 109 struct incoming_conn *conn;
paulo@0 110
paulo@0 111 conn = malloc (sizeof (struct incoming_conn));
paulo@0 112 if (!conn)
paulo@0 113 return NULL;
paulo@0 114
paulo@0 115 conn->c = c;
paulo@0 116 conn->timer = timer_add (INCOMING_TIMEOUT, (TimerCallback)conn_timeout,
paulo@0 117 conn);
paulo@0 118
paulo@0 119 return conn;
paulo@0 120 }
paulo@0 121
paulo@0 122 /*****************************************************************************/
paulo@0 123
paulo@0 124 BOOL gt_fdbuf_full (FDBuf *buf)
paulo@0 125 {
paulo@0 126 size_t len = MAX_FDBUF_SIZE;
paulo@0 127
paulo@0 128 if (fdbuf_data (buf, &len) == NULL)
paulo@0 129 return TRUE;
paulo@0 130
paulo@0 131 return len >= MAX_FDBUF_SIZE;
paulo@0 132 }
paulo@0 133
paulo@0 134 /*****************************************************************************/
paulo@0 135
paulo@0 136 /*
paulo@0 137 * Receive an incoming connection.
paulo@0 138 */
paulo@0 139 void gnutella_handle_incoming (int fd, input_id id, TCPC *listen)
paulo@0 140 {
paulo@0 141 TCPC *c;
paulo@0 142
paulo@0 143 if (!(c = tcp_accept (listen, FALSE)))
paulo@0 144 return;
paulo@0 145
paulo@0 146 if (HANDSHAKE_DEBUG)
paulo@0 147 GT->DBGSOCK (GT, c, "got a new connection");
paulo@0 148
paulo@0 149 id = INPUT_NONE;
paulo@0 150 gt_handshake_dispatch_incoming (fd, id, c);
paulo@0 151 }
paulo@0 152
paulo@0 153 /*
paulo@0 154 * Mark not firewalled if the other end isn't local.
paulo@0 155 */
paulo@0 156 static void fw_status_update (TCPC *c)
paulo@0 157 {
paulo@0 158 if (!c->outgoing && !net_match_host (c->host, "LOCAL"))
paulo@0 159 {
paulo@0 160 if (GT_SELF->firewalled)
paulo@0 161 GT->DBGSOCK (GT, c, "connected, clearing firewalled status");
paulo@0 162
paulo@0 163 gt_bind_clear_firewalled ();
paulo@0 164 }
paulo@0 165 }
paulo@0 166
paulo@0 167 /*
paulo@0 168 * Allocate a structure to track this connection and continue the dispatching
paulo@0 169 * process.
paulo@0 170 */
paulo@0 171 void gt_handshake_dispatch_incoming (int fd, input_id id, TCPC *c)
paulo@0 172 {
paulo@0 173 struct incoming_conn *conn;
paulo@0 174 in_addr_t peer_ip;
paulo@0 175
paulo@0 176 /*
paulo@0 177 * This will trigger if the connection was closed on the remote end
paulo@0 178 * or if there's a input timer setup to timeout this connection. In the
paulo@0 179 * latter case, we have to avoid adding any inputs for the connection.
paulo@0 180 */
paulo@0 181 if (net_sock_error (c->fd))
paulo@0 182 {
paulo@0 183 tcp_close (c);
paulo@0 184 return;
paulo@0 185 }
paulo@0 186
paulo@0 187 peer_ip = net_peer (c->fd);
paulo@0 188
paulo@0 189 /*
paulo@0 190 * While this might be nice for the banner, it's not nice to the ban-ee,
paulo@0 191 * and I'm sure there's some bugs in the banning code, so we should
paulo@0 192 * send back an error instead.
paulo@0 193 */
paulo@0 194 #if 0
paulo@0 195 if (gt_ban_ipv4_is_banned (peer_ip))
paulo@0 196 {
paulo@0 197 if (HANDSHAKE_DEBUG)
paulo@0 198 GT->DBGSOCK (GT, c, "not accepting connection [address banned]");
paulo@0 199
paulo@0 200 tcp_close (c);
paulo@0 201 return;
paulo@0 202 }
paulo@0 203 #endif
paulo@0 204
paulo@0 205 /*
paulo@0 206 * If there are too many HTTP connections for this IP, close the
paulo@0 207 * connection now before investing any more resources servicing it.
paulo@0 208 */
paulo@0 209 if (gt_http_connection_length (GT_TRANSFER_UPLOAD, peer_ip) >=
paulo@0 210 HTTP_MAX_PERUSER_UPLOAD_CONNS)
paulo@0 211 {
paulo@0 212 if (HTTP_DEBUG)
paulo@0 213 GT->DBGSOCK (GT, c, "too many connections for this user, closing");
paulo@0 214
paulo@0 215 tcp_close (c);
paulo@0 216 return;
paulo@0 217 }
paulo@0 218
paulo@0 219 /* local hosts_allow may need to be evaluated to keep outside sources
paulo@0 220 * away */
paulo@0 221 if (GNUTELLA_LOCAL_MODE)
paulo@0 222 {
paulo@0 223 if (!net_match_host (peer_ip, GNUTELLA_LOCAL_ALLOW))
paulo@0 224 {
paulo@0 225 if (HANDSHAKE_DEBUG)
paulo@0 226 GT->DBGSOCK (GT, c, "non-local connection, closing");
paulo@0 227
paulo@0 228 tcp_close (c);
paulo@0 229 return;
paulo@0 230 }
paulo@0 231 }
paulo@0 232
paulo@0 233 if (!(conn = incoming_conn_alloc (c)))
paulo@0 234 {
paulo@0 235 tcp_close (c);
paulo@0 236 return;
paulo@0 237 }
paulo@0 238
paulo@0 239 input_remove (id);
paulo@0 240 input_add (c->fd, conn, INPUT_READ,
paulo@0 241 (InputCallback)determine_method, 0);
paulo@0 242 }
paulo@0 243
paulo@0 244 /*
paulo@0 245 * Dispatch incoming connections to the proper subsystem.
paulo@0 246 */
paulo@0 247 static void determine_method (int fd, input_id id, struct incoming_conn *conn)
paulo@0 248 {
paulo@0 249 struct server_cmd *command;
paulo@0 250 FDBuf *fdbuf;
paulo@0 251 int ret;
paulo@0 252 char *request;
paulo@0 253 TCPC *c = conn->c;
paulo@0 254
paulo@0 255 fdbuf = tcp_readbuf (c);
paulo@0 256
paulo@0 257 if ((ret = fdbuf_delim (fdbuf, "\n")) < 0)
paulo@0 258 {
paulo@0 259 if (HANDSHAKE_DEBUG)
paulo@0 260 GT->DBGSOCK (GT, c, "error: %s", GIFT_NETERROR ());
paulo@0 261
paulo@0 262 incoming_conn_close (conn);
paulo@0 263 return;
paulo@0 264 }
paulo@0 265
paulo@0 266 /* some data was read: update fw status */
paulo@0 267 fw_status_update (c);
paulo@0 268
paulo@0 269 if (gt_fdbuf_full (fdbuf))
paulo@0 270 {
paulo@0 271 incoming_conn_close (conn);
paulo@0 272 return;
paulo@0 273 }
paulo@0 274
paulo@0 275 if (ret > 0)
paulo@0 276 return;
paulo@0 277
paulo@0 278 /*
paulo@0 279 * NOTE: fdbuf_release() is not called here, so the first line is left on
paulo@0 280 * the FDBuf.
paulo@0 281 */
paulo@0 282 request = fdbuf_data (fdbuf, NULL);
paulo@0 283
paulo@0 284 for (command = server_commands; command->name != NULL; command++)
paulo@0 285 {
paulo@0 286 if (!strncasecmp (command->name, request, strlen (command->name)))
paulo@0 287 {
paulo@0 288 input_remove (id);
paulo@0 289 input_add (fd, conn, INPUT_READ,
paulo@0 290 (InputCallback)command->callback, 0);
paulo@0 291
paulo@0 292 return;
paulo@0 293 }
paulo@0 294 }
paulo@0 295
paulo@0 296 if (HANDSHAKE_DEBUG)
paulo@0 297 GT->DBGFN (GT, "bad command: %s", request);
paulo@0 298
paulo@0 299 incoming_conn_close (conn);
paulo@0 300 }
paulo@0 301
paulo@0 302 /*****************************************************************************/
paulo@0 303
paulo@0 304 /*
paulo@0 305 * Main connection acceptance routine. This begins the connection's
paulo@0 306 * journey through the rest of the handshaking code.
paulo@0 307 */
paulo@0 308 GT_METHOD(gt_server_accept)
paulo@0 309 {
paulo@0 310 char *request;
paulo@0 311 char *version_str;
paulo@0 312 size_t request_len = 0;
paulo@0 313 int n;
paulo@0 314 GtNode *node;
paulo@0 315 TCPC *c = conn->c;
paulo@0 316 FDBuf *buf;
paulo@0 317
paulo@0 318 if (HANDSHAKE_DEBUG)
paulo@0 319 GT->DBGFN (GT, "entered");
paulo@0 320
paulo@0 321 buf = tcp_readbuf (c);
paulo@0 322
paulo@0 323 if ((n = fdbuf_delim (buf, "\n")) < 0)
paulo@0 324 {
paulo@0 325 if (HANDSHAKE_DEBUG)
paulo@0 326 GT->DBGSOCK (GT, c, "error on recv: %s", GIFT_NETERROR ());
paulo@0 327
paulo@0 328 incoming_conn_close (conn);
paulo@0 329 return;
paulo@0 330 }
paulo@0 331
paulo@0 332 if (gt_fdbuf_full (buf))
paulo@0 333 {
paulo@0 334 incoming_conn_close (conn);
paulo@0 335 return;
paulo@0 336 }
paulo@0 337
paulo@0 338 if (n > 0)
paulo@0 339 return;
paulo@0 340
paulo@0 341 request = fdbuf_data (buf, &request_len);
paulo@0 342
paulo@0 343 if (!gt_http_header_terminated (request, request_len))
paulo@0 344 return;
paulo@0 345
paulo@0 346 fdbuf_release (buf);
paulo@0 347
paulo@0 348 if (HANDSHAKE_DEBUG)
paulo@0 349 GT->DBGSOCK (GT, c, "accepted headers:\n%s", request);
paulo@0 350
paulo@0 351 version_str = strchr (request, '/');
paulo@0 352 if (version_str)
paulo@0 353 version_str++;
paulo@0 354
paulo@0 355 if (strncasecmp ("GNUTELLA CONNECT/", request,
paulo@0 356 sizeof ("GNUTELLA CONNECT/") - 1) != 0)
paulo@0 357 {
paulo@0 358 if (HANDSHAKE_DEBUG)
paulo@0 359 GT->DBGSOCK (GT, c, "bad handshake header");
paulo@0 360
paulo@0 361 incoming_conn_close (conn);
paulo@0 362 return;
paulo@0 363 }
paulo@0 364
paulo@0 365 /* deny 0.4 connections */
paulo@0 366 if (!version_str || strncasecmp (version_str, "0.4", 3) == 0)
paulo@0 367 {
paulo@0 368 if (HANDSHAKE_DEBUG)
paulo@0 369 GT->DBGSOCK (GT, c, "closing 0.4 connection");
paulo@0 370
paulo@0 371 incoming_conn_close (conn);
paulo@0 372 return;
paulo@0 373 }
paulo@0 374
paulo@0 375 /* make a node out of this connection */
paulo@0 376 if (!(node = gt_node_instantiate (c)))
paulo@0 377 {
paulo@0 378 if (HANDSHAKE_DEBUG)
paulo@0 379 GT->DBGFN (GT, "node_instantiate failed");
paulo@0 380
paulo@0 381 incoming_conn_close (conn);
paulo@0 382 return;
paulo@0 383 }
paulo@0 384
paulo@0 385 /*
paulo@0 386 * Update the start_connect_time so the node will stick around for a while
paulo@0 387 * even when the node list gets trimmed.
paulo@0 388 */
paulo@0 389 node->start_connect_time = time (NULL);
paulo@0 390
paulo@0 391 gt_node_state_set (node, GT_NODE_CONNECTING_1);
paulo@0 392 node->incoming = TRUE;
paulo@0 393
paulo@0 394 /*
paulo@0 395 * Store the http header.
paulo@0 396 *
paulo@0 397 * NOTE: We don't check the return code here. This function expects a
paulo@0 398 * properly formatted HTTP response, but we are handing it a
paulo@0 399 * Gnutella connection request instead, so it will return failure.
paulo@0 400 */
paulo@0 401 gnutella_parse_response_headers (request, &node->hdr);
paulo@0 402
paulo@0 403 /*
paulo@0 404 * Use the node handshake timeout timer now, and get rid of the
paulo@0 405 * generic incoming connection timeout timer.
paulo@0 406 */
paulo@0 407 gnutella_set_handshake_timeout (c, TIMEOUT_2 * SECONDS);
paulo@0 408 incoming_conn_free (conn);
paulo@0 409
paulo@0 410 input_remove (id);
paulo@0 411 input_add (fd, c, INPUT_WRITE,
paulo@0 412 (InputCallback)send_node_headers, TIMEOUT_DEF);
paulo@0 413 }
paulo@0 414
paulo@0 415 GT_METHOD(gt_server_giv)
paulo@0 416 {
paulo@0 417 FDBuf *buf;
paulo@0 418 int n;
paulo@0 419 in_addr_t peer_ip;
paulo@0 420 char *response;
paulo@0 421 size_t response_len = 0;
paulo@0 422 char *client_id;
paulo@0 423 gt_guid_t *guid;
paulo@0 424 TCPC *c = conn->c;
paulo@0 425
paulo@0 426 if (HTTP_DEBUG || HANDSHAKE_DEBUG)
paulo@0 427 GT->DBGFN (GT, "entered");
paulo@0 428
paulo@0 429 buf = tcp_readbuf (c);
paulo@0 430
paulo@0 431 if ((n = fdbuf_delim (buf, "\n")) < 0)
paulo@0 432 {
paulo@0 433 incoming_conn_close (conn);
paulo@0 434 return;
paulo@0 435 }
paulo@0 436
paulo@0 437 if (gt_fdbuf_full (buf))
paulo@0 438 {
paulo@0 439 incoming_conn_close (conn);
paulo@0 440 return;
paulo@0 441 }
paulo@0 442
paulo@0 443 if (n > 0)
paulo@0 444 return;
paulo@0 445
paulo@0 446 response = fdbuf_data (buf, &response_len);
paulo@0 447
paulo@0 448 if (!gt_http_header_terminated (response, response_len))
paulo@0 449 return;
paulo@0 450
paulo@0 451 fdbuf_release (buf);
paulo@0 452
paulo@0 453 if (HTTP_DEBUG || HANDSHAKE_DEBUG)
paulo@0 454 GT->DBGSOCK (GT, c, "giv response=%s", response);
paulo@0 455
paulo@0 456 string_sep (&response, " "); /* Skip "GIV " */
paulo@0 457 string_sep (&response, ":"); /* Skip the file index */
paulo@0 458
paulo@0 459 client_id = string_sep (&response, "/");
paulo@0 460 string_lower (client_id);
paulo@0 461
paulo@0 462 if (!(guid = gt_guid_bin (client_id)))
paulo@0 463 {
paulo@0 464 incoming_conn_close (conn);
paulo@0 465 return;
paulo@0 466 }
paulo@0 467
paulo@0 468 peer_ip = net_peer (c->fd);
paulo@0 469
paulo@0 470 /*
paulo@0 471 * This will continue the GtTransfer if one is found, and store
paulo@0 472 * the connection if one is not. The connection will be resumed
paulo@0 473 * when a chunk for this source is reissued.
paulo@0 474 */
paulo@0 475 gt_push_source_add_conn (guid, peer_ip, c);
paulo@0 476
paulo@0 477 incoming_conn_free (conn);
paulo@0 478 free (guid);
paulo@0 479 }
paulo@0 480
paulo@0 481 /*****************************************************************************/
paulo@0 482
paulo@0 483 GT_METHOD(gt_server_get)
paulo@0 484 {
paulo@0 485 if (HTTP_DEBUG)
paulo@0 486 GT->DBGFN (GT, "entered");
paulo@0 487
paulo@0 488 /* dispatch to the HTTP server code */
paulo@0 489 gt_http_server_dispatch (fd, id, conn->c);
paulo@0 490
paulo@0 491 /* get rid of the tracking information for the connection in this
paulo@0 492 * subsystem */
paulo@0 493 incoming_conn_free (conn);
paulo@0 494 }
paulo@0 495
paulo@0 496 /*****************************************************************************/
paulo@0 497 /* GNUTELLA/0.6 CONNECTIONS */
paulo@0 498
paulo@0 499 BOOL gt_http_header_terminated (char *data, size_t len)
paulo@0 500 {
paulo@0 501 int cnt;
paulo@0 502
paulo@0 503 assert (len > 0);
paulo@0 504 len--;
paulo@0 505
paulo@0 506 for (cnt = 0; len > 0 && cnt < 2; cnt++)
paulo@0 507 {
paulo@0 508 if (data[len--] != '\n')
paulo@0 509 break;
paulo@0 510
paulo@0 511 /* treat CRLF as LF */
paulo@0 512 if (data[len] == '\r')
paulo@0 513 len--;
paulo@0 514 }
paulo@0 515
paulo@0 516 return (cnt == 2);
paulo@0 517 }
paulo@0 518
paulo@0 519 /* TODO: header continuation and joining of multiple occurrences */
paulo@0 520 void gt_http_header_parse (char *headers, Dataset **d)
paulo@0 521 {
paulo@0 522 char *line, *key;
paulo@0 523
paulo@0 524 while ((line = string_sep_set (&headers, "\r\n")))
paulo@0 525 {
paulo@0 526 key = string_sep (&line, ":");
paulo@0 527
paulo@0 528 if (!key || !line)
paulo@0 529 continue;
paulo@0 530
paulo@0 531 string_trim (key);
paulo@0 532 string_trim (line);
paulo@0 533
paulo@0 534 /* dont allow empty key-values, need to check this too */
paulo@0 535 if (string_isempty (line))
paulo@0 536 continue;
paulo@0 537
paulo@0 538 dataset_insertstr (d, string_lower (key), line);
paulo@0 539 }
paulo@0 540 }
paulo@0 541
paulo@0 542 static void send_nodes (struct cached_node *node, String *s)
paulo@0 543 {
paulo@0 544 if (s->str[s->len - 1] != ':')
paulo@0 545 string_append (s, ",");
paulo@0 546 else
paulo@0 547 string_append (s, " ");
paulo@0 548
paulo@0 549 string_appendf (s, "%s:%hu", net_ip_str (node->addr.ip), node->addr.port);
paulo@0 550 }
paulo@0 551
paulo@0 552 static void deny_connection (TCPC *c, int code, char *msg)
paulo@0 553 {
paulo@0 554 String *s;
paulo@0 555 List *nodes;
paulo@0 556 in_addr_t ip;
paulo@0 557
paulo@0 558 if (!(s = string_new (NULL, 0, 0, TRUE)))
paulo@0 559 return;
paulo@0 560
paulo@0 561 string_appendf (s, "GNUTELLA/0.6 %d %s\r\n", code, msg);
paulo@0 562 string_appendf (s, "User-Agent: %s\r\n", gt_version());
paulo@0 563
paulo@0 564 ip = net_peer (c->fd);
paulo@0 565 if (!gt_is_local_ip (ip, 0))
paulo@0 566 string_appendf (s, "Remote-IP: %s\r\n", net_ip_str (ip));
paulo@0 567
paulo@0 568 /* append some different nodes to try */
paulo@0 569 nodes = gt_node_cache_get (10);
paulo@0 570
paulo@0 571 if (nodes)
paulo@0 572 {
paulo@0 573 string_append (s, "X-Try-Ultrapeers:");
paulo@0 574
paulo@0 575 list_foreach (nodes, (ListForeachFunc)send_nodes, s);
paulo@0 576 list_free (nodes);
paulo@0 577
paulo@0 578 string_append (s, "\r\n");
paulo@0 579 }
paulo@0 580
paulo@0 581 /* append message terminator */
paulo@0 582 string_append (s, "\r\n");
paulo@0 583
paulo@0 584 /* we will be closing the connection after this, so we don't
paulo@0 585 * care if the send fails or not. */
paulo@0 586 tcp_send (c, s->str, s->len);
paulo@0 587
paulo@0 588 if (HANDSHAKE_DEBUG)
paulo@0 589 GT->DBGSOCK (GT, c, "connection denied response:\n%s", s->str);
paulo@0 590
paulo@0 591 string_free (s);
paulo@0 592 }
paulo@0 593
paulo@0 594 static void setup_node_class (GtNode *node)
paulo@0 595 {
paulo@0 596 char *ultrapeer;
paulo@0 597 char *qrp;
paulo@0 598
paulo@0 599 ultrapeer = dataset_lookupstr (node->hdr, "x-ultrapeer");
paulo@0 600 qrp = dataset_lookupstr (node->hdr, "x-query-routing");
paulo@0 601
paulo@0 602 if (ultrapeer && !strcasecmp (ultrapeer, "true") && qrp != NULL)
paulo@0 603 gt_node_class_set (node, GT_NODE_ULTRA);
paulo@0 604 else
paulo@0 605 gt_node_class_set (node, GT_NODE_LEAF);
paulo@0 606 }
paulo@0 607
paulo@0 608 BOOL gnutella_auth_connection (TCPC *c)
paulo@0 609 {
paulo@0 610 GtNode *node;
paulo@0 611
paulo@0 612 node = GT_NODE(c);
paulo@0 613 assert (GT_NODE(c) == node && GT_CONN(node) == c);
paulo@0 614
paulo@0 615 /* set the class of this node based on the headers sent */
paulo@0 616 setup_node_class (node);
paulo@0 617
paulo@0 618 /*
paulo@0 619 * If the remote node is only crawling us, accept the connection
paulo@0 620 * no matter what.
paulo@0 621 */
paulo@0 622 if (dataset_lookupstr (node->hdr, "crawler"))
paulo@0 623 return TRUE;
paulo@0 624
paulo@0 625 /*
paulo@0 626 * If we are a leaf node, and so is this node, deny the connection,
paulo@0 627 * but send 'X-Try-Ultrapeer:' headers with the ultrapeers we
paulo@0 628 * are connected to in order to try to bootstrap the remote client.
paulo@0 629 */
paulo@0 630 if (!(GT_SELF->klass & GT_NODE_ULTRA) && (node->klass & GT_NODE_LEAF))
paulo@0 631 {
paulo@0 632 deny_connection (c, 503, "I am a shielded leaf node");
paulo@0 633 return FALSE;
paulo@0 634 }
paulo@0 635
paulo@0 636 if (gt_conn_need_connections (node->klass) <= 0)
paulo@0 637 {
paulo@0 638 deny_connection (c, 503, "Too many connections");
paulo@0 639 return FALSE;
paulo@0 640 }
paulo@0 641
paulo@0 642 if (gt_ban_ipv4_is_banned (node->ip))
paulo@0 643 {
paulo@0 644 deny_connection (c, 403, "Unauthorized");
paulo@0 645 return FALSE;
paulo@0 646 }
paulo@0 647
paulo@0 648 return TRUE;
paulo@0 649 }
paulo@0 650
paulo@0 651 static void send_node_headers (int fd, input_id id, TCPC *c)
paulo@0 652 {
paulo@0 653 if (net_sock_error (c->fd))
paulo@0 654 {
paulo@0 655 gt_node_error (c, NULL);
paulo@0 656 gt_node_disconnect (c);
paulo@0 657 return;
paulo@0 658 }
paulo@0 659
paulo@0 660 if (!gnutella_auth_connection (c))
paulo@0 661 {
paulo@0 662 gt_node_error (c, "[incoming] connection not authorized");
paulo@0 663 gt_node_disconnect (c);
paulo@0 664 return;
paulo@0 665 }
paulo@0 666
paulo@0 667 /* send OK response to the peer and send headers for this node also */
paulo@0 668 if (!gnutella_send_connection_headers (c, "GNUTELLA/0.6 200 OK"))
paulo@0 669 {
paulo@0 670 gt_node_error (c, NULL);
paulo@0 671 gt_node_disconnect (c);
paulo@0 672 return;
paulo@0 673 }
paulo@0 674
paulo@0 675 /* reset the timeout for this connection */
paulo@0 676 gnutella_set_handshake_timeout (c, TIMEOUT_3 * SECONDS);
paulo@0 677
paulo@0 678 input_remove (id);
paulo@0 679 input_add (fd, c, INPUT_READ,
paulo@0 680 (InputCallback)recv_final_handshake, 0);
paulo@0 681 }
paulo@0 682
paulo@0 683 /* TODO: this should be abstracted */
paulo@0 684 static char *field_has_value (Dataset *d, const char *key, const char *val)
paulo@0 685 {
paulo@0 686 char *value;
paulo@0 687 char *str;
paulo@0 688
paulo@0 689 if (!(value = dataset_lookupstr (d, key)))
paulo@0 690 return NULL;
paulo@0 691
paulo@0 692 if ((str = strstr (value, val)))
paulo@0 693 return str;
paulo@0 694
paulo@0 695 return NULL;
paulo@0 696 }
paulo@0 697
paulo@0 698 static BOOL need_inflate (GtNode *node)
paulo@0 699 {
paulo@0 700 if (!(field_has_value (node->hdr, "content-encoding", "deflate")))
paulo@0 701 return FALSE;
paulo@0 702
paulo@0 703 return TRUE;
paulo@0 704 }
paulo@0 705
paulo@0 706 static BOOL need_deflate (GtNode *node)
paulo@0 707 {
paulo@0 708 if (!(field_has_value (node->hdr, "accept-encoding", "deflate")))
paulo@0 709 return FALSE;
paulo@0 710
paulo@0 711 return TRUE;
paulo@0 712 }
paulo@0 713
paulo@0 714 void gnutella_mark_compression (GtNode *node)
paulo@0 715 {
paulo@0 716 if (need_inflate (node))
paulo@0 717 node->rx_inflated = TRUE;
paulo@0 718
paulo@0 719 if (need_deflate (node))
paulo@0 720 node->tx_deflated = TRUE;
paulo@0 721 }
paulo@0 722
paulo@0 723 static void add_key (ds_data_t *key, ds_data_t *value, Dataset **d)
paulo@0 724 {
paulo@0 725 char *hdr = key->data;
paulo@0 726 char *val = value->data;
paulo@0 727
paulo@0 728 dataset_insertstr (d, hdr, val);
paulo@0 729 }
paulo@0 730
paulo@0 731 static void recv_final_handshake (int fd, input_id id, TCPC *c)
paulo@0 732 {
paulo@0 733 FDBuf *buf;
paulo@0 734 int n;
paulo@0 735 char *response;
paulo@0 736 size_t response_len = 0;
paulo@0 737 Dataset *additional = NULL;
paulo@0 738
paulo@0 739 buf = tcp_readbuf (c);
paulo@0 740
paulo@0 741 if ((n = fdbuf_delim (buf, "\n")) < 0)
paulo@0 742 {
paulo@0 743 if (HANDSHAKE_DEBUG)
paulo@0 744 GT->DBGSOCK (GT, c, "fdbuf_delim: error %s", GIFT_NETERROR ());
paulo@0 745
paulo@0 746 gt_node_disconnect (c);
paulo@0 747 return;
paulo@0 748 }
paulo@0 749
paulo@0 750 if (gt_fdbuf_full (buf))
paulo@0 751 {
paulo@0 752 gt_node_disconnect (c);
paulo@0 753 return;
paulo@0 754 }
paulo@0 755
paulo@0 756 if (n > 0)
paulo@0 757 return;
paulo@0 758
paulo@0 759 response = fdbuf_data (buf, &response_len);
paulo@0 760
paulo@0 761 if (!gt_http_header_terminated (response, response_len))
paulo@0 762 return;
paulo@0 763
paulo@0 764 fdbuf_release (buf);
paulo@0 765
paulo@0 766 if (HANDSHAKE_DEBUG)
paulo@0 767 GT->DBGSOCK (GT, c, "stage3 response:\n%s", response);
paulo@0 768
paulo@0 769 if (!gnutella_parse_response_headers (response, &additional))
paulo@0 770 {
paulo@0 771 if (HANDSHAKE_DEBUG)
paulo@0 772 gt_node_error (c, "node denied us in stage3 of handshake");
paulo@0 773
paulo@0 774 gt_node_disconnect (c);
paulo@0 775 dataset_clear (additional);
paulo@0 776 return;
paulo@0 777 }
paulo@0 778
paulo@0 779 /*
paulo@0 780 * Append or replace the fields the node sent into the header for this
paulo@0 781 * node.
paulo@0 782 *
paulo@0 783 * TODO: should probably change the interface to parse_response_headers so
paulo@0 784 * we can just pass in the header list of the node.
paulo@0 785 */
paulo@0 786 dataset_foreach (additional, DS_FOREACH(add_key), &GT_NODE(c)->hdr);
paulo@0 787 dataset_clear (additional);
paulo@0 788
paulo@0 789 /* mark the compression flags on this GtNode */
paulo@0 790 gnutella_mark_compression (GT_NODE(c));
paulo@0 791
paulo@0 792 input_remove (id);
paulo@0 793 input_add (fd, c, INPUT_WRITE,
paulo@0 794 (InputCallback)gnutella_start_connection, TIMEOUT_DEF);
paulo@0 795 }