- fixes, intendation
[oweals/gnunet.git] / src / gns / gnunet-service-gns_resolver.c
index 80954d8ca1b49007abc953c15bbf16acc74455c4..580233da39e9491b9853b2649a727afec4da1bcc 100644 (file)
@@ -1,6 +1,6 @@
 /*
      This file is part of GNUnet.
-     (C) 2011-2013 Christian Grothoff (and other contributing authors)
+     Copyright (C) 2011-2013 Christian Grothoff (and other contributing authors)
 
      GNUnet is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published
  * @brief GNU Name System resolver logic
  * @author Martin Schanzenbach
  * @author Christian Grothoff
- *
- * TODO:
- * - GNS: handle special SRV names --- no delegation, direct lookup;
- *        can likely be done in 'resolver_lookup_get_next_label'. (#3003)
- * - revocation checks (use REVOCATION service!), (#3004)
- * - DNAME support (#3005)
  */
 #include "platform.h"
 #include "gnunet_util_lib.h"
@@ -340,7 +334,7 @@ struct GNS_ResolverHandle
   /**
    * ID of a task associated with the resolution process.
    */
-  GNUNET_SCHEDULER_TaskIdentifier task_id;
+  struct GNUNET_SCHEDULER_Task * task_id;
 
   /**
    * The name to resolve
@@ -365,7 +359,19 @@ struct GNS_ResolverHandle
   /**
    * Use only cache
    */
-  int only_cached;
+  enum GNUNET_GNS_LocalOptions options;
+
+  /**
+   * For SRV and TLSA records, the number of the
+   * protocol specified in the name.  0 if no protocol was given.
+   */
+  int protocol;
+
+  /**
+   * For SRV and TLSA records, the number of the
+   * service specified in the name.  0 if no service was given.
+   */
+  int service;
 
   /**
    * Desired type for the resolution.
@@ -466,37 +472,6 @@ static int use_cache;
  */
 static const struct GNUNET_CONFIGURATION_Handle *cfg;
 
-#if 0
-/**
- * Check if name is in srv format (_x._y.xxx)
- *
- * @param name
- * @return #GNUNET_YES if true
- */
-static int
-is_srv (const char *name)
-{
-  char *ndup;
-  int ret;
-
-  if (*name != '_')
-    return GNUNET_NO;
-  if (NULL == strstr (name, "._"))
-    return GNUNET_NO;
-  ret = GNUNET_YES;
-  ndup = GNUNET_strdup (name);
-  strtok (ndup, ".");
-  if (NULL == strtok (NULL, "."))
-    ret = GNUNET_NO;
-  if (NULL == strtok (NULL, "."))
-    ret = GNUNET_NO;
-  if (NULL != strtok (NULL, "."))
-    ret = GNUNET_NO;
-  GNUNET_free (ndup);
-  return ret;
-}
-#endif
-
 
 /**
  * Determine if this name is canonical (is a legal name in a zone, without delegation);
@@ -574,7 +549,7 @@ fail_resolution (void *cls,
 {
   struct GNS_ResolverHandle *rh = cls;
 
-  rh->task_id = GNUNET_SCHEDULER_NO_TASK;
+  rh->task_id = NULL;
   rh->proc (rh->proc_cls, 0, NULL);
   GNS_resolver_lookup_cancel (rh);
 }
@@ -583,7 +558,7 @@ fail_resolution (void *cls,
 #if (defined WINDOWS) || (defined DARWIN)
 /* Don't have this on W32, here's a naive implementation
  * Was somehow removed on OS X ...  */
-void *
+static void *
 memrchr (const void *s,
         int c,
         size_t n)
@@ -601,7 +576,17 @@ memrchr (const void *s,
 
 /**
  * Get the next, rightmost label from the name that we are trying to resolve,
- * and update the resolution position accordingly.
+ * and update the resolution position accordingly.  Labels usually consist
+ * of up to 63 characters without a period ("."); however, we use a special
+ * convention to support SRV and TLSA records where the domain name
+ * includes an encoding for a service and protocol in the name.  The
+ * syntax (see RFC 2782) here is "_Service._Proto.Name" and in this
+ * special case we include the "_Service._Proto" in the rightmost label.
+ * Thus, for "_443._tcp.foo.bar" we first return the label "bar" and then
+ * the label "_443._tcp.foo".  The special case is detected by the
+ * presence of labels beginning with an underscore.  Whenever a label
+ * begins with an underscore, it is combined with the label to its right
+ * (and the "." is preserved).
  *
  * @param rh handle to the resolution operation to get the next label from
  * @return NULL if there are no more labels
@@ -612,10 +597,17 @@ resolver_lookup_get_next_label (struct GNS_ResolverHandle *rh)
   const char *rp;
   const char *dot;
   size_t len;
+  char *ret;
+  char *srv_name;
+  char *proto_name;
+  struct protoent *pe;
+  struct servent *se;
 
   if (0 == rh->name_resolution_pos)
     return NULL;
-  dot = memrchr (rh->name, (int) '.', rh->name_resolution_pos);
+  dot = memrchr (rh->name,
+                 (int) '.',
+                 rh->name_resolution_pos);
   if (NULL == dot)
   {
     /* done, this was the last one */
@@ -630,7 +622,53 @@ resolver_lookup_get_next_label (struct GNS_ResolverHandle *rh)
     rp = dot + 1;
     rh->name_resolution_pos = dot - rh->name;
   }
-  return GNUNET_strndup (rp, len);
+  rh->protocol = 0;
+  rh->service = 0;
+  ret = GNUNET_strndup (rp, len);
+  /* If we have labels starting with underscore with label on
+   * the right (SRV/DANE/BOX case), determine port/protocol;
+   * The format of `rh->name` must be "_PORT._PROTOCOL".
+   */
+  if ( ('_' == rh->name[0]) &&
+       (NULL != (dot = memrchr (rh->name,
+                                (int) '.',
+                                rh->name_resolution_pos))) &&
+       ('_' == dot[1]) &&
+       (NULL == memrchr (rh->name,
+                         (int) '.',
+                         dot - rh->name)) )
+  {
+    srv_name = GNUNET_strndup (&rh->name[1],
+                               (dot - rh->name) - 1);
+    proto_name = GNUNET_strndup (&dot[2],
+                                 rh->name_resolution_pos - (dot - rh->name) - 1);
+    rh->name_resolution_pos = 0;
+    pe = getprotobyname (proto_name);
+    if (NULL == pe)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  _("Protocol `%s' unknown, skipping labels.\n"),
+                  proto_name);
+      GNUNET_free (proto_name);
+      GNUNET_free (srv_name);
+      return ret;
+    }
+    se = getservbyname (srv_name,
+                        proto_name);
+    if (NULL == se)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  _("Service `%s' unknown for protocol `%s', skipping labels.\n"),
+                  srv_name,
+                  proto_name);
+      GNUNET_free (proto_name);
+      GNUNET_free (srv_name);
+      return ret;
+    }
+    rh->protocol = pe->p_proto;
+    rh->service = se->s_port;
+  }
+  return ret;
 }
 
 
@@ -729,15 +767,15 @@ handle_dns_result (void *cls,
   const struct sockaddr_in *sa4;
   const struct sockaddr_in6 *sa6;
 
-  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
-             "Received %u bytes of DNS IP data\n",
-             addrlen);
   if (NULL == addr)
   {
     rh->std_resolve = NULL;
     transmit_lookup_dns_result (rh);
     return;
   }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+             "Received %u bytes of DNS IP data\n",
+             addrlen);
   switch (addr->sa_family)
   {
   case AF_INET:
@@ -807,7 +845,7 @@ dns_result_parser (void *cls,
 
   rh->dns_request = NULL;
   GNUNET_SCHEDULER_cancel (rh->task_id);
-  rh->task_id = GNUNET_SCHEDULER_NO_TASK;
+  rh->task_id = NULL;
   p = GNUNET_DNSPARSER_parse ((const char *) dns,
                              dns_len);
   if (NULL == p)
@@ -828,11 +866,11 @@ dns_result_parser (void *cls,
     {
       GNUNET_free (rh->name);
       rh->name = GNUNET_strdup (p->answers[0].data.hostname);
+      rh->name_resolution_pos = strlen (rh->name);
       start_resolver_lookup (rh);
       GNUNET_DNSPARSER_free_packet (p);
       return;
     }
-  /* FIXME: add DNAME support */
 
   /* convert from (parsed) DNS to (binary) GNS format! */
   rd_count = p->num_answers + p->num_authority_records + p->num_additional_records;
@@ -1103,6 +1141,7 @@ handle_gns_cname_result (struct GNS_ResolverHandle *rh,
   /* name is absolute, start from the beginning */
   GNUNET_free (rh->name);
   rh->name = GNUNET_strdup (cname);
+  rh->name_resolution_pos = strlen (rh->name);
   start_resolver_lookup (rh);
 }
 
@@ -1463,6 +1502,11 @@ handle_gns_resolution_result (void *cls,
     shorten_ac = rh->ac_tail;
     for (i=0;i<rd_count;i++)
     {
+      if ( (0 != rh->protocol) &&
+           (0 != rh->service) &&
+           (GNUNET_GNSRECORD_TYPE_BOX != rd[i].record_type) )
+        continue; /* we _only_ care about boxed records */
+
       rd_new[rd_off] = rd[i];
       /* Check if the embedded name(s) end in "+", and if so,
         replace the "+" with the zone at "ac_tail", changing the name
@@ -1584,10 +1628,7 @@ handle_gns_resolution_result (void *cls,
          struct GNUNET_DNSPARSER_SrvRecord *srv;
 
          off = 0;
-         /* FIXME: passing rh->name here is is not necessarily what we want
-            (SRV support not finished) */
-         srv = GNUNET_DNSPARSER_parse_srv (rh->name,
-                                           rd[i].data,
+         srv = GNUNET_DNSPARSER_parse_srv (rd[i].data,
                                            rd[i].data_size,
                                            &off);
          if ( (NULL == srv) ||
@@ -1597,7 +1638,6 @@ handle_gns_resolution_result (void *cls,
          }
          else
          {
-           srv->domain_name = translate_dot_plus (rh, srv->domain_name);
            srv->target = translate_dot_plus (rh, srv->target);
            scratch_start = scratch_off;
            if (GNUNET_OK !=
@@ -1623,6 +1663,7 @@ handle_gns_resolution_result (void *cls,
       case GNUNET_GNSRECORD_TYPE_NICK:
         {
           const char *nick;
+
           nick = rd[i].data;
           if ((rd[i].data_size > 0) &&
               (nick[rd[i].data_size -1] != '\0'))
@@ -1673,6 +1714,33 @@ handle_gns_resolution_result (void *cls,
                       "Found GNS2DNS record, delegating to DNS!\n");
           goto do_recurse;
         }
+      case GNUNET_GNSRECORD_TYPE_BOX:
+        {
+          /* unbox SRV/TLSA records if a specific one was requested */
+          if ( (0 != rh->protocol) &&
+               (0 != rh->service) &&
+               (rd[i].data_size >= sizeof (struct GNUNET_GNSRECORD_BoxRecord)) )
+          {
+            const struct GNUNET_GNSRECORD_BoxRecord *box;
+
+            box = rd[i].data;
+            if ( (ntohs (box->protocol) == rh->protocol) &&
+                 (ntohs (box->service) == rh->service) )
+            {
+              /* Box matches, unbox! */
+              rd_new[rd_off].record_type = ntohl (box->record_type);
+              rd_new[rd_off].data_size -= sizeof (struct GNUNET_GNSRECORD_BoxRecord);
+              rd_new[rd_off].data = &box[1];
+              rd_off++;
+            }
+          }
+          else
+          {
+            /* no specific protocol/service specified, preserve all BOX
+               records (for modern, GNS-enabled applications) */
+            rd_off++;
+          }
+        }
       default:
        rd_off++;
        break;
@@ -1771,6 +1839,7 @@ handle_gns_resolution_result (void *cls,
            resolver to use */
         g2dc = GNUNET_new (struct Gns2DnsContext);
         g2dc->ns = ns;
+
         g2dc->rh = GNUNET_new (struct GNS_ResolverHandle);
         g2dc->rh->authority_zone = rh->ac_tail->authority_info.gns_authority;
         ip = translate_dot_plus (rh, ip);
@@ -1779,7 +1848,7 @@ handle_gns_resolution_result (void *cls,
         g2dc->rh->proc = &handle_gns2dns_result;
         g2dc->rh->proc_cls = rh;
         g2dc->rh->record_type = GNUNET_GNSRECORD_TYPE_ANY;
-        g2dc->rh->only_cached = GNUNET_NO;
+        g2dc->rh->options = GNUNET_GNS_LO_DEFAULT;
         g2dc->rh->loop_limiter = rh->loop_limiter + 1;
         rh->g2dc = g2dc;
         start_resolver_lookup (g2dc->rh);
@@ -1972,6 +2041,27 @@ start_dht_request (struct GNS_ResolverHandle *rh,
 }
 
 
+/**
+ * Process a records that were decrypted from a block that we got from
+ * the namecache.  Simply calls #handle_gns_resolution_result().
+ *
+ * @param cls closure with the `struct GNS_ResolverHandle`
+ * @param rd_count number of entries in @a rd array
+ * @param rd array of records with data to store
+ */
+static void
+handle_gns_namecache_resolution_result (void *cls,
+                                        unsigned int rd_count,
+                                        const struct GNUNET_GNSRECORD_Data *rd)
+{
+  struct GNS_ResolverHandle *rh = cls;
+
+  handle_gns_resolution_result (rh,
+                                rd_count,
+                                rd);
+}
+
+
 /**
  * Process a record that was stored in the namecache.
  *
@@ -1990,19 +2080,21 @@ handle_namecache_block_response (void *cls,
 
   GNUNET_assert (NULL != rh->namecache_qe);
   rh->namecache_qe = NULL;
-  if ( (GNUNET_NO == rh->only_cached) &&
+  if ( ( (GNUNET_GNS_LO_DEFAULT == rh->options) ||
+        ( (GNUNET_GNS_LO_LOCAL_MASTER == rh->options) &&
+          (ac != rh->ac_head) ) ) &&
        ( (NULL == block) ||
         (0 == GNUNET_TIME_absolute_get_remaining (GNUNET_TIME_absolute_ntoh (block->expiration_time)).rel_value_us) ) )
   {
     /* namecache knows nothing; try DHT lookup */
+    GNUNET_GNSRECORD_query_from_public_key (auth,
+                                            label,
+                                            &query);
     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                 "Starting DHT lookup for `%s' in zone `%s' under key `%s'\n",
                 ac->label,
                 GNUNET_GNSRECORD_z2s (&ac->authority_info.gns_authority),
                 GNUNET_h2s (&query));
-    GNUNET_GNSRECORD_query_from_public_key (auth,
-                                            label,
-                                            &query);
     start_dht_request (rh, &query);
     return;
   }
@@ -2027,12 +2119,20 @@ handle_namecache_block_response (void *cls,
       GNUNET_GNSRECORD_block_decrypt (block,
                                      auth,
                                      label,
-                                     &handle_gns_resolution_result,
+                                     &handle_gns_namecache_resolution_result,
                                      rh))
   {
     GNUNET_break_op (0); /* block was ill-formed */
-    rh->proc (rh->proc_cls, 0, NULL);
-    GNS_resolver_lookup_cancel (rh);
+    /* try DHT instead */
+    GNUNET_GNSRECORD_query_from_public_key (auth,
+                                            label,
+                                            &query);
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Starting DHT lookup for `%s' in zone `%s' under key `%s'\n",
+                ac->label,
+                GNUNET_GNSRECORD_z2s (&ac->authority_info.gns_authority),
+                GNUNET_h2s (&query));
+    start_dht_request (rh, &query);
     return;
   }
 }
@@ -2058,10 +2158,11 @@ recursive_gns_resolution_namecache (struct GNS_ResolverHandle *rh)
                                          &query);
   if (GNUNET_YES == use_cache)
   {
-    rh->namecache_qe = GNUNET_NAMECACHE_lookup_block (namecache_handle,
-                                                   &query,
-                                                   &handle_namecache_block_response,
-                                                   rh);
+    rh->namecache_qe
+      = GNUNET_NAMECACHE_lookup_block (namecache_handle,
+                                       &query,
+                                       &handle_namecache_block_response,
+                                       rh);
     GNUNET_assert (NULL != rh->namecache_qe);
   }
   else
@@ -2131,7 +2232,7 @@ recursive_resolution (void *cls,
 {
   struct GNS_ResolverHandle *rh = cls;
 
-  rh->task_id = GNUNET_SCHEDULER_NO_TASK;
+  rh->task_id = NULL;
   if (MAX_RECURSION < rh->loop_limiter++)
   {
     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
@@ -2273,7 +2374,7 @@ start_resolver_lookup (struct GNS_ResolverHandle *rh)
  * @param record_type the record type to look up
  * @param name the name to look up
  * @param shorten_key a private key for use with PSEU import (can be NULL)
- * @param only_cached #GNUNET_NO to only check locally not DHT for performance
+ * @param options local options to control local lookup
  * @param proc the processor to call on result
  * @param proc_cls the closure to pass to @a proc
  * @return handle to cancel operation
@@ -2283,7 +2384,7 @@ GNS_resolver_lookup (const struct GNUNET_CRYPTO_EcdsaPublicKey *zone,
                     uint32_t record_type,
                     const char *name,
                     const struct GNUNET_CRYPTO_EcdsaPrivateKey *shorten_key,
-                    int only_cached,
+                    enum GNUNET_GNS_LocalOptions options,
                     GNS_ResultProcessor proc, void *proc_cls)
 {
   struct GNS_ResolverHandle *rh;
@@ -2300,7 +2401,7 @@ GNS_resolver_lookup (const struct GNUNET_CRYPTO_EcdsaPublicKey *zone,
   rh->authority_zone = *zone;
   rh->proc = proc;
   rh->proc_cls = proc_cls;
-  rh->only_cached = only_cached;
+  rh->options = options;
   rh->record_type = record_type;
   rh->name = GNUNET_strdup (name);
   rh->name_resolution_pos = strlen (name);
@@ -2355,10 +2456,10 @@ GNS_resolver_lookup_cancel (struct GNS_ResolverHandle *rh)
     GNUNET_free (rh->g2dc);
     rh->g2dc = NULL;
   }
-  if (GNUNET_SCHEDULER_NO_TASK != rh->task_id)
+  if (NULL != rh->task_id)
   {
     GNUNET_SCHEDULER_cancel (rh->task_id);
-    rh->task_id = GNUNET_SCHEDULER_NO_TASK;
+    rh->task_id = NULL;
   }
   if (NULL != rh->get_handle)
   {