/* 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 <netdb.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 <netinet/in.h>
#include <arpa/inet.h>
#include <rpcsvc/nis.h>
#include <rpc/key_prot.h>

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

#define NISENTRYVAL(idx,col,res) \
        ((res)->objects.objects_val[(idx)].EN_data.en_cols.en_cols_val[(col)].ec_value.ec_value_val)

#define NISENTRYLEN(idx,col,res) \
        ((res)->objects.objects_val[(idx)].EN_data.en_cols.en_cols_val[(col)].ec_value.ec_value_len)

static nis_server *
search_host (char *name)
{
  struct hostent *hent;
  char host[MAXHOSTNAMELEN + 1];
  char netname[MAXNETNAMELEN];
  char pkey[HEXKEYBYTES + 1];
  nis_server *serv;

  hent = gethostbyname (name);

  if (hent == NULL)
    {
      fprintf (stderr, _("%s: gethostbyname failed"), name);
      return NULL;
    }

  serv = calloc (1, sizeof (nis_server));
  if (!serv)
    return NULL;

  strncpy (host, hent->h_name, MAXHOSTNAMELEN);
  host[MAXHOSTNAMELEN] = '\0';
  if (strchr (host, '.') == NULL)
    {
      strncat (host, ".", MAXHOSTNAMELEN);
      strncat (host, nis_local_directory (), MAXHOSTNAMELEN);
    }
  else
    {
      *strchr (host, '.') = '\0';
      strncat (host, ".", MAXHOSTNAMELEN);
      strncat (host, nis_local_directory (), MAXHOSTNAMELEN);
  }
  serv->name = strdup(host);

  if (host2netname(netname, host, NULL) > 0 &&
      getpublickey(netname, pkey) > 0)
    {
      serv->key_type = NIS_PK_DH;
      serv->pkey.n_bytes = strdup (pkey);
      serv->pkey.n_len = strlen (pkey) + 1;
    }
  else
    {
      serv->key_type = NIS_PK_NONE;
      serv->pkey.n_bytes = NULL;
      serv->pkey.n_len = 0;
    }
  serv->ep.ep_len = 0;
  serv->ep.ep_val = NULL;

  while (*hent->h_addr_list != NULL)
    {
      char uaddr[NIS_MAXNAMELEN];
      struct in_addr *in;
      endpoint *ep;
      char *cp;

      in = *(struct in_addr **)hent->h_addr_list;
      serv->ep.ep_val =
	realloc (serv->ep.ep_val, sizeof (endpoint) * (serv->ep.ep_len + 3));
      if (serv->ep.ep_val == NULL)
	{
	  fputs (_("Out of memory!\n"), stderr);
	  exit (1);
	}

      cp = stpcpy (uaddr, inet_ntoa (*in));
      strcpy (cp, ".0.111");

      ep = &serv->ep.ep_val[serv->ep.ep_len];
      ep->proto = strdup ("udp");       /* For UDP. */
      ep->family = strdup ("inet");
      ep->uaddr = strdup (uaddr);
      ep++;
      ep->proto = strdup  ("tcp");       /* For TCP. */
      ep->family = strdup ("inet");
      ep->uaddr = strdup (uaddr);
      ep++;
      ep->proto = strdup ("-");         /* For broadcast */
      ep->family = strdup ("inet");
      ep->uaddr = strdup (uaddr);
      serv->ep.ep_len += 3;
      hent->h_addr_list++;
    }

  return serv;
}

/* Print the version information.  */
static inline void
print_version (void)
{
  fprintf (stdout, "nismkdir (%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 (_("nismkdir [-D defaults] [-m hostname] [-s hostname] dirname\n"),
	 stdout);
}

static void
print_help (void)
{
  print_usage ();
  fputs (_("nismkdir - create NIS+ directories\n\n"),
         stdout);

  fputs (_("  -D defaults    Specify a different set of defaults to be used\n"),
	 stdout);
  fputs (_("  -m hostname    Create directory on master \"hostname\"\n"),
	 stdout);
  fputs (_("  -s hostname    Specify that \"hostname\" will be a replica\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 void
print_error (void)
{
  const char *program = "nismkdir";

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

int
main (int argc, char *argv[])
{
  char *defaults = NULL;
  char *replica = NULL;
  char *master = NULL;

  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, "uhD:m:s:", long_options, &option_index);
      if (c == EOF)
	break;
      switch (c)
	{
	case 'D':
	  defaults = optarg;
	  break;
	case 'm':
	  if (replica)
	    {
	      fputs (_("You couldn't use -s and -m at the same time!\n"),
		     stderr);
	      print_error ();
	      return 1;
	    }
	  master = optarg;
	  break;
	case 's':
	  if (master)
	    {
	      fputs (_("You couldn't use -s and -m at the same time!\n"),
		     stderr);
	      print_error ();
	      return 1;
	    }
	  replica = optarg;
	  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 != 1)
    {
      fprintf (stderr, _("%s: Wrong number of arguments\n"), "nismkdir");
      print_error ();
      return 1;
    }

  if (argv[0][strlen (argv[0]) - 1] != '.')
    {
      fputs (_("dirname must be fully qualified.\n"), stderr);
      return  1;
    }
  else
    {
      char *cptr;
      nis_error err;
      nis_result *res;
      nis_object *obj;

      if (master != NULL)
	{
	  /* We have two possible choices: We create a new directory on
	     "master", or we make "master" the new master of an existing
	     domain */
	  res = nis_lookup (argv[0], MASTER_ONLY);
	  if (res == NULL)
	    {
	      fputs (_("Out of memory!\n"), stderr);
	      return 1;
	    }
	  if (res->status == NIS_NOTFOUND)
	    cptr = strdup (nis_domain_of (argv[0]));
	  else
	    cptr = strdup (argv[0]);
	}
      else if (replica != NULL)
	cptr = strdup (argv[0]);
      else
	cptr = strdup (nis_domain_of (argv[0]));

      res = nis_lookup (cptr, MASTER_ONLY);
      if (res == NULL)
	{
	  fputs (_("Out of memory!\n"), stderr);
	  return 1;
	}
      if (res->status != NIS_SUCCESS)
	{
	  fprintf (stderr, _("Error (%s): %s\n"), cptr,
		   nis_sperrno (res->status));
	  nis_freeresult (res);
	  free (cptr);
	  return 1;
	}
      if (res->objects.objects_len != 1)
	{
	  fputs (_("Error: Malformed Object!\n"), stderr);
	  nis_freeresult (res);
	  free (cptr);
	  return 1;
	}
      if (__type_of (NIS_RES_OBJECT(res)) != NIS_DIRECTORY_OBJ)
	{
	  fprintf (stderr, _("%s is not a directory!\n"), cptr);
	  nis_freeresult (res);
	  free (cptr);
	  return 1;
	}

      obj = nis_clone_object (res->objects.objects_val, NULL);
      nis_freeresult (res);

      if (replica)
	{
	  nis_server *sptr, *serv, *repserv;
	  u_long i, count = 0;

	  repserv = search_host (replica);
	  if (repserv == NULL)
	    {
	      fprintf (stderr, _("Could not find %s, please use the fully qualified name\n"), replica);
	      return 1;
	    }

	  for (i = 0; i < obj->DI_data.do_servers.do_servers_len; ++i)
	    if (strcmp (obj->DI_data.do_servers.do_servers_val[i].name,
			repserv->name) == 0)
	      ++count;

	  if (count > 0)
	    {
	      fprintf (stderr, _("%s already serves %s!\n"), replica,
		       argv[0]);
	      return 1;
	    }

	  serv = realloc (obj->DI_data.do_servers.do_servers_val,
			  sizeof (nis_server) *
			  (obj->DI_data.do_servers.do_servers_len + 1));
	  sptr = serv;
	  sptr += obj->DI_data.do_servers.do_servers_len;
	  memset (sptr, '\0', sizeof (nis_server));
	  obj->DI_data.do_servers.do_servers_len += 1;
	  sptr->name = repserv->name;
	  sptr->key_type = repserv->key_type;
	  sptr->pkey = repserv->pkey;
	  sptr->ep = repserv->ep;
	  obj->DI_data.do_servers.do_servers_val = serv;
	  res = nis_modify (argv[0], obj);
	  if (res->status != NIS_SUCCESS)
	    {
	      fprintf (stderr, _("Could not add replica to directory: %s\n"),
		       nis_sperrno (res->status));
	      return 1;
	    }
	} /* if (replica) */
      else
	{
	  if (master) /* Change the obj struct, so that we create the */
	    {	      /* directory not on the master		      */
	      nis_server *serv = search_host (master);
	      if (serv == NULL)
		{
		  fprintf (stderr, _("Could not find %s, please use the fully qualified name\n"),
			   replica);
		  return 1;
		}

	      res = nis_lookup (argv[0], EXPAND_NAME);
	      if (res == NULL)
		{
		  fputs (_("Error: NIS+ server not running?\n"), stdout);
		  return 1;
		}
	      /* Make a replica to the new master */
	      if (res->status == NIS_SUCCESS || res->status == NIS_S_SUCCESS)
		{
		  nis_result *ares;
		  u_long i;
		  directory_obj *dir = &NIS_RES_OBJECT(res)->DI_data;
		  nis_server *old = dir->do_servers.do_servers_val;

		  if (nis_dir_cmp (old[0].name, serv->name) == SAME_NAME)
		    {
		      fprintf (stderr, _("%s is already the master for %s!\n"),
			       serv->name, dir->do_name);
		      nis_freeresult (res);
		      exit (1);
		    }
		  for (i = 0; i < dir->do_servers.do_servers_len; ++i)
		    {
		      if (nis_dir_cmp (old[i].name, serv->name) == SAME_NAME)
                        break;
		    }
		  if (i < dir->do_servers.do_servers_len)
		    {
		      nis_server tmp;
		      memcpy (&tmp, &old[i], sizeof (nis_server));
		      memcpy (&old[i], &old[0], sizeof (nis_server));
		      memcpy (&old[0], &tmp, sizeof (nis_server));
		    }
		  else
		    {
		      nis_server *new;

		      dir->do_servers.do_servers_len++;
		      new = calloc(1, sizeof(nis_server) *
				   dir->do_servers.do_servers_len);
		      for (i = 0; i < dir->do_servers.do_servers_len; ++i)
			{
			  if (i)
			    memcpy (&new[i], &old[i-1], sizeof (nis_server));
			  else
			    memcpy (&new[i], master, sizeof (nis_server));
                        }
		      /* XXX give old free */
		      dir->do_servers.do_servers_val = new;
		    }

		  ares = nis_modify (dir->do_name, NIS_RES_OBJECT(res));
		  nis_freeresult (res);

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

		  if (ares->status != NIS_SUCCESS)
		    {
		      fprintf (stderr, _("couldn't change master: %s\n"),
			       nis_sperrno(ares->status));
		      nis_freeresult (ares);
		      return 1;
		    }

		  nis_freeresult (ares);
		  return 0;
		}

	      obj->DI_data.do_servers.do_servers_val = serv;
	      obj->DI_data.do_servers.do_servers_len = 1;
	    }
	  /* The following is the same for nismkdir with -m flag and
	     nismkdir without -s flag. The differnce is only the host
	     on which the rpc.nisd runs. */
	  obj->zo_name = strdup (nis_leaf_of (argv[0]));
	  /* obj->zo_domain = strdup (cptr); */
	  obj->DI_data.do_name = strdup (argv[0]);
	  obj->zo_owner = strdup (__nis_default_owner (defaults));
	  obj->zo_group = strdup (__nis_default_group (defaults));
	  obj->zo_access = __nis_default_access (defaults, obj->zo_access);

	  res = nis_add (argv[0], obj);

	  if (res->status != NIS_SUCCESS)
	    {
	      fprintf (stdout, _("can't add directory object: %s\n"),
		       nis_sperrno (res->status));
	      return 1;
	    }
	  if ((err = nis_mkdir (argv[0],
				obj->DI_data.do_servers.do_servers_val))
	      != NIS_SUCCESS)
	    {
	      nis_remove (argv[0], obj);
	      fprintf (stdout, _("can't make directory: %s\n"),
		       nis_sperrno (err));
	      return 1;
	    }
	}
    }
  return 0;
}
