1 /*
2  * nut-request.c - gnutella requests implementations
3  *
4  * Copyright (C) 2011-2013 Thien-Thi Nguyen
5  * Copyright (C) 2000, 2001, 2003 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 
27 #ifndef __MINGW32__
28 # include <sys/types.h>
29 # include <sys/socket.h>
30 #endif
31 
32 #include "networking-headers.h"
33 #include "libserveez.h"
34 #include "gnutella.h"
35 #include "nut-core.h"
36 #include "nut-transfer.h"
37 #include "nut-hostlist.h"
38 #include "nut-request.h"
39 #include "unused.h"
40 
41 /*
42  * This routine will be called when a search reply occurs.  Here we
43  * can check if the reply was created by a packet we sent ourselves.
44  */
45 int
nut_reply(svz_socket_t * sock,nut_header_t * hdr,uint8_t * packet)46 nut_reply (svz_socket_t *sock, nut_header_t *hdr, uint8_t *packet)
47 {
48   nut_config_t *cfg = sock->cfg;
49   nut_packet_t *pkt;
50   nut_record_t *record;
51   nut_client_t *client = sock->data;
52   char *p, *end, *file;
53   int n;
54   uint8_t *id;
55   nut_reply_t *reply;
56 
57   reply = nut_get_reply (packet);
58   nut_host_catcher (sock, reply->ip, reply->port);
59   pkt = (nut_packet_t *) svz_hash_get (cfg->packet, (char *) hdr->id);
60 
61   /* check client guid at the end of the packet */
62   id = packet + hdr->length - NUT_GUID_SIZE;
63   if (id < packet + SIZEOF_NUT_REPLY)
64     {
65 #if ENABLE_DEBUG
66       svz_log (SVZ_LOG_DEBUG, "nut: dropping invalid query hit\n");
67 #endif
68       client->dropped++;
69       return -1;
70     }
71 
72   /* is that query hit (reply) an answer to my own request?  */
73   if (pkt != NULL)
74     {
75       p = (char *) packet + SIZEOF_NUT_REPLY;
76       end = (char *) packet + hdr->length - NUT_GUID_SIZE;
77       memcpy (reply->id, id, NUT_GUID_SIZE);
78 
79 #if 0
80       printf ("records : %d\n", reply->records);
81       printf ("port    : %u\n", ntohs (reply->port));
82       printf ("ip      : %s\n", svz_inet_ntoa (reply->ip));
83       printf ("speed   : %u kbit/s\n", reply->speed);
84       printf ("guid    : %s\n", nut_print_guid (reply->id));
85 #endif /* 0 */
86 
87       /* process only if the connection has a minimum speed */
88       if (reply->speed < cfg->min_speed)
89         return 0;
90 
91       /* go through all query hit records */
92       for (n = 0; n < reply->records && p < end; n++)
93         {
94           record = nut_get_record ((uint8_t *) p);
95           p += SIZEOF_NUT_RECORD;
96           file = p;
97 
98           /* check if the reply is valid */
99           while (p < end && *p)
100             p++;
101           if (p == end || *(p + 1))
102             {
103 #if ENABLE_DEBUG
104               svz_log (SVZ_LOG_DEBUG, "nut: invalid query hit payload\n");
105 #endif
106               client->dropped++;
107               return -1;
108             }
109           p += 2;
110           nut_canonize_file (file);
111 #if 0
112           printf ("record %d\n", n + 1);
113           printf ("file index : %u\n", record->index);
114           printf ("file size  : %u\n", record->size);
115           printf ("file       : %s\n", file);
116 #endif
117 
118           /* startup transfer if possible */
119           if (cfg->dnloads < cfg->max_dnloads)
120             {
121               nut_init_transfer (sock, reply, record, file);
122             }
123         }
124     }
125   /* save the reply id to the reply hash for routing push requests */
126   else
127     {
128       svz_hash_put (cfg->reply, (char *) id, sock);
129     }
130 
131   return 0;
132 }
133 
134 /*
135  * This is the callback for push requests.
136  */
137 int
nut_push_request(svz_socket_t * sock,nut_header_t * hdr,uint8_t * packet)138 nut_push_request (svz_socket_t *sock, nut_header_t *hdr, uint8_t *packet)
139 {
140   nut_config_t *cfg = sock->cfg;
141   nut_client_t *client = sock->data;
142   svz_socket_t *xsock;
143   nut_push_t *push;
144   nut_file_t *entry;
145   uint8_t *header;
146 
147   push = nut_get_push (packet);
148 
149   /* is the guid of this push request in the reply hash?  */
150   if ((xsock = (svz_socket_t *)
151        svz_hash_get (cfg->reply, (char *) push->id)) != NULL)
152     {
153       header = nut_put_header (hdr);
154       if (svz_sock_write (xsock, (char *) header, SIZEOF_NUT_HEADER) == -1 ||
155           svz_sock_write (xsock, (char *) packet, SIZEOF_NUT_PUSH) == -1)
156         {
157           svz_sock_schedule_for_shutdown (xsock);
158           return -1;
159         }
160     }
161   /* push request for ourselves?  */
162   else if (!memcmp (cfg->guid, push->id, NUT_GUID_SIZE))
163     {
164 #if 0
165       printf ("push request for us\n"
166               "file index : %u\n", push->index);
167       printf ("ip         : %s\n", svz_inet_ntoa (push->ip));
168       printf ("port       : %u\n", htons (push->port));
169 #endif
170 
171       /* find requested file in database */
172       if (cfg->uploads <= cfg->max_uploads &&
173           (entry = nut_get_database (cfg, NULL, push->index)) != NULL)
174         {
175           svz_address_t *addr = svz_address_make (AF_INET, &push->ip);
176 
177           xsock = svz_tcp_connect (addr, push->port);
178           svz_free (addr);
179           /* try to connect to given host */
180           if (xsock != NULL)
181             {
182               svz_log (SVZ_LOG_NOTICE, "nut: connecting %s:%u\n",
183                        svz_inet_ntoa (push->ip), ntohs (push->port));
184 
185               xsock->userflags |= NUT_FLAG_UPLOAD;
186               xsock->cfg = cfg;
187               svz_sock_setparent (xsock, svz_sock_getparent (sock));
188 
189               /*
190                * we are not sure about the format of this line, but two
191                * of the reviewed clients (gtk_gnutella and gnutella itself)
192                * use it as is
193                */
194               if (svz_sock_printf (xsock, NUT_GIVE "%d:%s/%s\n\n",
195                                    entry->index, nut_text_guid (cfg->guid),
196                                    entry->file) == -1)
197                 {
198                   svz_sock_schedule_for_shutdown (xsock);
199                   return -1;
200                 }
201               xsock->check_request = nut_check_upload;
202               xsock->idle_func = nut_connect_timeout;
203               xsock->idle_counter = NUT_CONNECT_TIMEOUT;
204             }
205         }
206     }
207   /* drop this push request */
208   else
209     {
210 #if ENABLE_DEBUG
211       svz_log (SVZ_LOG_DEBUG, "nut: dropping push request\n");
212 #endif
213       client->dropped++;
214       return -1;
215     }
216 
217   return 0;
218 }
219 
220 in_addr_t
nut_v4addr_from(nut_config_t * cfg,struct sockaddr_in * addr,svz_socket_t * sock)221 nut_v4addr_from (nut_config_t *cfg,
222                  struct sockaddr_in *addr,
223                  svz_socket_t *sock)
224 {
225   if (cfg->ip)
226     return cfg->ip;
227   if (addr)
228     return addr->sin_addr.s_addr;
229   {
230     in_addr_t v4addr;
231 
232     svz_address_to (&v4addr, sock->local_addr);
233     return v4addr;
234   }
235 }
236 
237 /*
238  * This is called whenever there was a search query received.
239  */
240 int
nut_query(svz_socket_t * sock,nut_header_t * hdr,uint8_t * packet)241 nut_query (svz_socket_t *sock, nut_header_t *hdr, uint8_t *packet)
242 {
243   nut_config_t *cfg = sock->cfg;
244   nut_reply_t reply;
245   nut_record_t record;
246   nut_query_t *query;
247   nut_file_t *entry;
248   uint8_t *file, *p, *buffer = NULL;
249   unsigned n, len = 0, size;
250   struct sockaddr_in *addr = NULL;
251   svz_portcfg_t *port;
252 
253   /* shall we reply to this query?  */
254   query = nut_get_query (packet);
255   if (query->speed > cfg->speed)
256     return -1;
257 
258   /* check validity of search request */
259   p = file = packet + SIZEOF_NUT_QUERY;
260   len = SIZEOF_NUT_QUERY;
261   while (*p++ && len < hdr->length)
262     len++;
263   if (len >= hdr->length && *file)
264     {
265 #if ENABLE_DEBUG
266       svz_log (SVZ_LOG_DEBUG, "nut: invalid query payload\n");
267 #endif
268       return -1;
269     }
270 
271   /* create new gnutella header */
272   hdr->function = NUT_SEARCH_ACK;
273   hdr->ttl = hdr->hop;
274   hdr->hop = 0;
275 
276   /* go through database and build the record array */
277   for (size = 0, n = 0, entry = NULL; n < 256 && (int) n < cfg->search_limit;)
278     {
279       if ((entry = nut_find_database (cfg, entry, (char *) file)) != NULL)
280         {
281           len = strlen (entry->file) + 2;
282           size += SIZEOF_NUT_RECORD + len;
283           buffer = svz_realloc (buffer, size);
284           p = buffer + size - len;
285           memcpy (p, entry->file, len - 1);
286           p += len - 1;
287           *p = '\0';
288 
289           p = buffer + size - len - SIZEOF_NUT_RECORD;
290           record.index = entry->index;
291           record.size = entry->size;
292           memcpy (p, nut_put_record (&record), SIZEOF_NUT_RECORD);
293 
294           n++;
295         }
296       else
297         break;
298     }
299 
300   /* no files found in database */
301   if (!n)
302     return 0;
303 
304   /* create gnutella search reply packet */
305   reply.records = (uint8_t) n;
306   if ((port = svz_sock_portcfg (sock)) != NULL)
307     addr = svz_portcfg_addr (port);
308   reply.ip = nut_v4addr_from (cfg, addr, sock);
309   reply.port = (cfg->port ? cfg->port : addr ?
310                 addr->sin_port : sock->local_port);
311   reply.speed = (uint16_t) cfg->speed;
312 
313   /* save packet length */
314   hdr->length = SIZEOF_NUT_REPLY + size + NUT_GUID_SIZE;
315 
316   /* send header, reply, array of records and guid */
317   if (svz_sock_write (sock, (char *) nut_put_header (hdr),
318                       SIZEOF_NUT_HEADER) == -1 ||
319       svz_sock_write (sock, (char *) nut_put_reply (&reply),
320                       SIZEOF_NUT_REPLY) == -1 ||
321       svz_sock_write (sock, (char *) buffer, size) == -1 ||
322       svz_sock_write (sock, (char *) cfg->guid, NUT_GUID_SIZE) == -1)
323     {
324       svz_free (buffer);
325       return -1;
326     }
327 
328   svz_free (buffer);
329   return 0;
330 }
331 
332 /*
333  * This routine will be called when some gnutella server sent a
334  * ping reply.
335  */
336 int
nut_pong(svz_socket_t * sock,nut_header_t * hdr,uint8_t * packet)337 nut_pong (svz_socket_t *sock, nut_header_t *hdr, uint8_t *packet)
338 {
339   nut_config_t *cfg = sock->cfg;
340   nut_packet_t *pkt;
341   nut_pong_t *reply;
342   nut_client_t *client = sock->data;
343 
344   /* put to host catcher hash */
345   reply = nut_get_pong (packet);
346   nut_host_catcher (sock, reply->ip, reply->port);
347   pkt = (nut_packet_t *) svz_hash_get (cfg->packet, (char *) hdr->id);
348 
349   /* is this a reply to my own gnutella packet?  */
350   if (pkt != NULL)
351     {
352 #if 0
353       printf ("port    : %u\n", ntohs (reply->port));
354       printf ("ip      : %s\n", svz_inet_ntoa (reply->ip));
355       printf ("files   : %u\n", reply->files);
356       printf ("size    : %u kb\n", reply->size);
357 #endif
358       /* update statistics */
359       cfg->nodes++;
360       client->nodes++;
361       if (reply->files && reply->size)
362         {
363           cfg->files += reply->files;
364           cfg->size += reply->size;
365           client->files += reply->files;
366           client->size += reply->size;
367         }
368     }
369 
370   return 0;
371 }
372 
373 /*
374  * This callback is called if a ping request was received.  We just
375  * reply with our own configuration.
376  */
377 int
nut_ping(svz_socket_t * sock,nut_header_t * hdr,UNUSED uint8_t * null)378 nut_ping (svz_socket_t *sock, nut_header_t *hdr,
379           UNUSED uint8_t *null)
380 {
381   nut_config_t *cfg = sock->cfg;
382   nut_pong_t reply;
383   uint8_t *header, *pong;
384   struct sockaddr_in *addr = NULL;
385   svz_portcfg_t *port = NULL;
386 
387   /* create new gnutella packets */
388   hdr->function = NUT_PING_ACK;
389   hdr->length = SIZEOF_NUT_PONG;
390   hdr->ttl = hdr->hop;
391   hdr->hop = 0;
392 
393   if ((port = svz_sock_portcfg (sock)) != NULL)
394     addr = svz_portcfg_addr (port);
395   reply.ip = nut_v4addr_from (cfg, addr, sock);
396   reply.port = (cfg->port ? cfg->port : addr ?
397                 addr->sin_port : sock->local_port);
398   reply.files = cfg->db_files;
399   reply.size = cfg->db_size / 1024;
400   header = nut_put_header (hdr);
401   pong = nut_put_pong (&reply);
402 
403   /* try sending this packet */
404   if (svz_sock_write (sock, (char *) header, SIZEOF_NUT_HEADER) == -1 ||
405       svz_sock_write (sock, (char *) pong, SIZEOF_NUT_PONG) == -1)
406     {
407       svz_sock_schedule_for_shutdown (sock);
408       return -1;
409     }
410 
411   return 0;
412 }
413