]> git.ipfire.org Git - thirdparty/glibc.git/blobdiff - resolv/res_init.c
CVE-2016-10739: getaddrinfo: Fully parse IPv4 address strings [BZ #20018]
[thirdparty/glibc.git] / resolv / res_init.c
index e604a0212fa13624f565c5e7b03ce115ea27bd87..94743a252e39d64a1be0a868c01a03be8ea8c02b 100644 (file)
@@ -1,5 +1,5 @@
 /* Resolver state initialization and resolv.conf parsing.
-   Copyright (C) 1995-2017 Free Software Foundation, Inc.
+   Copyright (C) 1995-2018 Free Software Foundation, Inc.
    This file is part of the GNU C Library.
 
    The GNU C Library is free software; you can redistribute it and/or
@@ -85,6 +85,7 @@
 #include <ctype.h>
 #include <netdb.h>
 #include <resolv/resolv-internal.h>
+#include <res_hconf.h>
 #include <stdio.h>
 #include <stdio_ext.h>
 #include <stdlib.h>
 #include <sys/types.h>
 #include <inet/net-internal.h>
 #include <errno.h>
+#include <resolv_conf.h>
 
-static void res_setoptions (res_state, const char *, const char *);
 static uint32_t net_mask (struct in_addr);
 
-unsigned long long int __res_initstamp attribute_hidden;
-
 int
 res_ninit (res_state statp)
 {
@@ -122,176 +121,303 @@ is_sort_mask (char ch)
   return ch == '/' || ch == '&';
 }
 
-/* Internal helper function for __res_vinit, to aid with resource
-   deallocation and error handling.  Return true on success, false on
-   failure.  */
+/* Array of name server addresses.  */
+#define DYNARRAY_STRUCT nameserver_list
+#define DYNARRAY_ELEMENT const struct sockaddr *
+#define DYNARRAY_ELEMENT_FREE(e) free ((struct sockaddr *) *(e))
+#define DYNARRAY_INITIAL_SIZE 3
+#define DYNARRAY_PREFIX nameserver_list_
+#include <malloc/dynarray-skeleton.c>
+
+/* 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 6
+#define DYNARRAY_PREFIX search_list_
+#include <malloc/dynarray-skeleton.c>
+
+/* Array of name server addresses.  */
+#define DYNARRAY_STRUCT sort_list
+#define DYNARRAY_ELEMENT struct resolv_sortlist_entry
+#define DYNARRAY_INITIAL_SIZE 0
+#define DYNARRAY_PREFIX sort_list_
+#include <malloc/dynarray-skeleton.c>
+
+/* resolv.conf parser state and results.  */
+struct resolv_conf_parser
+{
+  char *buffer;            /* Temporary buffer for reading lines.  */
+
+  struct nameserver_list nameserver_list; /* Nameserver addresses.  */
+
+  char *search_list_store; /* Backing storage for search list entries.  */
+  struct search_list search_list; /* Points into search_list_store.  */
+
+  struct sort_list sort_list;   /* Address preference sorting list.  */
+
+  /* Configuration template.  The non-array elements are filled in
+     directly.  The array elements are updated prior to the call to
+     __resolv_conf_attach.  */
+  struct resolv_conf template;
+};
+
+/* Return true if *PREINIT contains actual preinitialization.  */
 static bool
-res_vinit_1 (res_state statp, bool preinit, FILE *fp)
+has_preinit_values (const struct __res_state *preinit)
 {
-  char *cp, **pp;
-  char buf[BUFSIZ];
-  int nserv = 0;    /* Number of nameservers read from file.  */
-  bool have_serv6 = false;
-  bool haveenv = false;
-  bool havesearch = false;
-  int nsort = 0;
-  char *net;
-  statp->_u._ext.initstamp = __res_initstamp;
+  return (preinit->retrans != 0 && preinit->retrans != RES_TIMEOUT)
+    || (preinit->retry != 0 && preinit->retry != RES_DFLRETRY)
+    || (preinit->options != 0
+        && (preinit->options & ~RES_INIT) != RES_DEFAULT);
+}
+
+static void
+resolv_conf_parser_init (struct resolv_conf_parser *parser,
+                         const struct __res_state *preinit)
+{
+  parser->buffer = NULL;
+  parser->search_list_store = NULL;
+  nameserver_list_init (&parser->nameserver_list);
+  search_list_init (&parser->search_list);
+  sort_list_init (&parser->sort_list);
+
+  if (preinit != NULL)
+    {
+      parser->template.retrans = preinit->retrans;
+      parser->template.retry = preinit->retry;
+      parser->template.options = preinit->options | RES_INIT;
+    }
+  else
+    {
+      parser->template.retrans = RES_TIMEOUT;
+      parser->template.retry = RES_DFLRETRY;
+      parser->template.options = RES_DEFAULT | RES_INIT;
+    }
+  parser->template.ndots = 1;
+}
+
+static void
+resolv_conf_parser_free (struct resolv_conf_parser *parser)
+{
+  free (parser->buffer);
+  free (parser->search_list_store);
+  nameserver_list_free (&parser->nameserver_list);
+  search_list_free (&parser->search_list);
+  sort_list_free (&parser->sort_list);
+}
+
+/* Allocate a struct sockaddr_in object on the heap, with the
+   specified address and port.  */
+static struct sockaddr *
+allocate_address_v4 (struct in_addr a, uint16_t port)
+{
+  struct sockaddr_in *sa4 = malloc (sizeof (*sa4));
+  if (sa4 == NULL)
+    return NULL;
+  sa4->sin_family = AF_INET;
+  sa4->sin_addr = a;
+  sa4->sin_port = htons (port);
+  return (struct sockaddr *) sa4;
+}
 
-  if (!preinit)
+/* 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)
     {
-      statp->retrans = RES_TIMEOUT;
-      statp->retry = RES_DFLRETRY;
-      statp->options = RES_DEFAULT;
-      statp->id = res_randomid ();
+      char *dot = strchr (buf, '.');
+      if (dot != NULL)
+        {
+          *result = __strdup (dot + 1);
+          if (*result == NULL)
+            return false;
+          return true;
+        }
     }
+  *result = NULL;
+  return true;
+}
 
-  statp->nscount = 0;
-  statp->defdname[0] = '\0';
-  statp->ndots = 1;
-  statp->pfcode = 0;
-  statp->_vcsock = -1;
-  statp->_flags = 0;
-  statp->__glibc_unused_qhook = NULL;
-  statp->__glibc_unused_rhook = NULL;
-  statp->_u._ext.nscount = 0;
-  for (int n = 0; n < MAXNS; n++)
-    statp->_u._ext.nsaddrs[n] = NULL;
+static void res_setoptions (struct resolv_conf_parser *, const char *options);
+
+/* 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 (FILE *fp, struct resolv_conf_parser *parser)
+{
+  char *cp;
+  size_t buffer_size = 0;
+  bool haveenv = false;
 
   /* 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)                       \
-  (!strncmp (line, name, sizeof (name) - 1)     \
-   && (line[sizeof (name) - 1] == ' '           \
-       || line[sizeof (name) - 1] == '\t'))
+  (!strncmp ((line), name, sizeof (name) - 1)     \
+   && ((line)[sizeof (name) - 1] == ' '           \
+       || (line)[sizeof (name) - 1] == '\t'))
 
   if (fp != NULL)
     {
       /* No threads use this stream.  */
       __fsetlocking (fp, FSETLOCKING_BYCALLER);
       /* Read the config file.  */
-      while (__fgets_unlocked (buf, sizeof (buf), fp) != NULL)
+      while (true)
         {
+          {
+            ssize_t ret = __getline (&parser->buffer, &buffer_size, fp);
+            if (ret <= 0)
+              {
+                if (_IO_ferror_unlocked (fp))
+                  return false;
+                else
+                  break;
+              }
+          }
+
           /* Skip comments.  */
-          if (*buf == ';' || *buf == '#')
+          if (*parser->buffer == ';' || *parser->buffer == '#')
             continue;
           /* Read default domain name.  */
-          if (MATCH (buf, "domain"))
+          if (MATCH (parser->buffer, "domain"))
             {
               if (haveenv)
                 /* LOCALDOMAIN overrides the configuration file.  */
                 continue;
-              cp = buf + 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 (buf, "search"))
+          if (MATCH (parser->buffer, "search"))
             {
               if (haveenv)
                 /* LOCALDOMAIN overrides the configuration file.  */
                 continue;
-              cp = buf + 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 (buf, "nameserver") && nserv < MAXNS)
+          if (MATCH (parser->buffer, "nameserver"))
             {
               struct in_addr a;
 
-              cp = buf + sizeof ("nameserver") - 1;
+              cp = parser->buffer + sizeof ("nameserver") - 1;
               while (*cp == ' ' || *cp == '\t')
                 cp++;
-              if ((*cp != '\0') && (*cp != '\n') && __inet_aton (cp, &a))
+
+              /* Ignore trailing contents on the name server line.  */
+              {
+                char *el;
+                if ((el = strpbrk (cp, " \t\n")) != NULL)
+                  *el = '\0';
+              }
+
+              struct sockaddr *sa;
+              if ((*cp != '\0') && (*cp != '\n') && __inet_aton_exact (cp, &a))
                 {
-                  statp->nsaddr_list[nserv].sin_addr = a;
-                  statp->nsaddr_list[nserv].sin_family = AF_INET;
-                  statp->nsaddr_list[nserv].sin_port = htons (NAMESERVER_PORT);
-                  nserv++;
+                  sa = allocate_address_v4 (a, NAMESERVER_PORT);
+                  if (sa == NULL)
+                    return false;
                 }
               else
                 {
                   struct in6_addr a6;
                   char *el;
-
-                  if ((el = strpbrk (cp, " \t\n")) != NULL)
-                    *el = '\0';
                   if ((el = strchr (cp, SCOPE_DELIMITER)) != NULL)
                     *el = '\0';
                   if ((*cp != '\0') && (__inet_pton (AF_INET6, cp, &a6) > 0))
@@ -300,7 +426,7 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp)
 
                       sa6 = malloc (sizeof (*sa6));
                       if (sa6 == NULL)
-                        return -1;
+                        return false;
 
                       sa6->sin6_family = AF_INET6;
                       sa6->sin6_port = htons (NAMESERVER_PORT);
@@ -313,36 +439,47 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp)
                            compatibility.  */
                         __inet6_scopeid_pton
                           (&a6, el + 1, &sa6->sin6_scope_id);
-
-                      statp->nsaddr_list[nserv].sin_family = 0;
-                      statp->_u._ext.nsaddrs[nserv] = sa6;
-                      statp->_u._ext.nssocks[nserv] = -1;
-                      have_serv6 = true;
-                      nserv++;
+                      sa = (struct sockaddr *) sa6;
+                    }
+                  else
+                    /* IPv6 address parse failure.  */
+                    sa = NULL;
+                }
+              if (sa != NULL)
+                {
+                  const struct sockaddr **p = nameserver_list_emplace
+                    (&parser->nameserver_list);
+                  if (p != NULL)
+                    *p = sa;
+                  else
+                    {
+                      free (sa);
+                      return false;
                     }
                 }
               continue;
             }
-          if (MATCH (buf, "sortlist"))
+          if (MATCH (parser->buffer, "sortlist"))
             {
               struct in_addr a;
 
-              cp = buf + sizeof ("sortlist") - 1;
-              while (nsort < MAXRESOLVSORT)
+              cp = parser->buffer + sizeof ("sortlist") - 1;
+              while (true)
                 {
                   while (*cp == ' ' || *cp == '\t')
                     cp++;
                   if (*cp == '\0' || *cp == '\n' || *cp == ';')
                     break;
-                  net = cp;
+                  char *net = cp;
                   while (*cp && !is_sort_mask (*cp) && *cp != ';'
                          && isascii (*cp) && !isspace (*cp))
                     cp++;
                   char separator = *cp;
                   *cp = 0;
-                  if (__inet_aton (net, &a))
+                  struct resolv_sortlist_entry e;
+                  if (__inet_aton_exact (net, &a))
                     {
-                      statp->sort_list[nsort].addr = a;
+                      e.addr = a;
                       if (is_sort_mask (separator))
                         {
                           *cp++ = separator;
@@ -352,70 +489,72 @@ res_vinit_1 (res_state statp, bool preinit, FILE *fp)
                             cp++;
                           separator = *cp;
                           *cp = 0;
-                          if (__inet_aton (net, &a))
-                            statp->sort_list[nsort].mask = a.s_addr;
+                          if (__inet_aton_exact (net, &a))
+                            e.mask = a.s_addr;
                           else
-                            statp->sort_list[nsort].mask
-                              = net_mask (statp->sort_list[nsort].addr);
+                            e.mask = net_mask (e.addr);
                         }
                       else
-                        statp->sort_list[nsort].mask
-                          = net_mask (statp->sort_list[nsort].addr);
-                      nsort++;
+                        e.mask = net_mask (e.addr);
+                      sort_list_add (&parser->sort_list, e);
                     }
                   *cp = separator;
                 }
               continue;
             }
-          if (MATCH (buf, "options"))
+          if (MATCH (parser->buffer, "options"))
             {
-              res_setoptions (statp, buf + sizeof ("options") - 1, "conf");
+              res_setoptions (parser, parser->buffer + sizeof ("options") - 1);
               continue;
             }
         }
-      statp->nscount = nserv;
-      if (have_serv6)
-        /* We try IPv6 servers again.  */
-        statp->ipv6_unavail = false;
-      statp->nsort = nsort;
       fclose (fp);
     }
-  if (__glibc_unlikely (statp->nscount == 0))
+  if (__glibc_unlikely (nameserver_list_size (&parser->nameserver_list) == 0))
     {
-      statp->nsaddr.sin_addr = __inet_makeaddr (IN_LOOPBACKNET, 1);
-      statp->nsaddr.sin_family = AF_INET;
-      statp->nsaddr.sin_port = htons (NAMESERVER_PORT);
-      statp->nscount = 1;
+      const struct sockaddr **p
+        = nameserver_list_emplace (&parser->nameserver_list);
+      if (p == NULL)
+        return false;
+      *p = allocate_address_v4 (__inet_makeaddr (IN_LOOPBACKNET, 1),
+                                NAMESERVER_PORT);
+      if (*p == NULL)
+        return false;
     }
-  if (statp->defdname[0] == 0
-      && __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, "env");
-  statp->options |= RES_INIT;
-  return 0;
+    res_setoptions (parser, cp);
+
+  if (nameserver_list_has_failed (&parser->nameserver_list)
+      || search_list_has_failed (&parser->search_list)
+      || sort_list_has_failed (&parser->sort_list))
+    {
+      __set_errno (ENOMEM);
+      return false;
+    }
+
+  return true;
 }
 
-/* Set up default settings.  If the /etc/resolv.conf configuration
-   file exist, the values there will have precedence.  Otherwise, the
-   server address is set to INADDR_LOOPBACK and the default domain
-   name comes from gethostname.  The RES_OPTIONS and LOCALDOMAIN
-   environment variables can be used to override some settings.
-   Return 0 if completes successfully, -1 on error.  */
-int
-__res_vinit (res_state statp, int preinit)
+struct resolv_conf *
+__resolv_conf_load (struct __res_state *preinit)
 {
+  /* Ensure that /etc/hosts.conf has been loaded (once).  */
+  _res_hconf_init ();
+
   FILE *fp = fopen (_PATH_RESCONF, "rce");
   if (fp == NULL)
     switch (errno)
@@ -432,21 +571,64 @@ __res_vinit (res_state statp, int preinit)
       default:
         /* Other errors refer to resource allocation problems and
            need to be handled by the application.  */
-        return -1;
+        return NULL;
       }
-  if (!res_vinit_1 (statp, preinit, fp))
+
+  struct resolv_conf_parser parser;
+  resolv_conf_parser_init (&parser, preinit);
+
+  struct resolv_conf *conf = NULL;
+  if (res_vinit_1 (fp, &parser))
+    {
+      parser.template.nameserver_list
+        = nameserver_list_begin (&parser.nameserver_list);
+      parser.template.nameserver_list_size
+        = nameserver_list_size (&parser.nameserver_list);
+      parser.template.search_list = search_list_begin (&parser.search_list);
+      parser.template.search_list_size
+        = search_list_size (&parser.search_list);
+      parser.template.sort_list = sort_list_begin (&parser.sort_list);
+      parser.template.sort_list_size = sort_list_size (&parser.sort_list);
+      conf = __resolv_conf_allocate (&parser.template);
+    }
+  resolv_conf_parser_free (&parser);
+
+  return conf;
+}
+
+/* Set up default settings.  If the /etc/resolv.conf configuration
+   file exist, the values there will have precedence.  Otherwise, the
+   server address is set to INADDR_LOOPBACK and the default domain
+   name comes from gethostname.  The RES_OPTIONS and LOCALDOMAIN
+   environment variables can be used to override some settings.
+   Return 0 if completes successfully, -1 on error.  */
+int
+__res_vinit (res_state statp, int preinit)
+{
+  struct resolv_conf *conf;
+  if (preinit && has_preinit_values (statp))
+    /* For the preinit case, we cannot use the cached configuration
+       because some settings could be different.  */
+    conf = __resolv_conf_load (statp);
+  else
+    conf = __resolv_conf_get_current ();
+  if (conf == NULL)
+    return -1;
+
+  bool ok = __resolv_conf_attach (statp, conf);
+  __resolv_conf_put (conf);
+  if (ok)
     {
-      /* Deallocate the name server addresses which have been
-         allocated.  */
-      for (int n = 0; n < MAXNS; n++)
-        free (statp->_u._ext.nsaddrs[n]);
-      return -1;
+      if (preinit)
+        statp->id = res_randomid ();
+      return 0;
     }
-  return 0;
+  else
+    return -1;
 }
 
 static void
-res_setoptions (res_state statp, const char *options, const char *source)
+res_setoptions (struct resolv_conf_parser *parser, const char *options)
 {
   const char *cp = options;
 
@@ -460,25 +642,25 @@ res_setoptions (res_state statp, const char *options, const char *source)
         {
           int i = atoi (cp + sizeof ("ndots:") - 1);
           if (i <= RES_MAXNDOTS)
-            statp->ndots = i;
+            parser->template.ndots = i;
           else
-            statp->ndots = RES_MAXNDOTS;
+            parser->template.ndots = RES_MAXNDOTS;
         }
       else if (!strncmp (cp, "timeout:", sizeof ("timeout:") - 1))
         {
           int i = atoi (cp + sizeof ("timeout:") - 1);
           if (i <= RES_MAXRETRANS)
-            statp->retrans = i;
+            parser->template.retrans = i;
           else
-            statp->retrans = RES_MAXRETRANS;
+            parser->template.retrans = RES_MAXRETRANS;
         }
       else if (!strncmp (cp, "attempts:", sizeof ("attempts:") - 1))
         {
           int i = atoi (cp + sizeof ("attempts:") - 1);
           if (i <= RES_MAXRETRY)
-            statp->retry = i;
+            parser->template.retry = i;
           else
-            statp->retry = RES_MAXRETRY;
+            parser->template.retry = RES_MAXRETRY;
         }
       else
         {
@@ -497,6 +679,7 @@ res_setoptions (res_state statp, const char *options, const char *source)
             { STRnLEN ("single-request"), 0, RES_SNGLKUP },
             { STRnLEN ("no_tld_query"), 0, RES_NOTLDQUERY },
             { STRnLEN ("no-tld-query"), 0, RES_NOTLDQUERY },
+            { STRnLEN ("no-reload"), 0, RES_NORELOAD },
             { STRnLEN ("use-vc"), 0, RES_USEVC }
           };
 #define noptions (sizeof (options) / sizeof (options[0]))
@@ -504,9 +687,9 @@ res_setoptions (res_state statp, const char *options, const char *source)
             if (strncmp (cp, options[i].str, options[i].len) == 0)
               {
                 if (options[i].clear)
-                  statp->options &= options[i].flag;
+                  parser->template.options &= options[i].flag;
                 else
-                  statp->options |= options[i].flag;
+                  parser->template.options |= options[i].flag;
                 break;
               }
         }