/* Copyright (c) 1997, 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

#include <sys/types.h>
#include <sys/stat.h>
#include <rpc/rpc.h>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>
#include <unistd.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#else
#include "lib/compat/getopt.h"
#endif
#include <locale.h>
#include <libintl.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <rpcsvc/nis.h>
#include <rpc/key_prot.h>
#include <rpc/pmap_clnt.h>
#include <rpcsvc/nis.h>
#include <nss.h>
#include "nis_xdr.h"

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

/* Collects all the answers for our query for hosts running rpc.nisd */
static struct sockaddr_in *nisserver = NULL;
static bool_t
eachresult (void *resp __attribute__ ((unused)), struct sockaddr_in *addr)
{
  if (nisserver == NULL)
    {
      nisserver =
	(struct sockaddr_in *) malloc (sizeof (struct sockaddr_in));
      nisserver->sin_family = AF_INET;
      nisserver->sin_port = 0;
      nisserver->sin_addr.s_addr = addr->sin_addr.s_addr;
    }
  return 1;
}

/* Print the version information.  */
static inline void
print_version (void)
{
  fprintf (stdout, "nisinit (%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");
  /* fprintf (stdout, _("Written by %s.\n"), "Thorsten Kukuk"); */
}

static inline void
print_usage (void)
{
  fputs (_("Usage: nisinit -r | -c -C file | -c -H host | -c -B\n"), stdout);
}

static void
print_help (void)
{
  print_usage ();
  fputs (_("nisinit - NIS+ client and server initialization utility\n\n"),
	 stdout);

  fputs (_("  -r             Initialize machine to be a NIS+ root server\n"),
	 stdout);
  fputs (_("  -c             Initialize machine to be a NIS+ client\n"),
	 stdout);
  fputs (_("  -C file        Use file as coldstart file\n"), stdout);
  fputs (_("  -H host        Use host as trusted NIS+ server\n"), stdout);
  fputs (_("  -B             Use broadcast to find a NIS+ server\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 = "nisinit";

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

int
main (int argc, char *argv[])
{
  nis_object *obj = calloc (1, sizeof (nis_object));
  int root = 0, client = 0, cbcast = 0;
  char *cfile = NULL, *chost = NULL;
  char domain[NIS_MAXNAMELEN];

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

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

  /* Don't use NIS+ for host lookups, only DNS and /etc/hosts.
     XXX This should be changed: Read the current setting and
     remove nisplus from it. */
  __nss_configure_lookup ("hosts", "dns files");

  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, "rcC:H:B", long_options, &option_index);
      if (c == (-1))
	break;
      switch (c)
	{
	case 'r':
	  root = 1;
	  if (client)
	    {
	      fputs (_("nisinit: You could not use -c and -r!\n"), stderr);
	      print_error ();
	      return 1;
	    }
	  break;
	case 'c':
	  client = 1;
	  if (root)
	    {
	      fputs (_("nisinit: You could not use -r and -c!\n"), stderr);
	      print_error ();
	      return 1;
	    }
	  break;
	case 'C':
	  if (client == 0)
	    {
	      fputs (_("nisinit: You must use -c before -C\n"), stderr);
	      print_error ();
	      return 1;
	    }
	  if (chost != NULL || cbcast != 0)
	    {
	      print_error ();
	      return 1;
	    }
	  cfile = optarg;
	  break;
	case 'H':
	  if (client == 0)
	    {
	      fputs (_("nisinit: You must use -c before -H\n"), stderr);
	      print_error ();
	      return 1;
	    }
	  if (cfile != NULL || cbcast != 0)
	    {
	      print_error ();
	      return 1;
	    }
	  chost = optarg;
	  break;
	case 'B':
	  if (client == 0)
	    {
	      fputs (_("nisinit: You must use -c before -B\n"), stderr);
	      print_error ();
	      return 1;
	    }
	  if (cfile != NULL || chost != NULL)
	    {
	      print_error ();
	      return 1;
	    }
	  cbcast = 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 (argc != 0 || (root == client) ||
      (client && !(cbcast || cfile || chost)))
    {
      print_error ();
      return 1;
    }

  strcpy (domain, nis_local_directory ());
  if (strlen (domain) < 1)
    {
      fputs (_("ERROR: No NIS domainname set!\n"), stderr);
      return 1;
    }

  /* Create /var/nis directory */
  if (access ("/var/nis", F_OK) == -1)
    if (mkdir ("/var/nis", S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH)
	== -1)
      {
	perror ("mkdir (\"/var/nis\")");
	return 1;
      }

  /* If we create a new */
  if (root)
    {
      FILE *file;
      char *cptr;
      char owner[NIS_MAXNAMELEN];
      char uaddr[NIS_MAXNAMELEN];
      struct hostent *h;
      endpoint *ep;
      int i;

      /* Create /var/nis/data directory */
      if (access ("/var/nis/data", F_OK) == -1)
	if (mkdir ("/var/nis/data", S_IRWXU) == -1)
	  {
	    perror ("mkdir (\"/var/nis/data\")");
	    return 1;
	  }

      /* Create root.object */
      gethostname (owner, sizeof (owner));
      h = gethostbyname (owner);

      /* If we don't have a FQDN, add the NIS+ domainname */
      if (strchr (owner, (int) '.') == NULL)
	{
	  strcat (owner, ".");
	  strcat (owner, domain);
	}
      else if (owner[strlen (owner) - 1] != '.')
	strcat (owner, ".");

      obj->zo_oid.ctime = 0;
      obj->zo_oid.mtime = 0;
      cptr = strchr (domain, '.');
      *cptr = '\0';
      ++cptr;
      obj->zo_name = strdup (domain);
      obj->zo_owner = strdup (owner);
      obj->zo_group = strdup (nis_local_group ());
      obj->zo_domain = strdup (cptr);
      obj->zo_access = (NIS_READ_ACC << 24) |
	((NIS_READ_ACC + NIS_MODIFY_ACC + NIS_CREATE_ACC +
	  NIS_DESTROY_ACC) << 16) |
	((NIS_READ_ACC + NIS_MODIFY_ACC + NIS_CREATE_ACC +
	  NIS_DESTROY_ACC) << 8) | NIS_READ_ACC;

      obj->zo_ttl = 86400;
      obj->zo_data.zo_type = NIS_DIRECTORY_OBJ;
      obj->DI_data.do_name = strdup (nis_local_directory ());
      obj->DI_data.do_type = NIS;

      obj->DI_data.do_armask.do_armask_len = 0;
      obj->DI_data.do_armask.do_armask_val = NULL;

      obj->DI_data.do_servers.do_servers_len = 1;
      obj->DI_data.do_servers.do_servers_val = malloc (sizeof (nis_server));
      memset (obj->DI_data.do_servers.do_servers_val, '\0',
	      sizeof (nis_server));
      obj->DI_data.do_servers.do_servers_val->name = obj->zo_owner;
      obj->DI_data.do_servers.do_servers_val->key_type = NIS_PK_NONE;
      obj->DI_data.do_servers.do_servers_val->pkey.n_len = 0;
      obj->DI_data.do_servers.do_servers_val->pkey.n_bytes = '\0';

      /* Count, how many interface our host has. We need to create an
         endpoing for each */
      i = 0;
      while (h->h_addr_list[i] != NULL)
	++i;

      obj->DI_data.do_servers.do_servers_val->ep.ep_len = 3 * i;
      obj->DI_data.do_servers.do_servers_val->ep.ep_val =
	calloc (1, 3 * i * sizeof (endpoint));
      ep = obj->DI_data.do_servers.do_servers_val->ep.ep_val;

      i = 0;
      while (h->h_addr_list[i] != NULL)
	{
	  struct in_addr in;
	  memcpy (&in, h->h_addr_list[i++], sizeof (in));

	  cptr = stpcpy (uaddr, inet_ntoa (in));
	  strcpy (cptr, ".0.111");
	  ep->proto = strdup ("udp");
	  ep->family = strdup ("inet");
	  ep->uaddr = strdup (uaddr);
	  ++ep;
	  ep->proto = strdup ("tcp");
	  ep->family = strdup ("inet");
	  ep->uaddr = strdup (uaddr);
	  ++ep;
	  ep->proto = strdup ("-");
	  ep->family = strdup ("inet");
	  ep->uaddr = strdup (uaddr);
	  ++ep;
	}

      /* Time to Live for the root object is 12 hour */
      obj->DI_data.do_ttl = 43200;

      umask (0177);

      if (nis_write_obj ("/var/nis/data/root.object", obj) != TRUE)
	{
	  fputs (_("WARNING: Could not create /var/nis/data/root.object!\n"),
		 stderr);
	  return 1;
	}

      /* serving_list contains all directories which we will serve */
      if ((file = fopen ("/var/nis/data/serving_list", "w+")) == NULL)
	{
	  fprintf (stderr, _("/var/nis/data/serving_list: %s"),
		   strerror (errno));
	  return 1;
	}
      fprintf (file, "%s\n", obj->DI_data.do_name);
      fclose (file);

      obj->DI_data.do_ttl = 842132458;
    }
  else
    { /* We are a client. At first, check, if we only need to copy
	 a NIS_COLD_START file to the correct position */
      if (cfile != NULL)
	{
	  XDR xdrs;
	  FILE *in;

	  in = fopen (cfile, "rb");
	  if (in == NULL)
	    {
	      fprintf (stderr, _("Error: Could not open %s!\n"), cfile);
	      return 1;
	    }
	  memset (&obj->DI_data, '\0', sizeof (obj->DI_data));
	  xdrstdio_create (&xdrs, in, XDR_DECODE);
	  if (!xdr_directory_obj (&xdrs, &obj->DI_data))
	    {
	      fprintf (stderr, _("Error while reading %s!\n"), cfile);
	      return 1;
	    }
	}
      else /* Get the NIS_COLD_START file from a NIS+ server */
	{
	  static struct timeval TIMEOUT = {25, 0};
	  nis_result *res = NULL;
	  CLIENT *clnt = NULL;
	  int status, clnt_sock;
	  struct ns_request req;

	  if (cbcast != 0)
	    {
	      /* Try a broadcast to find a NIS+ server */
	      fputs (_("Searching a NIS+ server ...\n"), stdout);
	      status = clnt_broadcast (NIS_PROG, NIS_VERSION, NULLPROC,
				       (xdrproc_t) xdr_void, NULL,
				       (xdrproc_t) xdr_void, NULL,
				       (resultproc_t) eachresult);
	      if (status != RPC_SUCCESS)
		{
		  fprintf (stderr, _("broadcast: %s.\n"),
			   clnt_sperrno (status));
		  fputs (_("Sorry, couldn't find a server, try nisinit -c -H hostname instead.\n"),
			 stderr);
		  return 1;
		}
	    }
	  else if (chost != NULL)
	    {
	      nisserver =
		(struct sockaddr_in *) malloc (sizeof (struct sockaddr_in));
	      nisserver->sin_family = AF_INET;
	      nisserver->sin_port = 0;
	      if (chost[0] >= '0' && chost[0] <= '9')
		{
		  if (inet_aton (chost, &nisserver->sin_addr) == 0)
		    {
		      fprintf (stderr, _("%s is not a valid IP address\n"),
			       chost);
		      return 1;
		    }
		}
	      else
		{
		  struct hostent *host;
		  struct in_addr in;

		  host = gethostbyname (chost);
		  if (host == NULL)
		    {
		      fprintf (stderr, _("Couldn't lookup %s\n"), chost);
		      return 1;
		    }
		  memcpy (&in, host->h_addr_list[0], sizeof (in));
		  if (inet_aton (inet_ntoa (in),
				 &nisserver->sin_addr) == 0)
		    {
		      fprintf (stderr, _("%s is not a valid\n"), chost);
		      return 1;
		    }
		}
	    }
	  else
	    {
	      fprintf (stderr,
		_("Internal nisinit (%s|%d) error, should never happen\n"),
		       __FILE__, __LINE__);
	      return 1;
	    }
	  fprintf (stdout, _("Using %s as NIS+ server ...\n"),
		   inet_ntoa (nisserver->sin_addr));
	  /* Build a tcp connection to the NIS+ server */
	  clnt_sock = RPC_ANYSOCK;
	  clnt = clnttcp_create (nisserver, NIS_PROG,
				 NIS_VERSION, &clnt_sock, 0, 0);
	  if (clnt == NULL)
	    {
	      fprintf (stderr, _("Couldn't connect to rpc.nisd on %s (%s)\n"),
		       chost, inet_ntoa (nisserver->sin_addr));
	      return 1;
	    }
	  clnt->cl_auth = authunix_create_default ();
	  req.ns_name = domain;
	  req.ns_object.ns_object_len = 0;
	  req.ns_object.ns_object_val = NULL;
	  res = malloc (sizeof (nis_result));
	  memset (res, '\0', sizeof (nis_result));
	  if (clnt_call (clnt, NIS_LOOKUP, (xdrproc_t) xdr_ns_request,
			 (caddr_t) & req, (xdrproc_t) xdr_nis_result,
			 (caddr_t) res, TIMEOUT) != RPC_SUCCESS)
	    {
	      clnt_perror (clnt, "clnt_call");
	      clnt_destroy (clnt);
	      return 1;
	    }
	  clnt_destroy (clnt);

	  if (res == NULL ||
	      (res->status != NIS_SUCCESS && res->status != NIS_S_SUCCESS))
	    {
	      if (res == NULL)
		fprintf (stderr, _("NIS+ not running on host %s?\n"), chost);
	      else
		nis_perror (res->status, domain);
	      nis_freeresult (res);
	      return 1;
	    }
	  memset (&obj->DI_data, '\0', sizeof (obj->DI_data));
	  nis_clone_directory (&res->objects.objects_val->DI_data,
			       &obj->DI_data);
	  nis_freeresult (res);
	}
    }

  umask (0133);

  if (writeColdStartFile (&obj->DI_data) != TRUE)
    {
      fputs (_("WARNING: Could not create /var/nis/NIS_COLD_START!\n"),
	     stderr);
      return 1;
    }

  return 0;
}
