1 /*
2 * gnutella.c - gnutella protocol implementation
3 *
4 * Copyright (C) 2011-2013 Thien-Thi Nguyen
5 * Copyright (C) 2000, 2001, 2002 Stefan Jahn <stefan@lkcc.org>
6 *
7 * This is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3, or (at your option)
10 * any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this package. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "config.h"
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <time.h>
27 #include <sys/stat.h>
28 #if HAVE_UNISTD_H
29 # include <unistd.h>
30 #endif
31 #include <sys/types.h>
32 #include <fcntl.h>
33 #include <errno.h>
34
35 #ifndef __MINGW32__
36 # include <sys/types.h>
37 # include <sys/socket.h>
38 #endif
39
40 #ifdef __MINGW32__
41 # include <io.h>
42 # include "woe-statpred.h"
43 #endif
44
45 #if HAVE_DIRECT_H
46 # include <direct.h>
47 #endif
48 #ifdef __MINGW32__
49 # define mkdir(path, mode) mkdir (path)
50 #endif
51
52 #include "networking-headers.h"
53 #include "libserveez.h"
54 #include "misc-macros.h"
55 #include "gnutella.h"
56 #include "nut-transfer.h"
57 #include "nut-route.h"
58 #include "nut-core.h"
59 #include "nut-hostlist.h"
60 #include "nut-request.h"
61 #include "unused.h"
62
63 /*
64 * Default search patterns.
65 */
66 char *nut_search_patterns[] =
67 {
68 "Puppe3000",
69 "Meret Becker"
70 };
71
72 /*
73 * Default configuration hash for the gnutella spider.
74 */
75 nut_config_t nut_config =
76 {
77 0, /* if set we do not listen on any port cfg */
78 NUT_MAX_TTL, /* maximum ttl for a gnutella packet */
79 NUT_TTL, /* initial ttl for a gnutella packet */
80 NULL, /* array of initial hosts */
81 NUT_GUID, /* this servers GUID */
82 NULL, /* routing table */
83 NULL, /* connected hosts hash */
84 NULL, /* default search pattern */
85 0, /* current search pattern index */
86 30, /* limit amount of search reply records */
87 NULL, /* this servers created packets */
88 0, /* routing errors */
89 0, /* files within connected network */
90 0, /* file size (in KB) */
91 0, /* hosts within the connected network */
92 "/tmp", /* where to store downloaded files */
93 "/tmp", /* local search database path */
94 0, /* concurrent downloads */
95 4, /* maximum concurrent downloads */
96 28, /* connection speed (KBit/s) */
97 28, /* minimum connection speed for searching */
98 NULL, /* file extensions */
99 NULL, /* host catcher */
100 4, /* number of connections to keep up */
101 NULL, /* force the local ip to this value */
102 0, /* calculated from `force_ip' */
103 0, /* force the local port to this value */
104 0, /* calculated from `force_port' */
105 NULL, /* recent query hash */
106 NULL, /* reply hash for routing push requests */
107 NULL, /* push request hash */
108 NULL, /* shared file array */
109 0, /* number of database files */
110 0, /* size of database in KB */
111 0, /* current number of uploads */
112 4, /* maximum number of uploads */
113 "gnutella-net", /* configurable gnutella net url */
114 NULL, /* detection string for the above value */
115 };
116
117 /*
118 * Defining configuration file associations with key-value-pairs.
119 */
120 svz_key_value_pair_t nut_config_prototype[] =
121 {
122 SVZ_REGISTER_STRARRAY ("hosts", nut_config.hosts, SVZ_ITEM_NOTDEFAULTABLE),
123 SVZ_REGISTER_STRARRAY ("search", nut_config.search, SVZ_ITEM_DEFAULTABLE),
124 SVZ_REGISTER_INT ("search-limit", nut_config.search_limit,
125 SVZ_ITEM_DEFAULTABLE),
126 SVZ_REGISTER_INT ("max-ttl", nut_config.max_ttl, SVZ_ITEM_DEFAULTABLE),
127 SVZ_REGISTER_INT ("ttl", nut_config.ttl, SVZ_ITEM_DEFAULTABLE),
128 SVZ_REGISTER_STR ("download-path", nut_config.save_path,
129 SVZ_ITEM_DEFAULTABLE),
130 SVZ_REGISTER_STR ("share-path", nut_config.share_path, SVZ_ITEM_DEFAULTABLE),
131 SVZ_REGISTER_INT ("max-downloads", nut_config.max_dnloads,
132 SVZ_ITEM_DEFAULTABLE),
133 SVZ_REGISTER_INT ("connection-speed", nut_config.speed,
134 SVZ_ITEM_DEFAULTABLE),
135 SVZ_REGISTER_INT ("min-speed", nut_config.min_speed, SVZ_ITEM_DEFAULTABLE),
136 SVZ_REGISTER_STRARRAY ("file-extensions", nut_config.extensions,
137 SVZ_ITEM_DEFAULTABLE),
138 SVZ_REGISTER_INT ("connections", nut_config.connections,
139 SVZ_ITEM_DEFAULTABLE),
140 SVZ_REGISTER_STR ("force-ip", nut_config.force_ip, SVZ_ITEM_DEFAULTABLE),
141 SVZ_REGISTER_INT ("force-port", nut_config.force_port, SVZ_ITEM_DEFAULTABLE),
142 SVZ_REGISTER_INT ("max-uploads", nut_config.max_uploads,
143 SVZ_ITEM_DEFAULTABLE),
144 SVZ_REGISTER_STR ("net-url", nut_config.net_url, SVZ_ITEM_DEFAULTABLE),
145 SVZ_REGISTER_BOOL ("disable", nut_config.disable, SVZ_ITEM_DEFAULTABLE),
146 SVZ_REGISTER_END ()
147 };
148
149 /*
150 * Definition of this server.
151 */
152 svz_servertype_t nut_server_definition =
153 {
154 "gnutella spider version " NUT_VERSION, /* long description */
155 "nut", /* instance description */
156 nut_global_init, /* global initializer */
157 nut_init, /* instance initializer */
158 nut_detect_proto, /* protocol detection */
159 nut_connect_socket, /* client connection callback */
160 nut_finalize, /* instance destructor */
161 nut_global_finalize, /* class destructor */
162 nut_info_client, /* server info callback */
163 nut_info_server, /* client info callback */
164 nut_server_notify, /* server timer routine */
165 NULL, /* no reset callback */
166 NULL, /* no handle request callback */
167 SVZ_CONFIG_DEFINE ("nut", nut_config, nut_config_prototype)
168 };
169
170 /*
171 * The next three functions `nut_hash_keylen', `nut_hash_equals' and
172 * `nut_hash_code' are the routing table hash callbacks to handle
173 * GUIDs as keys instead of plain NULL terminated character strings.
174 */
175 static size_t
nut_hash_keylen(UNUSED const char * id)176 nut_hash_keylen (UNUSED const char *id)
177 {
178 return NUT_GUID_SIZE;
179 }
180
181 static int
nut_hash_equals(const char * id1,const char * id2)182 nut_hash_equals (const char *id1, const char *id2)
183 {
184 return memcmp (id1, id2, NUT_GUID_SIZE);
185 }
186
187 static unsigned long
nut_hash_code(const char * id)188 nut_hash_code (const char *id)
189 {
190 int n;
191 unsigned long code = 0;
192
193 for (n = 0; n < NUT_GUID_SIZE; n++)
194 {
195 code = (code << 2) ^ id[n];
196 }
197
198 return code;
199 }
200
201 static svz_hash_t *
make_nut_kce_hash_table(svz_free_func_t destroy)202 make_nut_kce_hash_table (svz_free_func_t destroy)
203 {
204 return svz_hash_configure (svz_hash_create (4, destroy),
205 nut_hash_keylen,
206 nut_hash_code,
207 nut_hash_equals);
208 }
209
210 /*
211 * This is the default idle function for self connected gnutella hosts.
212 * It simply returns an error if the socket was not connected in a
213 * certain time.
214 */
215 int
nut_connect_timeout(svz_socket_t * sock)216 nut_connect_timeout (svz_socket_t *sock)
217 {
218 /*
219 * Did we try to connect to another host in order to download something,
220 * but failed within a certain time? Then we need to send a push request
221 * to the host providing the original data.
222 */
223 if (sock->userflags & NUT_FLAG_DNLOAD)
224 {
225 nut_send_push (sock->cfg, sock->data);
226 }
227 return -1;
228 }
229
230 /*
231 * The following function tries to connect to a given gnutella host specified
232 * by @var{ip:port} both in network byte order. It returns -1 on errors and
233 * zero otherwise.
234 */
235 static int
nut_connect_ip(nut_config_t * cfg,in_addr_t ip,in_port_t port)236 nut_connect_ip (nut_config_t *cfg, in_addr_t ip, in_port_t port)
237 {
238 svz_socket_t *sock;
239 svz_address_t *addr = svz_address_make (AF_INET, &ip);
240
241 /* try to connect to this host */
242 if ((sock = svz_tcp_connect (addr, port)) != NULL)
243 {
244 char buf[64];
245
246 svz_log (SVZ_LOG_NOTICE, "nut: connecting %s\n",
247 SVZ_PP_ADDR_PORT (buf, addr, port));
248 sock->cfg = cfg;
249 sock->flags |= SVZ_SOFLG_NOFLOOD;
250 sock->check_request = nut_detect_connect;
251 sock->idle_func = nut_connect_timeout;
252 sock->idle_counter = NUT_CONNECT_TIMEOUT;
253 svz_sock_printf (sock, NUT_CONNECT);
254 svz_free (addr);
255 return 0;
256 }
257 svz_free (addr);
258 return -1;
259 }
260
261 struct dns_closure
262 {
263 nut_config_t *cfg;
264 in_port_t port;
265 };
266
267 /*
268 * This routine gets called when the DNS coserver finally resolved a given
269 * hostname to an IP address and tries to connect to the gnutella client.
270 */
271 static int
nut_dns_done(char * host,void * closure)272 nut_dns_done (char *host, void *closure)
273 {
274 struct dns_closure *x = closure;
275 nut_config_t *cfg = x->cfg;
276 in_port_t port = x->port;
277 struct sockaddr_in addr;
278
279 svz_free (x);
280 if (host != NULL)
281 {
282 if (svz_inet_aton (host, &addr) == -1)
283 {
284 svz_log (SVZ_LOG_WARNING, "nut: invalid IP address `%s'\n", host);
285 return -1;
286 }
287 return nut_connect_ip (cfg, addr.sin_addr.s_addr, port);
288 }
289 return -1;
290 }
291
292 /*
293 * The following routine tries to connect to a given gnutella host. The
294 * @var{host} argument can be either in dotted decimal form or a hostname.
295 * It returns -1 on errors and zero otherwise.
296 */
297 static int
nut_connect_host(nut_config_t * cfg,char * host)298 nut_connect_host (nut_config_t *cfg, char *host)
299 {
300 nut_host_t *client;
301 in_addr_t ip;
302 in_port_t port;
303 char *dns = NULL;
304
305 /* try getting ip address and port */
306 if (nut_parse_addr (host, &ip, &port) == -1)
307 {
308 if ((dns = nut_parse_host (host, &port)) == NULL)
309 {
310 svz_log (SVZ_LOG_ERROR, "nut: invalid host `%s'\n", host);
311 return -1;
312 }
313 }
314
315 /* try to connect to this host */
316 if (dns != NULL)
317 {
318 struct dns_closure *x;
319
320 /* first resolve the hostname and then connect */
321 svz_log (SVZ_LOG_NOTICE, "nut: enqueuing %s\n", dns);
322 x = svz_malloc (sizeof (struct dns_closure));
323 x->cfg = cfg;
324 x->port = port;
325 svz_coserver_dns_invoke (dns, nut_dns_done, x);
326 svz_free (dns);
327 }
328 else
329 {
330 /* try connecting immediately */
331 if (nut_connect_ip (cfg, ip, port))
332 {
333 /* if we could not connect then delete the client from host catcher
334 hash and free the client structure */
335 if ((client = (nut_host_t *) svz_hash_get (cfg->net, host)) != NULL)
336 {
337 svz_hash_delete (cfg->net, host);
338 svz_free (client);
339 }
340 return -1;
341 }
342 }
343 return 0;
344 }
345
346 /*
347 * When establishing a new connection to another gnutella server this
348 * functions pings it to get all further servers behind this server.
349 */
350 int
nut_init_ping(svz_socket_t * sock)351 nut_init_ping (svz_socket_t *sock)
352 {
353 nut_config_t *cfg = sock->cfg;
354 nut_client_t *client = sock->data;
355 nut_packet_t *pkt;
356 nut_header_t hdr;
357 uint8_t *header;
358
359 /* create new gnutella header */
360 nut_calc_guid (hdr.id);
361 hdr.function = NUT_PING_REQ;
362 hdr.ttl = (uint8_t) cfg->ttl;
363 hdr.hop = 0;
364 hdr.length = 0;
365 header = nut_put_header (&hdr);
366
367 /* put into sent packet hash */
368 pkt = svz_malloc (sizeof (nut_packet_t));
369 pkt->sock = sock;
370 pkt->sent = time (NULL);
371 svz_hash_put (cfg->packet, (char *) hdr.id, pkt);
372
373 /* update client and server statistics */
374 cfg->nodes -= client->nodes;
375 cfg->files -= client->files;
376 cfg->size -= client->size;
377 client->nodes = 0;
378 client->files = 0;
379 client->size = 0;
380
381 return svz_sock_write (sock, (char *) header, SIZEOF_NUT_HEADER);
382 }
383
384 /*
385 * The gnutella servers global initializer.
386 */
387 int
nut_global_init(UNUSED svz_servertype_t * server)388 nut_global_init (UNUSED svz_servertype_t *server)
389 {
390 #ifdef __MINGW32__
391 /* try getting M$'s GUID creation routine */
392 if ((oleHandle = LoadLibrary ("ole32.dll")) != NULL)
393 {
394 CreateGuid = (CreateGuidProc)
395 GetProcAddress (oleHandle, "CoCreateGuid");
396 }
397 #endif /* __MINGW32__ */
398
399 /* initialize random seed */
400 srand (time (NULL));
401
402 /* initialize configuration default values */
403 nut_config.search = SVZ_COLLECT_STRARRAY (nut_search_patterns);
404
405 #if 0
406 /* Print structure sizes. */
407 printf ("header : %d\n", sizeof (nut_header_t));
408 printf ("ping reply : %d\n", sizeof (nut_pong_t));
409 printf ("query : %d\n", sizeof (nut_query_t));
410 printf ("record : %d\n", sizeof (nut_record_t));
411 printf ("reply : %d\n", sizeof (nut_reply_t));
412 printf ("push : %d\n", sizeof (nut_push_t));
413 printf ("host : %d\n", sizeof (nut_host_t));
414 printf ("client : %d\n", sizeof (nut_client_t));
415 printf ("packet : %d\n", sizeof (nut_packet_t));
416 printf ("push reply : %d\n", sizeof (nut_push_reply_t));
417 printf ("file : %d\n", sizeof (nut_file_t));
418 printf ("config : %d\n", sizeof (nut_config_t));
419 printf ("transfer : %d\n", sizeof (nut_transfer_t));
420 #endif
421 return 0;
422 }
423
424 /*
425 * Gnutella spider server's instance initializer.
426 */
427 int
nut_init(svz_server_t * server)428 nut_init (svz_server_t *server)
429 {
430 nut_config_t *cfg = server->cfg;
431 size_t n = 0;
432 struct stat buf;
433 char *p;
434
435 /* check the download and share path first */
436 if (strlen (cfg->save_path) == 0 || strlen (cfg->share_path) == 0)
437 {
438 svz_log (SVZ_LOG_ERROR, "nut: no download/share path given\n");
439 return -1;
440 }
441 p = cfg->save_path + strlen (cfg->save_path) - 1;
442 if (*p == '/' || *p == '\\')
443 *p = '\0';
444 p = cfg->share_path + strlen (cfg->share_path) - 1;
445 if (*p == '/' || *p == '\\')
446 *p = '\0';
447
448 /* check for existence and create them if necessary */
449 if (cfg->save_path[0])
450 {
451 if (stat (cfg->save_path, &buf) == -1)
452 {
453 /* create the download directory */
454 if (mkdir (cfg->save_path, 0755) == -1)
455 {
456 svz_log_sys_error ("nut: mkdir");
457 return -1;
458 }
459 }
460 /* check if the given path is a directory already */
461 else if (!S_ISDIR (buf.st_mode))
462 {
463 svz_log (SVZ_LOG_ERROR, "nut: %s is not a directory\n",
464 cfg->save_path);
465 return -1;
466 }
467 }
468
469 /* read shared files */
470 nut_read_database (cfg, cfg->share_path[0] ? cfg->share_path : "/");
471 svz_log (SVZ_LOG_NOTICE, "nut: %d files in database\n", cfg->db_files);
472
473 /* calculate forced local ip and port if necessary */
474 if (cfg->force_ip)
475 {
476 cfg->ip = inet_addr (cfg->force_ip);
477 }
478 cfg->port = htons (cfg->force_port);
479
480 /* create and modify packet hash */
481 cfg->packet = make_nut_kce_hash_table (svz_free);
482
483 /* create and modify reply hash */
484 cfg->reply = make_nut_kce_hash_table (NULL);
485
486 /* create current connection hash */
487 cfg->conn = svz_hash_create (4, NULL);
488
489 /* create host catcher hash */
490 cfg->net = svz_hash_create (4, svz_free);
491
492 /* create recent query hash */
493 cfg->query = svz_hash_create (4, NULL);
494
495 /* create push request hash */
496 cfg->push = svz_hash_create (4, (svz_free_func_t) nut_free_transfer);
497
498 /* create and modify the routing table hash */
499 cfg->route = make_nut_kce_hash_table (NULL);
500
501 /* calculate this server instance's GUID */
502 nut_calc_guid (cfg->guid);
503
504 /* create detection string for gnutella host list */
505 cfg->net_detect = svz_malloc (strlen (NUT_HOSTS) +
506 strlen (cfg->net_url) + 1);
507 sprintf (cfg->net_detect, NUT_HOSTS, cfg->net_url);
508
509 /* go through all given hosts and try to connect to them */
510 svz_array_foreach (cfg->hosts, p, n)
511 nut_connect_host (cfg, p);
512
513 return 0;
514 }
515
516 /*
517 * Instance finalizer.
518 */
519 int
nut_finalize(svz_server_t * server)520 nut_finalize (svz_server_t *server)
521 {
522 nut_config_t *cfg = server->cfg;
523
524 /* destroy sharing files */
525 nut_destroy_database (cfg);
526
527 svz_hash_destroy (cfg->conn);
528 svz_hash_destroy (cfg->route);
529 svz_hash_destroy (cfg->query);
530 svz_hash_destroy (cfg->reply);
531
532 /* destroy sent packet hash */
533 svz_hash_destroy (cfg->packet);
534
535 /* destroy host catcher hash */
536 svz_hash_destroy (cfg->net);
537
538 /* destroy push request hash */
539 svz_hash_destroy (cfg->push);
540
541 /* free detection string */
542 svz_free (cfg->net_detect);
543
544 return 0;
545 }
546
547 /*
548 * Global gnutella finalizer.
549 */
550 int
nut_global_finalize(UNUSED svz_servertype_t * server)551 nut_global_finalize (UNUSED svz_servertype_t *server)
552 {
553 #ifdef __MINGW32__
554 if (oleHandle)
555 FreeLibrary (oleHandle);
556 #endif /* __MINGW32__ */
557
558 /* destroy confguration defaults */
559 svz_array_destroy (nut_config.search);
560
561 return 0;
562 }
563
564 struct dead_packet
565 {
566 char *key;
567 nut_packet_t *pkt;
568 };
569
570 struct disconnect_closure
571 {
572 svz_socket_t *sock;
573 struct dead_packet d;
574 };
575
576 static void
disconnect_internal(void * k,void * v,void * closure)577 disconnect_internal (void *k, void *v, void *closure)
578 {
579 char *key = k;
580 nut_packet_t *pkt = v;
581 struct disconnect_closure *x = closure;
582
583 if (x->d.key)
584 return;
585
586 if (pkt->sock == x->sock)
587 {
588 x->d.key = key;
589 x->d.pkt = pkt;
590 }
591 }
592
593 static char *
nut_sock_client_key(svz_socket_t * sock)594 nut_sock_client_key (svz_socket_t *sock)
595 {
596 in_addr_t v4addr;
597
598 svz_address_to (&v4addr, sock->remote_addr);
599 return nut_client_key (v4addr, sock->remote_port);
600 }
601
602 /*
603 * This is the sock->disconnected_socket callback for gnutella
604 * connections.
605 */
606 int
nut_disconnect(svz_socket_t * sock)607 nut_disconnect (svz_socket_t *sock)
608 {
609 nut_config_t *cfg = sock->cfg;
610 nut_host_t *host;
611 uint8_t *id;
612 char *key;
613 nut_client_t *client = sock->data;
614
615 /* delete all push request routing information for this connection */
616 while ((id = (uint8_t *) svz_hash_contains (cfg->reply, sock)) != NULL)
617 svz_hash_delete (cfg->reply, (char *) id);
618
619 /* delete all routing information for this connection */
620 while ((id = (uint8_t *) svz_hash_contains (cfg->route, sock)) != NULL)
621 svz_hash_delete (cfg->route, (char *) id);
622
623 /* drop all packet information for this connection */
624 if (svz_hash_size (cfg->packet))
625 {
626 struct disconnect_closure x = { sock, { NULL, NULL } };
627
628 svz_hash_foreach (disconnect_internal, cfg->packet, &x);
629 if (x.d.key)
630 {
631 svz_hash_delete (cfg->packet, x.d.key);
632 svz_free (x.d.pkt);
633 }
634 }
635
636 /* remove this socket from the current connection hash */
637 key = nut_sock_client_key (sock);
638 svz_hash_delete (cfg->conn, key);
639
640 /* remove the connection from the host catcher */
641 if ((host = svz_hash_delete (cfg->net, key)) != NULL)
642 svz_free (host);
643
644 /* free client structure */
645 if (client)
646 {
647 cfg->nodes -= client->nodes;
648 cfg->files -= client->files;
649 cfg->size -= client->size;
650 svz_free (client);
651 sock->data = NULL;
652 }
653
654 return 0;
655 }
656
657 struct server_notify_closure
658 {
659 nut_config_t *cfg;
660 time_t t;
661 int connect;
662 svz_array_t *dead;
663 };
664
665 static void
server_notify_net_internal(void * k,UNUSED void * v,void * closure)666 server_notify_net_internal (void *k, UNUSED void *v, void *closure)
667 {
668 char *key = k;
669 struct server_notify_closure *x = closure;
670 nut_config_t *cfg = x->cfg;
671
672 if (! x->connect)
673 return;
674
675 /* Check if we are not already connected. */
676 if (NULL == svz_hash_get (cfg->conn, key)
677 && -1 != nut_connect_host (cfg, key))
678 x->connect--;
679 }
680
681 static void
server_notify_packet_internal(void * k,void * v,void * closure)682 server_notify_packet_internal (void *k, void *v, void *closure)
683 {
684 char *key = k;
685 nut_packet_t *pkt = v;
686 struct server_notify_closure *x = closure;
687
688 if (x->t - pkt->sent > NUT_ENTRY_AGE)
689 {
690 struct dead_packet *d = svz_malloc (sizeof (struct dead_packet));
691
692 d->key = key;
693 d->pkt = pkt;
694 svz_array_add (x->dead, d);
695 }
696 }
697
698 static void
server_notify_query_internal(void * k,void * v,void * closure)699 server_notify_query_internal (void *k, void *v, void *closure)
700 {
701 char *key = k;
702 time_t received = (time_t) SVZ_PTR2NUM (v);
703 struct server_notify_closure *x = closure;
704
705 if (x->t - received > NUT_ENTRY_AGE)
706 svz_array_add (x->dead, key);
707 }
708
709 /*
710 * This callback is regularly called in the `server_periodic_tasks'
711 * routine. Here we try connecting to more gnutella hosts.
712 */
713 int
nut_server_notify(svz_server_t * server)714 nut_server_notify (svz_server_t *server)
715 {
716 nut_config_t *cfg = server->cfg;
717 static int count = NUT_CONNECT_INTERVAL;
718 size_t n;
719 struct server_notify_closure x;
720
721 /* go sleep if we still do not want to do something */
722 if (count-- > 0)
723 return 0;
724
725 x.cfg = cfg;
726
727 /* do we have enough connections? */
728 x.connect = cfg->connections - svz_hash_size (cfg->conn);
729 if (x.connect > 0)
730 {
731 /* are there hosts in the host catcher hash? */
732 if (svz_hash_size (cfg->net))
733 svz_hash_foreach (server_notify_net_internal, cfg->net, &x);
734 }
735
736 /* go through the sent packet hash and drop old entries */
737 if (svz_hash_size (cfg->packet))
738 {
739 struct dead_packet *d;
740
741 x.t = time (NULL);
742 x.dead = svz_array_create (4, svz_free);
743 svz_hash_foreach (server_notify_packet_internal, cfg->packet, &x);
744 svz_array_foreach (x.dead, d, n)
745 {
746 svz_hash_delete (cfg->packet, d->key);
747 svz_free (d->pkt);
748 }
749 svz_array_destroy (x.dead);
750 }
751
752 /* drop older entries from the recent query hash */
753 if (svz_hash_size (cfg->query))
754 {
755 char *key;
756
757 x.t = time (NULL);
758 x.dead = svz_array_create (4, NULL);
759 svz_hash_foreach (server_notify_query_internal, cfg->query, &x);
760 svz_array_foreach (x.dead, key, n)
761 svz_hash_delete (cfg->query, key);
762 svz_array_destroy (x.dead);
763 }
764
765 /* wake up in a certain time */
766 count = NUT_CONNECT_INTERVAL;
767 return 0;
768 }
769
770 /*
771 * Whenever there is data arriving for this socket we call this
772 * routine.
773 */
774 int
nut_check_request(svz_socket_t * sock)775 nut_check_request (svz_socket_t *sock)
776 {
777 nut_client_t *client = sock->data;
778 nut_header_t *hdr;
779 uint8_t *packet;
780 int len = strlen (NUT_OK);
781 unsigned fill = sock->recv_buffer_fill;
782
783 /* go through all packets in the receive queue */
784 while ((fill = sock->recv_buffer_fill) >= SIZEOF_NUT_HEADER)
785 {
786 hdr = nut_get_header ((uint8_t *) sock->recv_buffer);
787
788 /* is there enough data to fulfill a complete packet? */
789 if (fill >= SIZEOF_NUT_HEADER + hdr->length)
790 {
791 len = SIZEOF_NUT_HEADER + hdr->length;
792 packet = (uint8_t *) sock->recv_buffer + SIZEOF_NUT_HEADER;
793 client->packets++;
794 #if 0
795 svz_hexdump (stdout, "gnutella packet", sock->sock_desc,
796 sock->recv_buffer, len, 0);
797 #endif
798
799 /* try to route the packet */
800 if (nut_route (sock, hdr, packet) == 0)
801 {
802 /* handle the packet */
803 switch (hdr->function)
804 {
805 case NUT_PING_REQ:
806 nut_ping (sock, hdr, NULL);
807 break;
808 case NUT_PING_ACK:
809 nut_pong (sock, hdr, packet);
810 break;
811 case NUT_PUSH_REQ:
812 nut_push_request (sock, hdr, packet);
813 break;
814 case NUT_SEARCH_REQ:
815 nut_query (sock, hdr, packet);
816 break;
817 case NUT_SEARCH_ACK:
818 nut_reply (sock, hdr, packet);
819 break;
820 }
821 }
822 else if (!(sock->flags & SVZ_SOFLG_KILLED))
823 {
824 client->dropped++;
825 }
826
827 /* return if this client connection has been killed */
828 if (sock->flags & SVZ_SOFLG_KILLED)
829 return -1;
830
831 /* cut this packet from the send buffer queue */
832 svz_sock_reduce_recv (sock, len);
833 }
834 else
835 break;
836 }
837 return 0;
838 }
839
840 /*
841 * This routine is the sock->idle_func callback for each gnutella
842 * connection. We will regularly search for specific files.
843 */
844 int
nut_idle_searching(svz_socket_t * sock)845 nut_idle_searching (svz_socket_t *sock)
846 {
847 nut_config_t *cfg = sock->cfg;
848 nut_packet_t *pkt;
849 nut_header_t hdr;
850 nut_query_t query;
851 uint8_t *header, *search;
852 char *text;
853
854 /* search strings given? */
855 if (cfg->search && svz_array_size (cfg->search) > 0)
856 {
857 /* get next search string */
858 if (svz_array_size (cfg->search) > cfg->search_index)
859 {
860 text = svz_array_get (cfg->search, cfg->search_index);
861 cfg->search_index++;
862 }
863 else
864 {
865 cfg->search_index = 0;
866 text = svz_array_get (cfg->search, 0);
867 }
868
869 /* create new gnutella packet */
870 nut_calc_guid (hdr.id);
871 hdr.function = NUT_SEARCH_REQ;
872 hdr.ttl = (uint8_t) cfg->ttl;
873 hdr.hop = 0;
874 hdr.length = SIZEOF_NUT_QUERY + strlen (text) + 1;
875 query.speed = (uint16_t) cfg->min_speed;
876 header = nut_put_header (&hdr);
877 search = nut_put_query (&query);
878
879 /* try sending this packet to this connection */
880 if (svz_sock_write (sock, (char *) header, SIZEOF_NUT_HEADER) == -1 ||
881 svz_sock_write (sock, (char *) search, SIZEOF_NUT_QUERY) == -1 ||
882 svz_sock_write (sock, text, strlen (text) + 1) == -1)
883 {
884 return -1;
885 }
886
887 /* save this packet for later routing */
888 pkt = svz_malloc (sizeof (nut_packet_t));
889 pkt->sock = sock;
890 pkt->sent = time (NULL);
891 svz_hash_put (cfg->packet, (char *) hdr.id, pkt);
892 }
893
894 /* wake up in a certain time */
895 sock->idle_counter = NUT_SEARCH_INTERVAL;
896 return 0;
897 }
898
899 /*
900 * Gnutella server info callback.
901 */
902 char *
nut_info_server(svz_server_t * server)903 nut_info_server (svz_server_t *server)
904 {
905 nut_config_t *cfg = server->cfg;
906 char bindings[256];
907 static char info[80 * 19];
908 char *e, *ext = NULL;
909 size_t n;
910
911 /* create file extension list */
912 if (cfg->extensions)
913 {
914 int len = 0;
915 char *w;
916
917 svz_array_foreach (cfg->extensions, e, n)
918 len += strlen (e);
919 len += svz_array_size (cfg->extensions) - 1; /* ';' sep */
920 len++; /* '\0' */
921 w = ext = svz_malloc (len);
922 svz_array_foreach (cfg->extensions, e, n)
923 {
924 if (n)
925 *w++ = ';';
926 len = strlen (e);
927 memcpy (w, e, len);
928 w += len;
929 }
930 *w = '\0';
931 }
932
933 svz_pp_server_bindings (bindings, 256, server);
934 sprintf (info,
935 " tcp bindings : %s\r\n"
936 " force ip : %s\r\n"
937 " force port : %s\r\n"
938 " maximum ttl : %u\r\n"
939 " default ttl : %u\r\n"
940 " speed : %u KBit/s\r\n"
941 " clientID128 : %s\r\n"
942 " download path : %s\r\n"
943 " share path : %s\r\n"
944 " search pattern : %s\r\n"
945 " file extensions : %s\r\n"
946 " routing table : %zu entries\r\n"
947 " connected hosts : %zu/%zu\r\n"
948 " sent packets : %zu\r\n"
949 " routing errors : %u\r\n"
950 " hosts : %zu gnutella clients seen\r\n"
951 " data pool : %u MB in %u files on %u hosts\r\n"
952 " database : %u MB in %u files\r\n"
953 " downloads : %u/%u\r\n"
954 " uploads : %u/%u\r\n"
955 " recent queries : %zu",
956 bindings,
957 cfg->ip ? svz_inet_ntoa (cfg->ip) : "no specified",
958 cfg->port ? svz_itoa (ntohs (cfg->port)) : "no specified",
959 cfg->max_ttl,
960 cfg->ttl,
961 cfg->speed,
962 nut_print_guid (cfg->guid),
963 cfg->save_path,
964 cfg->share_path,
965 cfg->search && svz_array_size (cfg->search) > 0 ?
966 cfg->search_index < svz_array_size (cfg->search) ?
967 (char *) svz_array_get (cfg->search, cfg->search_index) :
968 (char *) svz_array_get (cfg->search, 0) :
969 "none given",
970 ext ? ext : "no extensions",
971 svz_hash_size (cfg->route),
972 svz_hash_size (cfg->conn), cfg->connections,
973 svz_hash_size (cfg->packet),
974 cfg->errors,
975 svz_hash_size (cfg->net),
976 cfg->size / 1024, cfg->files, cfg->nodes,
977 cfg->db_size / 1024 / 1024, cfg->db_files,
978 cfg->dnloads, cfg->max_dnloads,
979 cfg->uploads, cfg->max_uploads,
980 svz_hash_size (cfg->query));
981
982 svz_free (ext);
983 return info;
984 }
985
986 /*
987 * Gnutella client info callback.
988 */
989 char *
nut_info_client(UNUSED svz_server_t * server,svz_socket_t * sock)990 nut_info_client (UNUSED svz_server_t *server, svz_socket_t *sock)
991 {
992 #define INFO_SIZE 80 * 3
993 static char info[INFO_SIZE];
994 nut_transfer_t *transfer = sock->data;
995 nut_client_t *client = sock->data;
996 unsigned current, all, elapsed;
997 char *crlf = "\r\n";
998 int avail = INFO_SIZE;
999 char *w = info;
1000
1001 #define MORE(fmt,...) do \
1002 { \
1003 int one = snprintf (w, avail, fmt, __VA_ARGS__); \
1004 \
1005 avail -= one; \
1006 w += one; \
1007 } \
1008 while (0)
1009
1010 MORE ("This is a gnutella spider client.%s%s", crlf, crlf);
1011
1012 /* normal gnutella host */
1013 if (sock->userflags & NUT_FLAG_CLIENT)
1014 MORE (" * usual gnutella host%s"
1015 " * dropped packets : %u/%u%s"
1016 " * invalid packets : %u%s"
1017 " * data pool : %u MB"
1018 " in %u files on %u hosts%s",
1019 crlf,
1020 client->dropped, client->packets, crlf,
1021 client->invalid, crlf,
1022 client->size / 1024,
1023 client->files, client->nodes, crlf);
1024
1025 /* file upload and download */
1026 if (sock->userflags & (NUT_FLAG_UPLOAD | NUT_FLAG_DNLOAD))
1027 {
1028 current = transfer->original_size - transfer->size;
1029 all = transfer->original_size;
1030 elapsed = time (NULL) - transfer->start;
1031 if (!all)
1032 all++;
1033 if (!elapsed)
1034 elapsed++;
1035 MORE (" * file : %s%s"
1036 " * %s progress : %u/%u - %u.%u%% - %u.%u kb/sec%s",
1037 transfer->file, crlf,
1038 (sock->userflags & NUT_FLAG_DNLOAD
1039 ? "download"
1040 : "upload"),
1041 current, all,
1042 current * 100 / all, (current * 1000 / all) % 10,
1043 current / 1024 / elapsed,
1044 (current * 10 / 1024 / elapsed) % 10, crlf);
1045 }
1046
1047 /* http header received? */
1048 if (sock->userflags & NUT_FLAG_HDR)
1049 MORE (" * header received%s", crlf);
1050
1051 /* host list */
1052 if (sock->userflags & NUT_FLAG_HOSTS)
1053 MORE (w, avail, " * sending host catcher list%s", crlf);
1054
1055 *w = '\0';
1056 return info;
1057 #undef MORE
1058 #undef INFO_SIZE
1059 }
1060
1061 /*
1062 * This is the protocol detection routine for self connected gnutella
1063 * hosts. It is used for normal gnutella network connections and
1064 * push requests (download).
1065 */
1066 int
nut_detect_connect(svz_socket_t * sock)1067 nut_detect_connect (svz_socket_t *sock)
1068 {
1069 nut_config_t *cfg = sock->cfg;
1070 int len = strlen (NUT_OK);
1071
1072 /* check for self connected response of normal gnutella host */
1073 if (sock->recv_buffer_fill >= len &&
1074 !memcmp (sock->recv_buffer, NUT_OK, len))
1075 {
1076 char buf[64];
1077
1078 sock->userflags |= (NUT_FLAG_CLIENT | NUT_FLAG_SELF);
1079 svz_log (SVZ_LOG_NOTICE, "nut: host %s connected\n",
1080 SVZ_PP_ADDR_PORT (buf, sock->remote_addr, sock->remote_port));
1081 svz_sock_reduce_recv (sock, len);
1082
1083 if (nut_connect_socket (svz_server_find (cfg), sock) == -1)
1084 return -1;
1085 return sock->check_request (sock);
1086 }
1087
1088 return 0;
1089 }
1090
1091 /*
1092 * Incoming connections will be protocol checked.
1093 */
1094 int
nut_detect_proto(svz_server_t * server,svz_socket_t * sock)1095 nut_detect_proto (svz_server_t *server, svz_socket_t *sock)
1096 {
1097 nut_config_t *cfg = server->cfg;
1098 int len = strlen (NUT_CONNECT);
1099
1100 /* detect normal connect (not if listener is disabled) */
1101 if (!cfg->disable)
1102 {
1103 len = strlen (NUT_CONNECT);
1104 if (sock->recv_buffer_fill >= len &&
1105 !memcmp (sock->recv_buffer, NUT_CONNECT, len))
1106 {
1107 sock->userflags |= NUT_FLAG_CLIENT;
1108 svz_log (SVZ_LOG_NOTICE, "gnutella protocol detected (client)\n");
1109 svz_sock_reduce_recv (sock, len);
1110 return -1;
1111 }
1112 }
1113
1114 /* detect upload request */
1115 len = strlen (NUT_GET);
1116 if (sock->recv_buffer_fill >= len &&
1117 !memcmp (sock->recv_buffer, NUT_GET, len))
1118 {
1119 sock->userflags |= NUT_FLAG_UPLOAD;
1120 svz_log (SVZ_LOG_NOTICE, "gnutella protocol detected (upload)\n");
1121 return -1;
1122 }
1123
1124 /* detect host catcher request */
1125 len = strlen (cfg->net_detect);
1126 if (sock->recv_buffer_fill >= len &&
1127 !memcmp (sock->recv_buffer, cfg->net_detect, len))
1128 {
1129 sock->userflags |= NUT_FLAG_HOSTS;
1130 svz_log (SVZ_LOG_NOTICE, "gnutella protocol detected (host list)\n");
1131 svz_sock_reduce_recv (sock, len);
1132 return -1;
1133 }
1134
1135 /* check for push request reply */
1136 len = strlen (NUT_GIVE);
1137 if (sock->recv_buffer_fill >= len &&
1138 !memcmp (sock->recv_buffer, NUT_GIVE, len))
1139 {
1140 sock->userflags |= NUT_FLAG_GIVEN;
1141 svz_log (SVZ_LOG_NOTICE, "gnutella protocol detected (giving)\n");
1142 return -1;
1143 }
1144
1145 return 0;
1146 }
1147
1148 /*
1149 * This routine will be called when the detection routine return
1150 * success.
1151 */
1152 int
nut_connect_socket(svz_server_t * server,svz_socket_t * sock)1153 nut_connect_socket (svz_server_t *server, svz_socket_t *sock)
1154 {
1155 nut_config_t *cfg = server->cfg;
1156
1157 /* assign download callbacks */
1158 if (sock->userflags & NUT_FLAG_GIVEN)
1159 {
1160 sock->check_request = nut_check_given;
1161 return 0;
1162 }
1163
1164 /* assign host catcher request routines */
1165 if (sock->userflags & NUT_FLAG_HOSTS)
1166 {
1167 sock->check_request = nut_hosts_check;
1168 sock->write_socket = nut_hosts_write;
1169 svz_sock_resize_buffers (sock, NUT_SEND_BUFSIZE, sock->recv_buffer_size);
1170 return 0;
1171 }
1172
1173 /* assign upload request routines */
1174 if (sock->userflags & NUT_FLAG_UPLOAD)
1175 {
1176 if (cfg->uploads <= cfg->max_uploads)
1177 {
1178 sock->check_request = nut_check_upload;
1179 return 0;
1180 }
1181 return -1;
1182 }
1183
1184 /* assign normal gnutella request routines */
1185 if (sock->userflags & NUT_FLAG_CLIENT)
1186 {
1187 /* check if we got enough clients already */
1188 if (svz_hash_size (cfg->conn) > cfg->connections)
1189 return -1;
1190
1191 /* send the first reply if necessary */
1192 if (!(sock->userflags & NUT_FLAG_SELF))
1193 if (svz_sock_printf (sock, NUT_OK) == -1)
1194 return -1;
1195
1196 /* assign gnutella specific callbacks */
1197 sock->flags |= SVZ_SOFLG_NOFLOOD;
1198 sock->disconnected_socket = nut_disconnect;
1199 sock->check_request = nut_check_request;
1200 sock->idle_func = nut_idle_searching;
1201 sock->idle_counter = NUT_SEARCH_INTERVAL;
1202 sock->data = nut_create_client ();
1203
1204 /* send initial ping */
1205 if (nut_init_ping (sock) == -1)
1206 return -1;
1207
1208 /* put this client to the current connection hash */
1209 svz_hash_put (cfg->conn, nut_sock_client_key (sock), sock);
1210
1211 return 0;
1212 }
1213
1214 return -1;
1215 }
1216