-stuff
[oweals/gnunet.git] / src / dns / dnsparser.c
index e02ce73fbe9b4460accc6db2f6d0d262777c8a2e..8346051d7a1b7699e6be201a3d71042cfe990667 100644 (file)
@@ -74,12 +74,14 @@ GNUNET_NETWORK_STRUCT_END
  * @param udp_payload_length length of udp_payload
  * @param off pointer to the offset of the name to parse in the udp_payload (to be
  *                    incremented by the size of the name)
+ * @param depth current depth of our recursion (to prevent stack overflow)
  * @return name as 0-terminated C string on success, NULL if the payload is malformed
  */
 static char *
 parse_name (const char *udp_payload,
            size_t udp_payload_length,
-           size_t *off)
+           size_t *off,
+           unsigned int depth)
 {
   const uint8_t *input = (const uint8_t *) udp_payload;
   char *ret;
@@ -114,13 +116,18 @@ parse_name (const char *udp_payload,
     }
     else if ((64 | 128) == (len & (64 | 128)) )
     {
+      if (depth > 32)
+       goto error; /* hard bound on stack to prevent "infinite" recursion, disallow! */
       /* pointer to string */
       if (*off + 1 > udp_payload_length)
        goto error;
       xoff = ((len - (64 | 128)) << 8) + input[*off+1];
       xstr = parse_name (udp_payload,
                         udp_payload_length,
-                        &xoff);
+                        &xoff,
+                        depth + 1);
+      if (NULL == xstr)
+       goto error;
       GNUNET_asprintf (&tmp,
                       "%s%s.",
                       ret,
@@ -128,6 +135,8 @@ parse_name (const char *udp_payload,
       GNUNET_free (ret);
       GNUNET_free (xstr);
       ret = tmp;
+      if (strlen (ret) > udp_payload_length)
+       goto error; /* we are looping (building an infinite string) */
       *off += 2;
       /* pointers always terminate names */
       break;
@@ -168,7 +177,7 @@ parse_query (const char *udp_payload,
 
   name = parse_name (udp_payload, 
                     udp_payload_length,
-                    off);
+                    off, 0);
   if (NULL == name)
     return GNUNET_SYSERR;
   q->name = name;
@@ -203,10 +212,11 @@ parse_record (const char *udp_payload,
   size_t old_off;
   struct soa_data soa;
   uint16_t mxpref;
+  uint16_t data_len;
 
   name = parse_name (udp_payload, 
                     udp_payload_length,
-                    off);
+                    off, 0);
   if (NULL == name)
     return GNUNET_SYSERR;
   r->name = name;
@@ -218,11 +228,9 @@ parse_record (const char *udp_payload,
   r->class = ntohs (rl.class);
   r->expiration_time = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
                                                                                        ntohl (rl.ttl)));
-  r->data_len = ntohs (rl.data_len);
-  if (*off + r->data_len > udp_payload_length)
+  data_len = ntohs (rl.data_len);
+  if (*off + data_len > udp_payload_length)
     return GNUNET_SYSERR;
-  if (0 == r->data_len)
-    return GNUNET_OK;
   switch (r->type)
   {
   case GNUNET_DNSPARSER_TYPE_NS:
@@ -231,9 +239,9 @@ parse_record (const char *udp_payload,
     old_off = *off;
     r->data.hostname = parse_name (udp_payload,
                                   udp_payload_length,
-                                  off);    
+                                  off, 0);    
     if ( (NULL == r->data.hostname) ||
-        (old_off + r->data_len != *off) )
+        (old_off + data_len != *off) )
       return GNUNET_SYSERR;
     return GNUNET_OK;
   case GNUNET_DNSPARSER_TYPE_SOA:
@@ -241,10 +249,10 @@ parse_record (const char *udp_payload,
     r->data.soa = GNUNET_malloc (sizeof (struct GNUNET_DNSPARSER_SoaRecord));
     r->data.soa->mname = parse_name (udp_payload,
                                     udp_payload_length,
-                                    off);
+                                    off, 0);
     r->data.soa->rname = parse_name (udp_payload,
                                     udp_payload_length,
-                                    off);
+                                    off, 0);
     if ( (NULL == r->data.soa->mname) ||
         (NULL == r->data.soa->rname) ||
         (*off + sizeof (soa) > udp_payload_length) )
@@ -256,7 +264,7 @@ parse_record (const char *udp_payload,
     r->data.soa->expire = ntohl (soa.expire);
     r->data.soa->minimum_ttl = ntohl (soa.minimum);
     (*off) += sizeof (soa);
-    if (old_off + r->data_len != *off) 
+    if (old_off + data_len != *off) 
       return GNUNET_SYSERR;
     return GNUNET_OK;
   case GNUNET_DNSPARSER_TYPE_MX:
@@ -269,16 +277,17 @@ parse_record (const char *udp_payload,
     r->data.mx->preference = ntohs (mxpref);
     r->data.mx->mxhost = parse_name (udp_payload,
                                     udp_payload_length,
-                                    off);
-    if (old_off + r->data_len != *off) 
+                                    off, 0);
+    if (old_off + data_len != *off) 
       return GNUNET_SYSERR;
     return GNUNET_OK;
   default:
-    r->data.raw = GNUNET_malloc (r->data_len);
-    memcpy (r->data.raw, &udp_payload[*off], r->data_len);
+    r->data.raw.data = GNUNET_malloc (data_len);
+    r->data.raw.data_len = data_len;
+    memcpy (r->data.raw.data, &udp_payload[*off], data_len);
     break;
   }
-  (*off) += r->data_len;
+  (*off) += data_len;
   return GNUNET_OK;  
 }
 
@@ -416,7 +425,7 @@ free_record (struct GNUNET_DNSPARSER_Record *r)
     GNUNET_free_non_null (r->data.hostname);
     break;
   default:
-    GNUNET_free_non_null (r->data.raw);
+    GNUNET_free_non_null (r->data.raw.data);
     break;
   }
 }
@@ -448,10 +457,247 @@ GNUNET_DNSPARSER_free_packet (struct GNUNET_DNSPARSER_Packet *p)
 }
 
 
+/* ********************** DNS packet assembly code **************** */
+
+
+/**
+ * Add a DNS name to the UDP packet at the given location.
+ *
+ * @param dst where to write the name
+ * @param dst_len number of bytes in dst
+ * @param off pointer to offset where to write the name (increment by bytes used)
+ *            must not be changed if there is an error
+ * @param name name to write
+ * @return GNUNET_SYSERR if 'name' is invalid
+ *         GNUNET_NO if 'name' did not fit
+ *         GNUNET_OK if 'name' was added to 'dst'
+ */
+static int
+add_name (char *dst,
+         size_t dst_len,
+         size_t *off,
+         const char *name)
+{
+  const char *dot;
+  size_t start;
+  size_t pos;
+  size_t len;
+
+  if (NULL == name)
+    return GNUNET_SYSERR;
+  start = *off;
+  if (start + strlen (name) + 2 > dst_len)
+    return GNUNET_NO;
+  pos = start;
+  do
+  {
+    dot = strchr (name, '.');
+    if (NULL == dot)
+      len = strlen (name);
+    else
+      len = dot - name;
+    if ( (len >= 64) || (len == 0) )
+      return GNUNET_NO; /* segment too long or empty */
+    dst[pos++] = (char) (uint8_t) len;
+    memcpy (&dst[pos], name, len);
+    pos += len;
+    name += len + 1; /* also skip dot */
+  }
+  while (NULL != dot);
+  dst[pos++] = '\0'; /* terminator */
+  *off = pos;
+  return GNUNET_OK;
+}
+
+
+/**
+ * Add a DNS query to the UDP packet at the given location.
+ *
+ * @param dst where to write the query
+ * @param dst_len number of bytes in dst
+ * @param off pointer to offset where to write the query (increment by bytes used)
+ *            must not be changed if there is an error
+ * @param query query to write
+ * @return GNUNET_SYSERR if 'query' is invalid
+ *         GNUNET_NO if 'query' did not fit
+ *         GNUNET_OK if 'query' was added to 'dst'
+ */
+static int
+add_query (char *dst,
+          size_t dst_len,
+          size_t *off,
+          const struct GNUNET_DNSPARSER_Query *query)
+{
+  int ret;
+  struct query_line ql;
+
+  ret = add_name (dst, dst_len - sizeof (struct query_line), off, query->name);
+  if (ret != GNUNET_OK)
+    return ret;
+  ql.type = htons (query->type);
+  ql.class = htons (query->class);
+  memcpy (&dst[*off], &ql, sizeof (ql));
+  (*off) += sizeof (ql);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Add an MX record to the UDP packet at the given location.
+ *
+ * @param dst where to write the mx record
+ * @param dst_len number of bytes in dst
+ * @param off pointer to offset where to write the mx information (increment by bytes used);
+ *            can also change if there was an error
+ * @param mx mx information to write
+ * @return GNUNET_SYSERR if 'mx' is invalid
+ *         GNUNET_NO if 'mx' did not fit
+ *         GNUNET_OK if 'mx' was added to 'dst'
+ */
+static int
+add_mx (char *dst,
+       size_t dst_len,
+       size_t *off,
+       const struct GNUNET_DNSPARSER_MxRecord *mx)
+{
+  uint16_t mxpref;
+
+  if (*off + sizeof (uint16_t) > dst_len)
+    return GNUNET_NO;
+  mxpref = htons (mx->preference);
+  memcpy (&dst[*off], &mxpref, sizeof (mxpref));
+  (*off) += sizeof (mxpref);
+  return add_name (dst, dst_len, off, mx->mxhost);
+}
+
+
+/**
+ * Add an SOA record to the UDP packet at the given location.
+ *
+ * @param dst where to write the SOA record
+ * @param dst_len number of bytes in dst
+ * @param off pointer to offset where to write the SOA information (increment by bytes used)
+ *            can also change if there was an error
+ * @param soa SOA information to write
+ * @return GNUNET_SYSERR if 'soa' is invalid
+ *         GNUNET_NO if 'soa' did not fit
+ *         GNUNET_OK if 'soa' was added to 'dst'
+ */
+static int
+add_soa (char *dst,
+        size_t dst_len,
+        size_t *off,
+        const struct GNUNET_DNSPARSER_SoaRecord *soa)
+{
+  struct soa_data sd;
+  int ret;
+
+  if ( (GNUNET_OK != (ret = add_name (dst,
+                                     dst_len,
+                                     off,
+                                     soa->mname))) ||
+       (GNUNET_OK != (ret = add_name (dst,
+                                     dst_len,
+                                     off,
+                                     soa->rname)) ) )
+    return ret;
+  if (*off + sizeof (soa) > dst_len)
+    return GNUNET_NO;
+  sd.serial = htonl (soa->serial);
+  sd.refresh = htonl (soa->refresh);
+  sd.retry = htonl (soa->retry);
+  sd.expire = htonl (soa->expire);
+  sd.minimum = htonl (soa->minimum_ttl);
+  memcpy (&dst[*off], &sd, sizeof (sd));
+  (*off) += sizeof (sd);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Add a DNS record to the UDP packet at the given location.
+ *
+ * @param dst where to write the query
+ * @param dst_len number of bytes in dst
+ * @param off pointer to offset where to write the query (increment by bytes used)
+ *            must not be changed if there is an error
+ * @param record record to write
+ * @return GNUNET_SYSERR if 'record' is invalid
+ *         GNUNET_NO if 'record' did not fit
+ *         GNUNET_OK if 'record' was added to 'dst'
+ */
+static int
+add_record (char *dst,
+           size_t dst_len,
+           size_t *off,
+           const struct GNUNET_DNSPARSER_Record *record)
+{
+  int ret;
+  size_t start;
+  size_t pos;
+  struct record_line rl;
+
+  start = *off;
+  ret = add_name (dst, dst_len - sizeof (struct record_line), off, record->name);
+  if (ret != GNUNET_OK)
+    return ret;
+  /* '*off' is now the position where we will need to write the record line */
+
+  pos = *off + sizeof (struct record_line);
+  switch (record->type)
+  { 
+  case GNUNET_DNSPARSER_TYPE_MX:
+    ret = add_mx (dst, dst_len, &pos, record->data.mx);    
+    break;
+  case GNUNET_DNSPARSER_TYPE_SOA:
+    ret = add_soa (dst, dst_len, &pos, record->data.soa);
+    break;
+  case GNUNET_DNSPARSER_TYPE_NS:
+  case GNUNET_DNSPARSER_TYPE_CNAME:
+  case GNUNET_DNSPARSER_TYPE_PTR:
+    ret = add_name (dst, dst_len, &pos, record->data.hostname);
+    break;
+  default:
+    if (pos + record->data.raw.data_len > dst_len)
+    {
+      ret = GNUNET_NO;
+      break;
+    }
+    memcpy (&dst[pos], record->data.raw.data, record->data.raw.data_len);
+    pos += record->data.raw.data_len;
+    ret = GNUNET_OK;
+    break;
+  }
+  if (ret != GNUNET_OK)
+  {
+    *off = start;
+    return GNUNET_NO;
+  }
+
+  if (pos - (*off + sizeof (struct record_line)) > UINT16_MAX)
+  {
+    /* record data too long */
+    *off = start;
+    return GNUNET_NO;
+  }
+  rl.type = htons (record->type);
+  rl.class = htons (record->class);
+  rl.ttl = htonl (GNUNET_TIME_absolute_get_remaining (record->expiration_time).rel_value / 1000); /* in seconds */
+  rl.data_len = htons ((uint16_t) (pos - (*off + sizeof (struct record_line))));
+  memcpy (&dst[*off], &rl, sizeof (struct record_line));
+  *off = pos;
+  return GNUNET_OK;  
+}
+
+
 /**
  * Given a DNS packet, generate the corresponding UDP payload.
+ * Note that we do not attempt to pack the strings with pointers
+ * as this would complicate the code and this is about being 
+ * simple and secure, not fast, fancy and broken like bind.
  *
  * @param p packet to pack
+ * @param max maximum allowed size for the resulting UDP payload
  * @param buf set to a buffer with the packed message
  * @param buf_length set to the length of buf
  * @return GNUNET_SYSERR if 'p' is invalid
@@ -459,18 +705,122 @@ GNUNET_DNSPARSER_free_packet (struct GNUNET_DNSPARSER_Packet *p)
  *         GNUNET_OK if 'p' was packed completely into '*buf'
  */
 int
-GNUNET_DNSPARSER_pack (struct GNUNET_DNSPARSER_Packet *p,
+GNUNET_DNSPARSER_pack (const struct GNUNET_DNSPARSER_Packet *p,
+                      uint16_t max,
                       char **buf,
                       size_t *buf_length)
-{
-  // FIXME: not implemented
-  GNUNET_break (0);
-  return GNUNET_SYSERR;
+{  
+  struct dns_header dns;
+  size_t off;
+  char tmp[max];
+  unsigned int i;
+  int ret;
+  int trc;
+  
+  if ( (p->num_queries > UINT16_MAX) ||
+       (p->num_answers > UINT16_MAX) ||
+       (p->num_authority_records > UINT16_MAX) ||
+       (p->num_additional_records > UINT16_MAX) )
+    return GNUNET_SYSERR;
+  dns.id = p->id;
+  dns.flags = p->flags;
+  dns.query_count = htons (p->num_queries);
+  dns.answer_rcount = htons (p->num_answers);
+  dns.authority_rcount = htons (p->num_authority_records);
+  dns.additional_rcount = htons (p->num_additional_records);
+
+  off = sizeof (struct dns_header);
+  trc = GNUNET_NO;
+  for (i=0;i<p->num_queries;i++)
+  {
+    ret = add_query (tmp, sizeof (tmp), &off, &p->queries[i]);  
+    if (GNUNET_SYSERR == ret)
+      return GNUNET_SYSERR;
+    if (GNUNET_NO == ret)
+    {
+      dns.query_count = htons ((uint16_t) (i-1));
+      trc = GNUNET_YES;      
+      break;
+    }
+  }
+  for (i=0;i<p->num_answers;i++)
+  {
+    ret = add_record (tmp, sizeof (tmp), &off, &p->answers[i]);  
+    if (GNUNET_SYSERR == ret)
+      return GNUNET_SYSERR;
+    if (GNUNET_NO == ret)
+    {
+      dns.answer_rcount = htons ((uint16_t) (i-1));
+      trc = GNUNET_YES;      
+      break;
+    }
+  }
+  for (i=0;i<p->num_authority_records;i++)
+  {
+    ret = add_record (tmp, sizeof (tmp), &off, &p->authority_records[i]);  
+    if (GNUNET_SYSERR == ret)
+      return GNUNET_SYSERR;
+    if (GNUNET_NO == ret)
+    {
+      dns.authority_rcount = htons ((uint16_t) (i-1));
+      trc = GNUNET_YES;      
+      break;
+    }
+  }
+  for (i=0;i<p->num_additional_records;i++)
+  {
+    ret = add_record (tmp, sizeof (tmp), &off, &p->additional_records[i]);  
+    if (GNUNET_SYSERR == ret)
+      return GNUNET_SYSERR;
+    if (GNUNET_NO == ret)
+    {
+      dns.additional_rcount = htons (i-1);
+      trc = GNUNET_YES;      
+      break;
+    }
+  }
+
+  if (GNUNET_YES == trc)
+    dns.flags.message_truncated = 1;    
+  memcpy (tmp, &dns, sizeof (struct dns_header));
+
+  *buf = GNUNET_malloc (off);
+  *buf_length = off;
+  memcpy (*buf, tmp, off);
+  if (GNUNET_YES == trc)
+    return GNUNET_NO;
+  return GNUNET_OK;
 }
 
 
 
 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
 /* legacy code follows */
 
 /**