summary refs log tree commit diff
path: root/hesiod/hesiod.c
diff options
context:
space:
mode:
Diffstat (limited to 'hesiod/hesiod.c')
-rw-r--r--hesiod/hesiod.c470
1 files changed, 470 insertions, 0 deletions
diff --git a/hesiod/hesiod.c b/hesiod/hesiod.c
new file mode 100644
index 0000000000..8e95dfb393
--- /dev/null
+++ b/hesiod/hesiod.c
@@ -0,0 +1,470 @@
+/* Copyright (c) 1996 by Internet Software Consortium.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
+ * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
+ * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
+ * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
+ * SOFTWARE.
+ */
+
+/* Copyright 1996 by the Massachusetts Institute of Technology.
+ *
+ * Permission to use, copy, modify, and distribute this
+ * software and its documentation for any purpose and without
+ * fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright
+ * notice and this permission notice appear in supporting
+ * documentation, and that the name of M.I.T. not be used in
+ * advertising or publicity pertaining to distribution of the
+ * software without specific, written prior permission.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is"
+ * without express or implied warranty.
+ */
+
+/* This file is part of the hesiod library.  It implements the core
+ * portion of the hesiod resolver.
+ *
+ * This file is loosely based on an interim version of hesiod.c from
+ * the BIND IRS library, which was in turn based on an earlier version
+ * of this file.  Extensive changes have been made on each step of the
+ * path.
+ *
+ * This implementation is not truly thread-safe at the moment because
+ * it uses res_send() and accesses _res.
+ */
+
+static const char rcsid[] = "$Id$";
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <errno.h>
+#include <netdb.h>
+#include <resolv.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "hesiod.h"
+#include "hesiod_p.h"
+
+/* A few operating systems don't define these. */
+#ifndef C_HS
+#define C_HS	4
+#endif
+#ifndef T_TXT
+#define T_TXT	16
+#endif
+
+static int read_config_file(struct hesiod_p *ctx, const char *filename);
+static char **get_txt_records(struct hesiod_p *ctx, int class,
+			      const char *name);
+static int cistrcmp(const char *s1, const char *s2);
+
+/* This function is called to initialize a hesiod_p. */
+int hesiod_init(void **context)
+{
+  struct hesiod_p *ctx;
+  const char *p, *configname;
+
+  ctx = malloc(sizeof(struct hesiod_p));
+  if (ctx)
+    {
+      *context = ctx;
+      configname = __secure_getenv("HESIOD_CONFIG");
+      if (!configname)
+	configname = SYSCONFDIR "/hesiod.conf";
+      if (read_config_file(ctx, configname) >= 0)
+	{
+	  /* The default rhs can be overridden by an environment variable. */
+	  p = __secure_getenv("HES_DOMAIN");
+	  if (p)
+	    {
+	      if (ctx->rhs)
+		free(ctx->rhs);
+	      ctx->rhs = malloc(strlen(p) + 2);
+	      if (ctx->rhs)
+		{
+		  *ctx->rhs = '.';
+		  strcpy(ctx->rhs + 1, (*p == '.') ? p + 1 : p);
+		  return 0;
+		}
+	      else
+		__set_errno (ENOMEM);
+	    }
+	  else
+	    return 0;
+	}
+    }
+  else
+    __set_errno (ENOMEM);
+
+  if (ctx->lhs)
+    free(ctx->lhs);
+  if (ctx->rhs)
+    free(ctx->rhs);
+  if (ctx)
+    free(ctx);
+  return -1;
+}
+
+/* This function deallocates the hesiod_p. */
+void hesiod_end(void *context)
+{
+  struct hesiod_p *ctx = (struct hesiod_p *) context;
+
+  free(ctx->rhs);
+  if (ctx->lhs)
+    free(ctx->lhs);
+  free(ctx);
+}
+
+/* This function takes a hesiod (name, type) and returns a DNS
+ * name which is to be resolved.
+ */
+char *hesiod_to_bind(void *context, const char *name, const char *type)
+{
+  struct hesiod_p *ctx = (struct hesiod_p *) context;
+  char bindname[MAXDNAME], *p, *ret, **rhs_list = NULL;
+  const char *rhs;
+  int len;
+	
+  strcpy(bindname, name);
+
+  /* Find the right right hand side to use, possibly truncating bindname. */
+  p = strchr(bindname, '@');
+  if (p)
+    {
+      *p++ = 0;
+      if (strchr(p, '.'))
+	rhs = name + (p - bindname);
+      else
+	{
+	  rhs_list = hesiod_resolve(context, p, "rhs-extension");
+	  if (rhs_list)
+	    rhs = *rhs_list;
+	  else
+	    {
+	      __set_errno (ENOENT);
+	      return NULL;
+	    }
+	}
+    } else
+      rhs = ctx->rhs;
+
+  /* See if we have enough room. */
+  len = strlen(bindname) + 1 + strlen(type);
+  if (ctx->lhs)
+    len += strlen(ctx->lhs) + ((ctx->lhs[0] != '.') ? 1 : 0);
+  len += strlen(rhs) + ((rhs[0] != '.') ? 1 : 0);
+  if (len > sizeof(bindname) - 1)
+    {
+      if (rhs_list)
+	hesiod_free_list(context, rhs_list);
+      __set_errno (EMSGSIZE);
+      return NULL;
+    }
+
+  /* Put together the rest of the domain. */
+  strcat(bindname, ".");
+  strcat(bindname, type);
+  if (ctx->lhs)
+    {
+      if (ctx->lhs[0] != '.')
+	strcat(bindname, ".");
+      strcat(bindname, ctx->lhs);
+    }
+  if (rhs[0] != '.')
+    strcat(bindname, ".");
+  strcat(bindname, rhs);
+
+  /* rhs_list is no longer needed, since we're done with rhs. */
+  if (rhs_list)
+    hesiod_free_list(context, rhs_list);
+
+  /* Make a copy of the result and return it to the caller. */
+  ret = malloc(strlen(bindname) + 1);
+  if (!ret)
+    {
+      __set_errno (ENOMEM);
+      return NULL;
+    }
+  strcpy(ret, bindname);
+  return ret;
+}
+
+/* This is the core function.  Given a hesiod name and type, it
+ * returns an array of strings returned by the resolver.
+ */
+char **hesiod_resolve(void *context, const char *name, const char *type)
+{
+  struct hesiod_p *ctx = (struct hesiod_p *) context;
+  char *bindname, **retvec;
+
+  bindname = hesiod_to_bind(context, name, type);
+  if (!bindname)
+    return NULL;
+
+  retvec = get_txt_records(ctx, ctx->classes[0], bindname);
+  if (retvec == NULL && errno == ENOENT && ctx->classes[1])
+    retvec = get_txt_records(ctx, ctx->classes[1], bindname);
+
+  free(bindname);
+  return retvec;
+}
+
+void hesiod_free_list(void *context, char **list)
+{
+  char **p;
+
+  for (p = list; *p; p++)
+    free(*p);
+  free(list);
+}
+
+/* This function parses the /etc/hesiod.conf file.  Returns 0 on success,
+ * -1 on failure.  On failure, it might leave values in ctx->lhs or
+ * ctx->rhs which need to be freed by the caller. */
+static int read_config_file(struct hesiod_p *ctx, const char *filename)
+{
+  char *key, *data, *p, **which;
+  char buf[MAXDNAME + 7];
+  int n;
+  FILE *fp;
+
+  /* Set default query classes. */
+  ctx->classes[0] = C_IN;
+  ctx->classes[1] = C_HS;
+
+  /* Try to open the configuration file. */
+  fp = fopen(filename, "r");
+  if (!fp)
+    {
+      /* Use compiled in default domain names. */
+      ctx->lhs = malloc(strlen(DEF_LHS) + 1);
+      ctx->rhs = malloc(strlen(DEF_RHS) + 1);
+      if (ctx->lhs && ctx->rhs)
+	{
+	  strcpy(ctx->lhs, DEF_LHS);
+	  strcpy(ctx->rhs, DEF_RHS);
+	  return 0;
+	}
+      else
+	{
+	  __set_errno (ENOMEM);
+	  return -1;
+	}
+    }
+
+  ctx->lhs = NULL;
+  ctx->rhs = NULL;
+  while (fgets(buf, sizeof(buf), fp) != NULL)
+    {
+      p = buf;
+      if (*p == '#' || *p == '\n' || *p == '\r')
+	continue;
+      while(*p == ' ' || *p == '\t')
+	p++;
+      key = p;
+      while(*p != ' ' && *p != '\t' && *p != '=')
+	p++;
+      *p++ = 0;
+		
+      while(isspace(*p) || *p == '=')
+	p++;
+      data = p;
+      while(!isspace(*p))
+	p++;
+      *p = 0;
+
+      if (cistrcmp(key, "lhs") == 0 || cistrcmp(key, "rhs") == 0)
+	{
+	  which = (strcmp(key, "lhs") == 0) ? &ctx->lhs : &ctx->rhs;
+	  *which = malloc(strlen(data) + 1);
+	  if (!*which)
+	    {
+	      __set_errno (ENOMEM);
+	      return -1;
+	    }
+	  strcpy(*which, data);
+	}
+      else if (cistrcmp(key, "classes") == 0)
+	{
+	  n = 0;
+	  while (*data && n < 2)
+	    {
+	      p = data;
+	      while (*p && *p != ',')
+		p++;
+	      if (*p)
+		*p++ = 0;
+	      if (cistrcmp(data, "IN") == 0)
+		ctx->classes[n++] = C_IN;
+	      else if (cistrcmp(data, "HS") == 0)
+		ctx->classes[n++] = C_HS;
+	      data = p;
+	    }
+	  while (n < 2)
+	    ctx->classes[n++] = 0;
+	}
+    }
+  fclose(fp);
+
+  if (!ctx->rhs || ctx->classes[0] == 0 || ctx->classes[0] == ctx->classes[1])
+    {
+      __set_errno (ENOEXEC);
+      return -1;
+    }
+
+  return 0;
+}	
+
+/* Given a DNS class and a DNS name, do a lookup for TXT records, and
+ * return a list of them.
+ */
+static char **get_txt_records(struct hesiod_p *ctx, int qclass,
+			      const char *name)
+{
+  HEADER *hp;
+  unsigned char qbuf[PACKETSZ], abuf[MAX_HESRESP], *p, *eom, *eor;
+  char *dst, **list;
+  int ancount, qdcount, i, j, n, skip, type, class, len;
+
+  /* Make sure the resolver is initialized. */
+  if ((_res.options & RES_INIT) == 0 && res_init() == -1)
+    return NULL;
+
+  /* Construct the query. */
+  n = res_mkquery(QUERY, name, qclass, T_TXT, NULL, 0,
+		  NULL, qbuf, PACKETSZ);
+  if (n < 0)
+      return NULL;
+
+  /* Send the query. */
+  n = res_send(qbuf, n, abuf, MAX_HESRESP);
+  if (n < 0)
+    {
+      __set_errno (ECONNREFUSED);
+      return NULL;
+    }
+
+  /* Parse the header of the result. */
+  hp = (HEADER *) abuf;
+  ancount = ntohs(hp->ancount);
+  qdcount = ntohs(hp->qdcount);
+  p = abuf + sizeof(HEADER);
+  eom = abuf + n;
+
+  /* Skip questions, trying to get to the answer section which follows. */
+  for (i = 0; i < qdcount; i++)
+    {
+      skip = dn_skipname(p, eom);
+      if (skip < 0 || p + skip + QFIXEDSZ > eom)
+	{
+	  __set_errno (EMSGSIZE);
+	  return NULL;
+	}
+      p += skip + QFIXEDSZ;
+    }
+
+  /* Allocate space for the text record answers. */
+  list = malloc((ancount + 1) * sizeof(char *));
+  if (!list)
+    {
+      __set_errno (ENOMEM);
+      return NULL;
+    }
+
+  /* Parse the answers. */
+  j = 0;
+  for (i = 0; i < ancount; i++)
+    {
+      /* Parse the header of this answer. */
+      skip = dn_skipname(p, eom);
+      if (skip < 0 || p + skip + 10 > eom)
+	break;
+      type = p[skip + 0] << 8 | p[skip + 1];
+      class = p[skip + 2] << 8 | p[skip + 3];
+      len = p[skip + 8] << 8 | p[skip + 9];
+      p += skip + 10;
+      if (p + len > eom)
+	{
+	  __set_errno (EMSGSIZE);
+	  break;
+	}
+
+      /* Skip entries of the wrong class and type. */
+      if (class != qclass || type != T_TXT)
+	{
+	  p += len;
+	  continue;
+	}
+
+      /* Allocate space for this answer. */
+      list[j] = malloc(len);
+      if (!list[j])
+	{
+	  __set_errno (ENOMEM);
+	  break;
+	}
+      dst = list[j++];
+
+      /* Copy answer data into the allocated area. */
+      eor = p + len;
+      while (p < eor)
+	{
+	  n = (unsigned char) *p++;
+	  if (p + n > eor)
+	    {
+	      __set_errno (EMSGSIZE);
+	      break;
+	    }
+	  memcpy(dst, p, n);
+	  p += n;
+	  dst += n;
+	}
+      if (p < eor)
+	{
+	  __set_errno (EMSGSIZE);
+	  break;
+	}
+      *dst = 0;
+    }
+
+  /* If we didn't terminate the loop normally, something went wrong. */
+  if (i < ancount)
+    {
+      for (i = 0; i < j; i++)
+	free(list[i]);
+      free(list);
+      return NULL;
+    }
+
+  if (j == 0)
+    {
+      __set_errno (ENOENT);
+      free(list);
+      return NULL;
+    }
+
+  list[j] = NULL;
+  return list;
+}
+
+static int cistrcmp(const char *s1, const char *s2)
+{
+  while (*s1 && tolower(*s1) == tolower(*s2))
+    {
+      s1++;
+      s2++;
+    }
+  return tolower(*s1) - tolower(*s2);
+}