/* 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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
#include <rpc/pmap_clnt.h>
#include <rpcsvc/nis.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include "nis_db.h"

#include "log_msg.h"
#include "nisd.h"

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

/* For pidfile */
static int lock_fd;
#ifdef _PATH_VARRUN
#define _NISD_PIDFILE _PATH_VARRUN"nisd.pid"
#else
#define _NISD_PIDFILE "/etc/nisd.pid"
#endif

/* Global variables */
int verbose = 0; /* Should we send the debug output to syslog ? */
int securelevel = 2; /* Default is DES auth (2) */
int readonly = 0; /* Do we run a checkpoint ? */
time_t boottime = 0; /* Time of start */
nis_object *root_obj = NULL; /* If we are root server, this is root object */
nis_object *parent_obj = NULL; /* parent object is not yet supported, maybe
				  later */

/* Print the version information.  */
static inline void
print_version (void)
{
  fprintf (stdout, "rpc.nisd (%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.nisd [-ADFhv] [-Y [-B]] [-d dictionary] [-L load] [-S level]\n"),
	 stdout);
}

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

  fputs (_("  -A             Log all authentication related activities\n"),
	 stdout);
  fputs (_("  -B             Provide ypserv compatible DNS forwarding for NIS\n"),
	 stdout);
  fputs (_("  -D             Debug mode\n"), stdout);
  fputs (_("  -d dictionary  Specify alternate dictionary for the database\n"),
	 stdout);
  fputs (_("  -F             Force checkpoint of database on start up\n"),
	 stdout);
  fputs (_("  -L load        Specify max. number of allowed child processes\n"),
	 stdout);
  fputs (_("  -S level       Set the authorization security level (0/1/2)\n"),
	 stdout);
  fputs (_("  -v, --verbose  Verbose mode\n"), stdout);
  fputs (_("  -Y             NIS (YP) compatibility mode\n"), stdout);
  fputs (_("  -h, --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.nisd";

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

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

  lock_fd = open (_NISD_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"), _NISD_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.nisd 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"));
  sprintf (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;
}

/* Thread for handling signals */
static void *
sig_handler (void *v_param __attribute__ ((unused)))
{
  struct flock lock;
  sigset_t sigs_to_catch;
  int caught;

  sigemptyset (&sigs_to_catch);
  sigaddset (&sigs_to_catch, SIGCHLD);
  sigaddset (&sigs_to_catch, SIGTERM);
  sigaddset (&sigs_to_catch, SIGINT);
  sigaddset (&sigs_to_catch, SIGQUIT);
  sigaddset (&sigs_to_catch, SIGSEGV);
  sigaddset (&sigs_to_catch, SIGHUP);

  while (1)
    {
      sigwait (&sigs_to_catch, &caught);
      switch (caught)
        {
        case SIGCHLD:
          log_msg (LOG_ERR, _("SIGCHLD arrived, what should I do ?"));
          break;
        case SIGTERM:
        case SIGINT:
        case SIGQUIT:
        case SIGSEGV:
          /* Clean up if we quit the program. */
          if (debug_flag)
            log_msg (LOG_DEBUG, _("Signal (%d) for quitting program arrived."),
                     caught);
          pmap_unset (NIS_PROG, NIS_VERSION);
          /* unlock pidfile */
          lock.l_type = F_UNLCK;
          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 unlock pidfile"));
          close (lock_fd);
          unlink (_NISD_PIDFILE);
          exit (0);
          break;
        case SIGHUP:
          /* Reload config file */
          if (debug_flag)
            log_msg (LOG_DEBUG, _("SIGHUP arrived, reloading config file."));
          break;
        default:
          log_msg (LOG_ERR, _("Unknown signal: %d"), caught);
          break;
        }
    }
}

int
main (int argc, char **argv)
{
  SVCXPRT *transp;
  sigset_t sigs_to_block;
  pthread_t sig_thread;

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

      c = getopt_long (argc, argv, "ABCDd:FhL:S:vY", long_options,
		       &option_index);
      if (c == (-1))
        break;
      switch (c)
        {
	case 'S':
	  securelevel = atoi (optarg);
	  break;
	case 'D':
	  debug_flag = 1;
	case 'v':
	  verbose = 1;
	  break;
	case 'h':
	  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.nisd");
      print_error ();
      return 1;
    }

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

  if (strcmp (nis_local_directory (), ".") == 0)
    {
      fputs (_("Please set the domainname first\n"), stderr);
      exit (1);
    }

  openlog ("rpc.nisd", LOG_PID, LOG_DAEMON);

  if (debug_flag)
    log_msg (LOG_DEBUG, _("[Welcome to rpc.nisd, version %s]\n"), VERSION);
  else
    {
      int i;

      if ((i = fork ()) > 0)
        exit (0);

      if (i < 0)
        {
          log_msg (LOG_ERR, _("Cannot fork: %s\n"), strerror (errno));
          exit (-1);
        }

      if (setsid () == -1)
        {
          log_msg (LOG_ERR, _("Cannot setsid: %s\n"), strerror (errno));
          exit (-1);
        }

      if ((i = fork ()) > 0)
        exit (0);

      if (i < 0)
        {
          log_msg (LOG_ERR, _("Cannot fork: %s\n"), strerror (errno));
          exit (-1);
        }

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

      umask (0);
      i = open ("/dev/null", O_RDWR);
      dup (i);
      dup (i);
    }

  create_pidfile ();

  /* get time of start */
  boottime = time(NULL);

  pmap_unset (NIS_PROG, NIS_VERSION);

  sigemptyset (&sigs_to_block);
  sigaddset (&sigs_to_block, SIGCHLD);
  sigaddset (&sigs_to_block, SIGTERM);
  sigaddset (&sigs_to_block, SIGINT);
  sigaddset (&sigs_to_block, SIGQUIT);
  sigaddset (&sigs_to_block, SIGSEGV);
  sigaddset (&sigs_to_block, SIGHUP);

  if (pthread_sigmask (SIG_BLOCK, &sigs_to_block, NULL) != 0)
    {
      log_msg (LOG_ERR, _("Could not block signals."));
      exit (1);
    }

  if (pthread_create (&sig_thread, NULL, &sig_handler, NULL) != 0)
    {
      log_msg (LOG_ERR, _("Cannot create thread for signal handling: %s"),
	       strerror (errno));
      exit (1);
    }

  /* Change current directory to _PATH_NIS_DATA */
  if (chdir (_PATH_NIS_DATA) < 0)
    {
      log_msg (LOG_ERR, _("Cannot change directory to %s"), _PATH_NIS_DATA);
      exit (1);
    }

  /* Set umask */
  umask (077);

  /* Load serving list */
  nis_load_srvlist ();

  /* Load the root.object, if it exists. */
  root_obj = nis_read_obj (_PATH_NIS_ROOT_OBJ);

  log_msg (LOG_NOTICE, _("Initializing database..."));
  /* Initialize the database backend */
  if (db_initialize (_NIS_CATALOG) == FALSE)
    {
      log_msg (LOG_ERR, _("failed to initialize database"));
      pmap_unset (NIS_PROG, NIS_VERSION);
      exit (1);
    }
  log_msg (LOG_NOTICE, _("...done."));

#if 0
  /* Checkpoint our databases if necessary. */
  if (do_checkpoint)
    db_checkpoint ("");
#endif

  /* See if the root directory has been created yet */
  if (root_obj != NULL && db_table_exists ("root_dir") != DB_SUCCESS)
    {
      if (db_create_dir ("root_dir") != NIS_SUCCESS)
	{
	  log_msg (LOG_ERR, _("failed to create root directory"));
	  exit (1);
	}
    }

  /* initialize statistic counters */
  nis_init_stats ();

  transp = svcudp_create (RPC_ANYSOCK);
  if (transp == NULL)
    {
      log_msg (LOG_ERR, _("Cannot create udp service."));
      exit (1);
    }
  if (!svc_register (transp, NIS_PROG, NIS_VERSION, nis_prog_3, IPPROTO_UDP))
    {
      log_msg (LOG_ERR,
               _("Unable to register (NIS_PROG, NIS_VERSION, 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, NIS_PROG, NIS_VERSION, nis_prog_3, IPPROTO_TCP))
    {
      log_msg (LOG_ERR, _("Unable to register (NIS_PROG, NIS_VERSION, tcp)."));
      exit (1);
    }

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