]> git.decadent.org.uk Git - nfs-utils.git/blobdiff - support/export/hostname.c
nfs: fix host_reliable_addrinfo
[nfs-utils.git] / support / export / hostname.c
index f88e0a485290cb878ab670f4b6917648c3903485..3e949a18fe52255b51a4d77713f28b1679f16b75 100644 (file)
 /*
- * support/export/hostname.c
+ * Copyright 2010 Oracle.  All rights reserved.
  *
- * Functions for hostname.
+ * This file is part of nfs-utils.
  *
+ * nfs-utils is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * nfs-utils is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with nfs-utils.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "config.h"
-
-/*
-#define TEST
-*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
 
 #include <string.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
 #include <stdlib.h>
-#include <xlog.h>
-#ifdef TEST
-#define xmalloc malloc
-#else
-#include "xmalloc.h"
-#include "misc.h"
-#endif
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <errno.h>
 
-#define ALIGNMENT      sizeof (char *)
+#include "sockaddr.h"
+#include "exportfs.h"
 
-static int
-align (int len, int al)
+/**
+ * host_ntop - generate presentation address given a sockaddr
+ * @sap: pointer to socket address
+ * @buf: working storage
+ * @buflen: size of @buf in bytes
+ *
+ * Returns a pointer to a @buf.
+ */
+#ifdef HAVE_GETNAMEINFO
+char *
+host_ntop(const struct sockaddr *sap, char *buf, const size_t buflen)
 {
-  int i;
-  i = len % al;
-  if (i)
-    len += al - i;
-  return len;
-}
+       socklen_t salen = nfs_sockaddr_length(sap);
+       int error;
+
+       memset(buf, 0, buflen);
+
+       if (salen == 0) {
+               (void)strncpy(buf, "bad family", buflen - 1);
+               return buf;
+       }
 
-struct hostent *
-get_hostent (const char *addr, int len, int type)
+       error = getnameinfo(sap, salen, buf, (socklen_t)buflen,
+                                               NULL, 0, NI_NUMERICHOST);
+       if (error != 0) {
+               buf[0] = '\0';
+               (void)strncpy(buf, "bad address", buflen - 1);
+       }
+
+       return buf;
+}
+#else  /* !HAVE_GETNAMEINFO */
+char *
+host_ntop(const struct sockaddr *sap, char *buf, const size_t buflen)
 {
-  struct hostent *cp;
-  int len_ent;
-  const char *name;
-  int len_name;
-  int num_aliases = 1;
-  int len_aliases = sizeof (char *);
-  int num_addr_list = 1;
-  int len_addr_list = sizeof (char *);
-  int pos;
-  struct in_addr *ipv4;
-
-  switch (type)
-    {
-    case AF_INET:
-      ipv4 = (struct in_addr *) addr;
-      name = inet_ntoa (*ipv4);
-      break;
-
-    default:
-      return NULL;
-    }
-
-  len_ent = align (sizeof (*cp), ALIGNMENT);
-  len_name = align (strlen (name) + 1, ALIGNMENT);
-
-  num_addr_list++;
-  len_addr_list += align (len, ALIGNMENT) + sizeof (char *);
-
-  cp = (struct hostent *) xmalloc (len_ent + len_name + len_aliases
-                                  + len_addr_list);
-
-  cp->h_addrtype = type;
-  cp->h_length = len;
-  pos = len_ent;
-  cp->h_name = (char *) &(((char *) cp) [pos]);
-  strcpy (cp->h_name, name);
-
-  pos += len_name;
-  cp->h_aliases = (char **) &(((char *) cp) [pos]);
-  pos += num_aliases * sizeof (char *);
-  cp->h_aliases [0] = NULL;
-
-  pos = len_ent + len_name + len_aliases;
-  cp->h_addr_list = (char **) &(((char *) cp) [pos]);
-  pos += num_addr_list * sizeof (char *);
-  cp->h_addr_list [0] = (char *) &(((char *) cp) [pos]);
-  memcpy (cp->h_addr_list [0], addr, cp->h_length);
-  pos += align (cp->h_length, ALIGNMENT);
-  cp->h_addr_list [1] = NULL;
-
-  return cp;
+       const struct sockaddr_in *sin = (const struct sockaddr_in *)(char *)sap;
+
+       memset(buf, 0, buflen);
+
+       if (sin->sin_family != AF_INET)
+               (void)strncpy(buf, "bad family", buflen - 1);
+               return buf;
+       }
+
+       if (inet_ntop(AF_INET, &sin->sin_addr.s_addr, buf, buflen) != NULL)
+               return buf;
+
+       buf[0] = '\0';
+       (void)strncpy(buf, "bad address", buflen - 1);
+       return buf;
 }
+#endif /* !HAVE_GETNAMEINFO */
 
-struct hostent *
-hostent_dup (struct hostent *hp)
+/**
+ * host_pton - return addrinfo for a given presentation address
+ * @paddr: pointer to a '\0'-terminated ASCII string containing an
+ *             IP presentation address
+ *
+ * Returns address info structure, or NULL if an error occurs.  Caller
+ * must free the returned structure with freeaddrinfo(3).
+ */
+__attribute_malloc__
+struct addrinfo *
+host_pton(const char *paddr)
 {
-  int len_ent = align (sizeof (*hp), ALIGNMENT);
-  int len_name = align (strlen (hp->h_name) + 1, ALIGNMENT);
-  int num_aliases = 1;
-  int len_aliases = sizeof (char *);
-  int num_addr_list = 1;
-  int len_addr_list = sizeof (char *);
-  int pos, i;
-  char **sp;
-  struct hostent *cp;
-
-  for (sp = hp->h_aliases; sp && *sp; sp++)
-    {
-      num_aliases++;
-      len_aliases += align (strlen (*sp) + 1, ALIGNMENT)
-                    + sizeof (char *);
-    }
-
-  for (sp = hp->h_addr_list; *sp; sp++)
-    {
-      num_addr_list++;
-      len_addr_list += align (hp->h_length, ALIGNMENT)
-                      + sizeof (char *);
-    }
-  
-  cp = (struct hostent *) xmalloc (len_ent + len_name + len_aliases
-                                  + len_addr_list);
-
-  *cp = *hp;
-  pos = len_ent;
-  cp->h_name = (char *) &(((char *) cp) [pos]);
-  strcpy (cp->h_name, hp->h_name);
-
-  pos += len_name;
-  cp->h_aliases = (char **) &(((char *) cp) [pos]);
-  pos += num_aliases * sizeof (char *);
-  for (sp = hp->h_aliases, i = 0; i < num_aliases; i++, sp++)
-    if (sp && *sp)
-      {
-       cp->h_aliases [i] = (char *) &(((char *) cp) [pos]);
-       strcpy (cp->h_aliases [i], *sp);
-       pos += align (strlen (*sp) + 1, ALIGNMENT);
-      }
-    else
-      cp->h_aliases [i] = NULL;
-
-  pos = len_ent + len_name + len_aliases;
-  cp->h_addr_list = (char **) &(((char *) cp) [pos]);
-  pos += num_addr_list * sizeof (char *);
-  for (sp = hp->h_addr_list, i = 0; i < num_addr_list; i++, sp++)
-    if (*sp)
-      {
-       cp->h_addr_list [i] = (char *) &(((char *) cp) [pos]);
-       memcpy (cp->h_addr_list [i], *sp, hp->h_length);
-       pos += align (hp->h_length, ALIGNMENT);
-      }
-    else
-      cp->h_addr_list [i] = *sp;
-
-  return cp;
+       struct addrinfo *ai = NULL;
+       struct addrinfo hint = {
+               /* don't return duplicates */
+               .ai_protocol    = (int)IPPROTO_UDP,
+               .ai_flags       = AI_NUMERICHOST,
+               .ai_family      = AF_UNSPEC,
+       };
+       struct sockaddr_in sin;
+       int error, inet4;
+
+       /*
+        * Although getaddrinfo(3) is easier to use and supports
+        * IPv6, it recognizes incomplete addresses like "10.4"
+        * as valid AF_INET addresses.  It also accepts presentation
+        * addresses that end with a blank.
+        *
+        * inet_pton(3) is much stricter.  Use it to be certain we
+        * have a real AF_INET presentation address, before invoking
+        * getaddrinfo(3) to generate the full addrinfo list.
+        */
+       inet4 = 1;
+       if (inet_pton(AF_INET, paddr, &sin.sin_addr) == 0)
+               inet4 = 0;
+
+       error = getaddrinfo(paddr, NULL, &hint, &ai);
+       switch (error) {
+       case 0:
+               if (!inet4 && ai->ai_addr->sa_family == AF_INET) {
+                       freeaddrinfo(ai);
+                       break;
+               }
+               return ai;
+       case EAI_NONAME:
+               if (paddr == NULL)
+                       xlog(D_GENERAL, "%s: passed a NULL presentation address",
+                               __func__);
+               break;
+       case EAI_SYSTEM:
+               xlog(D_GENERAL, "%s: failed to convert %s: (%d) %m",
+                               __func__, paddr, errno);
+               break;
+       default:
+               xlog(D_GENERAL, "%s: failed to convert %s: %s",
+                               __func__, paddr, gai_strerror(error));
+               break;
+       }
+
+       return NULL;
 }
 
-static int
-is_hostname(const char *sp)
+/**
+ * host_addrinfo - return addrinfo for a given hostname
+ * @hostname: pointer to a '\0'-terminated ASCII string containing a hostname
+ *
+ * Returns address info structure with ai_canonname filled in, or NULL
+ * if no information is available for @hostname.  Caller must free the
+ * returned structure with freeaddrinfo(3).
+ */
+__attribute_malloc__
+struct addrinfo *
+host_addrinfo(const char *hostname)
 {
-  if (*sp == '\0' || *sp == '@')
-    return 0;
-
-  for (; *sp; sp++)
-    {
-      if (*sp == '*' || *sp == '?' || *sp == '[' || *sp == '/')
-       return 0;
-      if (*sp == '\\' && sp[1])
-       sp++;
-    }
-
-  return 1;
+       struct addrinfo *ai = NULL;
+       struct addrinfo hint = {
+#ifdef IPV6_SUPPORTED
+               .ai_family      = AF_UNSPEC,
+#else
+               .ai_family      = AF_INET,
+#endif
+               /* don't return duplicates */
+               .ai_protocol    = (int)IPPROTO_UDP,
+               .ai_flags       = AI_CANONNAME,
+       };
+       int error;
+
+       error = getaddrinfo(hostname, NULL, &hint, &ai);
+       switch (error) {
+       case 0:
+               return ai;
+       case EAI_SYSTEM:
+               xlog(D_GENERAL, "%s: failed to resolve %s: (%d) %m",
+                               __func__, hostname, errno);
+               break;
+       default:
+               xlog(D_GENERAL, "%s: failed to resolve %s: %s",
+                               __func__, hostname, gai_strerror(error));
+               break;
+       }
+
+       return NULL;
 }
 
-int
-matchhostname (const char *h1, const char *h2)
+/**
+ * host_canonname - return canonical hostname bound to an address
+ * @sap: pointer to socket address to look up
+ *
+ * Discover the canonical hostname associated with the given socket
+ * address.  The host's reverse mapping is verified in the process.
+ *
+ * Returns a '\0'-terminated ASCII string containing a hostname, or
+ * NULL if no hostname can be found for @sap.  Caller must free
+ * the string.
+ */
+#ifdef HAVE_GETNAMEINFO
+__attribute_malloc__
+char *
+host_canonname(const struct sockaddr *sap)
 {
-  struct hostent *hp1, *hp2;
-  int status;
-
-  if (strcasecmp (h1, h2) == 0)
-    return 1;
-
-  if (!is_hostname (h1) || !is_hostname (h2))
-    return 0;
-
-  hp1 = gethostbyname (h1);
-  if (hp1 == NULL)
-    return 0;
-
-  hp1 = hostent_dup (hp1);
-
-  hp2 = gethostbyname (h2);
-  if (hp2)
-    {
-      if (strcasecmp (hp1->h_name, hp2->h_name) == 0)
-       status = 1;
-      else
-       {
-         char **ap1, **ap2;
-
-         status = 0;
-         for (ap1 = hp1->h_addr_list; *ap1 && status == 0; ap1++)
-           for (ap2 = hp2->h_addr_list; *ap2; ap2++)
-             if (memcmp (*ap1, *ap2, sizeof (struct in_addr)) == 0)
-               {
-                 status = 1;
-                 break;
-               }
+       socklen_t salen = nfs_sockaddr_length(sap);
+       char buf[NI_MAXHOST];
+       int error;
+
+       if (salen == 0) {
+               xlog(D_GENERAL, "%s: unsupported address family %d",
+                               __func__, sap->sa_family);
+               return NULL;
        }
-    }
-  else
-    status = 0;
 
-  free (hp1);
-  return status;
+       memset(buf, 0, sizeof(buf));
+       error = getnameinfo(sap, salen, buf, (socklen_t)sizeof(buf),
+                                                       NULL, 0, NI_NAMEREQD);
+       switch (error) {
+       case 0:
+               break;
+       case EAI_SYSTEM:
+               xlog(D_GENERAL, "%s: getnameinfo(3) failed: (%d) %m",
+                               __func__, errno);
+               return NULL;
+       default:
+               (void)getnameinfo(sap, salen, buf, (socklen_t)sizeof(buf),
+                                                       NULL, 0, NI_NUMERICHOST);
+               xlog(D_GENERAL, "%s: failed to resolve %s: %s",
+                               __func__, buf, gai_strerror(error));
+               return NULL;
+       }
+
+       return strdup(buf);
 }
+#else  /* !HAVE_GETNAMEINFO */
+__attribute_malloc__
+char *
+host_canonname(const struct sockaddr *sap)
+{
+       const struct sockaddr_in *sin = (const struct sockaddr_in *)(char *)sap;
+       const struct in_addr *addr = &sin->sin_addr;
+       struct hostent *hp;
 
+       if (sap->sa_family != AF_INET)
+               return NULL;
 
-/* Map IP to hostname, and then map back to addr to make sure it is a
- * reliable hostname 
+       hp = gethostbyaddr(addr, (socklen_t)sizeof(addr), AF_INET);
+       if (hp == NULL)
+               return NULL;
+
+       return strdup(hp->h_name);
+}
+#endif /* !HAVE_GETNAMEINFO */
+
+/**
+ * host_reliable_addrinfo - return addrinfo for a given address
+ * @sap: pointer to socket address to look up
+ *
+ * Reverse and forward lookups are performed to ensure the address has
+ * matching forward and reverse mappings.
+ *
+ * Returns addrinfo structure with just the provided address with
+ * ai_canonname filled in. If there is a problem with resolution or
+ * the resolved records don't match up properly then it returns NULL
+ *
+ * Caller must free the returned structure with freeaddrinfo(3).
  */
-struct hostent *
-get_reliable_hostbyaddr(const char *addr, int len, int type)
+__attribute_malloc__
+struct addrinfo *
+host_reliable_addrinfo(const struct sockaddr *sap)
 {
-       struct hostent *hp;
+       struct addrinfo *ai, *a;
+       char *hostname;
 
-       char **sp;
-       struct hostent *forward = NULL;
-       char *tmpname;
+       hostname = host_canonname(sap);
+       if (hostname == NULL)
+               return NULL;
 
-       hp = gethostbyaddr(addr, len , type);
-       if (!hp)
-               return hp;
+       ai = host_addrinfo(hostname);
+       if (!ai)
+               goto out_free_hostname;
 
-       /* must make sure the hostent is authorative. */
+       /* make sure there's a matching address in the list */
+       for (a = ai; a; a = a->ai_next)
+               if (nfs_compare_sockaddr(a->ai_addr, sap))
+                       break;
 
-       hp = hostent_dup (hp);
-       tmpname = xstrdup((hp)->h_name);
-       if (tmpname) {
-               forward = gethostbyname(tmpname);
-               free(tmpname);
-       }
-       if (forward) {
-               /* now make sure the "addr" is in the list */
-               for (sp = forward->h_addr_list ; *sp ; sp++) {
-                       if (memcmp(*sp, addr, forward->h_length)==0)
-                               break;
-               }
-               
-               if (!*sp) {
-                       /* it was a FAKE */
-                       xlog(L_WARNING, "Fake hostname %s for %s - forward lookup doesn't match reverse",
-                            forward->h_name, inet_ntoa(*(struct in_addr*)addr));
-                       return NULL;
-               }
-               free (hp);
-               hp = hostent_dup (forward);
+       freeaddrinfo(ai);
+       if (!a)
+               goto out_free_hostname;
+
+       /* get addrinfo with just the original address */
+       ai = host_numeric_addrinfo(sap);
+       if (!ai)
+               goto out_free_hostname;
+
+       /* and populate its ai_canonname field */
+       free(ai->ai_canonname);
+       ai->ai_canonname = hostname;
+       return ai;
+
+out_free_hostname:
+       free(hostname);
+       return NULL;
+}
+
+/**
+ * host_numeric_addrinfo - return addrinfo without doing DNS queries
+ * @sap: pointer to socket address
+ *
+ * Returns address info structure, or NULL if an error occurred.
+ * Caller must free the returned structure with freeaddrinfo(3).
+ */
+#ifdef HAVE_GETNAMEINFO
+__attribute_malloc__
+struct addrinfo *
+host_numeric_addrinfo(const struct sockaddr *sap)
+{
+       socklen_t salen = nfs_sockaddr_length(sap);
+       char buf[INET6_ADDRSTRLEN];
+       struct addrinfo *ai;
+       int error;
+
+       if (salen == 0) {
+               xlog(D_GENERAL, "%s: unsupported address family %d",
+                               __func__, sap->sa_family);
+               return NULL;
        }
-       else {
-               /* never heard of it. misconfigured DNS? */
-               xlog(L_WARNING, "Fake hostname %s for %s - forward lookup doesn't exist",
-                    forward->h_name, inet_ntoa(*(struct in_addr*)addr));
+
+       memset(buf, 0, sizeof(buf));
+       error = getnameinfo(sap, salen, buf, (socklen_t)sizeof(buf),
+                                               NULL, 0, NI_NUMERICHOST);
+       switch (error) {
+       case 0:
+               break;
+       case EAI_SYSTEM:
+               xlog(D_GENERAL, "%s: getnameinfo(3) failed: (%d) %m",
+                               __func__, errno);
+               return NULL;
+       default:
+               xlog(D_GENERAL, "%s: getnameinfo(3) failed: %s",
+                               __func__, gai_strerror(error));
                return NULL;
        }
-       return hp;
-}
 
+       ai = host_pton(buf);
+
+       /*
+        * getaddrinfo(AI_NUMERICHOST) never fills in ai_canonname
+        */
+       if (ai != NULL) {
+               free(ai->ai_canonname);         /* just in case */
+               ai->ai_canonname = strdup(buf);
+               if (ai->ai_canonname == NULL) {
+                       freeaddrinfo(ai);
+                       ai = NULL;
+               }
+       }
 
-#ifdef TEST
-void
-print_host (struct hostent *hp)
-{
-  char **sp;
-
-  if (hp)
-    {
-      printf ("official hostname: %s\n", hp->h_name); 
-      printf ("aliases:\n");
-      for (sp = hp->h_aliases; *sp; sp++)
-       printf ("  %s\n", *sp);
-      printf ("IP addresses:\n");
-      for (sp = hp->h_addr_list; *sp; sp++)
-       printf ("  %s\n", inet_ntoa (*(struct in_addr *) *sp));
-    }
-  else
-    printf ("Not host information\n");
+       return ai;
 }
-
-int
-main (int argc, char **argv)
+#else  /* !HAVE_GETNAMEINFO */
+__attribute_malloc__
+struct addrinfo *
+host_numeric_addrinfo(const struct sockaddr *sap)
 {
-  struct hostent *hp = gethostbyname (argv [1]);
-  struct hostent *cp;
-  struct in_addr addr;
-
-  print_host (hp);
-
-  if (hp)
-    {
-      cp = hostent_dup (hp);
-      print_host (cp);
-      free (cp);
-    }
-  printf ("127.0.0.1 == %s: %d\n", argv [1],
-         matchhostname ("127.0.0.1", argv [1]));
-  addr.s_addr = inet_addr(argv [2]);
-  printf ("%s\n", inet_ntoa (addr));
-  cp = get_hostent ((const char *)&addr, sizeof(addr), AF_INET);
-  print_host (cp);
-  return 0;
+       const struct sockaddr_in *sin = (const struct sockaddr_in *)sap;
+       const struct in_addr *addr = &sin->sin_addr;
+       char buf[INET_ADDRSTRLEN];
+       struct addrinfo *ai;
+
+       if (sap->sa_family != AF_INET)
+               return NULL;
+
+       memset(buf, 0, sizeof(buf));
+       if (inet_ntop(AF_INET, (char *)addr, buf,
+                                       (socklen_t)sizeof(buf)) == NULL)
+               return NULL;
+
+       ai = host_pton(buf);
+
+       /*
+        * getaddrinfo(AI_NUMERICHOST) never fills in ai_canonname
+        */
+       if (ai != NULL) {
+               ai->ai_canonname = strdup(buf);
+               if (ai->ai_canonname == NULL) {
+                       freeaddrinfo(ai);
+                       ai = NULL;
+               }
+       }
+
+       return ai;
 }
-#endif
+#endif /* !HAVE_GETNAMEINFO */