/* Copyright (c) 1998, 1999 Thorsten Kukuk
   Author: Thorsten Kukuk <kukuk@suse.de>

   This program 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, or (at your option)
   any later version.

   This program 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 this program; if not, write to the Free Software Foundation,
   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#define _GNU_SOURCE

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#else
#include "lib/compat/getopt.h"
#endif
#include <locale.h>
#include <libintl.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <rpcsvc/nis.h>
#include <rpc/key_prot.h>

#include "nis_xdr.h"

#ifndef _
#define _(String) gettext (String)
#endif

/* Print the version information.  */
static inline void
print_version (void)
{
  fprintf (stdout, "nisupdkeys (%s) %s\n", PACKAGE, VERSION);
  fprintf (stdout, gettext ("\
Copyright (C) %s Thorsten Kukuk.\n\
This is free software; see the source for copying conditions.  There is NO\n\
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
"), "1998, 1999");
  /* fprintf (stdout, _("Written by %s.\n"), "Thorsten Kukuk"); */
}

static inline void
print_usage (void)
{
  fputs (_("Usage: nisupdkeys -s -H host [-a|-C] | [-a|-C] [-H host] [directory]\n"), stdout);
}

static void
print_help (void)
{
  print_usage ();
  fputs (_("nisupdkeys - update the public keys in a NIS+ directory object\n\n"),
	 stdout);

  fputs (_("  -a             Update the universal addresses\n"), stdout);
  fputs (_("  -C             Clear rather than set the public key\n"), stdout);
  fputs (_("  -H host        Limit key changes only to the server named host\n"),
	 stdout);
  fputs (_("  -s             Update all NIS+ directory objects served by host\n"),
	 stdout);
  fputs (_("  --help         Give this help list\n"), stdout);
  fputs (_("  --usage        Give a short usage message\n"), stdout);
  fputs (_("  --version      Print program version\n"), stdout);
}

static inline void
print_error (void)
{
  const char *program = "nisupdkeys";

  fprintf (stderr,
	   _("Try `%s --help' or `%s --usage' for more information.\n"),
	   program, program);
}

/* XXX Write new and make it faster */
static nis_server *
makesrv (char *name)
{
  struct hostent *h;
  int i;
  endpoint *ep;
  nis_server *server;
  char pkey[HEXKEYBYTES + 1];
  char haddr[NIS_MAXNAMELEN];
  char hname[NIS_MAXNAMELEN];

  h = gethostbyname (name);

  if (h == NULL)
    {
      fprintf (stderr, _("gethostbyname (%s) failed\n"), name);
      exit (1);
    }

  ep = malloc (sizeof (endpoint));
  i = 0;

  while (*h->h_addr_list != NULL)
    {
      struct in_addr *in;
      endpoint *e;
      in = *(struct in_addr **) h->h_addr_list;
      e = ep = realloc (ep, sizeof (endpoint) * (i + 3));
      snprintf (haddr, sizeof (haddr), "%s.0.111", inet_ntoa (*in));
      e->uaddr = strdup (haddr);
      e->family = strdup ("inet");
      e->proto = strdup ("tcp");	/* For TCP. */
      e++;
      e->uaddr = strdup (haddr);
      e->family = strdup ("inet");
      e->proto = strdup ("udp");	/* For UDP. */
      e++;
      e->uaddr = strdup (haddr);
      e->family = strdup ("inet");
      e->proto = strdup ("-");	/* For broadcast (?) */
      i += 3;
      h->h_addr_list++;
    }

  server = calloc (1, sizeof (nis_server));

  strncpy (hname, name, sizeof (hname));
  if (strchr (hname, '.') == NULL)
    {
      strncat (hname, ".", sizeof (hname));
      strncat (hname, nis_local_directory (), sizeof (hname));
    }
  else
    {
      char *cp = strchr (hname, '.');
      *(++cp) = '\0';
      strncat (hname, nis_local_directory (), sizeof (hname));
    }

  server->name = strdup (hname);
  server->ep.ep_len = i;
  server->ep.ep_val = ep;

  if (getpublickey (server->name, pkey) != 1)
    {
      server->key_type = NIS_PK_NONE;
      server->pkey.n_bytes = NULL;
      server->pkey.n_len = 0;
    }
  else
    {
      server->key_type = NIS_PK_DH;
      server->pkey.n_bytes = pkey;
      server->pkey.n_len = strlen (pkey) + 1;
    }

  return server;
}

/* We only have the cname, not the netname */
static char *
getpkey_nisplus (char *cname)
{
  char buf[NIS_MAXNAMELEN];
  char pkey[HEXKEYBYTES + 1];
  char *p = NULL;
  nis_result *res;
  int len = 0;

  p = strchr (cname, '.');
  if (p == NULL)
    return NULL;

  p++;

  snprintf (buf, sizeof (buf), "[cname=%s,auth_type=DES],cred.org_dir.%s",
            cname, p);

  res = nis_list (buf, MASTER_ONLY | USE_DGRAM | NO_AUTHINFO | FOLLOW_LINKS
		  | FOLLOW_PATH, NULL, NULL);

  if (res == NULL)
    {
      fputs (_("Out of memory!\n"), stderr);
      return NULL;
    }

  if (res->status != NIS_SUCCESS)
    {
      fprintf (stderr, "%s: %s\n", cname, nis_sperrno (res->status));
      nis_freeresult (res);
      return NULL;
    }

  len = ENTRY_LEN (NIS_RES_OBJECT (res), 3);
  strncpy (pkey, ENTRY_VAL (NIS_RES_OBJECT (res), 3), len);
  pkey[len] = '\0';
  p = strchr (pkey, ':');
  if (p != NULL)
    *p = '\0';

  nis_freeresult (res);

  return strdup (pkey);
}


static int
update_dir (nis_name dir, nis_name server, int newip, int clear_only)
{
  nis_result *res;
  nis_object *new;
  directory_obj *d;
  int changes = 0;
  u_long i;
  nis_server *s, *n;
  char *p;

  res = nis_lookup (dir, MASTER_ONLY | NO_AUTHINFO);
  if (res == NULL)
    {
      fputs (_("Out of memory!\n"), stderr);
      return 1;
    }

  if (res->status != NIS_SUCCESS)
    {
      fprintf (stderr, "%s: %s\n", dir, nis_sperrno (res->status));
      nis_freeresult (res);
      return 1;
    }

  if (__type_of (NIS_RES_OBJECT (res)) != NIS_DIRECTORY_OBJ)
    {
      fprintf (stderr, _("%s is not a directory!\n"), dir);
      nis_freeresult (res);
      return 1;
    }

  new = nis_clone_object (NIS_RES_OBJECT (res), NULL);
  nis_freeresult (res);

  d = &new->DI_data;

  for (i = 0; i < d->do_servers.do_servers_len; ++i)
    {
      s = &d->do_servers.do_servers_val[i];
      if (server != NULL && nis_dir_cmp (s->name, server) != SAME_NAME)
        continue;
      changes++;
      if (clear_only)
	{
          s->key_type = NIS_PK_NONE;
          free (s->pkey.n_bytes);
          s->pkey.n_bytes = NULL;
          s->pkey.n_len = 0;
	}
      else if (newip)
	{
          if (server == NULL)
            n = makesrv (s->name);
          else
            n = makesrv (server);
          if (n == NULL)
            {
              fprintf (stderr, _("failed to get IP address for %s!\n"),
		       server);
              break;
            }
          xdr_free ((xdrproc_t) xdr_nis_server, (char *) s);
          memcpy ((char *) s, (char *) n, sizeof (nis_server));
          free (n);
	}
      else
	{
          if (server != NULL)
            p = getpkey_nisplus (server);
          else
            p = getpkey_nisplus (s->name);
          if (p == NULL)
            {
              fprintf (stderr, _("failed to get public key for %s!\n"),
		       server);
              break;
            }
          s->key_type = NIS_PK_DH;
          if (s->pkey.n_len)
            free (s->pkey.n_bytes);
          s->pkey.n_bytes = p;
          s->pkey.n_len = strlen (p) + 1;
	}
    }

  if (!changes && server != NULL)
    {
      fprintf (stderr, _("%s does not serve %s!\n"), server, dir);
      nis_destroy_object (new);
      return 1;
    }

  res = nis_modify (dir, new);

  if (res == NULL)
    {
      fputs (_("Out of memory!\n"), stderr);
      return 1;
    }

  if (res->status != NIS_SUCCESS)
    {
      fprintf (stderr, _("failed to modify %s: %s\n"), dir,
	       nis_sperrno (res->status));
      nis_freeresult (res);
      nis_destroy_object (new);
      return 1;
    }

  nis_freeresult (res);
  nis_destroy_object (new);

  return 0;
}


static int
update_all (nis_name server, int newip, int clear_only)
{
  nis_tag tags = {TAG_DIRLIST, "directory list"};
  nis_tag *result;
  nis_error error;
  char *p;
  nis_server *srv;

  srv = makesrv (server);

  if (srv == NULL)
    {
      fprintf (stderr, _("couldn't create nis_server entry for %s.\n"),
	       server);
      return 1;
    }

  error = nis_stats (srv, &tags, 1, &result);
  if (error != NIS_SUCCESS)
    {
      fprintf (stderr, _("couldn't get dirlist from %s: %s\n"), server,
	       nis_sperrno (error));
      if (result)
	nis_freetags (result, 1);
      return 1;
    }

  if (result->tag_type != TAG_DIRLIST || strchr (result->tag_val, '.') == NULL)
    {
      fprintf (stderr, _("bad dirlist from %s: %s\n"), server,
	       result->tag_val);
      nis_freetags (result, 1);
      return 1;
    }

  while ((p = strsep (&result->tag_val, " \n")) != NULL)
    update_dir (p, srv->name, newip, clear_only);

  nis_freetags (result, 1);
  return 0;
}

int
main (int argc, char *argv[])
{
  char *server = NULL;
  int upd_all = 0;
  int newip = 0;
  int clear_only = 0;

  setlocale (LC_MESSAGES, "");
  bindtextdomain (PACKAGE, LOCALEDIR);
  textdomain (PACKAGE);

  while (1)
    {
      int c;
      int option_index = 0;
      static struct option long_options[] =
      {
	{"version", no_argument, NULL, '\255'},
	{"usage", no_argument, NULL, '\254'},
	{"help", no_argument, NULL, '\253'},
	{NULL, 0, NULL, '\0'}
      };

      c = getopt_long (argc, argv, "aCH:s", long_options, &option_index);
      if (c == (-1))
	break;
      switch (c)
	{
	case 'a':
	  newip = 1;
	  break;
	case 'C':
	  clear_only = 1;
	  break;
	case 'H':
	  server = optarg;
	  break;
	case 's':
	  upd_all = 1;
	  break;
	case '\253':
	  print_help ();
	  return 0;
	case '\255':
	  print_version ();
	  return 0;
	case '\254':
	  print_usage ();
	  return 0;
	default:
	  print_error ();
	  return 1;
	}
    }

  argc -= optind;
  argv += optind;

  if ((upd_all && server == NULL) || (upd_all && argc > 0))
    {
      print_error ();
      return 1;
    }

  if (upd_all)
    return update_all (server, newip, clear_only);
  else if (server != NULL)
    {
      int result;
      nis_server *s;

      s = makesrv (server);
      if (s == NULL)
	{
	  fprintf (stderr, _("failed to convert %s to nis_server\n"), server);
	  return 1;
	}
      if (argc)
	result = update_dir (argv[0], s->name, newip, clear_only);
      else
	result = update_dir (nis_local_directory (), s->name, newip,
			     clear_only);

      xdr_free ((xdrproc_t) xdr_nis_server, (caddr_t) s);
      free (s);
      return result;
    }
  else
    {
      if (argc)
	return update_dir (argv[0], NULL, newip, clear_only);
      else
	return update_dir (nis_local_directory (), NULL, newip, clear_only);
    }

  return 1;
}
