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