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