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