/* Copyright (C) 1998 Thorsten Kukuk
   Author: Thorsten Kukuk <kukuk@vt.uni-paderborn.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 <malloc.h>
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
#include <rpcsvc/nis.h>

#include "log_msg.h"
#include "npd_nis.h"
#include "npd_yp.h"

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

typedef void (*my_sighandler_t)(int);

/* For pidfile */
static int lock_fd;

#ifdef HAVE_PATHS_H
#include <paths.h>
#endif
#ifndef _PATH_VARRUN
#define _PATH_VARRUN "/etc/"
#endif
#define _NISPASSWDD_PIDFILE _PATH_VARRUN"nispasswdd.pid"

/* Global variables */
int verbose = 0;		/* Should we send the debug output to syslog ? */
int attempts = 3;		/* After how many tries should we stop responding ? */
int minutes = 30;		/* How long shouldn't we answer ? */
int gen_des = 0;		/* By default, don't generate new DES keys */
static int yp_compat_mode = 0;  /* Is rpc.nisd in YP Comapt mode ? */
struct dir_list *dirlist = NULL;

/* Print the version information.  */
static inline void
print_version (void)
{
  fprintf (stdout, "rpc.nispasswdd (%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: rpc.nispasswdd [-a attempts] [-c minutes] [-D] [-g] [-v] [-Y]\n"),
	 stdout);
}

static void
print_help (void)
{
  print_usage ();
  fputs (_("rpc.nispasswdd - NIS+ password update daemon\n\n"),
	 stdout);

  fputs (_("  -a attempts    After how many tries should we stop responding\n"),
	 stdout);
  fputs (_("  -c minutes     How long shouldn't we answer\n"),
	 stdout);
  fputs (_("  -D, --debug    Debug mode\n"), stdout);
  fputs (_("  -g             Generate DES key if user has no key\n"),
	 stdout);
  fputs (_("  -v, --verbose  Verbose\n"), stdout);
  fputs (_("  -Y             Force YP compat mode\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 = "rpc.nispasswdd";

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

/* Create a pidfile on startup */
static void
create_pidfile (void)
{
  struct flock lock;
  int left, written;
  pid_t pid;
  char pbuf[10], *ptr;
  int flags;

  lock_fd = open (_NISPASSWDD_PIDFILE, O_CREAT | O_RDWR,
		  S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  if (lock_fd < 0)
    log_msg (LOG_ERR, _("cannot create pidfile %s"), _NISPASSWDD_PIDFILE);

  /* Make sure file gets correctly closed when process finished.  */
  flags = fcntl (lock_fd, F_GETFD, 0);
  if (flags == -1)
    {
      /* Cannot get file flags.  */
      close (lock_fd);
      return;
    }
  flags |= FD_CLOEXEC;		/* Close on exit.  */
  if (fcntl (lock_fd, F_SETFD, flags) < 0)
    {
      /* Cannot set new flags.  */
      close (lock_fd);
      return;
    }

  lock.l_type = F_WRLCK;
  lock.l_start = 0;
  lock.l_whence = SEEK_SET;
  lock.l_len = 0;

  /* Is the pidfile locked by another ypserv ? */
  if (fcntl (lock_fd, F_GETLK, &lock) < 0)
    log_msg (LOG_ERR, _("fcntl error"));

  if (lock.l_type == F_UNLCK)
    pid = 0;			/* false, region is not locked by another proc */
  else
    pid = lock.l_pid;		/* true, return pid of lock owner */

  if (0 != pid)
    {
      log_msg (LOG_ERR, _("rpc.nispasswdd already running (pid %d) - exiting"),
	       pid);
      exit (1);
    }

  /* write lock */
  lock.l_type = F_WRLCK;
  lock.l_start = 0;
  lock.l_whence = SEEK_SET;
  lock.l_len = 0;
  if (fcntl (lock_fd, F_SETLK, &lock) != 0)
    log_msg (LOG_ERR, _("cannot lock pidfile"));
  snprintf (pbuf, sizeof (pbuf), "%ld\n", (long) getpid ());
  left = strlen (pbuf);
  ptr = pbuf;
  while (left > 0)
    {
      if ((written = write (lock_fd, ptr, left)) <= 0)
	return;			/* error */
      left -= written;
      ptr += written;
    }

  return;
}

/* Clean up if we quit the program. */
static void
sig_quit (void)
{
  pmap_unset (NISPASSWD_PROG, NISPASSWD_VERS);
  if (yp_compat_mode)
    pmap_unset (YPPASSWDPROG, YPPASSWDVERS);
  unlink (_NISPASSWDD_PIDFILE);

  exit (0);
}

static void
nispasswd_prog_1 (struct svc_req *rqstp, register SVCXPRT * transp)
{
  union
  {
    npd_request nispasswd_authenticate_1_arg;
    npd_update nispasswd_update_1_arg;
  }
  argument;
  union
    {
      nispasswd_authresult nispasswd_authenticate_1_res;
      nispasswd_updresult nispasswd_update_1_res;
    }
  result;
  bool_t retval;
  xdrproc_t xdr_argument, xdr_result;
  bool_t (*local) (char *, void *, struct svc_req *);

  switch (rqstp->rq_proc)
    {
    case NULLPROC:
      svc_sendreply (transp, (xdrproc_t) xdr_void, (char *) NULL);
      return;
    case NISPASSWD_AUTHENTICATE:
      xdr_argument = (xdrproc_t) xdr_npd_request;
      xdr_result = (xdrproc_t) xdr_nispasswd_authresult;
      local = (bool_t (*)(char *, void *, struct svc_req *)) nispasswd_authenticate_1_svc;
      break;
    case NISPASSWD_UPDATE:
      xdr_argument = (xdrproc_t) xdr_npd_update;
      xdr_result = (xdrproc_t) xdr_nispasswd_updresult;
      local = (bool_t (*)(char *, void *, struct svc_req *)) nispasswd_update_1_svc;
      break;
    default:
      svcerr_noproc (transp);
      return;
    }
  memset ((char *) &argument, 0, sizeof (argument));
  if (!svc_getargs (transp, xdr_argument, (caddr_t) & argument))
    {
      svcerr_decode (transp);
      return;
    }
  retval = (bool_t) (*local) ((char *) &argument, (void *) &result, rqstp);
  if (retval > 0 && !svc_sendreply (transp, xdr_result, (char *) &result))
    svcerr_systemerr (transp);
  if (!svc_freeargs (transp, xdr_argument, (caddr_t) & argument))
    {
      fputs (_("unable to free arguments"), stderr);
      exit (1);
    }
  if (!nispasswd_prog_1_freeresult (xdr_result, (caddr_t) & result))
    {
      fputs (_("unable to free results"), stderr);
      exit (1);
    }
  return;
}

static struct nis_server local_serv;

static void
init_local_server (void)
{
  local_serv.name = "localhost";
  local_serv.ep.ep_len = 3;
  local_serv.ep.ep_val = malloc (3 * sizeof (endpoint));
  local_serv.ep.ep_val[0].proto = (char *)"udp";
  local_serv.ep.ep_val[0].family = (char *)"inet";
  local_serv.ep.ep_val[0].uaddr = (char *)"127.0.0.1.0.111";
  local_serv.ep.ep_val[1].proto = (char *)"tcp";
  local_serv.ep.ep_val[1].family = (char *)"inet";
  local_serv.ep.ep_val[1].uaddr = (char *)"127.0.0.1.0.111";
  local_serv.ep.ep_val[2].proto = (char *)"-";
  local_serv.ep.ep_val[2].family = (char *)"inet";
  local_serv.ep.ep_val[2].uaddr = (char *)"127.0.0.1.0.111";
  local_serv.key_type = NIS_PK_NONE;
  local_serv.pkey.n_len = 0;
  local_serv.pkey.n_bytes = '\0';
}

/* Returns 1, if we are root master for local domain, else 0 */
static int
check_root_master (void)
{
  nis_tag query_val[] = {
    {TAG_NISCOMPAT, (char *)""},
    {TAG_DIRLIST, (char *)""},
  };
  int query_len = 3;
  nis_tag *result = NULL;

  if (nis_stats (&local_serv, query_val, query_len, &result) == NIS_SUCCESS)
    {
      int idx;
      char *dir, *cptr1, *cptr2;;

      if ((result[0].tag_type == TAG_NISCOMPAT &&
	   strcmp (result[0].tag_val, "ON") == 0) ||
	  (result[1].tag_type == TAG_NISCOMPAT &&
	   strcmp (result[1].tag_val, "ON") == 0))
	yp_compat_mode = 1;

      if (result[0].tag_type == TAG_DIRLIST)
	idx = 0;
      else if (result[1].tag_type == TAG_DIRLIST)
	idx = 1;
      else
	return 0;

      dir = alloca (strlen (result[idx].tag_val) + 1);

      strcpy (dir, result[idx].tag_val);
      cptr1 = dir;
      do
	{
	  nis_result *res;

	  cptr2 = strpbrk (cptr1, "\n\t ");
	  if (cptr2 != NULL)
	    {
	      *cptr2 = '\0';
	      ++cptr2;
	    }
	  res = nis_lookup (cptr1, 0);
	  if (res != NULL && res->status == NIS_SUCCESS)
	    if ((NIS_RES_OBJECT (res)->DI_data.do_servers.do_servers_val != NULL) &&
		strcmp (NIS_RES_OBJECT (res)->DI_data.do_servers.do_servers_val->name,
			nis_local_host()) == 0)
	      {
		struct dir_list *work;

		if (dirlist == NULL)
		  {
		    dirlist = malloc (sizeof (struct dir_list));
		    dirlist->name = strdup (cptr1);
		    dirlist->next = NULL;
		  }
		else
		  {
		    work = dirlist;
		    while (work->next != NULL)
		      work = work->next;
		    work->next = malloc (sizeof (struct dir_list));
		    work->next->name = strdup (cptr1);
		    work->next->next = NULL;
		  }
	      }

	  if (res != NULL)
	    nis_freeresult (res);

	  cptr1 = cptr2;
	}
      while (cptr2 != NULL && cptr2[0] != '\0');

      if (dirlist == NULL)
	return 0;
      else
	return 1;
    }
  else
    return 0;
}

int
main (int argc, char **argv)
{
  SVCXPRT *transp;

  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'},
	{"debug", no_argument, NULL, 'D'},
	{"verbose", no_argument, NULL, 'v'},
	{NULL, 0, NULL, '\0'}
      };

      c = getopt_long (argc, argv, "a:c:DgvY", long_options,
		       &option_index);
      if (c == (-1))
	break;
      switch (c)
	{
	case 'D':
	  debug_flag = 1;
	case 'v':
	  verbose = 1;
	  break;
	case 'a':
	  attempts = atoi (optarg);
	  break;
	case 'c':
	  minutes = atoi (optarg);
	  break;
	case 'g':
	  gen_des = 1;
	  break;
	case 'Y':
	  yp_compat_mode = 1;
	  break;
	case 'h':
	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)
    {
      fprintf (stderr, _("%s: To many arguments\n"), "rpc.nispasswdd");
      print_error ();
      return 1;
    }

  init_local_server ();

  if (!check_root_master ())
    exit (0);

#if 0
  if (getuid () != 0)
    {
      fputs (_("rpc.nispasswdd must be run as root\n"), stderr);
      exit (1);
    }
#endif

  if (debug_flag)
   fprintf (stdout, "[Welcome to rpc.nispasswdd, version %s]\n", VERSION);
  else
    {
      int i;

      if (fork ())
	exit (0);

      for (i = 0; i < getdtablesize (); i++)
	close (i);

      if (fork ())
	exit (0);

      openlog ("rpc.nispasswdd", LOG_PID, LOG_DAEMON);
    }
  chdir ("/");

  create_pidfile ();

  signal (SIGPIPE, SIG_IGN);
  signal (SIGINT, (my_sighandler_t) sig_quit);
  signal (SIGQUIT, (my_sighandler_t) sig_quit);
  signal (SIGTERM, (my_sighandler_t) sig_quit);

  pmap_unset (NISPASSWD_PROG, NISPASSWD_VERS);

  transp = svcudp_create (RPC_ANYSOCK);
  if (transp == NULL)
    {
      log_msg (LOG_ERR, _("Cannot create udp service."));
      exit (1);
    }
  if (!svc_register (transp, NISPASSWD_PROG, NISPASSWD_VERS,
		     nispasswd_prog_1, IPPROTO_UDP))
    {
      log_msg (LOG_ERR,
	   _("Unable to register (NISPASSWD_PROG, NISPASSWD_VERS, udp)."));
      exit (1);
    }

  transp = svctcp_create (RPC_ANYSOCK, 0, 0);
  if (transp == NULL)
    {
      log_msg (LOG_ERR, _("Cannot create tcp service."));
      exit (1);
    }
  if (!svc_register (transp, NISPASSWD_PROG, NISPASSWD_VERS,
		     nispasswd_prog_1, IPPROTO_TCP))
    {
      log_msg (LOG_ERR,
	       _("Unable to register (NISPASSWD_PROG, NISPASSWD_VERS, tcp)."));
      exit (1);
    }

  if (yp_compat_mode)
    {
      pmap_unset(YPPASSWDPROG, YPPASSWDVERS);

      transp = svcudp_create(RPC_ANYSOCK);
      if (transp == NULL)
	{
	  log_msg (LOG_ERR, _("Cannot create udp service."));
	  exit (1);
	}
      if (!svc_register (transp, YPPASSWDPROG, YPPASSWDVERS, yppasswdprog_1,
			 IPPROTO_UDP))
	{
	  log_msg (LOG_ERR,
		   _("Unable to register (YPPASSWDPROG, YPPASSWDVERS, udp).\n"));
	  exit (1);
	}
    }

  svc_run ();
  log_msg (LOG_ERR, _("svc_run returned."));
  unlink (_NISPASSWDD_PIDFILE);
  return 0;
  /* NOTREACHED */
}
