1b49c294aaacdff5db0ac88a48200a504ab00b29
[oweals/gnunet.git] / src / gns / gnunet-dns2gns.c
1 /*
2      This file is part of GNUnet.
3      (C) 2012-2013 Christian Grothoff (and other contributing authors)
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., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20 /**
21  * @file gnunet-dns2gns.c
22  * @brief DNS server that translates DNS requests to GNS
23  * @author Christian Grothoff
24  */
25 #include "platform.h"
26 #include <gnunet_util_lib.h>
27 #include <gnunet_dnsparser_lib.h>
28 #include <gnunet_gns_service.h>
29 #include <gnunet_dnsstub_lib.h>
30 #include "gns.h"
31
32 /**
33  * Timeout for DNS requests.
34  */
35 #define TIMEOUT GNUNET_TIME_UNIT_MINUTES
36
37 /**
38  * Default suffix
39  */
40 #define DNS_SUFFIX ".zkey.eu"
41
42 /**
43  * FCFS suffix
44  */
45 #define FCFS_SUFFIX "fcfs.zkey.eu"
46
47 /**
48  * Data kept per request.
49  */
50 struct Request
51 {
52   /**
53    * Socket to use for sending the reply.
54    */
55   struct GNUNET_NETWORK_Handle *lsock;
56
57   /**
58    * Destination address to use.
59    */
60   const void *addr;
61
62   /**
63    * Initially, this is the DNS request, it will then be
64    * converted to the DNS response.
65    */
66   struct GNUNET_DNSPARSER_Packet *packet;
67   
68   /**
69    * Our GNS request handle.
70    */
71   struct GNUNET_GNS_LookupRequest *lookup;
72
73   /**
74    * Our DNS request handle
75    */
76   struct GNUNET_DNSSTUB_RequestSocket *dns_lookup;
77
78   /**
79    * Task run on timeout or shutdown to clean up without
80    * response.
81    */
82   GNUNET_SCHEDULER_TaskIdentifier timeout_task;
83
84   /**
85    * Number of bytes in 'addr'.
86    */ 
87   size_t addr_len;
88
89 };
90
91
92 /**
93  * Handle to GNS resolver.
94  */
95 struct GNUNET_GNS_Handle *gns;
96
97 /**
98  * Stub resolver
99  */
100 struct GNUNET_DNSSTUB_Context *dns_stub;
101
102 /**
103  * Listen socket for IPv4.
104  */
105 static struct GNUNET_NETWORK_Handle *listen_socket4;
106
107 /**
108  * Listen socket for IPv6.
109  */
110 static struct GNUNET_NETWORK_Handle *listen_socket6;
111
112 /**
113  * Task for IPv4 socket.
114  */
115 static GNUNET_SCHEDULER_TaskIdentifier t4;
116
117 /**
118  * Task for IPv6 socket.
119  */
120 static GNUNET_SCHEDULER_TaskIdentifier t6;
121
122 /**
123  * DNS suffix, suffix of this gateway in DNS; defaults to '.zkey.eu'
124  */
125 static char *dns_suffix;
126
127 /**
128  * FCFS suffix, suffix of FCFS-authority in DNS; defaults to 'fcfs.zkey.eu'.
129  */
130 static char *fcfs_suffix;
131
132 /**
133  * IP of DNS server
134  */
135 static char *dns_ip;
136
137 /**
138  * UDP Port we listen on for inbound DNS requests.
139  */
140 static unsigned int listen_port = 53;
141
142 /**
143  * Which GNS zone do we translate incoming DNS requests to?
144  */
145 static struct GNUNET_CRYPTO_EccPublicKey my_zone;
146
147 /**
148  * '-z' option with the main zone to use.
149  */
150 static char *gns_zone_str;
151
152
153 /**
154  * Task run on shutdown.  Cleans up everything.
155  *
156  * @param cls unused
157  * @param tc scheduler context
158  */
159 static void
160 do_shutdown (void *cls,
161              const struct GNUNET_SCHEDULER_TaskContext *tc)
162 {
163   if (GNUNET_SCHEDULER_NO_TASK != t4)
164     GNUNET_SCHEDULER_cancel (t4);
165   if (GNUNET_SCHEDULER_NO_TASK != t6)
166     GNUNET_SCHEDULER_cancel (t6);
167   if (NULL != listen_socket4)
168   {
169     GNUNET_NETWORK_socket_close (listen_socket4);
170     listen_socket4 = NULL;
171   }
172   if (NULL != listen_socket6)
173   {
174     GNUNET_NETWORK_socket_close (listen_socket6);
175     listen_socket6 = NULL;
176   }
177   GNUNET_GNS_disconnect (gns);
178   gns = NULL;
179   GNUNET_DNSSTUB_stop (dns_stub);
180   dns_stub = NULL;
181 }
182
183
184 /**
185  * Send the response for the given request and clean up.
186  *
187  * @param request context for the request.
188  */
189 static void
190 send_response (struct Request *request)
191 {
192   char *buf;
193   size_t size;
194   
195   if (GNUNET_SYSERR ==
196       GNUNET_DNSPARSER_pack (request->packet,
197                              UINT16_MAX /* is this not too much? */,
198                              &buf,
199                              &size))
200     {
201       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
202                   _("Failed to pack DNS response into UDP packet!\n"));
203     }
204   else
205     {
206       if (size !=
207           GNUNET_NETWORK_socket_sendto (request->lsock,
208                                         buf, size,
209                                         request->addr,
210                                         request->addr_len))
211         GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "sendto");
212       GNUNET_free (buf);
213     }
214   GNUNET_SCHEDULER_cancel (request->timeout_task);
215   GNUNET_DNSPARSER_free_packet (request->packet);
216   GNUNET_free (request);
217 }
218
219
220 /**
221  * Task run on timeout.  Cleans up request.
222  *
223  * @param cls 'struct Request' of the request to clean up
224  * @param tc scheduler context
225  */
226 static void
227 do_timeout (void *cls,
228             const struct GNUNET_SCHEDULER_TaskContext *tc)
229 {
230   struct Request *request = cls;
231
232   if (NULL != request->packet)
233     GNUNET_DNSPARSER_free_packet (request->packet);
234   if (NULL != request->lookup)
235     GNUNET_GNS_lookup_cancel (request->lookup);
236   if (NULL != request->dns_lookup)
237     GNUNET_DNSSTUB_resolve_cancel (request->dns_lookup);
238   GNUNET_free (request);
239 }
240
241
242 /**
243  * Iterator called on obtained result for a DNS lookup
244  *
245  * @param cls closure
246  * @param rs the request socket
247  * @param dns the DNS udp payload
248  * @param r size of the DNS payload
249  */
250 static void
251 dns_result_processor (void *cls,
252                       struct GNUNET_DNSSTUB_RequestSocket *rs,
253                       const struct GNUNET_TUN_DnsHeader *dns,
254                       size_t r)
255 {
256   struct Request *request = cls;
257
258   request->packet = GNUNET_DNSPARSER_parse ((char*)dns, r);
259   send_response (request);
260 }
261
262
263 /**
264  * Iterator called on obtained result for a GNS
265  * lookup
266  *
267  * @param cls closure
268  * @param rd_count number of records
269  * @param rd the records in reply
270  */
271 static void
272 result_processor (void *cls,
273                   uint32_t rd_count,
274                   const struct GNUNET_NAMESTORE_RecordData *rd)
275 {
276   struct Request *request = cls;
277   struct GNUNET_DNSPARSER_Packet *packet;
278   uint32_t i;
279   struct GNUNET_DNSPARSER_Record rec;
280
281   request->lookup = NULL;
282   packet = request->packet;
283   packet->flags.query_or_response = 1;
284   packet->flags.return_code = GNUNET_TUN_DNS_RETURN_CODE_NO_ERROR;
285   packet->flags.checking_disabled = 0;
286   packet->flags.authenticated_data = 1;
287   packet->flags.zero = 0;
288   packet->flags.recursion_available = 1;
289   packet->flags.message_truncated = 0;
290   packet->flags.authoritative_answer = 0;
291   //packet->flags.opcode = GNUNET_TUN_DNS_OPCODE_STATUS; // ???
292   for (i=0;i<rd_count;i++)
293     {
294       rec.expiration_time.abs_value_us = rd[i].expiration_time;
295       switch (rd[i].record_type)
296         {
297         case GNUNET_DNSPARSER_TYPE_A:
298           GNUNET_assert (sizeof (struct in_addr) == rd[i].data_size);
299           rec.name = GNUNET_strdup (packet->queries[0].name);
300           rec.class = GNUNET_TUN_DNS_CLASS_INTERNET;
301           rec.type = GNUNET_DNSPARSER_TYPE_A;
302           rec.data.raw.data = GNUNET_malloc (sizeof (struct in_addr));
303           memcpy (rec.data.raw.data,
304                   rd[i].data,
305                   rd[i].data_size);
306           rec.data.raw.data_len = sizeof (struct in_addr);
307           GNUNET_array_append (packet->answers,
308                                packet->num_answers,
309                                rec);
310           break;
311         case GNUNET_DNSPARSER_TYPE_AAAA:
312           GNUNET_assert (sizeof (struct in6_addr) == rd[i].data_size);
313           rec.name = GNUNET_strdup (packet->queries[0].name);
314           rec.data.raw.data = GNUNET_malloc (sizeof (struct in6_addr));
315           rec.class = GNUNET_TUN_DNS_CLASS_INTERNET;
316           rec.type = GNUNET_DNSPARSER_TYPE_AAAA;
317           memcpy (rec.data.raw.data,
318                   rd[i].data,
319                   rd[i].data_size);
320           rec.data.raw.data_len = sizeof (struct in6_addr);
321           GNUNET_array_append (packet->answers,
322                                packet->num_answers,
323                                rec);
324           break;
325         case GNUNET_DNSPARSER_TYPE_CNAME:
326           rec.name = GNUNET_strdup (packet->queries[0].name);
327           rec.data.hostname = strdup (rd[i].data);
328           rec.class = GNUNET_TUN_DNS_CLASS_INTERNET;
329           rec.type = GNUNET_DNSPARSER_TYPE_CNAME;
330           memcpy (rec.data.hostname,
331                   rd[i].data,
332                   rd[i].data_size);
333           GNUNET_array_append (packet->answers,
334                                packet->num_answers,
335                                rec);
336           break;
337         default:
338           /* skip */
339           break;
340         }
341     }
342   send_response (request);
343 }
344
345
346 /**
347  * Handle DNS request.
348  *
349  * @param lsock socket to use for sending the reply
350  * @param addr address to use for sending the reply
351  * @param addr_len number of bytes in addr
352  * @param udp_msg DNS request payload
353  * @param udp_msg_size number of bytes in udp_msg 
354  */
355 static void
356 handle_request (struct GNUNET_NETWORK_Handle *lsock,
357                 const void *addr,
358                 size_t addr_len,
359                 const char *udp_msg,
360                 size_t udp_msg_size)
361 {
362   struct Request *request;
363   struct GNUNET_DNSPARSER_Packet *packet;
364   char *name;
365   size_t name_len;
366   int type;
367   int use_gns;
368
369   packet = GNUNET_DNSPARSER_parse (udp_msg, udp_msg_size);
370   if (NULL == packet)
371     {
372       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
373                   _("Cannot parse DNS request from %s\n"),
374                   GNUNET_a2s (addr, addr_len));
375       return;
376     }
377   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
378               "Received request for `%s' with flags %u, #answers %d, #auth %d, #additional %d\n",
379               packet->queries[0].name,
380               (unsigned int) packet->flags.query_or_response,
381               (int) packet->num_answers,
382               (int) packet->num_authority_records,
383               (int) packet->num_additional_records);
384   if ( (0 != packet->flags.query_or_response) || 
385        (0 != packet->num_answers) ||
386        (0 != packet->num_authority_records))
387     {
388       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
389                   _("Received malformed DNS request from %s\n"),
390                   GNUNET_a2s (addr, addr_len));
391       GNUNET_DNSPARSER_free_packet (packet);
392       return;
393     }
394   if ( (1 != packet->num_queries) )
395     {
396       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
397                   _("Received unsupported DNS request from %s\n"),
398                   GNUNET_a2s (addr, addr_len));
399       GNUNET_DNSPARSER_free_packet (packet);
400       return;
401     }
402   request = GNUNET_malloc (sizeof (struct Request) + addr_len);
403   request->lsock = lsock;
404   request->packet = packet;
405   request->addr = &request[1];
406   request->addr_len = addr_len;
407   memcpy (&request[1], addr, addr_len);
408   request->timeout_task = GNUNET_SCHEDULER_add_delayed (TIMEOUT,
409                                                         &do_timeout,
410                                                         request);
411   name = GNUNET_strdup (packet->queries[0].name);
412   name_len = strlen (name);
413   use_gns = GNUNET_NO;
414
415   
416   if ( (name_len > strlen (fcfs_suffix)) &&
417        (0 == strcasecmp (fcfs_suffix,
418                          &name[name_len - strlen (fcfs_suffix)])) )
419   {
420     /* replace ".fcfs.zkey.eu" with ".gnu" */
421     strcpy (&name[name_len - strlen (fcfs_suffix)],
422             ".gnu");
423     use_gns = GNUNET_YES;
424   } else if ( (name_len > strlen (dns_suffix)) &&
425        (0 == strcasecmp (dns_suffix,
426                          &name[name_len - strlen (dns_suffix)])) )
427   {
428     /* replace ".fcfs.zkey.eu" with ".zkey" */
429     strcpy (&name[name_len - strlen (dns_suffix)],
430             ".zkey");
431     use_gns = GNUNET_YES;
432   } else if ( (name_len > strlen (".gnu")) &&
433        (0 == strcasecmp (".gnu",
434                          &name[name_len - strlen (".gnu")])) )
435   {
436     /* name is in GNS */
437     use_gns = GNUNET_YES;
438   }
439   if (GNUNET_YES == use_gns)
440   {
441     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
442                 "Calling GNS on `%s'\n",
443                 name);
444     type = packet->queries[0].type;
445     request->lookup = GNUNET_GNS_lookup (gns,
446                                          name,
447                                          &my_zone,
448                                          type,
449                                          GNUNET_NO,
450                                          NULL /* no shorten */,
451                                          &result_processor,
452                                          request);
453   }
454   else
455   {
456     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
457                 "Using DNS resolver IP `%s' to resolve `%s'\n", 
458                 dns_ip,
459                 name);
460     GNUNET_DNSPARSER_free_packet (request->packet);
461     request->packet = NULL;
462     request->dns_lookup = GNUNET_DNSSTUB_resolve2 (dns_stub,
463                                                    udp_msg,
464                                                    udp_msg_size,
465                                                    &dns_result_processor,
466                                                    request);
467   }
468   GNUNET_free (name);
469 }
470
471
472 /**
473  * Task to read IPv4 DNS packets.
474  *
475  * @param cls the 'listen_socket4'
476  * @param tc scheduler context
477  */ 
478 static void
479 read_dns4 (void *cls,
480            const struct GNUNET_SCHEDULER_TaskContext *tc)
481 {
482   struct sockaddr_in v4;
483   socklen_t addrlen;
484   ssize_t size;
485
486   GNUNET_assert (listen_socket4 == cls);
487   t4 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
488                                       listen_socket4,
489                                       &read_dns4,
490                                       listen_socket4);
491   if (0 == (GNUNET_SCHEDULER_REASON_READ_READY & tc->reason))
492     return; /* shutdown? */
493   size = GNUNET_NETWORK_socket_recvfrom_amount (listen_socket4);
494   if (0 > size)
495     {
496       GNUNET_break (0);
497       return; /* read error!? */
498     }
499   {
500     char buf[size];
501     
502     addrlen = sizeof (v4);
503     GNUNET_break (size == 
504                   GNUNET_NETWORK_socket_recvfrom (listen_socket4,
505                                                   buf,
506                                                   size,
507                                                   (struct sockaddr *) &v4,
508                                                   &addrlen));
509     handle_request (listen_socket4, &v4, addrlen,
510                     buf, size);
511   }
512 }
513
514
515 /**
516  * Task to read IPv6 DNS packets.
517  *
518  * @param cls the 'listen_socket6'
519  * @param tc scheduler context
520  */ 
521 static void
522 read_dns6 (void *cls,
523            const struct GNUNET_SCHEDULER_TaskContext *tc)
524 {
525   struct sockaddr_in6 v6;
526   socklen_t addrlen;
527   ssize_t size;
528
529   GNUNET_assert (listen_socket6 == cls);
530   t6 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
531                                       listen_socket6,
532                                       &read_dns6,
533                                       listen_socket6);
534   if (0 == (GNUNET_SCHEDULER_REASON_READ_READY & tc->reason))
535     return; /* shutdown? */
536   size = GNUNET_NETWORK_socket_recvfrom_amount (listen_socket6);
537   if (0 > size)
538     {
539       GNUNET_break (0);
540       return; /* read error!? */
541     }
542   {
543     char buf[size];
544     
545     addrlen = sizeof (v6);
546     GNUNET_break (size == 
547                   GNUNET_NETWORK_socket_recvfrom (listen_socket6,
548                                                   buf,
549                                                   size,
550                                                   (struct sockaddr *) &v6,
551                                                   &addrlen));
552     handle_request (listen_socket6, &v6, addrlen,
553                     buf, size);
554   }
555 }
556
557
558 /**
559  * Main function that will be run.
560  *
561  * @param cls closure
562  * @param args remaining command-line arguments
563  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
564  * @param cfg configuration
565  */
566 static void
567 run (void *cls, char *const *args, const char *cfgfile,
568      const struct GNUNET_CONFIGURATION_Handle *cfg)
569 {
570   if (NULL == dns_ip)
571   {
572     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
573                 _("No DNS server specified!\n"));
574     return;
575   }
576   if ( (NULL == gns_zone_str) ||
577        (GNUNET_OK !=
578         GNUNET_CRYPTO_ecc_public_key_from_string (gns_zone_str,
579                                                   strlen (gns_zone_str),
580                                                   &my_zone)) )
581   {
582     GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 
583                 _("No valid GNS zone specified!\n"));
584     return;
585   }
586
587   if (NULL == dns_suffix)
588     dns_suffix = DNS_SUFFIX;
589   if (NULL == fcfs_suffix)
590     fcfs_suffix = FCFS_SUFFIX;
591   if (NULL == (gns = GNUNET_GNS_connect (cfg)))
592     return;
593   if (NULL == (dns_stub = GNUNET_DNSSTUB_start (dns_ip)))
594   {
595     GNUNET_GNS_disconnect (gns);
596     gns = NULL;
597     return;
598   }
599   listen_socket4 = GNUNET_NETWORK_socket_create (PF_INET,
600                                                  SOCK_DGRAM, 
601                                                  IPPROTO_UDP);
602   if (NULL != listen_socket4)
603     {
604       struct sockaddr_in v4;
605
606       memset (&v4, 0, sizeof (v4));
607       v4.sin_family = AF_INET;
608 #if HAVE_SOCKADDR_IN_SIN_LEN
609       v4.sin_len = sizeof (v4);
610 #endif
611       v4.sin_port = htons (listen_port);
612       if (GNUNET_OK !=
613           GNUNET_NETWORK_socket_bind (listen_socket4,
614                                       (struct sockaddr *) &v4,
615                                       sizeof (v4),
616                                       0))
617         {
618           GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "bind");
619           GNUNET_NETWORK_socket_close (listen_socket4);
620           listen_socket4 = NULL;
621         }
622     }
623   listen_socket6 = GNUNET_NETWORK_socket_create (PF_INET6,
624                                                 SOCK_DGRAM, 
625                                                 IPPROTO_UDP);
626   if (NULL != listen_socket6)
627     {
628       struct sockaddr_in6 v6;
629
630       memset (&v6, 0, sizeof (v6));
631       v6.sin6_family = AF_INET6;
632 #if HAVE_SOCKADDR_IN_SIN_LEN
633       v6.sin6_len = sizeof (v6);
634 #endif
635       v6.sin6_port = htons (listen_port);
636       if (GNUNET_OK !=
637           GNUNET_NETWORK_socket_bind (listen_socket6,
638                                       (struct sockaddr *) &v6,
639                                       sizeof (v6),
640                                       0))
641         {
642           GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "bind");
643           GNUNET_NETWORK_socket_close (listen_socket6);
644           listen_socket6 = NULL;
645         }
646     }
647   if ( (NULL == listen_socket4) &&
648        (NULL == listen_socket6) )
649     {
650       GNUNET_GNS_disconnect (gns);
651       gns = NULL;
652       GNUNET_DNSSTUB_stop (dns_stub);
653       dns_stub = NULL;
654       return;
655     }
656   if (NULL != listen_socket4)
657     t4 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
658                                         listen_socket4,
659                                         &read_dns4,
660                                         listen_socket4);
661   if (NULL != listen_socket6)
662     t6 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL,
663                                         listen_socket6,
664                                         &read_dns6,
665                                         listen_socket6);
666
667   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
668                                 &do_shutdown, NULL);
669 }
670
671
672 /**
673  * The main function for the fcfs daemon.
674  *
675  * @param argc number of arguments from the command line
676  * @param argv command line arguments
677  * @return 0 ok, 1 on error
678  */
679 int
680 main (int argc, 
681       char *const *argv)
682 {
683   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
684     {'d', "dns", "IP",
685       gettext_noop ("IP of recursive DNS resolver to use (required)"), 1,
686       &GNUNET_GETOPT_set_string, &dns_ip},
687     {'f', "fcfs", "NAME",
688       gettext_noop ("Authoritative FCFS suffix to use (optional); default: fcfs.zkey.eu"), 1,
689       &GNUNET_GETOPT_set_string, &fcfs_suffix},
690     {'s', "suffix", "SUFFIX",
691       gettext_noop ("Authoritative DNS suffix to use (optional); default: zkey.eu"), 1,
692       &GNUNET_GETOPT_set_string, &dns_suffix},
693     {'p', "port", "UDPPORT",
694       gettext_noop ("UDP port to listen on for inbound DNS requests; default: 53"), 1,
695       &GNUNET_GETOPT_set_uint, &listen_port},
696     {'z', "zone", "PUBLICKEY",
697       gettext_noop ("Public key of the GNS zone to use (required)"), 1,
698       &GNUNET_GETOPT_set_string, &gns_zone_str},
699     GNUNET_GETOPT_OPTION_END
700   };
701   int ret;
702
703   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv,
704                                                  &argc, &argv))
705     return 2;
706   GNUNET_log_setup ("gnunet-dns2gns", "WARNING", NULL);
707   ret =
708       (GNUNET_OK ==
709        GNUNET_PROGRAM_run (argc, argv, "gnunet-dns2gns",
710                            _("GNUnet DNS-to-GNS proxy (a DNS server)"), 
711                            options,
712                            &run, NULL)) ? 0 : 1;
713   GNUNET_free ((void*) argv);
714   return ret;
715 }
716
717 /* end of gnunet-dns2gns.c */