ftbfs
[oweals/gnunet.git] / src / dns / gnunet-zonewalk.c
1 /*
2      This file is part of GNUnet
3      Copyright (C) 2018 GNUnet e.V.
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      Affero General Public License for more details.
14     
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21 /**
22  * @file src/dns/gnunet-zoneimport.c
23  * @brief import a DNS zone for analysis, brute force
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include <gnunet_util_lib.h>
28 #include <gnunet_dnsstub_lib.h>
29 #include <gnunet_dnsparser_lib.h>
30
31 /**
32  * Request we should make.
33  */
34 struct Request
35 {
36   /**
37    * Requests are kept in a DLL.
38    */
39   struct Request *next;
40
41   /**
42    * Requests are kept in a DLL.
43    */
44   struct Request *prev;
45
46   /**
47    * Socket used to make the request, NULL if not active.
48    */
49   struct GNUNET_DNSSTUB_RequestSocket *rs;
50
51   /**
52    * Raw DNS query.
53    */
54   void *raw;
55
56   /**
57    * Number of bytes in @e raw.
58    */
59   size_t raw_len;
60
61   /**
62    * Hostname we are resolving.
63    */
64   char *hostname;
65
66   /**
67    * When did we last issue this request?
68    */
69   time_t time;
70
71   /**
72    * How often did we issue this query?
73    */
74   int issue_num;
75
76   /**
77    * random 16-bit DNS query identifier.
78    */
79   uint16_t id;
80 };
81
82
83 /**
84  * Context for DNS resolution.
85  */
86 static struct GNUNET_DNSSTUB_Context *ctx;
87
88 /**
89  * The number of queries that are outstanding
90  */
91 static unsigned int pending;
92
93 /**
94  * Number of lookups we performed overall.
95  */
96 static unsigned int lookups;
97
98 /**
99  * Number of lookups that failed.
100  */
101 static unsigned int failures;
102
103 /**
104  * Number of records we found.
105  */
106 static unsigned int records;
107
108 /**
109  * Head of DLL of all requests to perform.
110  */
111 static struct Request *req_head;
112
113 /**
114  * Tail of DLL of all requests to perform.
115  */
116 static struct Request *req_tail;
117
118 /**
119  * Main task.
120  */
121 static struct GNUNET_SCHEDULER_Task *t;
122
123 /**
124  * Maximum number of queries pending at the same time.
125  */
126 #define THRESH 20
127
128 /**
129  * TIME_THRESH is in usecs.  How quickly do we submit fresh queries.
130  * Used as an additional throttle.
131  */
132 #define TIME_THRESH 10
133
134 /**
135  * How often do we retry a query before giving up for good?
136  */
137 #define MAX_RETRIES 5
138
139
140 /**
141  * We received @a rec for @a req. Remember the answer.
142  *
143  * @param req request
144  * @param rec response
145  */
146 static void
147 process_record (struct Request *req,
148                 struct GNUNET_DNSPARSER_Record *rec)
149 {
150   char buf[INET6_ADDRSTRLEN];
151
152   records++;
153   switch (rec->type)
154   {
155   case GNUNET_DNSPARSER_TYPE_A:
156     fprintf (stdout,
157              "%s A %s\n",
158              req->hostname,
159              inet_ntop (AF_INET,
160                         rec->data.raw.data,
161                         buf,
162                         sizeof (buf)));
163     break;
164   case GNUNET_DNSPARSER_TYPE_AAAA:
165     fprintf (stdout,
166              "%s AAAA %s\n",
167              req->hostname,
168              inet_ntop (AF_INET6,
169                         rec->data.raw.data,
170                         buf,
171                         sizeof (buf)));
172     break;
173   case GNUNET_DNSPARSER_TYPE_NS:
174     fprintf (stdout,
175              "%s NS %s\n",
176              req->hostname,
177              rec->data.hostname);
178     break;
179   case GNUNET_DNSPARSER_TYPE_CNAME:
180     fprintf (stdout,
181              "%s CNAME %s\n",
182              req->hostname,
183              rec->data.hostname);
184     break;
185   case GNUNET_DNSPARSER_TYPE_MX:
186     fprintf (stdout,
187              "%s MX %u %s\n",
188              req->hostname,
189              (unsigned int) rec->data.mx->preference,
190              rec->data.mx->mxhost);
191     break;
192   case GNUNET_DNSPARSER_TYPE_SOA:
193     fprintf (stdout,
194              "%s SOA %s %s %u %u %u %u %u\n",
195              req->hostname,
196              rec->data.soa->mname,
197              rec->data.soa->rname,
198              (unsigned int) rec->data.soa->serial,
199              (unsigned int) rec->data.soa->refresh,
200              (unsigned int) rec->data.soa->retry,
201              (unsigned int) rec->data.soa->expire,
202              (unsigned int) rec->data.soa->minimum_ttl);
203     break;
204   case GNUNET_DNSPARSER_TYPE_SRV:
205     fprintf (stdout,
206              "%s SRV %s %u %u %u\n",
207              req->hostname,
208              rec->data.srv->target,
209              rec->data.srv->priority,
210              rec->data.srv->weight,
211              rec->data.srv->port);
212     break;
213   case GNUNET_DNSPARSER_TYPE_PTR:
214     fprintf (stdout,
215              "%s PTR %s\n",
216              req->hostname,
217              rec->data.hostname);
218     break;
219   case GNUNET_DNSPARSER_TYPE_TXT:
220     fprintf (stdout,
221              "%s TXT %.*s\n",
222              req->hostname,
223              (int) rec->data.raw.data_len,
224              (char *) rec->data.raw.data);
225     break;
226   case GNUNET_DNSPARSER_TYPE_DNAME:
227     fprintf (stdout,
228              "%s DNAME %s\n",
229              req->hostname,
230              rec->data.hostname);
231     break;
232
233     /* obscure records */
234   case GNUNET_DNSPARSER_TYPE_AFSDB:
235   case GNUNET_DNSPARSER_TYPE_NAPTR:
236   case GNUNET_DNSPARSER_TYPE_APL:
237   case GNUNET_DNSPARSER_TYPE_DHCID:
238   case GNUNET_DNSPARSER_TYPE_HIP:
239   case GNUNET_DNSPARSER_TYPE_LOC:
240   case GNUNET_DNSPARSER_TYPE_RP:
241   case GNUNET_DNSPARSER_TYPE_TKEY:
242   case GNUNET_DNSPARSER_TYPE_TSIG:
243   case GNUNET_DNSPARSER_TYPE_URI:
244   case GNUNET_DNSPARSER_TYPE_TA:
245
246     /* DNSSEC */
247   case GNUNET_DNSPARSER_TYPE_DS:
248   case GNUNET_DNSPARSER_TYPE_RRSIG:
249   case GNUNET_DNSPARSER_TYPE_NSEC:
250   case GNUNET_DNSPARSER_TYPE_DNSKEY:
251   case GNUNET_DNSPARSER_TYPE_NSEC3:
252   case GNUNET_DNSPARSER_TYPE_NSEC3PARAM:
253   case GNUNET_DNSPARSER_TYPE_CDS:
254   case GNUNET_DNSPARSER_TYPE_CDNSKEY:
255
256     /* DNSSEC payload */
257   case GNUNET_DNSPARSER_TYPE_CERT:
258   case GNUNET_DNSPARSER_TYPE_SSHFP:
259   case GNUNET_DNSPARSER_TYPE_IPSECKEY:
260   case GNUNET_DNSPARSER_TYPE_TLSA:
261   case GNUNET_DNSPARSER_TYPE_OPENPGPKEY:
262
263     /* obsolete records */
264   case GNUNET_DNSPARSER_TYPE_SIG:
265   case GNUNET_DNSPARSER_TYPE_KEY:
266   case GNUNET_DNSPARSER_TYPE_KX:
267     {
268       char *base32;
269
270       base32 = GNUNET_STRINGS_data_to_string_alloc (rec->data.raw.data,
271                                                     rec->data.raw.data_len);
272       fprintf (stdout,
273                "%s (%u) %s\n",
274                req->hostname,
275                rec->type,
276                base32);
277       GNUNET_free (base32);
278     }
279     break;
280   default:
281     fprintf (stderr,
282              "Unsupported type %u\n",
283              (unsigned int) rec->type);
284     break;
285   }
286 }
287
288
289 /**
290  * Function called with the result of a DNS resolution.
291  *
292  * @param cls closure with the `struct Request`
293  * @param dns dns response, never NULL
294  * @param dns_len number of bytes in @a dns
295  */
296 static void
297 process_result (void *cls,
298                 const struct GNUNET_TUN_DnsHeader *dns,
299                 size_t dns_len)
300 {
301   struct Request *req = cls;
302   struct GNUNET_DNSPARSER_Packet *p;
303
304   if (NULL == dns)
305   {
306     /* stub gave up */
307     pending--;
308     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
309                 "Stub gave up on DNS reply for `%s'\n",
310                 req->hostname);
311     GNUNET_CONTAINER_DLL_remove (req_head,
312                                  req_tail,
313                                  req);
314     if (req->issue_num > MAX_RETRIES)
315     {
316       failures++;
317       GNUNET_free (req->hostname);
318       GNUNET_free (req->raw);
319       GNUNET_free (req);
320       return;
321     }
322     GNUNET_CONTAINER_DLL_insert_tail (req_head,
323                                       req_tail,
324                                       req);
325     req->rs = NULL;
326     return;
327   }
328   if (req->id != dns->id)
329     return;
330   pending--;
331   GNUNET_DNSSTUB_resolve_cancel (req->rs);
332   req->rs = NULL;
333   GNUNET_CONTAINER_DLL_remove (req_head,
334                                req_tail,
335                                req);
336   p = GNUNET_DNSPARSER_parse ((const char *) dns,
337                               dns_len);
338   if (NULL == p)
339   {
340     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
341                 "Failed to parse DNS reply for `%s'\n",
342                 req->hostname);
343     if (req->issue_num > MAX_RETRIES)
344     {
345       failures++;
346       GNUNET_free (req->hostname);
347       GNUNET_free (req->raw);
348       GNUNET_free (req);
349       return;
350     }
351     GNUNET_CONTAINER_DLL_insert_tail (req_head,
352                                       req_tail,
353                                       req);
354     return;
355   }
356   for (unsigned int i=0;i<p->num_answers;i++)
357   {
358     struct GNUNET_DNSPARSER_Record *rs = &p->answers[i];
359
360     process_record (req,
361                     rs);
362   }
363   for (unsigned int i=0;i<p->num_authority_records;i++)
364   {
365     struct GNUNET_DNSPARSER_Record *rs = &p->authority_records[i];
366
367     process_record (req,
368                     rs);
369   }
370   for (unsigned int i=0;i<p->num_additional_records;i++)
371   {
372     struct GNUNET_DNSPARSER_Record *rs = &p->additional_records[i];
373
374     process_record (req,
375                     rs);
376   }
377   GNUNET_DNSPARSER_free_packet (p);
378   GNUNET_free (req->hostname);
379   GNUNET_free (req->raw);
380   GNUNET_free (req);
381 }
382
383
384 /**
385  * Submit a request to DNS unless we need to slow down because
386  * we are at the rate limit.
387  *
388  * @param req request to submit
389  * @return #GNUNET_OK if request was submitted
390  *         #GNUNET_NO if request was already submitted
391  *         #GNUNET_SYSERR if we are at the rate limit
392  */
393 static int
394 submit_req (struct Request *req)
395 {
396   static struct timeval last_request;
397   struct timeval now;
398
399   if (NULL != req->rs)
400     return GNUNET_NO; /* already submitted */
401   gettimeofday (&now,
402                 NULL);
403   if ( ( ( (now.tv_sec - last_request.tv_sec) == 0) &&
404          ( (now.tv_usec - last_request.tv_usec) < TIME_THRESH) ) ||
405        (pending >= THRESH) )
406     return GNUNET_SYSERR;
407   GNUNET_assert (NULL == req->rs);
408   req->rs = GNUNET_DNSSTUB_resolve (ctx,
409                                     req->raw,
410                                     req->raw_len,
411                                     &process_result,
412                                     req);
413   GNUNET_assert (NULL != req->rs);
414   req->issue_num++;
415   last_request = now;
416   lookups++;
417   pending++;
418   req->time = time (NULL);
419   return GNUNET_OK;
420 }
421
422
423 /**
424  * Process as many requests as possible from the queue.
425  *
426  * @param cls NULL
427  */
428 static void
429 process_queue(void *cls)
430 {
431   (void) cls;
432   t = NULL;
433   for (struct Request *req = req_head;
434        NULL != req;
435        req = req->next)
436   {
437     if (GNUNET_SYSERR == submit_req (req))
438       break;
439   }
440   if (NULL != req_head)
441     t = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MILLISECONDS,
442                                   &process_queue,
443                                   NULL);
444   else
445     GNUNET_SCHEDULER_shutdown ();
446 }
447
448
449 /**
450  * Clean up and terminate the process.
451  *
452  * @param cls NULL
453  */
454 static void
455 do_shutdown (void *cls)
456 {
457   (void) cls;
458   if (NULL != t)
459   {
460     GNUNET_SCHEDULER_cancel (t);
461     t = NULL;
462   }
463   GNUNET_DNSSTUB_stop (ctx);
464   ctx = NULL;
465 }
466
467
468 /**
469  * Process requests from the queue, then if the queue is
470  * not empty, try again.
471  *
472  * @param cls NULL
473  */
474 static void
475 run (void *cls)
476 {
477   (void) cls;
478
479   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
480                                  NULL);
481   t = GNUNET_SCHEDULER_add_now (&process_queue,
482                                 NULL);
483 }
484
485
486 /**
487  * Add @a hostname to the list of requests to be made.
488  *
489  * @param hostname name to resolve
490  */
491 static void
492 queue (const char *hostname)
493 {
494   struct GNUNET_DNSPARSER_Packet p;
495   struct GNUNET_DNSPARSER_Query q;
496   struct Request *req;
497   char *raw;
498   size_t raw_size;
499   int ret;
500
501   if (GNUNET_OK !=
502       GNUNET_DNSPARSER_check_name (hostname))
503   {
504     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
505                 "Refusing invalid hostname `%s'\n",
506                 hostname);
507     return;
508   }
509   q.name = (char *) hostname;
510   q.type = GNUNET_DNSPARSER_TYPE_NS;
511   q.dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET;
512
513   memset (&p,
514           0,
515           sizeof (p));
516   p.num_queries = 1;
517   p.queries = &q;
518   p.id = (uint16_t) GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
519                                               UINT16_MAX);
520   ret = GNUNET_DNSPARSER_pack (&p,
521                                UINT16_MAX,
522                                &raw,
523                                &raw_size);
524   if (GNUNET_OK != ret)
525   {
526     if (GNUNET_NO == ret)
527       GNUNET_free (raw); 
528     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
529                 "Failed to pack query for hostname `%s'\n",
530                 hostname);
531     return;
532   }
533
534   req = GNUNET_new (struct Request);
535   req->hostname = strdup (hostname);
536   req->raw = raw;
537   req->raw_len = raw_size;
538   req->id = p.id;
539   GNUNET_CONTAINER_DLL_insert_tail (req_head,
540                                     req_tail,
541                                     req);
542 }
543
544
545 /**
546  * Call with IP address of resolver to query.
547  *
548  * @param argc should be 2
549  * @param argv[1] should contain IP address
550  * @return 0 on success
551  */
552 int
553 main (int argc,
554       char **argv)
555 {
556   char hn[256];
557
558   if (2 != argc)
559   {
560     fprintf (stderr,
561              "Missing required configuration argument\n");
562     return -1;
563   }
564   ctx = GNUNET_DNSSTUB_start (256);
565   if (NULL == ctx)
566   {
567     fprintf (stderr,
568              "Failed to initialize GNUnet DNS STUB\n");
569     return 1;
570   }
571   if (GNUNET_OK !=
572       GNUNET_DNSSTUB_add_dns_ip (ctx,
573                                  argv[1]))
574   {
575     fprintf (stderr,
576              "Failed to use `%s' for DNS resolver\n",
577              argv[1]);
578     return 1;
579   }
580
581   while (NULL !=
582          fgets (hn,
583                 sizeof (hn),
584                 stdin))
585   {
586     if (strlen(hn) > 0)
587       hn[strlen(hn)-1] = '\0'; /* eat newline */
588     queue (hn);
589   }
590   GNUNET_SCHEDULER_run (&run,
591                         NULL);
592   fprintf (stderr,
593            "Did %u lookups, found %u records, %u lookups failed, %u pending on shutdown\n",
594            lookups,
595            records,
596            failures,
597            pending);
598   return 0;
599 }