doxygen
[oweals/gnunet.git] / src / dns / dnsparser.c
1 /*
2       This file is part of GNUnet
3       (C) 2010, 2011, 2012 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 2, 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 "dnsparser.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_USE_STD3_ASCII_RULES))
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_USE_STD3_ASCII_RULES))
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 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_USE_STD3_ASCII_RULES)))
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 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 query_line 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 query_line) > 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 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 record_line rl;
274   size_t old_off;
275   struct soa_data soa;
276   uint16_t mxpref;
277   uint16_t data_len;
278   struct srv_data 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 record_line) > 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_malloc (sizeof (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 soa_data) > udp_payload_length) )
324       return GNUNET_SYSERR;
325     memcpy (&soa, &udp_payload[*off], sizeof (struct soa_data));
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 soa_data);
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_malloc (sizeof (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 srv_data) > udp_payload_length)
356       return GNUNET_SYSERR;
357     memcpy (&srv, &udp_payload[*off], sizeof (struct srv_data));    
358     (*off) += sizeof (struct srv_data);
359     r->data.srv = GNUNET_malloc (sizeof (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 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_malloc (sizeof (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 static void
538 free_record (struct GNUNET_DNSPARSER_Record *r)
539 {
540   GNUNET_free_non_null (r->name);
541   switch (r->type)
542   {
543   case GNUNET_DNSPARSER_TYPE_MX:
544     free_mx (r->data.mx);
545     break;
546   case GNUNET_DNSPARSER_TYPE_SOA:
547     free_soa (r->data.soa);
548     break;
549   case GNUNET_DNSPARSER_TYPE_SRV:
550     free_srv (r->data.srv);
551     break;
552   case GNUNET_DNSPARSER_TYPE_NS:
553   case GNUNET_DNSPARSER_TYPE_CNAME:
554   case GNUNET_DNSPARSER_TYPE_PTR:
555     GNUNET_free_non_null (r->data.hostname);
556     break;
557   default:
558     GNUNET_free_non_null (r->data.raw.data);
559     break;
560   }
561 }
562
563
564 /**
565  * Free memory taken by a packet.
566  *
567  * @param p packet to free
568  */
569 void
570 GNUNET_DNSPARSER_free_packet (struct GNUNET_DNSPARSER_Packet *p)
571 {
572   unsigned int i;
573
574   for (i=0;i<p->num_queries;i++)
575     GNUNET_free_non_null (p->queries[i].name);
576   GNUNET_free_non_null (p->queries);
577   for (i=0;i<p->num_answers;i++)
578     free_record (&p->answers[i]);
579   GNUNET_free_non_null (p->answers);
580   for (i=0;i<p->num_authority_records;i++)
581     free_record (&p->authority_records[i]);
582   GNUNET_free_non_null (p->authority_records);
583   for (i=0;i<p->num_additional_records;i++)
584     free_record (&p->additional_records[i]);
585   GNUNET_free_non_null (p->additional_records);
586   GNUNET_free (p);
587 }
588
589
590 /* ********************** DNS packet assembly code **************** */
591
592
593 /**
594  * Add a DNS name to the UDP packet at the given location.
595  *
596  * @param dst where to write the name
597  * @param dst_len number of bytes in dst
598  * @param off pointer to offset where to write the name (increment by bytes used)
599  *            must not be changed if there is an error
600  * @param name name to write
601  * @return GNUNET_SYSERR if 'name' is invalid
602  *         GNUNET_NO if 'name' did not fit
603  *         GNUNET_OK if 'name' was added to 'dst'
604  */
605 static int
606 add_name (char *dst,
607           size_t dst_len,
608           size_t *off,
609           const char *name)
610 {
611   const char *dot;
612   const char *idna_name;
613   char *idna_start;
614   size_t start;
615   size_t pos;
616   size_t len;
617   Idna_rc rc;
618
619   if (NULL == name)
620     return GNUNET_SYSERR;
621
622   if (IDNA_SUCCESS != 
623       (rc = idna_to_ascii_8z (name, &idna_start, IDNA_USE_STD3_ASCII_RULES)))
624   {
625     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
626                 _("Failed to convert UTF-8 name `%s' to DNS IDNA format: %s\n"),
627                 name,
628                 idna_strerror (rc));
629     return GNUNET_NO;
630   }
631   idna_name = idna_start;
632   start = *off;
633   if (start + strlen (idna_name) + 2 > dst_len)
634     goto fail;
635   pos = start;
636   do
637   {
638     dot = strchr (idna_name, '.');
639     if (NULL == dot)
640       len = strlen (idna_name);
641     else
642       len = dot - idna_name;
643     if ( (len >= 64) || (len == 0) )
644       goto fail; /* segment too long or empty */  
645     dst[pos++] = (char) (uint8_t) len;
646     memcpy (&dst[pos], idna_name, len);
647     pos += len;
648     idna_name += len + 1; /* also skip dot */
649   }
650   while (NULL != dot);
651   dst[pos++] = '\0'; /* terminator */
652   *off = pos;
653 #if WINDOWS
654   idn_free (idna_start);
655 #else
656   free (idna_start);
657 #endif
658   return GNUNET_OK;
659  fail:
660 #if WINDOWS
661   idn_free (idna_start);
662 #else
663   free (idna_start);
664 #endif
665   return GNUNET_NO; 
666 }
667
668
669 /**
670  * Add a DNS query to the UDP packet at the given location.
671  *
672  * @param dst where to write the query
673  * @param dst_len number of bytes in dst
674  * @param off pointer to offset where to write the query (increment by bytes used)
675  *            must not be changed if there is an error
676  * @param query query to write
677  * @return GNUNET_SYSERR if 'query' is invalid
678  *         GNUNET_NO if 'query' did not fit
679  *         GNUNET_OK if 'query' was added to 'dst'
680  */
681 static int
682 add_query (char *dst,
683            size_t dst_len,
684            size_t *off,
685            const struct GNUNET_DNSPARSER_Query *query)
686 {
687   int ret;
688   struct query_line ql;
689
690   ret = add_name (dst, dst_len - sizeof (struct query_line), off, query->name);
691   if (ret != GNUNET_OK)
692     return ret;
693   ql.type = htons (query->type);
694   ql.class = htons (query->class);
695   memcpy (&dst[*off], &ql, sizeof (ql));
696   (*off) += sizeof (ql);
697   return GNUNET_OK;
698 }
699
700
701 /**
702  * Add an MX record to the UDP packet at the given location.
703  *
704  * @param dst where to write the mx record
705  * @param dst_len number of bytes in dst
706  * @param off pointer to offset where to write the mx information (increment by bytes used);
707  *            can also change if there was an error
708  * @param mx mx information to write
709  * @return GNUNET_SYSERR if 'mx' is invalid
710  *         GNUNET_NO if 'mx' did not fit
711  *         GNUNET_OK if 'mx' was added to 'dst'
712  */
713 static int
714 add_mx (char *dst,
715         size_t dst_len,
716         size_t *off,
717         const struct GNUNET_DNSPARSER_MxRecord *mx)
718 {
719   uint16_t mxpref;
720
721   if (*off + sizeof (uint16_t) > dst_len)
722     return GNUNET_NO;
723   mxpref = htons (mx->preference);
724   memcpy (&dst[*off], &mxpref, sizeof (mxpref));
725   (*off) += sizeof (mxpref);
726   return add_name (dst, dst_len, off, mx->mxhost);
727 }
728
729
730 /**
731  * Add an SOA record to the UDP packet at the given location.
732  *
733  * @param dst where to write the SOA record
734  * @param dst_len number of bytes in dst
735  * @param off pointer to offset where to write the SOA information (increment by bytes used)
736  *            can also change if there was an error
737  * @param soa SOA information to write
738  * @return GNUNET_SYSERR if 'soa' is invalid
739  *         GNUNET_NO if 'soa' did not fit
740  *         GNUNET_OK if 'soa' was added to 'dst'
741  */
742 static int
743 add_soa (char *dst,
744          size_t dst_len,
745          size_t *off,
746          const struct GNUNET_DNSPARSER_SoaRecord *soa)
747 {
748   struct soa_data sd;
749   int ret;
750
751   if ( (GNUNET_OK != (ret = add_name (dst,
752                                       dst_len,
753                                       off,
754                                       soa->mname))) ||
755        (GNUNET_OK != (ret = add_name (dst,
756                                       dst_len,
757                                       off,
758                                       soa->rname)) ) )
759     return ret;
760   if (*off + sizeof (struct soa_data) > dst_len)
761     return GNUNET_NO;
762   sd.serial = htonl (soa->serial);
763   sd.refresh = htonl (soa->refresh);
764   sd.retry = htonl (soa->retry);
765   sd.expire = htonl (soa->expire);
766   sd.minimum = htonl (soa->minimum_ttl);
767   memcpy (&dst[*off], &sd, sizeof (sd));
768   (*off) += sizeof (sd);
769   return GNUNET_OK;
770 }
771
772
773 /**
774  * Add an SRV record to the UDP packet at the given location.
775  *
776  * @param dst where to write the SRV record
777  * @param dst_len number of bytes in dst
778  * @param off pointer to offset where to write the SRV information (increment by bytes used)
779  *            can also change if there was an error
780  * @param srv SRV information to write
781  * @return GNUNET_SYSERR if 'srv' is invalid
782  *         GNUNET_NO if 'srv' did not fit
783  *         GNUNET_OK if 'srv' was added to 'dst'
784  */
785 static int
786 add_srv (char *dst,
787          size_t dst_len,
788          size_t *off,
789          const struct GNUNET_DNSPARSER_SrvRecord *srv)
790 {
791   struct srv_data sd;
792   int ret;
793
794   if (*off + sizeof (struct srv_data) > dst_len)
795     return GNUNET_NO;
796   sd.prio = htons (srv->priority);
797   sd.weight = htons (srv->weight);
798   sd.port = htons (srv->port);
799   memcpy (&dst[*off], &sd, sizeof (sd));
800   (*off) += sizeof (sd);
801   if (GNUNET_OK != (ret = add_name (dst,
802                                     dst_len,
803                                     off,
804                                     srv->target)))
805     return ret;
806   return GNUNET_OK;
807 }
808
809
810 /**
811  * Add a DNS record to the UDP packet at the given location.
812  *
813  * @param dst where to write the query
814  * @param dst_len number of bytes in dst
815  * @param off pointer to offset where to write the query (increment by bytes used)
816  *            must not be changed if there is an error
817  * @param record record to write
818  * @return GNUNET_SYSERR if 'record' is invalid
819  *         GNUNET_NO if 'record' did not fit
820  *         GNUNET_OK if 'record' was added to 'dst'
821  */
822 static int
823 add_record (char *dst,
824             size_t dst_len,
825             size_t *off,
826             const struct GNUNET_DNSPARSER_Record *record)
827 {
828   int ret;
829   size_t start;
830   size_t pos;
831   struct record_line rl;
832   char *name;
833   
834   start = *off;
835   /* for SRV records, we can create the name from the details
836      of the record if needed */
837   name = record->name;
838   if  ( (GNUNET_DNSPARSER_TYPE_SRV == record->type) &&
839         (NULL == name) )
840     GNUNET_asprintf (&name,
841                      "_%s._%s.%s",
842                      record->data.srv->service,
843                      record->data.srv->proto,
844                      record->data.srv->domain_name);
845   ret = add_name (dst, dst_len - sizeof (struct record_line), off, name);
846   if (name != record->name)
847     GNUNET_free (name);
848   if (GNUNET_OK != ret)
849     return ret;
850   /* '*off' is now the position where we will need to write the record line */
851
852   pos = *off + sizeof (struct record_line);
853   switch (record->type)
854   { 
855   case GNUNET_DNSPARSER_TYPE_MX:
856     ret = add_mx (dst, dst_len, &pos, record->data.mx);    
857     break;
858   case GNUNET_DNSPARSER_TYPE_SOA:
859     ret = add_soa (dst, dst_len, &pos, record->data.soa);
860     break;
861   case GNUNET_DNSPARSER_TYPE_NS:
862   case GNUNET_DNSPARSER_TYPE_CNAME:
863   case GNUNET_DNSPARSER_TYPE_PTR:
864     ret = add_name (dst, dst_len, &pos, record->data.hostname);
865     break;
866   case GNUNET_DNSPARSER_TYPE_SRV:
867     ret = add_srv (dst, dst_len, &pos, record->data.srv);
868     break;
869   default:
870     if (pos + record->data.raw.data_len > dst_len)
871     {
872       ret = GNUNET_NO;
873       break;
874     }
875     memcpy (&dst[pos], record->data.raw.data, record->data.raw.data_len);
876     pos += record->data.raw.data_len;
877     ret = GNUNET_OK;
878     break;
879   }
880   if (GNUNET_OK != ret)
881   {
882     *off = start;
883     return GNUNET_NO;
884   }
885
886   if (pos - (*off + sizeof (struct record_line)) > UINT16_MAX)
887   {
888     /* record data too long */
889     *off = start;
890     return GNUNET_NO;
891   }
892   rl.type = htons (record->type);
893   rl.class = htons (record->class);
894   rl.ttl = htonl (GNUNET_TIME_absolute_get_remaining (record->expiration_time).rel_value / 1000); /* in seconds */
895   rl.data_len = htons ((uint16_t) (pos - (*off + sizeof (struct record_line))));
896   memcpy (&dst[*off], &rl, sizeof (struct record_line));
897   *off = pos;
898   return GNUNET_OK;  
899 }
900
901
902 /**
903  * Given a DNS packet, generate the corresponding UDP payload.
904  * Note that we do not attempt to pack the strings with pointers
905  * as this would complicate the code and this is about being 
906  * simple and secure, not fast, fancy and broken like bind.
907  *
908  * @param p packet to pack
909  * @param max maximum allowed size for the resulting UDP payload
910  * @param buf set to a buffer with the packed message
911  * @param buf_length set to the length of buf
912  * @return GNUNET_SYSERR if 'p' is invalid
913  *         GNUNET_NO if 'p' was truncated (but there is still a result in 'buf')
914  *         GNUNET_OK if 'p' was packed completely into '*buf'
915  */
916 int
917 GNUNET_DNSPARSER_pack (const struct GNUNET_DNSPARSER_Packet *p,
918                        uint16_t max,
919                        char **buf,
920                        size_t *buf_length)
921 {  
922   struct GNUNET_TUN_DnsHeader dns;
923   size_t off;
924   char tmp[max];
925   unsigned int i;
926   int ret;
927   int trc;
928   
929   if ( (p->num_queries > UINT16_MAX) ||
930        (p->num_answers > UINT16_MAX) ||
931        (p->num_authority_records > UINT16_MAX) ||
932        (p->num_additional_records > UINT16_MAX) )
933     return GNUNET_SYSERR;
934   dns.id = p->id;
935   dns.flags = p->flags;
936   dns.query_count = htons (p->num_queries);
937   dns.answer_rcount = htons (p->num_answers);
938   dns.authority_rcount = htons (p->num_authority_records);
939   dns.additional_rcount = htons (p->num_additional_records);
940
941   off = sizeof (struct GNUNET_TUN_DnsHeader);
942   trc = GNUNET_NO;
943   for (i=0;i<p->num_queries;i++)
944   {
945     ret = add_query (tmp, sizeof (tmp), &off, &p->queries[i]);  
946     if (GNUNET_SYSERR == ret)
947       return GNUNET_SYSERR;
948     if (GNUNET_NO == ret)
949     {
950       dns.query_count = htons ((uint16_t) (i-1));
951       trc = GNUNET_YES;      
952       break;
953     }
954   }
955   for (i=0;i<p->num_answers;i++)
956   {
957     ret = add_record (tmp, sizeof (tmp), &off, &p->answers[i]);  
958     if (GNUNET_SYSERR == ret)
959       return GNUNET_SYSERR;
960     if (GNUNET_NO == ret)
961     {
962       dns.answer_rcount = htons ((uint16_t) (i-1));
963       trc = GNUNET_YES;      
964       break;
965     }
966   }
967   for (i=0;i<p->num_authority_records;i++)
968   {
969     ret = add_record (tmp, sizeof (tmp), &off, &p->authority_records[i]);  
970     if (GNUNET_SYSERR == ret)
971       return GNUNET_SYSERR;
972     if (GNUNET_NO == ret)
973     {
974       dns.authority_rcount = htons ((uint16_t) (i-1));
975       trc = GNUNET_YES;      
976       break;
977     }
978   }
979   for (i=0;i<p->num_additional_records;i++)
980   {
981     ret = add_record (tmp, sizeof (tmp), &off, &p->additional_records[i]);  
982     if (GNUNET_SYSERR == ret)
983       return GNUNET_SYSERR;
984     if (GNUNET_NO == ret)
985     {
986       dns.additional_rcount = htons (i-1);
987       trc = GNUNET_YES;      
988       break;
989     }
990   }
991
992   if (GNUNET_YES == trc)
993     dns.flags.message_truncated = 1;    
994   memcpy (tmp, &dns, sizeof (struct GNUNET_TUN_DnsHeader));
995
996   *buf = GNUNET_malloc (off);
997   *buf_length = off;
998   memcpy (*buf, tmp, off);
999   if (GNUNET_YES == trc)
1000     return GNUNET_NO;
1001   return GNUNET_OK;
1002 }
1003
1004 /* end of dnsparser.c */