/*
** ypserv.c                     YP server main routines.
**
** Copyright (c) 1993 Signum Support AB, Sweden
** Copyright (c) 1996, 1997 Thorsten Kukuk
**
** This file is part of the NYS YP Server.
**
** The NYS YP Server 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 of the
** License, or (at your option) any later version.
**
** The NYS YP Server 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 the NYS YP Server; see the file COPYING.  If
** not, write to the Free Software Foundation, Inc., 675 Mass Ave,
** Cambridge, MA 02139, USA.
**
** Author: Peter Eriksson <pen@signum.se>
** Modified: Thorsten Kukuk <kukuk@uni-paderborn.de>
*/

#ifndef LINT
static const char rcsid[] = "$Id: ypserv.c,v 1.3 1997/01/29 19:16:29 kukuk Exp $";
#endif

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

#include "system.h"
#include "version.h"

#include "yp.h"
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <unistd.h>
#include <syslog.h>
#ifndef LOG_DAEMON
#include <sys/syslog.h>
#endif
#include <signal.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
#if defined(HAVE_GETOPT_H) && defined(HAVE_GETOPT_LONG)
#include <getopt.h>
#else
#include <compat/getopt.h>
#endif

#ifndef HAVE_GETOPT_LONG
#include <compat/getopt.c>
#include <compat/getopt1.c>
#endif

#ifndef HAVE_STRERROR
#include <compat/strerror.c>
#endif

#include "yp_msg.h"
#include "ypserv.h"

#ifndef SA_RESTART
#define SA_RESTART 0
#endif

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

int version_flag = 0;
int debug_flag = 0;
int dns_flag = 0;
int sunos_kludge_flag = 1;
int tryresolve_flag = 0;

#ifndef YPMAPDIR
#define YPMAPDIR   "/var/yp"
#endif
char *path_ypdb = YPMAPDIR;

char *progname;

#if HAVE__RPC_DTABLESIZE
extern int _rpc_dtablesize(void);
#elif HAVE_GETDTABLESIZE
int _rpc_dtablesize()
{
        static int size;
        
        if (size == 0) {
                size = getdtablesize();
        }
        return (size);
}
#else

#include <sys/resource.h>

int _rpc_dtablesize()
{
    static int size = 0;
    struct rlimit rlb;

    if (size == 0)
    {
        if (getrlimit(RLIMIT_NOFILE, &rlb) >= 0)
            size = rlb.rlim_cur;
    }

    return size;
}
#endif

#ifdef __STDC__
#define SIG_PF void(*)(int)
#endif

static void
ypprog_2(struct svc_req *rqstp, register SVCXPRT *transp)
{
  union {
    domainname ypproc_domain_2_arg;
    domainname ypproc_domain_nonack_2_arg;
    ypreq_key ypproc_match_2_arg;
    ypreq_key ypproc_first_2_arg;
    ypreq_key ypproc_next_2_arg;
    ypreq_xfr ypproc_xfr_2_arg;
    ypreq_nokey ypproc_all_2_arg;
    ypreq_nokey ypproc_master_2_arg;
    ypreq_nokey ypproc_order_2_arg;
    domainname ypproc_maplist_2_arg;
  } argument;
  char *result;
  xdrproc_t xdr_argument, xdr_result;
  char *(*local)(char *, struct svc_req *);

  switch (rqstp->rq_proc) 
    {
    case YPPROC_NULL:
      xdr_argument = (xdrproc_t) xdr_void;
      xdr_result = (xdrproc_t) xdr_void;
      local = (char *(*)(char *, struct svc_req *)) ypproc_null_2_svc;
      break;
      
    case YPPROC_DOMAIN:
      xdr_argument = (xdrproc_t) xdr_domainname;
      xdr_result = (xdrproc_t) xdr_bool;
      local = (char *(*)(char *, struct svc_req *)) ypproc_domain_2_svc;
      break;
      
    case YPPROC_DOMAIN_NONACK:
      xdr_argument = (xdrproc_t) xdr_domainname;
      xdr_result = (xdrproc_t) xdr_bool;
      local = (char *(*)(char *, struct svc_req *)) ypproc_domain_nonack_2_svc;
      break;
      
    case YPPROC_MATCH:
      xdr_argument = (xdrproc_t) xdr_ypreq_key;
      xdr_result = (xdrproc_t) xdr_ypresp_val;
      local = (char *(*)(char *, struct svc_req *)) ypproc_match_2_svc;
      break;
      
    case YPPROC_FIRST:
#if 0 /* Bug in Sun's yp.x RPC prototype file */
      xdr_argument = (xdrproc_t) xdr_ypreq_key;
#else
      xdr_argument = (xdrproc_t) xdr_ypreq_nokey;
#endif
      xdr_result = (xdrproc_t) xdr_ypresp_key_val;
      local = (char *(*)(char *, struct svc_req *)) ypproc_first_2_svc;
      break;
      
    case YPPROC_NEXT:
      xdr_argument = (xdrproc_t) xdr_ypreq_key;
      xdr_result = (xdrproc_t) xdr_ypresp_key_val;
      local = (char *(*)(char *, struct svc_req *)) ypproc_next_2_svc;
      break;
      
    case YPPROC_XFR:
      xdr_argument = (xdrproc_t) xdr_ypreq_xfr;
      xdr_result = (xdrproc_t) xdr_ypresp_xfr;
      local = (char *(*)(char *, struct svc_req *)) ypproc_xfr_2_svc;
      break;
      
    case YPPROC_CLEAR:
      xdr_argument = (xdrproc_t) xdr_void;
      xdr_result = (xdrproc_t) xdr_void;
      local = (char *(*)(char *, struct svc_req *)) ypproc_clear_2_svc;
      break;
      
    case YPPROC_ALL:
      xdr_argument = (xdrproc_t) xdr_ypreq_nokey;
      xdr_result = (xdrproc_t) xdr_ypresp_all;
      local = (char *(*)(char *, struct svc_req *)) ypproc_all_2_svc;
      break;
      
    case YPPROC_MASTER:
      xdr_argument = (xdrproc_t) xdr_ypreq_nokey;
      xdr_result = (xdrproc_t) xdr_ypresp_master;
      local = (char *(*)(char *, struct svc_req *)) ypproc_master_2_svc;
      break;
      
    case YPPROC_ORDER:
      xdr_argument = (xdrproc_t) xdr_ypreq_nokey;
      xdr_result = (xdrproc_t) xdr_ypresp_order;
      local = (char *(*)(char *, struct svc_req *)) ypproc_order_2_svc;
      break;
      
    case YPPROC_MAPLIST:
      xdr_argument = (xdrproc_t) xdr_domainname;
      xdr_result = (xdrproc_t) xdr_ypresp_maplist;
      local = (char *(*)(char *, struct svc_req *)) ypproc_maplist_2_svc;
      break;
      
    default:
      if (debug_flag)
	yp_msg("Unknown YPPROC call!\n");
      svcerr_noproc(transp);
      return;
    }

  memset((char *)&argument, 0, sizeof (argument));
  if (!svc_getargs(transp, xdr_argument, (caddr_t) &argument)) 
    {
      svcerr_decode(transp);
      return;
    }
  result = (*local)((char *)&argument, rqstp);
  if (result != NULL && !svc_sendreply(transp, xdr_result, result)) 
    {
      svcerr_systemerr(transp);
    }
  if (!svc_freeargs(transp, xdr_argument, (caddr_t) &argument)) 
    {
      yp_msg("unable to free arguments");
      exit(1);
    }
  return;
}

void ypserv_svc_run(void)
{
#ifdef FD_SETSIZE
  fd_set readfds;
#else
  int readfds;
#endif /* def FD_SETSIZE */
  extern int forked;
  int pid;
  
  /* Establish the identity of the parent ypserv process. */
  pid = getpid();
  
  for (;;) {
#ifdef FD_SETSIZE
    readfds = svc_fdset;
#else
    readfds = svc_fds;
#endif /* def FD_SETSIZE */
    switch (select(_rpc_dtablesize(), &readfds, NULL, NULL,
		   (struct timeval *)0)) 
      {
      case -1:
	if (errno == EINTR) 
	    continue;
	syslog(LOG_ERR, "svc_run: - select failed: %m");
	return;
      case 0:
	continue;
      default:
	svc_getreqset(&readfds);
	if (forked && pid != getpid())
	  exit(0);
      }
  }
}

/*
** Create a pidfile on startup
*/
void create_pidfile(void)
{
  int fd, left, written;
  pid_t pid;
  char pbuf[10], *ptr;
  struct flock lock;
  
  fd = open(_YPSERV_PIDFILE, O_CREAT | O_RDWR, 
	    S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
  if (fd < 0)
    {
      yp_msg("cannot create pidfile %s", _YPSERV_PIDFILE);
      if(debug_flag) yp_msg("\n");
    }

  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(fd, F_GETLK, &lock) < 0)
    {
      yp_msg("fcntl error");
      if(debug_flag) yp_msg("\n");
    }
  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)
    {
      yp_msg("ypserv already running (pid %d) - exiting", pid);
      if(debug_flag) yp_msg("\n");
      exit(1);
    }

  /* write lock */
  lock.l_type = F_WRLCK;
  lock.l_start = 0;
  lock.l_whence = SEEK_SET;
  lock.l_len = 0;
  if (0 != fcntl(fd, F_SETLK, &lock))
    yp_msg("cannot lock pidfile");
  sprintf(pbuf, "%d\n", getpid());
  left = strlen(pbuf);
  ptr = pbuf;
  while (left > 0) 
    {
      if ((written = write(fd, ptr, left)) <= 0)
	return;               /* error */
      left -= written;
      ptr   += written;
    }

  return;
}

/*
** Clean up after child processes signal their termination.
*/
void sig_child(int sig)
{
   int st;

#ifdef HAVE_WAIT3   
   while (wait3(&st, WNOHANG, NULL) > 0)
#else 
   while (waitpid(-1, &st, WNOHANG) > 0)
#endif
      children--;
}

/*
** Clean up if we quit the program.
*/
void sig_quit(int sig)
{
  (void) pmap_unset(YPPROG, YPVERS);
  if(sunos_kludge_flag)
    (void) pmap_unset(YPPROG, 1);
  unlink(_YPSERV_PIDFILE);

  exit(0);
}

static SVCXPRT    *main_transp;
/*
** Reload securenets and config file
*/
void sig_hup(int sig)
{
  int old_sunos_kludge;

  old_sunos_kludge = sunos_kludge_flag;

#ifndef HAVE_LIBWRAP
  load_securenets();
#endif
  load_config();

  if(sunos_kludge_flag && ! old_sunos_kludge)
    {/* We must register it new */
      if (!svc_register(main_transp, YPPROG, 1, ypprog_2, IPPROTO_UDP))
	{
	  yp_msg("unable to register (YPPROG, 1, udp).");
	  sunos_kludge_flag = 0;
	}
    }
  else
    if(!sunos_kludge_flag && old_sunos_kludge)
      { /* We must unregister it now */
	(void) pmap_unset(YPPROG, 1);
      }
}

int main(int argc, char **argv)
{
    int	         	i;
    int			my_port = -1;
    int			my_socket;
    struct sockaddr_in	socket_address;
    int			result;
    struct sigaction    sa;

    progname = strrchr (argv[0], '/');
    if (progname == (char *) NULL)
	progname = argv[0];
    else
	progname++;

    openlog(progname, LOG_PID, LOG_DAEMON);

    for (i = 1; i < argc && argv[i][0] == '-'; i++)
      {
	if (strcmp( argv[i], "--version") == 0 || 
	    strcmp(argv[i], "-version") == 0 || strcmp(argv[i], "-v") == 0)
	  version_flag = 1;
	else if (strcmp(argv[i], "-debug") == 0 || strcmp(argv[i], "-d") == 0)
	  debug_flag = 1;
	else if (strcmp(argv[i], "-dns") == 0 || strcmp(argv[i], "-b") == 0)
	  dns_flag = 1;
	else if ((argv[i][1] == 'p') && (argv[i][2] >= '0') && (argv[i][2] <= '9'))
	  my_port = atoi(argv[i] + 2);
	else
	  {
	    fprintf(stderr, "%s: Unknown command line switch: %s\n",
		   progname,
		   argv[i]);
	    exit(1);
	  }
      }

   if (version_flag)
     {
       debug_flag=1;
       yp_msg("ypserv - NYS YP Server version %s (with %s)\n",
	      version,
#if HAVE_LIBWRAP
	      "tcp wrapper"
#else
	      "securenets"
#endif
	      );
       exit(0);
     }

   if (debug_flag)
	yp_msg("[Welcome to the NYS YP Server, version %s (with %s)]\n",
		version,
#if HAVE_LIBWRAP
	       "tcp wrapper"
#else
	       "securenets"
#endif
	       );
    else
    {
	int i;
	
	if (fork())
	    exit(0);

	for (i = 0; i < 255; i++)
	    close(i);
	
	if (fork())
	    exit(0);
    }

    if (i < argc)
    {
	path_ypdb = argv[i];
	if (debug_flag)
	    yp_msg("Using database directory: %s\n", path_ypdb);
    }

    /* Change current directory to database location */
    if (chdir(path_ypdb) < 0)
    {
	yp_msg("%s: chdir: %", argv[0], strerror(errno));
	exit(1);
    }

    create_pidfile();
    
#ifndef HAVE_LIBWRAP
    load_securenets();
#endif
    load_config();

    /*
     * Ignore SIGPIPEs. They can hurt us if someone does a ypcat
     * and then hits CTRL-C before it terminates.
     */
    sigaction(SIGPIPE, NULL, &sa);
    sa.sa_handler = SIG_IGN;
#if !defined(sun) || (defined(sun) && defined(__svr4__))
    sa.sa_flags |= SA_RESTART;
    /* 
     * The opposite to SA_ONESHOT, do  not  restore
     * the  signal  action.  This provides behavior
     * compatible with BSD signal semantics. 
     */
#endif
    sigemptyset(&sa.sa_mask);
    sigaction(SIGPIPE, &sa, NULL);
    /*
     * Clear up if child exists
     */
    sigaction(SIGCHLD, NULL, &sa);
#if !defined(sun) || (defined(sun) && defined(__svr4__))
    sa.sa_flags |= SA_RESTART;
#endif
    sa.sa_handler = sig_child;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGCHLD, &sa, NULL);
    /*
     * If program quits, give ports free.
     */
    sigaction(SIGTERM, NULL, &sa);
#if !defined(sun) || (defined(sun) && defined(__svr4__))
    sa.sa_flags |= SA_RESTART;
#endif
    sa.sa_handler = sig_quit;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGTERM, &sa, NULL);

    sigaction(SIGINT, NULL, &sa);
#if !defined(sun) || (defined(sun) && defined(__svr4__))
    sa.sa_flags |= SA_RESTART;
#endif
    sa.sa_handler = sig_quit;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);

    /*
     * If we get a SIGHUP, reload the securenets and config file.
     */
    sigaction(SIGHUP, NULL, &sa);
#if !defined(sun) || (defined(sun) && defined(__svr4__))
    sa.sa_flags |= SA_RESTART;
#endif
    sa.sa_handler = sig_hup;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGHUP, &sa, NULL);

    (void) pmap_unset(YPPROG, YPVERS);
    if(sunos_kludge_flag)
      (void) pmap_unset(YPPROG, 1);
    
    if (my_port >= 0)
    {
	my_socket = socket (AF_INET, SOCK_DGRAM, 0);
	if (my_socket < 0)
	{
	    yp_msg("can not create UDP: %s",strerror(errno));
	    exit (1);
	}

	memset((char *) &socket_address, 0, sizeof(socket_address));
	socket_address.sin_family = AF_INET;
	socket_address.sin_addr.s_addr = htonl (INADDR_ANY);
	socket_address.sin_port = htons (my_port);

	result = bind (my_socket, (struct sockaddr *) &socket_address,
		       sizeof (socket_address));
	if (result < 0)
	{
	    yp_msg("%s: can not bind UDP: %s ",
		     progname,strerror(errno));
	    exit (1);
	}
    }
    else
	my_socket = RPC_ANYSOCK;

    main_transp = svcudp_create(my_socket);
    if (main_transp == NULL) {
        yp_msg("cannot create udp service.");
	exit(1);
    }
    if (!svc_register(main_transp, YPPROG, YPVERS, ypprog_2, IPPROTO_UDP)) {
	yp_msg("unable to register (YPPROG, YPVERS, udp).");
	exit(1);
    }

    /*
    ** This is just to make us reply to YP version 1 calls which SunOS 4's
    ** ypbind seems to insist on finding. If someone _really_ tries to
    ** use this then they will probably be bitten - _hard_, since I haven't
    ** got the faintest idea on how the XDR calls for YP version 1 should
    ** look like. The Domain_NoNack call seems to be compatible though :-)
    */
    if(sunos_kludge_flag)
      if (!svc_register(main_transp, YPPROG, 1, ypprog_2, IPPROTO_UDP))
	{
	  yp_msg("unable to register (YPPROG, 1, udp).");
	  exit(1);
	}
    
    if (my_port >= 0)
    {
	my_socket = socket (AF_INET, SOCK_STREAM, 0);
	if (my_socket < 0)
	{
	    yp_msg ("%s: can not create TCP ",
		     progname);
	    exit (1);
	}

	memset((char *) &socket_address, 0, sizeof(socket_address));
	socket_address.sin_family = AF_INET;
	socket_address.sin_addr.s_addr = htonl (INADDR_ANY);
	socket_address.sin_port = htons (my_port);

	result = bind (my_socket, (struct sockaddr *) &socket_address,
		       sizeof (socket_address));
	if (result < 0)
	{
	    yp_msg("%s: can not bind TCP ",
		     progname);
	    exit (1);
	}
    }
    else
	my_socket = RPC_ANYSOCK;

    main_transp = svctcp_create(my_socket, 0, 0);
    if (main_transp == NULL) {
	yp_msg("%s: cannot create tcp service\n", progname);
	exit(1);
    }
    if (!svc_register(main_transp, YPPROG, YPVERS, ypprog_2, IPPROTO_TCP)) {
        yp_msg("%s: unable to register (YPPROG, YPVERS, tcp)\n", progname);
	exit(1);
    }

    ypserv_svc_run();
    yp_msg("svc_run returned");
    unlink(_YPSERV_PIDFILE);
    exit(1);
    /* NOTREACHED */
}
