fix typo
[oweals/gnunet.git] / src / namestore / gnunet-zoneimport.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
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      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      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18      Boston, MA 02110-1301, USA.
19 */
20 /**
21  * @file src/namestore/gnunet-zoneimport.c
22  * @brief import a DNS zone for publication in GNS, incremental
23  * @author Christian Grothoff
24  */
25 #include "platform.h"
26 #include <gnunet_util_lib.h>
27 #include <gnunet_dnsstub_lib.h>
28 #include <gnunet_dnsparser_lib.h>
29 #include <gnunet_gnsrecord_lib.h>
30 #include <gnunet_namestore_service.h>
31 #include <gnunet_identity_service.h>
32
33
34 /**
35  * Maximum number of queries pending at the same time.
36  */
37 #define THRESH 100
38
39 /**
40  * TIME_THRESH is in usecs.  How quickly do we submit fresh queries.
41  * Used as an additional throttle.
42  */
43 #define TIME_THRESH 10
44
45 /**
46  * How often do we retry a query before giving up for good?
47  */
48 #define MAX_RETRIES 5
49
50
51 /**
52  * Some zones may include authoritative records for other
53  * zones, such as foo.com.uk or bar.com.fr.  As for GNS
54  * each dot represents a zone cut, we then need to create a
55  * zone on-the-fly to capture those records properly.
56  */
57 struct Zone
58 {
59
60   /**
61    * Kept in a DLL.
62    */
63   struct Zone *next;
64
65   /**
66    * Kept in a DLL.
67    */
68   struct Zone *prev;
69
70   /**
71    * Domain of the zone (i.e. "fr" or "com.fr")
72    */
73   char *domain;
74
75   /**
76    * Private key of the zone.
77    */
78   struct GNUNET_CRYPTO_EcdsaPrivateKey key;
79
80 };
81
82
83 /**
84  * Record for the request to be stored by GNS.
85  */
86 struct Record
87 {
88   /**
89    * Kept in a DLL.
90    */
91   struct Record *next;
92
93   /**
94    * Kept in a DLL.
95    */
96   struct Record *prev;
97
98   /**
99    * GNS record.
100    */
101   struct GNUNET_GNSRECORD_Data grd;
102
103 };
104
105
106 /**
107  * Request we should make.
108  */
109 struct Request
110 {
111   /**
112    * Requests are kept in a heap while waiting to be resolved.
113    */
114   struct GNUNET_CONTAINER_HeapNode *hn;
115
116   /**
117    * Active requests are kept in a DLL.
118    */
119   struct Request *next;
120
121   /**
122    * Active requests are kept in a DLL.
123    */
124   struct Request *prev;
125
126   /**
127    * Head of records that should be published in GNS for
128    * this hostname.
129    */
130   struct Record *rec_head;
131
132   /**
133    * Tail of records that should be published in GNS for
134    * this hostname.
135    */
136   struct Record *rec_tail;
137
138   /**
139    * Socket used to make the request, NULL if not active.
140    */
141   struct GNUNET_DNSSTUB_RequestSocket *rs;
142
143   /**
144    * Raw DNS query.
145    */
146   void *raw;
147
148   /**
149    * Hostname we are resolving.
150    */
151   char *hostname;
152
153   /**
154    * Label (without TLD) which we are resolving.
155    */
156   char *label;
157
158   /**
159    * Namestore operation pending for this record.
160    */
161   struct GNUNET_NAMESTORE_QueueEntry *qe;
162
163   /**
164    * Zone responsible for this request.
165    */
166   const struct Zone *zone;
167
168   /**
169    * Answer we got back and are currently parsing, or NULL
170    * if not active.
171    */
172   struct GNUNET_DNSPARSER_Packet *p;
173
174   /**
175    * At what time does the (earliest) of the returned records
176    * for this name expire? At this point, we need to re-fetch
177    * the record.
178    */
179   struct GNUNET_TIME_Absolute expires;
180
181   /**
182    * Number of bytes in @e raw.
183    */
184   size_t raw_len;
185
186   /**
187    * When did we last issue this request?
188    */
189   time_t time;
190
191   /**
192    * How often did we issue this query? (And failed, reset
193    * to zero once we were successful.)
194    */
195   int issue_num;
196
197   /**
198    * random 16-bit DNS query identifier.
199    */
200   uint16_t id;
201 };
202
203
204 /**
205  * Handle to the identity service.
206  */
207 static struct GNUNET_IDENTITY_Handle *id;
208
209 /**
210  * Namestore handle.
211  */
212 static struct GNUNET_NAMESTORE_Handle *ns;
213
214 /**
215  * Context for DNS resolution.
216  */
217 static struct GNUNET_DNSSTUB_Context *ctx;
218
219 /**
220  * The number of queries that are outstanding
221  */
222 static unsigned int pending;
223
224 /**
225  * Number of lookups we performed overall.
226  */
227 static unsigned int lookups;
228
229 /**
230  * How many hostnames did we reject (malformed).
231  */
232 static unsigned int rejects;
233
234 /**
235  * Number of lookups that failed.
236  */
237 static unsigned int failures;
238
239 /**
240  * Number of records we found.
241  */
242 static unsigned int records;
243
244 /**
245  * #GNUNET_YES if we have more work to be read from `stdin`.
246  */
247 static int stdin_waiting;
248
249 /**
250  * Heap of all requests to perform, sorted by
251  * the time we should next do the request (i.e. by expires).
252  */
253 static struct GNUNET_CONTAINER_Heap *req_heap;
254
255 /**
256  * Active requests are kept in a DLL.
257  */
258 static struct Request *req_head;
259
260 /**
261  * Active requests are kept in a DLL.
262  */
263 static struct Request *req_tail;
264
265 /**
266  * Main task.
267  */
268 static struct GNUNET_SCHEDULER_Task *t;
269
270 /**
271  * Head of list of zones we are managing.
272  */
273 static struct Zone *zone_head;
274
275 /**
276  * Tail of list of zones we are managing.
277  */
278 static struct Zone *zone_tail;
279
280
281 /**
282  * Callback for #for_all_records
283  *
284  * @param cls closure
285  * @param rec a DNS record
286  */
287 typedef void
288 (*RecordProcessor) (void *cls,
289                     const struct GNUNET_DNSPARSER_Record *rec);
290
291
292 /**
293  * Call @a rp for each record in @a p, regardless of
294  * what response section it is in.
295  *
296  * @param p packet from DNS
297  * @param rp function to call
298  * @param rp_cls closure for @a rp
299  */
300 static void
301 for_all_records (const struct GNUNET_DNSPARSER_Packet *p,
302                  RecordProcessor rp,
303                  void *rp_cls)
304 {
305   for (unsigned int i=0;i<p->num_answers;i++)
306   {
307     struct GNUNET_DNSPARSER_Record *rs = &p->answers[i];
308
309     rp (rp_cls,
310         rs);
311   }
312   for (unsigned int i=0;i<p->num_authority_records;i++)
313   {
314     struct GNUNET_DNSPARSER_Record *rs = &p->authority_records[i];
315
316     rp (rp_cls,
317         rs);
318   }
319   for (unsigned int i=0;i<p->num_additional_records;i++)
320   {
321     struct GNUNET_DNSPARSER_Record *rs = &p->additional_records[i];
322
323     rp (rp_cls,
324         rs);
325   }
326 }
327
328
329 /**
330  * Free @a req and data structures reachable from it.
331  *
332  * @param req request to free
333  */
334 static void
335 free_request (struct Request *req)
336 {
337   struct Record *rec;
338
339   while (NULL != (rec = req->rec_head))
340   {
341     GNUNET_CONTAINER_DLL_remove (req->rec_head,
342                                  req->rec_tail,
343                                  rec);
344     GNUNET_free (rec);
345   }
346   GNUNET_free (req->hostname);
347   GNUNET_free (req->label);
348   GNUNET_free (req->raw);
349   GNUNET_free (req);
350 }
351
352
353 /**
354  * Process as many requests as possible from the queue.
355  *
356  * @param cls NULL
357  */
358 static void
359 process_queue (void *cls);
360
361
362 /**
363  * Insert @a req into DLL sorted by next fetch time.
364  *
365  * @param req request to insert into #req_heap
366  */
367 static void
368 insert_sorted (struct Request *req)
369 {
370   req->hn = GNUNET_CONTAINER_heap_insert (req_heap,
371                                           req,
372                                           req->expires.abs_value_us);
373   if (req == GNUNET_CONTAINER_heap_peek (req_heap))
374   {
375     if (NULL != t)
376       GNUNET_SCHEDULER_cancel (t);
377     t = GNUNET_SCHEDULER_add_at (req->expires,
378                                  &process_queue,
379                                  NULL);
380   }
381 }
382
383
384 /**
385  * Add record to the GNS record set for @a req.
386  *
387  * @param req the request to expand GNS record set for
388  * @param type type to use
389  * @param expiration_time when should @a rec expire
390  * @param data raw data to store
391  * @param data_len number of bytes in @a data
392  */
393 static void
394 add_record (struct Request *req,
395             uint32_t type,
396             struct GNUNET_TIME_Absolute expiration_time,
397             const void *data,
398             size_t data_len)
399 {
400   struct Record *rec;
401
402   rec = GNUNET_malloc (sizeof (struct Record) + data_len);
403   rec->grd.data = &rec[1];
404   rec->grd.expiration_time = expiration_time.abs_value_us;
405   rec->grd.data_size = data_len;
406   rec->grd.record_type = type;
407   rec->grd.flags = GNUNET_GNSRECORD_RF_NONE;
408   GNUNET_memcpy (&rec[1],
409                  data,
410                  data_len);
411   GNUNET_CONTAINER_DLL_insert (req->rec_head,
412                                req->rec_tail,
413                                rec);
414 }
415
416
417 /**
418  * Closure for #check_for_glue.
419  */
420 struct GlueClosure
421 {
422   /**
423    * Overall request we are processing.
424    */
425   struct Request *req;
426
427   /**
428    * NS name we are looking for glue for.
429    */
430   const char *ns;
431
432   /**
433    * Set to #GNUNET_YES if glue was found.
434    */
435   int found;
436 };
437
438
439 /**
440  * Try to find glue records for a given NS record.
441  *
442  * @param cls a `struct GlueClosure *`
443  * @param rec record that may contain glue information
444  */
445 static void
446 check_for_glue (void *cls,
447                 const struct GNUNET_DNSPARSER_Record *rec)
448 {
449   struct GlueClosure *gc = cls;
450   char dst[65536];
451   size_t dst_len;
452   size_t off;
453   char ip[INET6_ADDRSTRLEN+1];
454   socklen_t ip_size = (socklen_t) sizeof (ip);
455
456   if (0 != strcasecmp (rec->name,
457                        gc->ns))
458     return;
459   dst_len = sizeof (dst);
460   off = 0;
461   switch (rec->type)
462   {
463   case GNUNET_DNSPARSER_TYPE_A:
464     if (sizeof (struct in_addr) != rec->data.raw.data_len)
465     {
466       GNUNET_break (0);
467       return;
468     }
469     if (NULL ==
470         inet_ntop (AF_INET,
471                    rec->data.raw.data,
472                    ip,
473                    ip_size))
474     {
475       GNUNET_break (0);
476       return;
477     }
478     if ( (GNUNET_OK ==
479           GNUNET_DNSPARSER_builder_add_name (dst,
480                                              dst_len,
481                                              &off,
482                                              gc->req->hostname)) &&
483          (GNUNET_OK ==
484           GNUNET_DNSPARSER_builder_add_name (dst,
485                                              dst_len,
486                                              &off,
487                                              ip)) )
488     {
489       add_record (gc->req,
490                   GNUNET_GNSRECORD_TYPE_GNS2DNS,
491                   rec->expiration_time,
492                   dst,
493                   off);
494       gc->found = GNUNET_YES;
495     }
496     break;
497   case GNUNET_DNSPARSER_TYPE_AAAA:
498     if (sizeof (struct in6_addr) != rec->data.raw.data_len)
499     {
500       GNUNET_break (0);
501       return;
502     }
503     if (NULL ==
504         inet_ntop (AF_INET6,
505                    rec->data.raw.data,
506                    ip,
507                    ip_size))
508     {
509       GNUNET_break (0);
510       return;
511     }
512     if ( (GNUNET_OK ==
513           GNUNET_DNSPARSER_builder_add_name (dst,
514                                              dst_len,
515                                              &off,
516                                              gc->req->hostname)) &&
517          (GNUNET_OK ==
518           GNUNET_DNSPARSER_builder_add_name (dst,
519                                              dst_len,
520                                              &off,
521                                              ip)) )
522     {
523       add_record (gc->req,
524                   GNUNET_GNSRECORD_TYPE_GNS2DNS,
525                   rec->expiration_time,
526                   dst,
527                   off);
528       gc->found = GNUNET_YES;
529     }
530     break;
531   case GNUNET_DNSPARSER_TYPE_CNAME:
532     if ( (GNUNET_OK ==
533           GNUNET_DNSPARSER_builder_add_name (dst,
534                                              dst_len,
535                                              &off,
536                                              gc->req->hostname)) &&
537          (GNUNET_OK ==
538           GNUNET_DNSPARSER_builder_add_name (dst,
539                                              dst_len,
540                                              &off,
541                                              rec->data.hostname)) )
542     {
543       add_record (gc->req,
544                   GNUNET_GNSRECORD_TYPE_GNS2DNS,
545                   rec->expiration_time,
546                   dst,
547                   off);
548       gc->found = GNUNET_YES;
549     }
550     break;
551   default:
552     /* useless, do nothing */
553     break;
554   }
555 }
556
557
558 /**
559  * We received @a rec for @a req. Remember the answer.
560  *
561  * @param cls a `struct Request`
562  * @param rec response
563  */
564 static void
565 process_record (void *cls,
566                 const struct GNUNET_DNSPARSER_Record *rec)
567 {
568   struct Request *req = cls;
569   char dst[65536];
570   size_t dst_len;
571   size_t off;
572
573   dst_len = sizeof (dst);
574   off = 0;
575   records++;
576   if (0 != strcasecmp (rec->name,
577                        req->hostname))
578   {
579     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
580                 "DNS returned record for `%s' of type %u while resolving `%s'\n",
581                 rec->name,
582                 (unsigned int) rec->type,
583                 req->hostname);
584     return; /* does not match hostname, might be glue, but
585                not useful for this pass! */
586   }
587   if (0 ==
588       GNUNET_TIME_absolute_get_remaining (rec->expiration_time).rel_value_us)
589   {
590     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
591                 "DNS returned expired record for `%s'\n",
592                 req->hostname);
593     return; /* record expired */
594   }
595   switch (rec->type)
596   {
597   case GNUNET_DNSPARSER_TYPE_NS:
598     {
599       struct GlueClosure gc;
600
601       /* check for glue */
602       gc.req = req;
603       gc.ns = rec->data.hostname;
604       gc.found = GNUNET_NO;
605       for_all_records (req->p,
606                        &check_for_glue,
607                        &gc);
608       if ( (GNUNET_NO == gc.found) &&
609            (GNUNET_OK ==
610             GNUNET_DNSPARSER_builder_add_name (dst,
611                                                dst_len,
612                                                &off,
613                                                req->hostname)) &&
614            (GNUNET_OK ==
615             GNUNET_DNSPARSER_builder_add_name (dst,
616                                                dst_len,
617                                                &off,
618                                                rec->data.hostname)) )
619       {
620         /* FIXME: actually check if this is out-of-bailiwick,
621            and if not request explicit resolution... */
622         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
623                     "Converted OOB (`%s') NS record for `%s'\n",
624                     rec->data.hostname,
625                     rec->name);
626         add_record (req,
627                     GNUNET_GNSRECORD_TYPE_GNS2DNS,
628                     rec->expiration_time,
629                     dst,
630                     off);
631       }
632       else
633       {
634         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
635                     "Converted NS record for `%s' using glue\n",
636                     rec->name);
637       }
638       break;
639     }
640   case GNUNET_DNSPARSER_TYPE_CNAME:
641     if (GNUNET_OK ==
642         GNUNET_DNSPARSER_builder_add_name (dst,
643                                            dst_len,
644                                            &off,
645                                            rec->data.hostname))
646     {
647       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
648                   "Converting CNAME (`%s') record for `%s'\n",
649                   rec->data.hostname,
650                   rec->name);
651       add_record (req,
652                   rec->type,
653                   rec->expiration_time,
654                   dst,
655                   off);
656     }
657     break;
658   case GNUNET_DNSPARSER_TYPE_DNAME:
659     /* No support for DNAME in GNS yet! FIXME: support later! */
660     fprintf (stdout,
661              "FIXME: not supported: %s DNAME %s\n",
662              rec->name,
663              rec->data.hostname);
664     break;
665   case GNUNET_DNSPARSER_TYPE_MX:
666     if (GNUNET_OK ==
667         GNUNET_DNSPARSER_builder_add_mx (dst,
668                                          dst_len,
669                                          &off,
670                                          rec->data.mx))
671     {
672       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
673                   "Converting MX (`%s') record for `%s'\n",
674                   rec->data.mx->mxhost,
675                   rec->name);
676       add_record (req,
677                   rec->type,
678                   rec->expiration_time,
679                   dst,
680                   off);
681     }
682     break;
683   case GNUNET_DNSPARSER_TYPE_SOA:
684     if (GNUNET_OK ==
685         GNUNET_DNSPARSER_builder_add_soa (dst,
686                                           dst_len,
687                                           &off,
688                                           rec->data.soa))
689     {
690       /* NOTE: GNS does not really use SOAs */
691       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
692                   "Converting SOA record for `%s'\n",
693                   rec->name);
694       add_record (req,
695                   rec->type,
696                   rec->expiration_time,
697                   dst,
698                   off);
699     }
700     break;
701   case GNUNET_DNSPARSER_TYPE_SRV:
702     if (GNUNET_OK ==
703         GNUNET_DNSPARSER_builder_add_srv (dst,
704                                           dst_len,
705                                           &off,
706                                           rec->data.srv))
707     {
708       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
709                   "Converting SRV record for `%s'\n",
710                   rec->name);
711       add_record (req,
712                   rec->type,
713                   rec->expiration_time,
714                   dst,
715                   off);
716     }
717     break;
718   case GNUNET_DNSPARSER_TYPE_PTR:
719     if (GNUNET_OK ==
720         GNUNET_DNSPARSER_builder_add_name (dst,
721                                            dst_len,
722                                            &off,
723                                            rec->data.hostname))
724     {
725       /* !?: what does a PTR record do in a regular TLD??? */
726       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
727                   "Converting PTR record for `%s' (weird)\n",
728                   rec->name);
729       add_record (req,
730                   rec->type,
731                   rec->expiration_time,
732                   dst,
733                   off);
734     }
735     break;
736   case GNUNET_DNSPARSER_TYPE_CERT:
737     if (GNUNET_OK ==
738         GNUNET_DNSPARSER_builder_add_cert (dst,
739                                            dst_len,
740                                            &off,
741                                            rec->data.cert))
742     {
743       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
744                   "Converting CERT record for `%s'\n",
745                   rec->name);
746       add_record (req,
747                   rec->type,
748                   rec->expiration_time,
749                   dst,
750                   off);
751     }
752     break;
753     /* Rest is 'raw' encoded and just needs to be copied IF
754        the hostname matches the requested name; otherwise we
755        simply cannot use it. */
756   case GNUNET_DNSPARSER_TYPE_A:
757   case GNUNET_DNSPARSER_TYPE_AAAA:
758   case GNUNET_DNSPARSER_TYPE_TXT:
759   default:
760     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
761                 "Converting record of type %u for `%s'\n",
762                 (unsigned int) rec->type,
763                 rec->name);
764     add_record (req,
765                 rec->type,
766                 rec->expiration_time,
767                 rec->data.raw.data,
768                 rec->data.raw.data_len);
769     break;
770   }
771 }
772
773
774 /**
775  * Continuation called to notify client about result of the
776  * operation.
777  *
778  * @param cls closure with our `struct Request`
779  * @param success #GNUNET_SYSERR on failure (including timeout/queue drop/failure to validate)
780  *                #GNUNET_NO if content was already there or not found
781  *                #GNUNET_YES (or other positive value) on success
782  * @param emsg NULL on success, otherwise an error message
783  */
784 static void
785 store_completed_cb (void *cls,
786                     int32_t success,
787                     const char *emsg)
788 {
789   static unsigned int pdot;
790   struct Request *req = cls;
791   struct Record *rec;
792
793   req->qe = NULL;
794   pending--;
795   if (GNUNET_SYSERR == success)
796   {
797     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
798                 "Failed to store zone data for `%s': %s\n",
799                 req->hostname,
800                 emsg);
801   }
802   else
803   {
804     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
805                 "Stored records under `%s'\n",
806                 req->label);
807     pdot++;
808     if (0 == pdot % 1000)
809       fprintf (stderr, ".");
810   }
811   /* Free records */
812   while (NULL != (rec = req->rec_head))
813   {
814     GNUNET_CONTAINER_DLL_remove (req->rec_head,
815                                  req->rec_tail,
816                                  rec);
817     GNUNET_free (rec);
818   }
819 }
820
821
822 /**
823  * Function called with the result of a DNS resolution.
824  *
825  * @param cls closure with the `struct Request`
826  * @param dns dns response, never NULL
827  * @param dns_len number of bytes in @a dns
828  */
829 static void
830 process_result (void *cls,
831                 const struct GNUNET_TUN_DnsHeader *dns,
832                 size_t dns_len)
833 {
834   struct Request *req = cls;
835   struct Record *rec;
836   struct GNUNET_DNSPARSER_Packet *p;
837   unsigned int rd_count;
838
839   GNUNET_assert (NULL == req->hn);
840   if (NULL == dns)
841   {
842     /* stub gave up */
843     GNUNET_CONTAINER_DLL_remove (req_head,
844                                  req_tail,
845                                  req);
846     pending--;
847     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
848                 "Stub gave up on DNS reply for `%s'\n",
849                 req->hostname);
850     if (req->issue_num > MAX_RETRIES)
851     {
852       failures++;
853       free_request (req);
854       return;
855     }
856     req->rs = NULL;
857     insert_sorted (req);
858     return;
859   }
860   if (req->id != dns->id)
861     return;
862   GNUNET_CONTAINER_DLL_remove (req_head,
863                                req_tail,
864                                req);
865   GNUNET_DNSSTUB_resolve_cancel (req->rs);
866   req->rs = NULL;
867   p = GNUNET_DNSPARSER_parse ((const char *) dns,
868                               dns_len);
869   if (NULL == p)
870   {
871     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
872                 "Failed to parse DNS reply for `%s'\n",
873                 req->hostname);
874     if (req->issue_num > MAX_RETRIES)
875     {
876       failures++;
877       insert_sorted (req);
878       pending--;
879       return;
880     }
881     insert_sorted (req);
882     pending--;
883     return;
884   }
885   /* import new records */
886   req->issue_num = 0; /* success, reset counter! */
887   req->p = p;
888   for_all_records (p,
889                    &process_record,
890                    req);
891   req->p = NULL;
892   GNUNET_DNSPARSER_free_packet (p);
893   /* count records found, determine minimum expiration time */
894   req->expires = GNUNET_TIME_UNIT_FOREVER_ABS;
895   rd_count = 0;
896   for (rec = req->rec_head; NULL != rec; rec = rec->next)
897   {
898     struct GNUNET_TIME_Absolute at;
899
900     at.abs_value_us = rec->grd.expiration_time;
901     req->expires = GNUNET_TIME_absolute_min (req->expires,
902                                              at);
903     rd_count++;
904   }
905   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
906               "Obtained %u records for `%s'\n",
907               rd_count,
908               req->hostname);
909   /* Instead of going for SOA, simplified for now to look each
910      day in case we got an empty response */
911   if (0 == rd_count)
912     req->expires
913       = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS);
914   /* convert records to namestore import format */
915   {
916     struct GNUNET_GNSRECORD_Data rd[GNUNET_NZL(rd_count)];
917     unsigned int off = 0;
918
919     /* convert linked list into array */
920     for (rec = req->rec_head; NULL != rec; rec =rec->next)
921       rd[off++] = rec->grd;
922     req->qe = GNUNET_NAMESTORE_records_store (ns,
923                                               &req->zone->key,
924                                               req->label,
925                                               rd_count,
926                                               rd,
927                                               &store_completed_cb,
928                                               req);
929   }
930   insert_sorted (req);
931 }
932
933
934 /**
935  * Submit a request to DNS unless we need to slow down because
936  * we are at the rate limit.
937  *
938  * @param req request to submit
939  * @return #GNUNET_OK if request was submitted
940  *         #GNUNET_NO if request was already submitted
941  *         #GNUNET_SYSERR if we are at the rate limit
942  */
943 static int
944 submit_req (struct Request *req)
945 {
946   static struct GNUNET_TIME_Absolute last_request;
947   struct GNUNET_TIME_Absolute now;
948
949   if (NULL != req->qe)
950     return GNUNET_NO; /* namestore op still pending */
951   if (NULL != req->rs)
952   {
953     GNUNET_break (0);
954     return GNUNET_NO; /* already submitted */
955   }
956   now = GNUNET_TIME_absolute_get ();
957   if ( (now.abs_value_us - last_request.abs_value_us < TIME_THRESH) ||
958        (pending >= THRESH) )
959     return GNUNET_SYSERR;
960   GNUNET_CONTAINER_DLL_insert (req_head,
961                                req_tail,
962                                req);
963   GNUNET_assert (NULL == req->rs);
964   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
965               "Requesting resolution for `%s'\n",
966               req->hostname);
967   req->rs = GNUNET_DNSSTUB_resolve (ctx,
968                                     req->raw,
969                                     req->raw_len,
970                                     &process_result,
971                                     req);
972   GNUNET_assert (NULL != req->rs);
973   req->issue_num++;
974   last_request = now;
975   lookups++;
976   pending++;
977   req->time = time (NULL);
978   return GNUNET_OK;
979 }
980
981
982 /**
983  * Process as many requests as possible from the queue.
984  *
985  * @param cls NULL
986  */
987 static void
988 process_queue (void *cls)
989 {
990   struct Request *req;
991
992   (void) cls;
993   t = NULL;
994   while (1)
995   {
996     req = GNUNET_CONTAINER_heap_peek (req_heap);
997     if (NULL == req)
998       break;
999     if (GNUNET_TIME_absolute_get_remaining (req->expires).rel_value_us > 0)
1000       break;
1001     if (GNUNET_OK != submit_req (req))
1002       break;
1003     GNUNET_assert (req ==
1004                    GNUNET_CONTAINER_heap_remove_root (req_heap));
1005     req->hn = NULL;
1006   }
1007
1008   req = GNUNET_CONTAINER_heap_peek (req_heap);
1009   if (NULL == req)
1010     return;
1011   if (GNUNET_TIME_absolute_get_remaining (req->expires).rel_value_us > 0)
1012   {
1013     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1014                 "Waiting until %s for next record (`%s') to expire\n",
1015                 GNUNET_STRINGS_absolute_time_to_string (req->expires),
1016                 req->hostname);
1017     if (NULL != t)
1018       GNUNET_SCHEDULER_cancel (t);
1019     t = GNUNET_SCHEDULER_add_at (req->expires,
1020                                  &process_queue,
1021                                  NULL);
1022   }
1023   else
1024   {
1025     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1026                 "Throttling for 1ms\n");
1027     if (NULL != t)
1028       GNUNET_SCHEDULER_cancel (t);
1029     t = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MILLISECONDS,
1030                                       &process_queue,
1031                                       NULL);
1032   }
1033 }
1034
1035
1036 /**
1037  * Clean up and terminate the process.
1038  *
1039  * @param cls NULL
1040  */
1041 static void
1042 do_shutdown (void *cls)
1043 {
1044   struct Request *req;
1045   struct Zone *zone;
1046
1047   (void) cls;
1048   if (NULL != id)
1049   {
1050     GNUNET_IDENTITY_disconnect (id);
1051     id = NULL;
1052   }
1053   if (NULL != t)
1054   {
1055     GNUNET_SCHEDULER_cancel (t);
1056     t = NULL;
1057   }
1058   while (NULL != (req = req_head))
1059   {
1060     GNUNET_CONTAINER_DLL_remove (req_head,
1061                                  req_tail,
1062                                  req);
1063     if (NULL != req->qe)
1064       GNUNET_NAMESTORE_cancel (req->qe);
1065     free_request (req);
1066   }
1067   while (NULL != (req = GNUNET_CONTAINER_heap_remove_root (req_heap)))
1068   {
1069     req->hn = NULL;
1070     if (NULL != req->qe)
1071       GNUNET_NAMESTORE_cancel (req->qe);
1072     free_request (req);
1073   }
1074   if (NULL != ns)
1075   {
1076     GNUNET_NAMESTORE_disconnect (ns);
1077     ns = NULL;
1078   }
1079   if (NULL != ctx)
1080   {
1081     GNUNET_DNSSTUB_stop (ctx);
1082     ctx = NULL;
1083   }
1084   if (NULL != req_heap)
1085   {
1086     GNUNET_CONTAINER_heap_destroy (req_heap);
1087     req_heap = NULL;
1088   }
1089   while (NULL != (zone = zone_head))
1090   {
1091     GNUNET_CONTAINER_DLL_remove (zone_head,
1092                                  zone_tail,
1093                                  zone);
1094     GNUNET_free (zone->domain);
1095     GNUNET_free (zone);
1096   }
1097 }
1098
1099
1100 /**
1101  * Begin processing hostnames from stdin.
1102  *
1103  * @param cls NULL
1104  */
1105 static void
1106 process_stdin (void *cls);
1107
1108
1109 /**
1110  * If applicable, continue processing from stdin.
1111  */
1112 static void
1113 continue_stdin ()
1114 {
1115   if ( (pending < THRESH) &&
1116        (stdin_waiting) )
1117   {
1118     if (NULL != t)
1119       GNUNET_SCHEDULER_cancel (t);
1120     t = GNUNET_SCHEDULER_add_now (&process_stdin,
1121                                   NULL);
1122   }
1123 }
1124
1125
1126 /**
1127  * Function called if #GNUNET_NAMESTORE_records_lookup() failed.
1128  * Continues resolution based on assumption namestore has no data.
1129  *
1130  * @param cls a `struct Request`
1131  */
1132 static void
1133 ns_lookup_error_cb (void *cls)
1134 {
1135   struct Request *req = cls;
1136
1137   req->qe = NULL;
1138   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1139               "Failed to load data from namestore for `%s'\n",
1140               req->label);
1141   insert_sorted (req);
1142   pending--;
1143   continue_stdin ();
1144 }
1145
1146
1147 /**
1148  * Process a record that was stored in the namestore.
1149  *
1150  * @param cls a `struct Request *`
1151  * @param zone private key of the zone
1152  * @param label label of the records
1153  * @param rd_count number of entries in @a rd array, 0 if label was deleted
1154  * @param rd array of records with data to store
1155  */
1156 static void
1157 ns_lookup_result_cb (void *cls,
1158                      const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
1159                      const char *label,
1160                      unsigned int rd_count,
1161                      const struct GNUNET_GNSRECORD_Data *rd)
1162 {
1163   struct Request *req = cls;
1164
1165   req->qe = NULL;
1166   pending--;
1167   GNUNET_break (0 == memcmp (zone,
1168                              &req->zone->key,
1169                              sizeof (*zone)));
1170   GNUNET_break (0 == strcasecmp (label,
1171                                  req->label));
1172   for (unsigned int i=0;i<rd_count;i++)
1173   {
1174     struct GNUNET_TIME_Absolute at;
1175
1176     at.abs_value_us = rd->expiration_time;
1177     add_record (req,
1178                 rd->record_type,
1179                 at,
1180                 rd->data,
1181                 rd->data_size);
1182   }
1183   if (0 == rd_count)
1184   {
1185     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1186                 "Empty record set in namestore for `%s'\n",
1187                 req->label);
1188   }
1189   else
1190   {
1191     unsigned int pos = 0;
1192
1193     req->expires = GNUNET_TIME_UNIT_FOREVER_ABS;
1194     for (struct Record *rec = req->rec_head;
1195          NULL != rec;
1196          rec = rec->next)
1197     {
1198       struct GNUNET_TIME_Absolute at;
1199
1200       at.abs_value_us = rec->grd.expiration_time;
1201       req->expires = GNUNET_TIME_absolute_min (req->expires,
1202                                                at);
1203       pos++;
1204     }
1205     if (0 == pos)
1206       req->expires = GNUNET_TIME_UNIT_ZERO_ABS;
1207     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1208                 "Hot-start with %u existing records for `%s'\n",
1209                 pos,
1210                 req->label);
1211   }
1212   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1213               "Adding `%s' to worklist to start at %s\n",
1214               req->hostname,
1215               GNUNET_STRINGS_absolute_time_to_string (req->expires));
1216   insert_sorted (req);
1217   continue_stdin ();
1218 }
1219
1220
1221 /**
1222  * Add @a hostname to the list of requests to be made.
1223  *
1224  * @param hostname name to resolve
1225  */
1226 static void
1227 queue (const char *hostname)
1228 {
1229   struct GNUNET_DNSPARSER_Packet p;
1230   struct GNUNET_DNSPARSER_Query q;
1231   struct Request *req;
1232   char *raw;
1233   size_t raw_size;
1234   const char *dot;
1235   struct Zone *zone;
1236
1237   if (GNUNET_OK !=
1238       GNUNET_DNSPARSER_check_name (hostname))
1239   {
1240     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1241                 "Refusing invalid hostname `%s'\n",
1242                 hostname);
1243     rejects++;
1244     continue_stdin ();
1245     return;
1246   }
1247   dot = strchr (hostname,
1248                 (unsigned char) '.');
1249   if (NULL == dot)
1250   {
1251     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1252                 "Refusing invalid hostname `%s' (lacks '.')\n",
1253                 hostname);
1254     rejects++;
1255     continue_stdin ();
1256     return;
1257   }
1258   for (zone = zone_head; NULL != zone; zone = zone->next)
1259     if (0 == strcmp (zone->domain,
1260                      dot + 1))
1261       break;
1262   if (NULL == zone)
1263   {
1264     rejects++;
1265     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1266                 "Domain name `%s' not in ego list!\n",
1267                 dot + 1);
1268     continue_stdin ();
1269     return;
1270   }
1271   q.name = (char *) hostname;
1272   q.type = GNUNET_DNSPARSER_TYPE_NS;
1273   q.dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET;
1274
1275   memset (&p,
1276           0,
1277           sizeof (p));
1278   p.num_queries = 1;
1279   p.queries = &q;
1280   p.id = (uint16_t) GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
1281                                               UINT16_MAX);
1282
1283   if (GNUNET_OK !=
1284       GNUNET_DNSPARSER_pack (&p,
1285                              UINT16_MAX,
1286                              &raw,
1287                              &raw_size))
1288   {
1289     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1290                 "Failed to pack query for hostname `%s'\n",
1291                 hostname);
1292     rejects++;
1293     continue_stdin ();
1294     return;
1295   }
1296
1297   pending++;
1298   req = GNUNET_new (struct Request);
1299   req->zone = zone;
1300   req->hostname = GNUNET_strdup (hostname);
1301   req->raw = raw;
1302   req->raw_len = raw_size;
1303   req->id = p.id;
1304   req->label = GNUNET_strndup (hostname,
1305                                dot - hostname);
1306   req->qe = GNUNET_NAMESTORE_records_lookup (ns,
1307                                              &req->zone->key,
1308                                              req->label,
1309                                              &ns_lookup_error_cb,
1310                                              req,
1311                                              &ns_lookup_result_cb,
1312                                              req);
1313 }
1314
1315
1316 /**
1317  * Begin processing hostnames from stdin.
1318  *
1319  * @param cls NULL
1320  */
1321 static void
1322 process_stdin (void *cls)
1323 {
1324   static unsigned int pdot;
1325   char hn[256];
1326
1327   (void) cls;
1328   t = NULL;
1329   if (NULL != id)
1330   {
1331     GNUNET_IDENTITY_disconnect (id);
1332     id = NULL;
1333   }
1334   if (NULL !=
1335       fgets (hn,
1336              sizeof (hn),
1337              stdin))
1338   {
1339     if (strlen(hn) > 0)
1340       hn[strlen(hn)-1] = '\0'; /* eat newline */
1341     pdot++;
1342     if (0 == pdot % 1000)
1343       fprintf (stderr, ".");
1344     queue (hn);
1345     return;
1346   }
1347   stdin_waiting = GNUNET_NO;
1348   fprintf (stderr, "\n");
1349   t = GNUNET_SCHEDULER_add_now (&process_queue,
1350                                 NULL);
1351 }
1352
1353
1354 /**
1355  * Method called to inform about the egos of this peer.
1356  *
1357  * When used with #GNUNET_IDENTITY_connect, this function is
1358  * initially called for all egos and then again whenever a
1359  * ego's name changes or if it is deleted.  At the end of
1360  * the initial pass over all egos, the function is once called
1361  * with 'NULL' for @a ego. That does NOT mean that the callback won't
1362  * be invoked in the future or that there was an error.
1363  *
1364  * When used with #GNUNET_IDENTITY_create or #GNUNET_IDENTITY_get,
1365  * this function is only called ONCE, and 'NULL' being passed in
1366  * @a ego does indicate an error (i.e. name is taken or no default
1367  * value is known).  If @a ego is non-NULL and if '*ctx'
1368  * is set in those callbacks, the value WILL be passed to a subsequent
1369  * call to the identity callback of #GNUNET_IDENTITY_connect (if
1370  * that one was not NULL).
1371  *
1372  * When an identity is renamed, this function is called with the
1373  * (known) @a ego but the NEW @a name.
1374  *
1375  * When an identity is deleted, this function is called with the
1376  * (known) ego and "NULL" for the @a name.  In this case,
1377  * the @a ego is henceforth invalid (and the @a ctx should also be
1378  * cleaned up).
1379  *
1380  * @param cls closure
1381  * @param ego ego handle
1382  * @param ctx context for application to store data for this ego
1383  *                 (during the lifetime of this process, initially NULL)
1384  * @param name name assigned by the user for this ego,
1385  *                   NULL if the user just deleted the ego and it
1386  *                   must thus no longer be used
1387  */
1388 static void
1389 identity_cb (void *cls,
1390              struct GNUNET_IDENTITY_Ego *ego,
1391              void **ctx,
1392              const char *name)
1393 {
1394   (void) cls;
1395   (void) ctx;
1396   if (NULL == ego)
1397   {
1398     if (NULL != zone_head)
1399     {
1400       stdin_waiting = GNUNET_YES;
1401       t = GNUNET_SCHEDULER_add_now (&process_stdin,
1402                                     NULL);
1403     }
1404     else
1405     {
1406       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1407                   "Specified zone not found\n");
1408       GNUNET_SCHEDULER_shutdown ();
1409       return;
1410     }
1411   }
1412   if (NULL != name)
1413   {
1414     struct Zone *zone;
1415
1416     zone = GNUNET_new (struct Zone);
1417     zone->key = *GNUNET_IDENTITY_ego_get_private_key (ego);
1418     zone->domain = GNUNET_strdup (name);
1419     GNUNET_CONTAINER_DLL_insert (zone_head,
1420                                  zone_tail,
1421                                  zone);
1422   }
1423 }
1424
1425
1426 /**
1427  * Process requests from the queue, then if the queue is
1428  * not empty, try again.
1429  *
1430  * @param cls NULL
1431  * @param args remaining command-line arguments
1432  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
1433  * @param cfg configuration
1434  */
1435 static void
1436 run (void *cls,
1437      char *const *args,
1438      const char *cfgfile,
1439      const struct GNUNET_CONFIGURATION_Handle *cfg)
1440 {
1441   (void) cls;
1442   (void) args;
1443   (void) cfgfile;
1444   req_heap = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN);
1445   ctx = GNUNET_DNSSTUB_start (256);
1446   if (NULL == ctx)
1447   {
1448     fprintf (stderr,
1449              "Failed to initialize GNUnet DNS STUB\n");
1450     return;
1451   }
1452   if (NULL == args[0])
1453   {
1454     fprintf (stderr,
1455              "You must provide a list of DNS resolvers on the command line\n");
1456     return;
1457   }
1458   for (unsigned int i=0;NULL != args[i];i++)
1459   {
1460     if (GNUNET_OK !=
1461         GNUNET_DNSSTUB_add_dns_ip (ctx,
1462                                    args[i]))
1463     {
1464       fprintf (stderr,
1465                "Failed to use `%s' for DNS resolver\n",
1466                args[i]);
1467       return;
1468     }
1469   }
1470
1471
1472   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
1473                                  NULL);
1474   ns = GNUNET_NAMESTORE_connect (cfg);
1475   if (NULL == ns)
1476   {
1477     GNUNET_SCHEDULER_shutdown ();
1478     return;
1479   }
1480   id = GNUNET_IDENTITY_connect (cfg,
1481                                 &identity_cb,
1482                                 NULL);
1483 }
1484
1485
1486 /**
1487  * Call with IP address of resolver to query.
1488  *
1489  * @param argc should be 2
1490  * @param argv[1] should contain IP address
1491  * @return 0 on success
1492  */
1493 int
1494 main (int argc,
1495       char *const*argv)
1496 {
1497   struct GNUNET_GETOPT_CommandLineOption options[] = {
1498     GNUNET_GETOPT_OPTION_END
1499   };
1500
1501   if (GNUNET_OK !=
1502       GNUNET_STRINGS_get_utf8_args (argc, argv,
1503                                     &argc, &argv))
1504     return 2;
1505   GNUNET_PROGRAM_run (argc,
1506                       argv,
1507                       "gnunet-zoneimport",
1508                       "import DNS zone into namestore",
1509                       options,
1510                       &run,
1511                       NULL);
1512   GNUNET_free ((void*) argv);
1513   fprintf (stderr,
1514            "Rejected %u names, did %u lookups, found %u records, %u lookups failed, %u pending on shutdown\n",
1515            rejects,
1516            lookups,
1517            records,
1518            failures,
1519            pending);
1520   return 0;
1521 }
1522
1523 /* end of gnunet-zoneimport.c */