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