summary refs log tree commit diff
path: root/resolv/res_init.c
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2017-07-01 00:53:05 +0200
committerFlorian Weimer <fweimer@redhat.com>2017-07-03 21:01:42 +0200
commit3f853f22c87f0b671c0366eb290919719fa56c0e (patch)
tree23f8835838728d4be3efca524754c1a2f5bb0396 /resolv/res_init.c
parentf30a54b21b83f254533c59ca72ad17af5249c6be (diff)
downloadglibc-3f853f22c87f0b671c0366eb290919719fa56c0e.tar.gz
glibc-3f853f22c87f0b671c0366eb290919719fa56c0e.tar.xz
glibc-3f853f22c87f0b671c0366eb290919719fa56c0e.zip
resolv: Lift domain search list limits [BZ #19569] [BZ #21475]
This change uses the extended resolver state in struct resolv_conf to
store the search list.  If applications have not patched the _res
object directly, this extended search list will be used by the stub
resolver during name resolution.
Diffstat (limited to 'resolv/res_init.c')
-rw-r--r--resolv/res_init.c222
1 files changed, 149 insertions, 73 deletions
diff --git a/resolv/res_init.c b/resolv/res_init.c
index 659d3ea81f..5941d37f32 100644
--- a/resolv/res_init.c
+++ b/resolv/res_init.c
@@ -124,18 +124,75 @@ is_sort_mask (char ch)
   return ch == '/' || ch == '&';
 }
 
+/* Array of strings for the search array.  The backing store is
+   managed separately.  */
+#define DYNARRAY_STRUCT search_list
+#define DYNARRAY_ELEMENT const char *
+#define DYNARRAY_INITIAL_SIZE 4
+#define DYNARRAY_PREFIX search_list_
+#include <malloc/dynarray-skeleton.c>
+
+/* resolv.conf parser state and results.  */
+struct resolv_conf_parser
+{
+  char *buffer;            /* Temporary buffer for reading lines.  */
+  char *search_list_store; /* Backing storage for search list entries.  */
+  struct search_list search_list; /* Points into search_list_store.  */
+};
+
+static void
+resolv_conf_parser_init (struct resolv_conf_parser *parser)
+{
+  parser->buffer = NULL;
+  parser->search_list_store = NULL;
+  search_list_init (&parser->search_list);
+}
+
+static void
+resolv_conf_parser_free (struct resolv_conf_parser *parser)
+{
+  free (parser->buffer);
+  free (parser->search_list_store);
+  search_list_free (&parser->search_list);
+}
+
+/* Try to obtain the domain name from the host name and store it in
+   *RESULT.  Return false on memory allocation failure.  If the domain
+   name cannot be determined for any other reason, write NULL to
+   *RESULT and return true.  */
+static bool
+domain_from_hostname (char **result)
+{
+  char buf[256];
+  /* gethostbyname may not terminate the buffer.  */
+  buf[sizeof (buf) - 1] = '\0';
+  if (__gethostname (buf, sizeof (buf) - 1) == 0)
+    {
+      char *dot = strchr (buf, '.');
+      if (dot != NULL)
+        {
+          *result = __strdup (dot + 1);
+          if (*result == NULL)
+            return false;
+          return true;
+        }
+    }
+  *result = NULL;
+  return true;
+}
+
 /* Internal helper function for __res_vinit, to aid with resource
    deallocation and error handling.  Return true on success, false on
    failure.  */
 static bool
-res_vinit_1 (res_state statp, bool preinit, FILE *fp, char **buffer)
+res_vinit_1 (res_state statp, bool preinit, FILE *fp,
+             struct resolv_conf_parser *parser)
 {
-  char *cp, **pp;
+  char *cp;
   size_t buffer_size = 0;
   int nserv = 0;    /* Number of nameservers read from file.  */
   bool have_serv6 = false;
   bool haveenv = false;
-  bool havesearch = false;
   int nsort = 0;
   char *net;
 
@@ -162,39 +219,40 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp, char **buffer)
   /* Allow user to override the local domain definition.  */
   if ((cp = getenv ("LOCALDOMAIN")) != NULL)
     {
-      strncpy (statp->defdname, cp, sizeof (statp->defdname) - 1);
-      statp->defdname[sizeof (statp->defdname) - 1] = '\0';
+      /* The code below splits the string in place.  */
+      cp = __strdup (cp);
+      if (cp == NULL)
+        return false;
+      free (parser->search_list_store);
+      parser->search_list_store = cp;
       haveenv = true;
 
+      /* The string will be truncated as needed below.  */
+      search_list_add (&parser->search_list, cp);
+
       /* Set search list to be blank-separated strings from rest of
          env value.  Permits users of LOCALDOMAIN to still have a
          search list, and anyone to set the one that they want to use
          as an individual (even more important now that the rfc1535
          stuff restricts searches).  */
-      cp = statp->defdname;
-      pp = statp->dnsrch;
-      *pp++ = cp;
-      for (int n = 0; *cp && pp < statp->dnsrch + MAXDNSRCH; cp++)
+      for (bool in_name = true; *cp != '\0'; cp++)
         {
           if (*cp == '\n')
-            break;
+            {
+              *cp = '\0';
+              break;
+            }
           else if (*cp == ' ' || *cp == '\t')
             {
-              *cp = 0;
-              n = 1;
+              *cp = '\0';
+              in_name = false;
             }
-          else if (n > 0)
+          else if (!in_name)
             {
-              *pp++ = cp;
-              n = 0;
-              havesearch = true;
+              search_list_add (&parser->search_list, cp);
+              in_name = true;
             }
         }
-      /* Null terminate last domain if there are excess.  */
-      while (*cp != '\0' && *cp != ' ' && *cp != '\t' && *cp != '\n')
-        cp++;
-      *cp = '\0';
-      *pp++ = 0;
     }
 
 #define MATCH(line, name)                       \
@@ -210,7 +268,7 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp, char **buffer)
       while (true)
         {
           {
-            ssize_t ret = __getline (buffer, &buffer_size, fp);
+            ssize_t ret = __getline (&parser->buffer, &buffer_size, fp);
             if (ret <= 0)
               {
                 if (_IO_ferror_unlocked (fp))
@@ -221,73 +279,82 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp, char **buffer)
           }
 
           /* Skip comments.  */
-          if (**buffer == ';' || **buffer == '#')
+          if (*parser->buffer == ';' || *parser->buffer == '#')
             continue;
           /* Read default domain name.  */
-          if (MATCH (*buffer, "domain"))
+          if (MATCH (parser->buffer, "domain"))
             {
               if (haveenv)
                 /* LOCALDOMAIN overrides the configuration file.  */
                 continue;
-              cp = *buffer + sizeof ("domain") - 1;
+              cp = parser->buffer + sizeof ("domain") - 1;
               while (*cp == ' ' || *cp == '\t')
                 cp++;
               if ((*cp == '\0') || (*cp == '\n'))
                 continue;
-              strncpy (statp->defdname, cp, sizeof (statp->defdname) - 1);
-              statp->defdname[sizeof (statp->defdname) - 1] = '\0';
-              if ((cp = strpbrk (statp->defdname, " \t\n")) != NULL)
+
+              cp = __strdup (cp);
+              if (cp == NULL)
+                return false;
+              free (parser->search_list_store);
+              parser->search_list_store = cp;
+              search_list_clear (&parser->search_list);
+              search_list_add (&parser->search_list, cp);
+              /* Replace trailing whitespace.  */
+              if ((cp = strpbrk (cp, " \t\n")) != NULL)
                 *cp = '\0';
-              havesearch = false;
               continue;
             }
           /* Set search list.  */
-          if (MATCH (*buffer, "search"))
+          if (MATCH (parser->buffer, "search"))
             {
               if (haveenv)
                 /* LOCALDOMAIN overrides the configuration file.  */
                 continue;
-              cp = *buffer + sizeof ("search") - 1;
+              cp = parser->buffer + sizeof ("search") - 1;
               while (*cp == ' ' || *cp == '\t')
                 cp++;
               if ((*cp == '\0') || (*cp == '\n'))
                 continue;
-              strncpy (statp->defdname, cp, sizeof (statp->defdname) - 1);
-              statp->defdname[sizeof (statp->defdname) - 1] = '\0';
-              if ((cp = strchr (statp->defdname, '\n')) != NULL)
-                *cp = '\0';
+
+              {
+                char *p = strchr (cp, '\n');
+                if (p != NULL)
+                  *p = '\0';
+              }
+              cp = __strdup (cp);
+              if (cp == NULL)
+                return false;
+              free (parser->search_list_store);
+              parser->search_list_store = cp;
+
+              /* The string is truncated below.  */
+              search_list_clear (&parser->search_list);
+              search_list_add (&parser->search_list, cp);
+
               /* Set search list to be blank-separated strings on rest
                  of line.  */
-              cp = statp->defdname;
-              pp = statp->dnsrch;
-              *pp++ = cp;
-              for (int n = 0; *cp && pp < statp->dnsrch + MAXDNSRCH; cp++)
+              for (bool in_name = true; *cp != '\0'; cp++)
                 {
                   if (*cp == ' ' || *cp == '\t')
                     {
-                      *cp = 0;
-                      n = 1;
+                      *cp = '\0';
+                      in_name = false;
                     }
-                  else if (n)
+                  else if (!in_name)
                     {
-                      *pp++ = cp;
-                      n = 0;
+                      search_list_add (&parser->search_list, cp);
+                      in_name = true;
                     }
                 }
-              /* Null terminate last domain if there are excess.  */
-              while (*cp != '\0' && *cp != ' ' && *cp != '\t')
-                cp++;
-              *cp = '\0';
-              *pp++ = 0;
-              havesearch = true;
               continue;
             }
           /* Read nameservers to query.  */
-          if (MATCH (*buffer, "nameserver") && nserv < MAXNS)
+          if (MATCH (parser->buffer, "nameserver") && nserv < MAXNS)
             {
               struct in_addr a;
 
-              cp = *buffer + sizeof ("nameserver") - 1;
+              cp = parser->buffer + sizeof ("nameserver") - 1;
               while (*cp == ' ' || *cp == '\t')
                 cp++;
               if ((*cp != '\0') && (*cp != '\n') && __inet_aton (cp, &a))
@@ -335,11 +402,11 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp, char **buffer)
                 }
               continue;
             }
-          if (MATCH (*buffer, "sortlist"))
+          if (MATCH (parser->buffer, "sortlist"))
             {
               struct in_addr a;
 
-              cp = *buffer + sizeof ("sortlist") - 1;
+              cp = parser->buffer + sizeof ("sortlist") - 1;
               while (nsort < MAXRESOLVSORT)
                 {
                   while (*cp == ' ' || *cp == '\t')
@@ -379,9 +446,9 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp, char **buffer)
                 }
               continue;
             }
-          if (MATCH (*buffer, "options"))
+          if (MATCH (parser->buffer, "options"))
             {
-              res_setoptions (statp, *buffer + sizeof ("options") - 1);
+              res_setoptions (statp, parser->buffer + sizeof ("options") - 1);
               continue;
             }
         }
@@ -399,25 +466,29 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp, char **buffer)
       statp->nsaddr.sin_port = htons (NAMESERVER_PORT);
       statp->nscount = 1;
     }
-  if (statp->defdname[0] == 0)
-    {
-      char buf[sizeof (statp->defdname)];
-      if (__gethostname (buf, sizeof (statp->defdname) - 1) == 0
-          && (cp = strchr (buf, '.')) != NULL)
-        strcpy (statp->defdname, cp + 1);
-    }
 
-  /* Find components of local domain that might be searched.  */
-  if (!havesearch)
+  if (search_list_size (&parser->search_list) == 0)
     {
-      pp = statp->dnsrch;
-      *pp++ = statp->defdname;
-      *pp = NULL;
-
+      char *domain;
+      if (!domain_from_hostname (&domain))
+        return false;
+      if (domain != NULL)
+        {
+          free (parser->search_list_store);
+          parser->search_list_store = domain;
+          search_list_add (&parser->search_list, domain);
+        }
     }
 
   if ((cp = getenv ("RES_OPTIONS")) != NULL)
     res_setoptions (statp, cp);
+
+  if (search_list_has_failed (&parser->search_list))
+    {
+      __set_errno (ENOMEM);
+      return false;
+    }
+
   statp->options |= RES_INIT;
   return true;
 }
@@ -453,13 +524,17 @@ __res_vinit (res_state statp, int preinit)
         return -1;
       }
 
-  char *buffer = NULL;
-  bool ok = res_vinit_1 (statp, preinit, fp, &buffer);
-  free (buffer);
+  struct resolv_conf_parser parser;
+  resolv_conf_parser_init (&parser);
+  bool ok = res_vinit_1 (statp, preinit, fp, &parser);
 
   if (ok)
     {
-      struct resolv_conf init = { 0 }; /* No data yet.  */
+      struct resolv_conf init =
+        {
+          .search_list = search_list_begin (&parser.search_list),
+          .search_list_size = search_list_size (&parser.search_list),
+        };
       struct resolv_conf *conf = __resolv_conf_allocate (&init);
       if (conf == NULL)
         ok = false;
@@ -469,6 +544,7 @@ __res_vinit (res_state statp, int preinit)
           __resolv_conf_put (conf);
         }
     }
+  resolv_conf_parser_free (&parser);
 
   if (!ok)
     {