configure option --disable-autostart to disable auto-startup of services by ARM
[oweals/gnunet.git] / src / dns / dnsparser.c
1 /*
2       This file is part of GNUnet
3       (C) 2010-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 /**
22  * @file dns/dnsparser.c
23  * @brief helper library to parse DNS packets.
24  * @author Philipp Toelke
25  * @author Christian Grothoff
26  */
27 #include "platform.h"
28 #include <idna.h>
29 #if WINDOWS
30 #include <idn-free.h>
31 #endif
32 #include "gnunet_util_lib.h"
33 #include "gnunet_dnsparser_lib.h"
34 #include "gnunet_tun_lib.h"
35
36
37 /**
38  * Check if a label in UTF-8 format can be coded into valid IDNA.
39  * This can fail if the ASCII-conversion becomes longer than 63 characters.
40  *
41  * @param label label to check (UTF-8 string)
42  * @return #GNUNET_OK if the label can be converted to IDNA,
43  *         #GNUNET_SYSERR if the label is not valid for DNS names
44  */
45 int
46 GNUNET_DNSPARSER_check_label (const char *label)
47 {
48   char *output;
49   size_t slen;
50
51   if (NULL != strchr (label, '.'))
52     return GNUNET_SYSERR; /* not a label! Did you mean GNUNET_DNSPARSER_check_name? */
53   if (IDNA_SUCCESS !=
54       idna_to_ascii_8z (label, &output, IDNA_ALLOW_UNASSIGNED))
55     return GNUNET_SYSERR;
56   slen = strlen (output);
57 #if WINDOWS
58   idn_free (output);
59 #else
60   free (output);
61 #endif
62   return (slen > 63) ? GNUNET_SYSERR : GNUNET_OK;
63 }
64
65
66 /**
67  * Check if a label in UTF-8 format can be coded into valid IDNA.
68  * This can fail if the ASCII-conversion becomes longer than 253 characters.
69  *
70  * @param name name to check (UTF-8 string)
71  * @return #GNUNET_OK if the label can be converted to IDNA,
72  *         #GNUNET_SYSERR if the label is not valid for DNS names
73  */
74 int
75 GNUNET_DNSPARSER_check_name (const char *name)
76 {
77   char *ldup;
78   char *output;
79   size_t slen;
80   char *tok;
81
82   ldup = GNUNET_strdup (name);
83   for (tok = strtok (ldup, "."); NULL != tok; tok = strtok (NULL, "."))
84     if (GNUNET_OK !=
85         GNUNET_DNSPARSER_check_label (tok))
86     {
87       GNUNET_free (ldup);
88       return GNUNET_SYSERR;
89     }
90   GNUNET_free (ldup);
91   if (IDNA_SUCCESS !=
92       idna_to_ascii_8z (name, &output, IDNA_ALLOW_UNASSIGNED))
93     return GNUNET_SYSERR;
94   slen = strlen (output);
95 #if WINDOWS
96   idn_free (output);
97 #else
98   free (output);
99 #endif
100   return (slen > 253) ? GNUNET_SYSERR : GNUNET_OK;
101 }
102
103
104 /**
105  * Free SOA information record.
106  *
107  * @param soa record to free
108  */
109 void
110 GNUNET_DNSPARSER_free_soa (struct GNUNET_DNSPARSER_SoaRecord *soa)
111 {
112   if (NULL == soa)
113     return;
114   GNUNET_free_non_null (soa->mname);
115   GNUNET_free_non_null (soa->rname);
116   GNUNET_free (soa);
117 }
118
119
120 /**
121  * Free SRV information record.
122  *
123  * @param srv record to free
124  */
125 void
126 GNUNET_DNSPARSER_free_srv (struct GNUNET_DNSPARSER_SrvRecord *srv)
127 {
128   if (NULL == srv)
129     return;
130   GNUNET_free_non_null (srv->target);
131   GNUNET_free_non_null (srv->domain_name);
132   GNUNET_free_non_null (srv->proto);
133   GNUNET_free_non_null (srv->service);
134   GNUNET_free (srv);
135 }
136
137
138 /**
139  * Free MX information record.
140  *
141  * @param mx record to free
142  */
143 void
144 GNUNET_DNSPARSER_free_mx (struct GNUNET_DNSPARSER_MxRecord *mx)
145 {
146   if (NULL == mx)
147     return;
148   GNUNET_free_non_null (mx->mxhost);
149   GNUNET_free (mx);
150 }
151
152
153 /**
154  * Free the given DNS record.
155  *
156  * @param r record to free
157  */
158 void
159 GNUNET_DNSPARSER_free_record (struct GNUNET_DNSPARSER_Record *r)
160 {
161   GNUNET_free_non_null (r->name);
162   switch (r->type)
163   {
164   case GNUNET_DNSPARSER_TYPE_MX:
165     GNUNET_DNSPARSER_free_mx (r->data.mx);
166     break;
167   case GNUNET_DNSPARSER_TYPE_SOA:
168     GNUNET_DNSPARSER_free_soa (r->data.soa);
169     break;
170   case GNUNET_DNSPARSER_TYPE_SRV:
171     GNUNET_DNSPARSER_free_srv (r->data.srv);
172     break;
173   case GNUNET_DNSPARSER_TYPE_NS:
174   case GNUNET_DNSPARSER_TYPE_CNAME:
175   case GNUNET_DNSPARSER_TYPE_PTR:
176     GNUNET_free_non_null (r->data.hostname);
177     break;
178   default:
179     GNUNET_free_non_null (r->data.raw.data);
180     break;
181   }
182 }
183
184
185 /**
186  * Parse name inside of a DNS query or record.
187  *
188  * @param udp_payload entire UDP payload
189  * @param udp_payload_length length of @a udp_payload
190  * @param off pointer to the offset of the name to parse in the udp_payload (to be
191  *                    incremented by the size of the name)
192  * @param depth current depth of our recursion (to prevent stack overflow)
193  * @return name as 0-terminated C string on success, NULL if the payload is malformed
194  */
195 static char *
196 parse_name (const char *udp_payload,
197             size_t udp_payload_length,
198             size_t *off,
199             unsigned int depth)
200 {
201   const uint8_t *input = (const uint8_t *) udp_payload;
202   char *ret;
203   char *tmp;
204   char *xstr;
205   uint8_t len;
206   size_t xoff;
207   char *utf8;
208   Idna_rc rc;
209
210   ret = GNUNET_strdup ("");
211   while (1)
212   {
213     if (*off >= udp_payload_length)
214     {
215       GNUNET_break_op (0);
216       goto error;
217     }
218     len = input[*off];
219     if (0 == len)
220     {
221       (*off)++;
222       break;
223     }
224     if (len < 64)
225     {
226       if (*off + 1 + len > udp_payload_length)
227       {
228         GNUNET_break_op (0);
229         goto error;
230       }
231       GNUNET_asprintf (&tmp,
232                        "%.*s",
233                        (int) len,
234                        &udp_payload[*off + 1]);
235       if (IDNA_SUCCESS !=
236           (rc = idna_to_unicode_8z8z (tmp, &utf8, IDNA_ALLOW_UNASSIGNED)))
237       {
238         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
239                     _("Failed to convert DNS IDNA name `%s' to UTF-8: %s\n"),
240                     tmp,
241                     idna_strerror (rc));
242         GNUNET_free (tmp);
243         GNUNET_asprintf (&tmp,
244                          "%s%.*s.",
245                          ret,
246                          (int) len,
247                          &udp_payload[*off + 1]);
248       }
249       else
250       {
251         GNUNET_free (tmp);
252         GNUNET_asprintf (&tmp,
253                          "%s%s.",
254                          ret,
255                          utf8);
256 #if WINDOWS
257         idn_free (utf8);
258 #else
259         free (utf8);
260 #endif
261       }
262       GNUNET_free (ret);
263       ret = tmp;
264       *off += 1 + len;
265     }
266     else if ((64 | 128) == (len & (64 | 128)) )
267     {
268       if (depth > 32)
269       {
270         GNUNET_break_op (0);
271         goto error; /* hard bound on stack to prevent "infinite" recursion, disallow! */
272       }
273       /* pointer to string */
274       if (*off + 1 > udp_payload_length)
275       {
276         GNUNET_break_op (0);
277         goto error;
278       }
279       xoff = ((len - (64 | 128)) << 8) + input[*off+1];
280       xstr = parse_name (udp_payload,
281                          udp_payload_length,
282                          &xoff,
283                          depth + 1);
284       if (NULL == xstr)
285       {
286         GNUNET_break_op (0);
287         goto error;
288       }
289       GNUNET_asprintf (&tmp,
290                        "%s%s.",
291                        ret,
292                        xstr);
293       GNUNET_free (ret);
294       GNUNET_free (xstr);
295       ret = tmp;
296       if (strlen (ret) > udp_payload_length)
297       {
298         GNUNET_break_op (0);
299         goto error; /* we are looping (building an infinite string) */
300       }
301       *off += 2;
302       /* pointers always terminate names */
303       break;
304     }
305     else
306     {
307       /* neither pointer nor inline string, not supported... */
308       GNUNET_break_op (0);
309       goto error;
310     }
311   }
312   if (0 < strlen(ret))
313     ret[strlen(ret)-1] = '\0'; /* eat tailing '.' */
314   return ret;
315  error:
316   GNUNET_break_op (0);
317   GNUNET_free (ret);
318   return NULL;
319 }
320
321
322 /**
323  * Parse name inside of a DNS query or record.
324  *
325  * @param udp_payload entire UDP payload
326  * @param udp_payload_length length of @a udp_payload
327  * @param off pointer to the offset of the name to parse in the udp_payload (to be
328  *                    incremented by the size of the name)
329  * @return name as 0-terminated C string on success, NULL if the payload is malformed
330  */
331 char *
332 GNUNET_DNSPARSER_parse_name (const char *udp_payload,
333                              size_t udp_payload_length,
334                              size_t *off)
335 {
336   return parse_name (udp_payload, udp_payload_length, off, 0);
337 }
338
339
340 /**
341  * Parse a DNS query entry.
342  *
343  * @param udp_payload entire UDP payload
344  * @param udp_payload_length length of @a udp_payload
345  * @param off pointer to the offset of the query to parse in the udp_payload (to be
346  *                    incremented by the size of the query)
347  * @param q where to write the query information
348  * @return #GNUNET_OK on success, #GNUNET_SYSERR if the query is malformed
349  */
350 int
351 GNUNET_DNSPARSER_parse_query (const char *udp_payload,
352                               size_t udp_payload_length,
353                               size_t *off,
354                               struct GNUNET_DNSPARSER_Query *q)
355 {
356   char *name;
357   struct GNUNET_TUN_DnsQueryLine ql;
358
359   name = GNUNET_DNSPARSER_parse_name (udp_payload,
360                                       udp_payload_length,
361                                       off);
362   if (NULL == name)
363   {
364     GNUNET_break_op (0);
365     return GNUNET_SYSERR;
366   }
367   q->name = name;
368   if (*off + sizeof (struct GNUNET_TUN_DnsQueryLine) > udp_payload_length)
369   {
370     GNUNET_break_op (0);
371     return GNUNET_SYSERR;
372   }
373   memcpy (&ql, &udp_payload[*off], sizeof (ql));
374   *off += sizeof (ql);
375   q->type = ntohs (ql.type);
376   q->dns_traffic_class = ntohs (ql.dns_traffic_class);
377   return GNUNET_OK;
378 }
379
380
381 /**
382  * Parse a DNS SOA record.
383  *
384  * @param udp_payload reference to UDP packet
385  * @param udp_payload_length length of @a udp_payload
386  * @param off pointer to the offset of the query to parse in the SOA record (to be
387  *                    incremented by the size of the record), unchanged on error
388  * @return the parsed SOA record, NULL on error
389  */
390 struct GNUNET_DNSPARSER_SoaRecord *
391 GNUNET_DNSPARSER_parse_soa (const char *udp_payload,
392                             size_t udp_payload_length,
393                             size_t *off)
394 {
395   struct GNUNET_DNSPARSER_SoaRecord *soa;
396   struct GNUNET_TUN_DnsSoaRecord soa_bin;
397   size_t old_off;
398
399   old_off = *off;
400   soa = GNUNET_new (struct GNUNET_DNSPARSER_SoaRecord);
401   soa->mname = GNUNET_DNSPARSER_parse_name (udp_payload,
402                                             udp_payload_length,
403                                             off);
404   soa->rname = GNUNET_DNSPARSER_parse_name (udp_payload,
405                                             udp_payload_length,
406                                             off);
407   if ( (NULL == soa->mname) ||
408        (NULL == soa->rname) ||
409        (*off + sizeof (struct GNUNET_TUN_DnsSoaRecord) > udp_payload_length) )
410   {
411     GNUNET_break_op (0);
412     GNUNET_DNSPARSER_free_soa (soa);
413     *off = old_off;
414     return NULL;
415   }
416   memcpy (&soa_bin,
417           &udp_payload[*off],
418           sizeof (struct GNUNET_TUN_DnsSoaRecord));
419   soa->serial = ntohl (soa_bin.serial);
420   soa->refresh = ntohl (soa_bin.refresh);
421   soa->retry = ntohl (soa_bin.retry);
422   soa->expire = ntohl (soa_bin.expire);
423   soa->minimum_ttl = ntohl (soa_bin.minimum);
424   (*off) += sizeof (struct GNUNET_TUN_DnsSoaRecord);
425   return soa;
426 }
427
428
429 /**
430  * Parse a DNS MX record.
431  *
432  * @param udp_payload reference to UDP packet
433  * @param udp_payload_length length of @a udp_payload
434  * @param off pointer to the offset of the query to parse in the MX record (to be
435  *                    incremented by the size of the record), unchanged on error
436  * @return the parsed MX record, NULL on error
437  */
438 struct GNUNET_DNSPARSER_MxRecord *
439 GNUNET_DNSPARSER_parse_mx (const char *udp_payload,
440                            size_t udp_payload_length,
441                            size_t *off)
442 {
443   struct GNUNET_DNSPARSER_MxRecord *mx;
444   uint16_t mxpref;
445   size_t old_off;
446
447   old_off = *off;
448   if (*off + sizeof (uint16_t) > udp_payload_length)
449   {
450     GNUNET_break_op (0);
451     return NULL;
452   }
453   memcpy (&mxpref, &udp_payload[*off], sizeof (uint16_t));
454   (*off) += sizeof (uint16_t);
455   mx = GNUNET_new (struct GNUNET_DNSPARSER_MxRecord);
456   mx->preference = ntohs (mxpref);
457   mx->mxhost = GNUNET_DNSPARSER_parse_name (udp_payload,
458                                             udp_payload_length,
459                                             off);
460   if (NULL == mx->mxhost)
461   {
462     GNUNET_break_op (0);
463     GNUNET_DNSPARSER_free_mx (mx);
464     *off = old_off;
465     return NULL;
466   }
467   return mx;
468 }
469
470
471 /**
472  * Parse a DNS SRV record.
473  *
474  * @param r_name name of the SRV record
475  * @param udp_payload reference to UDP packet
476  * @param udp_payload_length length of @a udp_payload
477  * @param off pointer to the offset of the query to parse in the SRV record (to be
478  *                    incremented by the size of the record), unchanged on error
479  * @return the parsed SRV record, NULL on error
480  */
481 struct GNUNET_DNSPARSER_SrvRecord *
482 GNUNET_DNSPARSER_parse_srv (const char *r_name,
483                             const char *udp_payload,
484                             size_t udp_payload_length,
485                             size_t *off)
486 {
487   struct GNUNET_DNSPARSER_SrvRecord *srv;
488   struct GNUNET_TUN_DnsSrvRecord srv_bin;
489   size_t old_off;
490   char *ndup;
491   char *tok;
492
493   if ('_' != *r_name)
494     return NULL; /* all valid srv names must start with "_" */
495   if (NULL == strstr (r_name, "._"))
496     return NULL; /* necessary string from "._$PROTO" not present */
497   old_off = *off;
498   if (*off + sizeof (struct GNUNET_TUN_DnsSrvRecord) > udp_payload_length)
499     return NULL;
500   memcpy (&srv_bin,
501           &udp_payload[*off],
502           sizeof (struct GNUNET_TUN_DnsSrvRecord));
503   (*off) += sizeof (struct GNUNET_TUN_DnsSrvRecord);
504   srv = GNUNET_new (struct GNUNET_DNSPARSER_SrvRecord);
505   srv->priority = ntohs (srv_bin.prio);
506   srv->weight = ntohs (srv_bin.weight);
507   srv->port = ntohs (srv_bin.port);
508   /* parse 'data.hostname' into components, which are
509      "_$SERVICE._$PROTO.$DOMAIN_NAME" */
510   ndup = GNUNET_strdup (r_name);
511   tok = strtok (ndup, ".");
512   GNUNET_assert (NULL != tok);
513   GNUNET_assert ('_' == *tok);
514   srv->service = GNUNET_strdup (&tok[1]);
515   tok = strtok (NULL, ".");
516   if ( (NULL == tok) || ('_' != *tok) )
517   {
518     GNUNET_DNSPARSER_free_srv (srv);
519     GNUNET_free (ndup);
520     *off = old_off;
521     return NULL;
522   }
523   srv->proto = GNUNET_strdup (&tok[1]);
524   tok = strtok (NULL, ".");
525   if (NULL == tok)
526   {
527     GNUNET_DNSPARSER_free_srv (srv);
528     GNUNET_free (ndup);
529     *off = old_off;
530     return NULL;
531   }
532   srv->domain_name = GNUNET_strdup (tok);
533   GNUNET_free (ndup);
534   srv->target = GNUNET_DNSPARSER_parse_name (udp_payload,
535                                              udp_payload_length,
536                                              off);
537   if (NULL == srv->target)
538   {
539     GNUNET_DNSPARSER_free_srv (srv);
540     *off = old_off;
541     return NULL;
542   }
543   return srv;
544 }
545
546
547 /**
548  * Parse a DNS record entry.
549  *
550  * @param udp_payload entire UDP payload
551  * @param udp_payload_length length of @a udp_payload
552  * @param off pointer to the offset of the record to parse in the udp_payload (to be
553  *                    incremented by the size of the record)
554  * @param r where to write the record information
555  * @return #GNUNET_OK on success, #GNUNET_SYSERR if the record is malformed
556  */
557 int
558 GNUNET_DNSPARSER_parse_record (const char *udp_payload,
559                                size_t udp_payload_length,
560                                size_t *off,
561                                struct GNUNET_DNSPARSER_Record *r)
562 {
563   char *name;
564   struct GNUNET_TUN_DnsRecordLine rl;
565   size_t old_off;
566   uint16_t data_len;
567
568   name = GNUNET_DNSPARSER_parse_name (udp_payload,
569                                       udp_payload_length,
570                                       off);
571   if (NULL == name)
572   {
573     GNUNET_break_op (0);
574     return GNUNET_SYSERR;
575   }
576   r->name = name;
577   if (*off + sizeof (struct GNUNET_TUN_DnsRecordLine) > udp_payload_length)
578   {
579     GNUNET_break_op (0);
580     return GNUNET_SYSERR;
581   }
582   memcpy (&rl, &udp_payload[*off], sizeof (rl));
583   (*off) += sizeof (rl);
584   r->type = ntohs (rl.type);
585   r->dns_traffic_class = ntohs (rl.dns_traffic_class);
586   r->expiration_time = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
587                                                                                         ntohl (rl.ttl)));
588   data_len = ntohs (rl.data_len);
589   if (*off + data_len > udp_payload_length)
590   {
591     GNUNET_break_op (0);
592     return GNUNET_SYSERR;
593   }
594   old_off = *off;
595   switch (r->type)
596   {
597   case GNUNET_DNSPARSER_TYPE_NS:
598   case GNUNET_DNSPARSER_TYPE_CNAME:
599   case GNUNET_DNSPARSER_TYPE_PTR:
600     r->data.hostname = GNUNET_DNSPARSER_parse_name (udp_payload,
601                                                     udp_payload_length,
602                                                     off);
603     if ( (NULL == r->data.hostname) ||
604          (old_off + data_len != *off) )
605       return GNUNET_SYSERR;
606     return GNUNET_OK;
607   case GNUNET_DNSPARSER_TYPE_SOA:
608     r->data.soa = GNUNET_DNSPARSER_parse_soa (udp_payload,
609                                               udp_payload_length,
610                                               off);
611     if ( (NULL == r->data.soa) ||
612          (old_off + data_len != *off) )
613     {
614       GNUNET_break_op (0);
615       return GNUNET_SYSERR;
616     }
617     return GNUNET_OK;
618   case GNUNET_DNSPARSER_TYPE_MX:
619     r->data.mx = GNUNET_DNSPARSER_parse_mx (udp_payload,
620                                             udp_payload_length,
621                                             off);
622     if ( (NULL == r->data.mx) ||
623          (old_off + data_len != *off) )
624     {
625       GNUNET_break_op (0);
626       return GNUNET_SYSERR;
627     }
628     return GNUNET_OK;
629   case GNUNET_DNSPARSER_TYPE_SRV:
630     r->data.srv = GNUNET_DNSPARSER_parse_srv (r->name,
631                                               udp_payload,
632                                               udp_payload_length,
633                                               off);
634     if ( (NULL == r->data.srv) ||
635          (old_off + data_len != *off) )
636     {
637       GNUNET_break_op (0);
638       return GNUNET_SYSERR;
639     }
640     return GNUNET_OK;
641   default:
642     r->data.raw.data = GNUNET_malloc (data_len);
643     r->data.raw.data_len = data_len;
644     memcpy (r->data.raw.data, &udp_payload[*off], data_len);
645     break;
646   }
647   (*off) += data_len;
648   return GNUNET_OK;
649 }
650
651
652 /**
653  * Parse a UDP payload of a DNS packet in to a nice struct for further
654  * processing and manipulation.
655  *
656  * @param udp_payload wire-format of the DNS packet
657  * @param udp_payload_length number of bytes in @a udp_payload
658  * @return NULL on error, otherwise the parsed packet
659  */
660 struct GNUNET_DNSPARSER_Packet *
661 GNUNET_DNSPARSER_parse (const char *udp_payload,
662                         size_t udp_payload_length)
663 {
664   struct GNUNET_DNSPARSER_Packet *p;
665   const struct GNUNET_TUN_DnsHeader *dns;
666   size_t off;
667   unsigned int n;
668   unsigned int i;
669
670   if (udp_payload_length < sizeof (struct GNUNET_TUN_DnsHeader))
671     return NULL;
672   dns = (const struct GNUNET_TUN_DnsHeader *) udp_payload;
673   off = sizeof (struct GNUNET_TUN_DnsHeader);
674   p = GNUNET_new (struct GNUNET_DNSPARSER_Packet);
675   p->flags = dns->flags;
676   p->id = dns->id;
677   n = ntohs (dns->query_count);
678   if (n > 0)
679   {
680     p->queries = GNUNET_malloc (n * sizeof (struct GNUNET_DNSPARSER_Query));
681     p->num_queries = n;
682     for (i=0;i<n;i++)
683       if (GNUNET_OK !=
684           GNUNET_DNSPARSER_parse_query (udp_payload,
685                                         udp_payload_length,
686                                         &off,
687                                         &p->queries[i]))
688         goto error;
689   }
690   n = ntohs (dns->answer_rcount);
691   if (n > 0)
692   {
693     p->answers = GNUNET_malloc (n * sizeof (struct GNUNET_DNSPARSER_Record));
694     p->num_answers = n;
695     for (i=0;i<n;i++)
696       if (GNUNET_OK !=
697           GNUNET_DNSPARSER_parse_record (udp_payload,
698                                          udp_payload_length,
699                                          &off,
700                                          &p->answers[i]))
701         goto error;
702   }
703   n = ntohs (dns->authority_rcount);
704   if (n > 0)
705   {
706     p->authority_records = GNUNET_malloc (n * sizeof (struct GNUNET_DNSPARSER_Record));
707     p->num_authority_records = n;
708     for (i=0;i<n;i++)
709       if (GNUNET_OK !=
710           GNUNET_DNSPARSER_parse_record (udp_payload,
711                                          udp_payload_length,
712                                          &off,
713                                          &p->authority_records[i]))
714         goto error;
715   }
716   n = ntohs (dns->additional_rcount);
717   if (n > 0)
718   {
719     p->additional_records = GNUNET_malloc (n * sizeof (struct GNUNET_DNSPARSER_Record));
720     p->num_additional_records = n;
721     for (i=0;i<n;i++)
722       if (GNUNET_OK !=
723           GNUNET_DNSPARSER_parse_record (udp_payload,
724                                          udp_payload_length,
725                                          &off,
726                                          &p->additional_records[i]))
727         goto error;
728   }
729   return p;
730  error:
731   GNUNET_break_op (0);
732   GNUNET_DNSPARSER_free_packet (p);
733   return NULL;
734 }
735
736
737 /**
738  * Free memory taken by a packet.
739  *
740  * @param p packet to free
741  */
742 void
743 GNUNET_DNSPARSER_free_packet (struct GNUNET_DNSPARSER_Packet *p)
744 {
745   unsigned int i;
746
747   for (i=0;i<p->num_queries;i++)
748     GNUNET_free_non_null (p->queries[i].name);
749   GNUNET_free_non_null (p->queries);
750   for (i=0;i<p->num_answers;i++)
751     GNUNET_DNSPARSER_free_record (&p->answers[i]);
752   GNUNET_free_non_null (p->answers);
753   for (i=0;i<p->num_authority_records;i++)
754     GNUNET_DNSPARSER_free_record (&p->authority_records[i]);
755   GNUNET_free_non_null (p->authority_records);
756   for (i=0;i<p->num_additional_records;i++)
757     GNUNET_DNSPARSER_free_record (&p->additional_records[i]);
758   GNUNET_free_non_null (p->additional_records);
759   GNUNET_free (p);
760 }
761
762
763 /* ********************** DNS packet assembly code **************** */
764
765
766 /**
767  * Add a DNS name to the UDP packet at the given location, converting
768  * the name to IDNA notation as necessary.
769  *
770  * @param dst where to write the name (UDP packet)
771  * @param dst_len number of bytes in @a dst
772  * @param off pointer to offset where to write the name (increment by bytes used)
773  *            must not be changed if there is an error
774  * @param name name to write
775  * @return #GNUNET_SYSERR if @a name is invalid
776  *         #GNUNET_NO if @a name did not fit
777  *         #GNUNET_OK if @a name was added to @a dst
778  */
779 int
780 GNUNET_DNSPARSER_builder_add_name (char *dst,
781                                    size_t dst_len,
782                                    size_t *off,
783                                    const char *name)
784 {
785   const char *dot;
786   const char *idna_name;
787   char *idna_start;
788   size_t start;
789   size_t pos;
790   size_t len;
791   Idna_rc rc;
792
793   if (NULL == name)
794     return GNUNET_SYSERR;
795
796   if (IDNA_SUCCESS !=
797       (rc = idna_to_ascii_8z (name, &idna_start, IDNA_ALLOW_UNASSIGNED)))
798   {
799     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
800                 _("Failed to convert UTF-8 name `%s' to DNS IDNA format: %s\n"),
801                 name,
802                 idna_strerror (rc));
803     return GNUNET_NO;
804   }
805   idna_name = idna_start;
806   start = *off;
807   if (start + strlen (idna_name) + 2 > dst_len)
808     goto fail;
809   pos = start;
810   do
811   {
812     dot = strchr (idna_name, '.');
813     if (NULL == dot)
814       len = strlen (idna_name);
815     else
816       len = dot - idna_name;
817     if ( (len >= 64) || (0 == len) )
818     {
819       GNUNET_break (0);
820       goto fail; /* segment too long or empty */
821     }
822     dst[pos++] = (char) (uint8_t) len;
823     memcpy (&dst[pos], idna_name, len);
824     pos += len;
825     idna_name += len + 1; /* also skip dot */
826   }
827   while (NULL != dot);
828   dst[pos++] = '\0'; /* terminator */
829   *off = pos;
830 #if WINDOWS
831   idn_free (idna_start);
832 #else
833   free (idna_start);
834 #endif
835   return GNUNET_OK;
836  fail:
837 #if WINDOWS
838   idn_free (idna_start);
839 #else
840   free (idna_start);
841 #endif
842   return GNUNET_NO;
843 }
844
845
846 /**
847  * Add a DNS query to the UDP packet at the given location.
848  *
849  * @param dst where to write the query
850  * @param dst_len number of bytes in @a dst
851  * @param off pointer to offset where to write the query (increment by bytes used)
852  *            must not be changed if there is an error
853  * @param query query to write
854  * @return #GNUNET_SYSERR if @a query is invalid
855  *         #GNUNET_NO if @a query did not fit
856  *         #GNUNET_OK if @a query was added to @a dst
857  */
858 int
859 GNUNET_DNSPARSER_builder_add_query (char *dst,
860                                     size_t dst_len,
861                                     size_t *off,
862                                     const struct GNUNET_DNSPARSER_Query *query)
863 {
864   int ret;
865   struct GNUNET_TUN_DnsQueryLine ql;
866
867   ret = GNUNET_DNSPARSER_builder_add_name (dst, dst_len - sizeof (struct GNUNET_TUN_DnsQueryLine), off, query->name);
868   if (ret != GNUNET_OK)
869     return ret;
870   ql.type = htons (query->type);
871   ql.dns_traffic_class = htons (query->dns_traffic_class);
872   memcpy (&dst[*off], &ql, sizeof (ql));
873   (*off) += sizeof (ql);
874   return GNUNET_OK;
875 }
876
877
878 /**
879  * Add an MX record to the UDP packet at the given location.
880  *
881  * @param dst where to write the mx record
882  * @param dst_len number of bytes in @a dst
883  * @param off pointer to offset where to write the mx information (increment by bytes used);
884  *            can also change if there was an error
885  * @param mx mx information to write
886  * @return #GNUNET_SYSERR if @a mx is invalid
887  *         #GNUNET_NO if @a mx did not fit
888  *         #GNUNET_OK if @a mx was added to @a dst
889  */
890 int
891 GNUNET_DNSPARSER_builder_add_mx (char *dst,
892                                  size_t dst_len,
893                                  size_t *off,
894                                  const struct GNUNET_DNSPARSER_MxRecord *mx)
895 {
896   uint16_t mxpref;
897
898   if (*off + sizeof (uint16_t) > dst_len)
899     return GNUNET_NO;
900   mxpref = htons (mx->preference);
901   memcpy (&dst[*off], &mxpref, sizeof (mxpref));
902   (*off) += sizeof (mxpref);
903   return GNUNET_DNSPARSER_builder_add_name (dst, dst_len, off, mx->mxhost);
904 }
905
906
907 /**
908  * Add an SOA record to the UDP packet at the given location.
909  *
910  * @param dst where to write the SOA record
911  * @param dst_len number of bytes in @a dst
912  * @param off pointer to offset where to write the SOA information (increment by bytes used)
913  *            can also change if there was an error
914  * @param soa SOA information to write
915  * @return #GNUNET_SYSERR if @a soa is invalid
916  *         #GNUNET_NO if @a soa did not fit
917  *         #GNUNET_OK if @a soa was added to @a dst
918  */
919 int
920 GNUNET_DNSPARSER_builder_add_soa (char *dst,
921                                   size_t dst_len,
922                                   size_t *off,
923                                   const struct GNUNET_DNSPARSER_SoaRecord *soa)
924 {
925   struct GNUNET_TUN_DnsSoaRecord sd;
926   int ret;
927
928   if ( (GNUNET_OK != (ret = GNUNET_DNSPARSER_builder_add_name (dst,
929                                       dst_len,
930                                       off,
931                                       soa->mname))) ||
932        (GNUNET_OK != (ret = GNUNET_DNSPARSER_builder_add_name (dst,
933                                       dst_len,
934                                       off,
935                                       soa->rname)) ) )
936     return ret;
937   if (*off + sizeof (struct GNUNET_TUN_DnsSoaRecord) > dst_len)
938     return GNUNET_NO;
939   sd.serial = htonl (soa->serial);
940   sd.refresh = htonl (soa->refresh);
941   sd.retry = htonl (soa->retry);
942   sd.expire = htonl (soa->expire);
943   sd.minimum = htonl (soa->minimum_ttl);
944   memcpy (&dst[*off], &sd, sizeof (sd));
945   (*off) += sizeof (sd);
946   return GNUNET_OK;
947 }
948
949
950 /**
951  * Add an SRV record to the UDP packet at the given location.
952  *
953  * @param dst where to write the SRV record
954  * @param dst_len number of bytes in @a dst
955  * @param off pointer to offset where to write the SRV information (increment by bytes used)
956  *            can also change if there was an error
957  * @param srv SRV information to write
958  * @return #GNUNET_SYSERR if @a srv is invalid
959  *         #GNUNET_NO if @a srv did not fit
960  *         #GNUNET_OK if @a srv was added to @a dst
961  */
962 int
963 GNUNET_DNSPARSER_builder_add_srv (char *dst,
964                                   size_t dst_len,
965                                   size_t *off,
966                                   const struct GNUNET_DNSPARSER_SrvRecord *srv)
967 {
968   struct GNUNET_TUN_DnsSrvRecord sd;
969   int ret;
970
971   if (*off + sizeof (struct GNUNET_TUN_DnsSrvRecord) > dst_len)
972     return GNUNET_NO;
973   sd.prio = htons (srv->priority);
974   sd.weight = htons (srv->weight);
975   sd.port = htons (srv->port);
976   memcpy (&dst[*off], &sd, sizeof (sd));
977   (*off) += sizeof (sd);
978   if (GNUNET_OK != (ret = GNUNET_DNSPARSER_builder_add_name (dst,
979                                     dst_len,
980                                     off,
981                                     srv->target)))
982     return ret;
983   return GNUNET_OK;
984 }
985
986
987 /**
988  * Add a DNS record to the UDP packet at the given location.
989  *
990  * @param dst where to write the query
991  * @param dst_len number of bytes in @a dst
992  * @param off pointer to offset where to write the query (increment by bytes used)
993  *            must not be changed if there is an error
994  * @param record record to write
995  * @return #GNUNET_SYSERR if @a record is invalid
996  *         #GNUNET_NO if @a record did not fit
997  *         #GNUNET_OK if @a record was added to @a dst
998  */
999 static int
1000 add_record (char *dst,
1001             size_t dst_len,
1002             size_t *off,
1003             const struct GNUNET_DNSPARSER_Record *record)
1004 {
1005   int ret;
1006   size_t start;
1007   size_t pos;
1008   struct GNUNET_TUN_DnsRecordLine rl;
1009   char *name;
1010
1011   start = *off;
1012   /* for SRV records, we can create the name from the details
1013      of the record if needed */
1014   name = record->name;
1015   if  ( (GNUNET_DNSPARSER_TYPE_SRV == record->type) &&
1016         (NULL == name) )
1017     GNUNET_asprintf (&name,
1018                      "_%s._%s.%s",
1019                      record->data.srv->service,
1020                      record->data.srv->proto,
1021                      record->data.srv->domain_name);
1022   ret = GNUNET_DNSPARSER_builder_add_name (dst, dst_len - sizeof (struct GNUNET_TUN_DnsRecordLine), off, name);
1023   if (name != record->name)
1024     GNUNET_free (name);
1025   if (GNUNET_OK != ret)
1026     return ret;
1027   /* '*off' is now the position where we will need to write the record line */
1028
1029   pos = *off + sizeof (struct GNUNET_TUN_DnsRecordLine);
1030   switch (record->type)
1031   {
1032   case GNUNET_DNSPARSER_TYPE_MX:
1033     ret = GNUNET_DNSPARSER_builder_add_mx (dst, dst_len, &pos, record->data.mx);
1034     break;
1035   case GNUNET_DNSPARSER_TYPE_SOA:
1036     ret = GNUNET_DNSPARSER_builder_add_soa (dst, dst_len, &pos, record->data.soa);
1037     break;
1038   case GNUNET_DNSPARSER_TYPE_NS:
1039   case GNUNET_DNSPARSER_TYPE_CNAME:
1040   case GNUNET_DNSPARSER_TYPE_PTR:
1041     ret = GNUNET_DNSPARSER_builder_add_name (dst, dst_len, &pos, record->data.hostname);
1042     break;
1043   case GNUNET_DNSPARSER_TYPE_SRV:
1044     ret = GNUNET_DNSPARSER_builder_add_srv (dst, dst_len, &pos, record->data.srv);
1045     break;
1046   default:
1047     if (pos + record->data.raw.data_len > dst_len)
1048     {
1049       ret = GNUNET_NO;
1050       break;
1051     }
1052     memcpy (&dst[pos], record->data.raw.data, record->data.raw.data_len);
1053     pos += record->data.raw.data_len;
1054     ret = GNUNET_OK;
1055     break;
1056   }
1057   if (GNUNET_OK != ret)
1058   {
1059     *off = start;
1060     return GNUNET_NO;
1061   }
1062
1063   if (pos - (*off + sizeof (struct GNUNET_TUN_DnsRecordLine)) > UINT16_MAX)
1064   {
1065     /* record data too long */
1066     *off = start;
1067     return GNUNET_NO;
1068   }
1069   rl.type = htons (record->type);
1070   rl.dns_traffic_class = htons (record->dns_traffic_class);
1071   rl.ttl = htonl (GNUNET_TIME_absolute_get_remaining (record->expiration_time).rel_value_us / 1000LL / 1000LL); /* in seconds */
1072   rl.data_len = htons ((uint16_t) (pos - (*off + sizeof (struct GNUNET_TUN_DnsRecordLine))));
1073   memcpy (&dst[*off], &rl, sizeof (struct GNUNET_TUN_DnsRecordLine));
1074   *off = pos;
1075   return GNUNET_OK;
1076 }
1077
1078
1079 /**
1080  * Given a DNS packet @a p, generate the corresponding UDP payload.
1081  * Note that we do not attempt to pack the strings with pointers
1082  * as this would complicate the code and this is about being
1083  * simple and secure, not fast, fancy and broken like bind.
1084  *
1085  * @param p packet to pack
1086  * @param max maximum allowed size for the resulting UDP payload
1087  * @param buf set to a buffer with the packed message
1088  * @param buf_length set to the length of @a buf
1089  * @return #GNUNET_SYSERR if @a p is invalid
1090  *         #GNUNET_NO if @a p was truncated (but there is still a result in @a buf)
1091  *         #GNUNET_OK if @a p was packed completely into @a buf
1092  */
1093 int
1094 GNUNET_DNSPARSER_pack (const struct GNUNET_DNSPARSER_Packet *p,
1095                        uint16_t max,
1096                        char **buf,
1097                        size_t *buf_length)
1098 {
1099   struct GNUNET_TUN_DnsHeader dns;
1100   size_t off;
1101   char tmp[max];
1102   unsigned int i;
1103   int ret;
1104   int trc;
1105
1106   if ( (p->num_queries > UINT16_MAX) ||
1107        (p->num_answers > UINT16_MAX) ||
1108        (p->num_authority_records > UINT16_MAX) ||
1109        (p->num_additional_records > UINT16_MAX) )
1110     return GNUNET_SYSERR;
1111   dns.id = p->id;
1112   dns.flags = p->flags;
1113   dns.query_count = htons (p->num_queries);
1114   dns.answer_rcount = htons (p->num_answers);
1115   dns.authority_rcount = htons (p->num_authority_records);
1116   dns.additional_rcount = htons (p->num_additional_records);
1117
1118   off = sizeof (struct GNUNET_TUN_DnsHeader);
1119   trc = GNUNET_NO;
1120   for (i=0;i<p->num_queries;i++)
1121   {
1122     ret = GNUNET_DNSPARSER_builder_add_query (tmp, sizeof (tmp), &off, &p->queries[i]);
1123     if (GNUNET_SYSERR == ret)
1124       return GNUNET_SYSERR;
1125     if (GNUNET_NO == ret)
1126     {
1127       dns.query_count = htons ((uint16_t) (i-1));
1128       trc = GNUNET_YES;
1129       break;
1130     }
1131   }
1132   for (i=0;i<p->num_answers;i++)
1133   {
1134     ret = add_record (tmp, sizeof (tmp), &off, &p->answers[i]);
1135     if (GNUNET_SYSERR == ret)
1136       return GNUNET_SYSERR;
1137     if (GNUNET_NO == ret)
1138     {
1139       dns.answer_rcount = htons ((uint16_t) (i-1));
1140       trc = GNUNET_YES;
1141       break;
1142     }
1143   }
1144   for (i=0;i<p->num_authority_records;i++)
1145   {
1146     ret = add_record (tmp, sizeof (tmp), &off, &p->authority_records[i]);
1147     if (GNUNET_SYSERR == ret)
1148       return GNUNET_SYSERR;
1149     if (GNUNET_NO == ret)
1150     {
1151       dns.authority_rcount = htons ((uint16_t) (i-1));
1152       trc = GNUNET_YES;
1153       break;
1154     }
1155   }
1156   for (i=0;i<p->num_additional_records;i++)
1157   {
1158     ret = add_record (tmp, sizeof (tmp), &off, &p->additional_records[i]);
1159     if (GNUNET_SYSERR == ret)
1160       return GNUNET_SYSERR;
1161     if (GNUNET_NO == ret)
1162     {
1163       dns.additional_rcount = htons (i-1);
1164       trc = GNUNET_YES;
1165       break;
1166     }
1167   }
1168
1169   if (GNUNET_YES == trc)
1170     dns.flags.message_truncated = 1;
1171   memcpy (tmp, &dns, sizeof (struct GNUNET_TUN_DnsHeader));
1172
1173   *buf = GNUNET_malloc (off);
1174   *buf_length = off;
1175   memcpy (*buf, tmp, off);
1176   if (GNUNET_YES == trc)
1177     return GNUNET_NO;
1178   return GNUNET_OK;
1179 }
1180
1181 /* end of dnsparser.c */