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