/* Copyright 1988, 1996 by the Massachusetts Institute of Technology.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in
 *   the documentation and/or other materials provided with the
 *   distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* This file is a test driver for the Hesiod library. */

static const char rcsid[] = "$Id: hestest.c,v 1.3 1999-10-23 19:29:16 danw Exp $";

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>
#include <pwd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdarg.h>
#include "hesiod.h"

static char *word_end(char *s);
static char *find_word(char *s);
static char *get_word(char *p, char *buf);
static char *get_field(char *p, int delim, char *buf);
static int compare_vector(char **vector, char *spec);
static int compare_pwnam(struct passwd *pw, char *spec);
static int compare_serv(struct servent *serv, char *spec);
static int compare_office(struct hesiod_postoffice *office, char *spec);
static int compare_compat_office(struct hes_postoffice *office, char *spec);
static void free_ptrs(char **ptrs);
static void failure(const char *fmt, ...);

int saw_failure = 0;

int main(int argc, char **argv)
{
  FILE *fp;
  char buf[1024], *p, *q, name[128], type[128], proto[128], **list;
  int line, errval;
  struct passwd *pw;
  struct servent *serv;
  struct hesiod_postoffice *po;
  struct hes_postoffice *compatpo;
  void *context;

  if (argc != 2)
    {
      fprintf(stderr, "Usage: %s filename\n", argv[0]);
      return 1;
    }

  fp = fopen(argv[1], "r");
  if (!fp)
    {
      fprintf(stderr, "Couldn't open %s for reading.\n", argv[1]);
      return 1;
    }

  if (hesiod_init(&context) < 0)
    {
      fprintf(stderr, "Can't initialize hesiod library.\n");
      return 1;
    }

  line = 0;
  while (fgets(buf, sizeof(buf), fp))
    {
      line++;

      /* Strip off trailing spaces (inefficiently). */
      while (isspace((unsigned char)buf[strlen(buf) - 1]))
	buf[strlen(buf) - 1] = 0;

      /* Get the first word, discard comment lines and invalid lines. */
      q = word_end(p = find_word(buf));
      if (*p == '#' || !*p)
	continue;

      if (*q && q - p == 7 && strncmp(p, "resolve", 7) == 0)
	{
	  /* Test for hesiod_resolve and hes_resolve. */
	  q = get_word(find_word(q), name);
	  q = get_word(find_word(q), type);
	  p = find_word(q);
	  list = hesiod_resolve(context, name, type);
	  errval = errno;
	  if (!list && !(*p == 'E' && p[1] == 0))
	    {
	      failure("Line %d failed (hesiod_resolve error %d).\n", line,
		      errval);
	      continue;
	    }
	  if (list && compare_vector(list, p) < 0)
	    {
	      failure("Line %d failed in hesiod_resolve().\n", line);
	      hesiod_free_list(context, list);
	      continue;
	    }
	  if (list)
	    hesiod_free_list(context, list);
	  list = hes_resolve(name, type);
	  if (!list)
	    {
	      if (*p == 'E' && p[1] == 0)
		printf("Line %d passed (%d, %d).\n", line, errval,
		       hes_error());
	      else
		failure("Line %d failed (hes_resolve error %d).\n", line,
			hes_error());
	      continue;
	    }
	  if (compare_vector(list, p) < 0)
	    failure("Line %d failed in hes_resolve().\n", line);
	  else
	    printf("Line %d passed.\n", line);
	  free_ptrs(list);
	}
      else if (*q && q - p == 8 && strncmp(p, "getpwnam", 8) == 0)
	{
	  /* Test for hesiod_getpwnam and hes_getpwnam. */
	  q = get_word(find_word(q), name);
	  p = find_word(q);
	  pw = hesiod_getpwnam(context, name);
	  errval = errno;
	  if (!pw && !(*p == 'E' && p[1] == 0))
	    {
	      failure("Line %d failed (hesiod_getpwnam error %d).\n", line,
		      errval);
	      continue;
	    }
	  if (pw && compare_pwnam(pw, p) < 0)
	    {
	      failure("Line %d failed in hesiod_getpwnam().\n", line);
	      continue;
	    }
	  if (pw)
	    hesiod_free_passwd(context, pw);
	  pw = hes_getpwnam(name);
	  if (!pw)
	    {
	      if (*p == 'E' && p[1] == 0)
		printf("Line %d passed (%d, %d).\n", line, errval,
		       hes_error());
	      else
		failure("Line %d failed (hes_getpwnam error %d).\n", line,
			hes_error());
	      continue;
	    }
	  if (compare_pwnam(pw, p) < 0)
	    failure("Line %d failed in hes_getpwnam().\n", line);
	  else
	    printf("Line %d passed.\n", line);
	}
      else if (*q && q - p == 8 && strncmp(p, "getpwuid", 8) == 0)
	{
	  /* Test for hesiod_getpwuid and hes_getpwuid. */
	  q = get_word(find_word(q), name);
	  p = find_word(q);
	  pw = hesiod_getpwuid(context, atoi(name));
	  errval = errno;
	  if (!pw && !(*p == 'E' && p[1] == 0))
	    {
	      failure("Line %d failed (hesiod_getpwuid error %d).\n", line,
		      errval);
	      continue;
	    }
	  if (pw && compare_pwnam(pw, p) < 0)
	    {
	      failure("Line %d failed in hesiod_getpwuid().\n", line);
	      continue;
	    }
	  if (pw)
	    hesiod_free_passwd(context, pw);
	  pw = hes_getpwuid(atoi(name));
	  if (!pw)
	    {
	      if (*p == 'E' && p[1] == 0)
		printf("Line %d passed (%d, %d).\n", line, errval,
		       hes_error());
	      else
		failure("Line %d failed (hes_getpwuid error %d).\n", line,
			hes_error());
	      continue;
	    }
	  if (compare_pwnam(pw, p) < 0)
	    failure("Line %d failed in hes_getpwuid().\n", line);
	  else
	    printf("Line %d passed.\n", line);
	}
      else if (*q && q - p == 13 && strncmp(p, "getservbyname", 13) == 0)
	{
	  /* Test for hesiod_getservbyname and hes_getservbyname. */
	  q = get_word(find_word(q), name);
	  q = get_word(find_word(q), proto);
	  p = find_word(q);
	  serv = hesiod_getservbyname(context, name, proto);
	  errval = errno;
	  if (!serv && !(*p == 'E' && p[1] == 0))
	    {
	      failure("Line %d failed (hesiod_getservbyname error %d).\n",
		      line, errno);
	      continue;
	    }
	  if (serv && compare_serv(serv, p) < 0)
	    {
	      failure("Line %d failed in hesiod_getservbyname.\n", line);
	      continue;
	    }
	  if (serv)
	    hesiod_free_servent(context, serv);
	  serv = hes_getservbyname(name, proto);
	  if (!serv)
	    {
	      if (*p == 'E' && p[1] == 0)
		printf("Line %d passed (%d, %d).\n", line, errval,
		       hes_error());
	      else
		failure("Line %d failed (hes_getservbyname error %d).\n", line,
			hes_error());
	      continue;
	    }
	  if (compare_serv(serv, p) < 0)
	    failure("Line %d failed in hes_getservbyname().\n", line);
	  else
	    printf("Line %d passed.\n", line);
	}
      else if (*q && q - p == 11 && strncmp(p, "getmailhost", 11) == 0)
	{
	  /* Test for hesiod_getmailhost and hes_getmailhost. */
	  q = get_word(find_word(q), name);
	  p = find_word(q);
	  po = hesiod_getmailhost(context, name);
	  errval = errno;
	  if (!po && !(*p == 'E' && p[1] == 0))
	    {
	      failure("Line %d failed (hesiod_getmailhost error %d).\n",
		      line, errval);
	      continue;
	    }
	  if (po && compare_office(po, p) < 0)
	    {
	      failure("Line %d failed in hesiod_getmailhost.\n", line);
	      continue;
	    }
	  if (po)
	    hesiod_free_postoffice(context, po);
	  compatpo = hes_getmailhost(name);
	  if (!compatpo)
	    {
	      if (*p == 'E' && p[1] == 0)
		printf("Line %d passed (%d, %d).\n", line, errval,
		       hes_error());
	      else
		failure("Line %d failed (hes_getmailhost error %d).\n", line,
			hes_error());
	      continue;
	    }
	  if (compare_compat_office(compatpo, p) < 0)
	    failure("Line %d failed in hes_getmailhost().\n", line);
	  else
	    printf("Line %d passed.\n", line);

	}
      else
	{
	  fprintf(stderr, "Line %d invalid: %s\n", line, buf);
	  return 2;
	}
    }

  return saw_failure;
}

static char *word_end(char *s)
{
  while (*s && !isspace((unsigned char)*s))
    s++;
  return s;
}

static char *find_word(char *s)
{
  while (isspace((unsigned char)*s))
    s++;
  return s;
}

static char *get_word(char *p, char *buf)
{
  char *q = word_end(p);

  strncpy(buf, p, q - p);
  buf[q - p] = 0;
  return q;
}

static char *get_field(char *p, int delim, char *buf)
{
  char *q = strchr(p, delim);

  if (q) {
    strncpy(buf, p, q - p);
    buf[q - p] = 0;
    return q + 1;
  } else {
    strcpy(buf, p);
    return NULL;
  }
}

static int compare_vector(char **vector, char *spec)
{
  char field[100];

  for (; *vector; vector++) {
    spec = get_field(spec, '\\', field);
    if ((!spec && vector[1]) || strcmp(*vector, field) != 0)
      return -1;
  }
  return (spec) ? -1 : 0;
}

static int compare_pwnam(struct passwd *pw, char *spec)
{
  char field[100];

  spec = get_field(spec, ':', field);
  if (!spec || strcmp(pw->pw_name, field) != 0)
    return -1;
  spec = get_field(spec, ':', field);
  if (!spec || strcmp(pw->pw_passwd, field) != 0)
    return -1;
  spec = get_field(spec, ':', field);
  if (pw->pw_uid != atoi(field))
    return -1;
  spec = get_field(spec, ':', field);
  if (pw->pw_gid != atoi(field))
    return -1;
  spec = get_field(spec, ':', field);
  if (!spec || strcmp(pw->pw_gecos, field) != 0)
    return -1;
  spec = get_field(spec, ':', field);
  if (!spec || strcmp(pw->pw_dir, field) != 0)
    return -1;
  spec = get_field(spec, ':', field);
  if (spec || strcmp(pw->pw_shell, field) != 0)
    return -1;
  return 0;
}

static int compare_serv(struct servent *serv, char *spec)
{
  char field[100], **aliases;

  spec = get_field(spec, ':', field);
  if (!spec || strcmp(serv->s_name, field) != 0)
    return -1;
  spec = get_field(spec, ':', field);
  if (!spec || strcmp(serv->s_proto, field) != 0)
    return -1;
  spec = get_field(spec, ':', field);
  if (serv->s_port != htons(atoi(field)))
    return -1;
  for (aliases = serv->s_aliases; *aliases; aliases++)
    {
      if (!spec)
	return -1;
      spec = get_field(spec, '\\', field);
      if (strcmp(*aliases, field) != 0)
	return -1;
    }
  return (spec) ? -1 : 0;
}

static int compare_office(struct hesiod_postoffice *office, char *spec)
{
  char field[100];

  spec = get_field(spec, ':', field);
  if (!spec || strcmp(office->hesiod_po_type, field) != 0)
    return -1;
  spec = get_field(spec, ':', field);
  if (!spec || strcmp(office->hesiod_po_host, field) != 0)
    return -1;
  spec = get_field(spec, ':', field);
  if (spec || strcmp(office->hesiod_po_name, field) != 0)
    return -1;
  return 0;
}

static int compare_compat_office(struct hes_postoffice *office, char *spec)
{
  char field[100];

  spec = get_field(spec, ':', field);
  if (!spec || strcmp(office->po_type, field) != 0)
    return -1;
  spec = get_field(spec, ':', field);
  if (!spec || strcmp(office->po_host, field) != 0)
    return -1;
  spec = get_field(spec, ':', field);
  if (spec || strcmp(office->po_name, field) != 0)
    return -1;
  return 0;
}

static void free_ptrs(char **ptrs)
{
  for (; *ptrs; ptrs++)
    free(*ptrs);
}

static void failure(const char *fmt, ...)
{
  va_list ap;

  saw_failure = 1;
  va_start(ap, fmt);
  vprintf(fmt, ap);
  va_end(ap);
}
