/*
** 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: Thorsten Kukuk <kukuk@uni-paderborn.de>
*/

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

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

#include "system.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <rpc/rpc.h>
#include <time.h>
#include "yp.h"
#include <rpcsvc/ypclnt.h>
#include <arpa/inet.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <ctype.h>
#include <netdb.h>
#include <errno.h>
#include <gdbm.h>
#include <string.h>
#include <memory.h>
#if defined(HAVE_GETOPT_H) && defined(HAVE_GETOPT_LONG)
#include <getopt.h>
#else
#include <compat/getopt.h>
#endif

#include "version.h"
#include "yp_msg.h"
#include "yppush.h"

#ifndef HAVE_STRDUP
#include <compat/strdup.c>
#endif

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

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

#ifndef YPMAPDIR
#define YPMAPDIR "/var/yp"
#endif

struct hostlist
{
  char *hostname;
  struct hostlist *next;
};

struct hostlist *hostliste = NULL;

static char *DomainName=NULL;
int verbose_flag=0;
int debug_flag=1;
static char local_hostname[MAXHOSTNAMELEN+2];
static char *current_map;
static u_int CallbackProg=0;
static u_int timeout=90;
static u_int MapOrderNum;
static u_int maxchildren = 1;
static u_int children = 0;

#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

static char *yppush_err_string(enum yppush_status status) 
{
  switch(status) 
    {
    case YPPUSH_SUCC:      return "Success";
    case YPPUSH_AGE:       return "Master's version not newer";
    case YPPUSH_NOMAP:     return "Can't find server for map";
    case YPPUSH_NODOM:     return "Domain not supported";
    case YPPUSH_RSRC:      return "Local resource alloc failure";
    case YPPUSH_RPC:       return "RPC failure talking to server";
    case YPPUSH_MADDR:     return "Can't get master address";
    case YPPUSH_YPERR:     return "YP server/map db error";
    case YPPUSH_BADARGS:   return "Request arguments bad";
    case YPPUSH_DBM:       return "Local dbm operation failed";
    case YPPUSH_FILE:      return "Local file I/O operation failed";
    case YPPUSH_SKEW:      return "Map version skew during transfer";
    case YPPUSH_CLEAR:     return "Can't send \"Clear\" req to local ypserv";
    case YPPUSH_FORCE:     return "No local order number in map  use -f flag.";
    case YPPUSH_XFRERR:    return "ypxfr error";
    case YPPUSH_REFUSED:   return "Transfer request refused by ypserv";
    }
  return "YPPUSH: Unknown Error, this should not happen!";
}

void *yppushproc_null_1_svc(void *req, struct svc_req * rqstp)
{
  static void *resp = NULL;

  if(verbose_flag > 1)
    yp_msg("yppushproc_null_1_svc\n");

  return &resp;
}

yppushresp_xfr *yppushproc_xfrresp_1_svc(void *req, struct svc_req * rqstp)
{
  static yppushresp_xfr resp;
  struct sockaddr_in *sin;
  char *h;
  struct hostent *hp;
  
  if(verbose_flag > 1)
    yp_msg("yppushproc_xfrresp_1_svc\n");

  sin = svc_getcaller(rqstp->rq_xprt);
  
  hp = gethostbyaddr((char *) &sin->sin_addr.s_addr,
                     sizeof (sin->sin_addr.s_addr), AF_INET);
  h = (hp && hp->h_name) ? hp->h_name : inet_ntoa(sin->sin_addr);

  memcpy(&resp, (yppushresp_xfr*)req, sizeof(yppushresp_xfr));
  if(verbose_flag)
    {
      yp_msg("Status received from ypxfr on %s:\n",h);
      yp_msg("\tTransfer %sdone: %s\n",resp.status==YPPUSH_SUCC?"":"not ",
             yppush_err_string(resp.status));
    }
  else
    if (resp.status!=YPPUSH_SUCC)
      yp_msg("%s: %s\n", h, yppush_err_string(resp.status));

  return &resp;
}

void yppush_xfrrespprog_1( struct svc_req *rqstp, SVCXPRT *transp)
{
  union {
    int fill;
  } argument;
  static char *result;
  static bool_t (*xdr_argument)(XDR *, void *), (*xdr_result)(XDR *, void *);
  static char *(*local)( void *, struct svc_req *);
 
  if(verbose_flag > 1)
    yp_msg("yppush_xfrrespprog_1\n");
 
  switch (rqstp->rq_proc) 
    {
    case YPPUSHPROC_NULL:
      xdr_argument = (bool_t (*)(XDR *, void *))xdr_void;
      xdr_result = (bool_t (*)(XDR *, void *))xdr_void;
      local = (char *(*)( void *, struct svc_req *)) yppushproc_null_1_svc;
      break;
      
    case YPPUSHPROC_XFRRESP:
      xdr_argument = (bool_t (*) (XDR *, void *))xdr_yppushresp_xfr;
      xdr_result = (bool_t (*)(XDR *, void *))xdr_void;
      local = (char *(*)( void *, struct svc_req *)) yppushproc_xfrresp_1_svc;
      break;
      
    default:
      svcerr_noproc(transp);
      exit(1);
    }
  memset(&argument, '\0',  sizeof(argument));
  if (!svc_getargs(transp, xdr_argument, (void *) &argument)) 
    {
      svcerr_decode(transp);
      exit(1);
    }
  result = (*local)(&argument, rqstp);
  if (result != NULL && 
      !svc_sendreply(transp, (xdrproc_t) xdr_result, result)) 
    {
      svcerr_systemerr(transp);
    }
  if (!svc_freeargs(transp, xdr_argument, (void *) &argument)) 
    {
      yp_msg("unable to free arguments\n");
      exit(1);
    }
  exit(0);
}

static void yppush_svc_run(char *target)
{
#ifdef FD_SETSIZE
  fd_set readfds;
#else
  int readfds;
#endif /* def FD_SETSIZE */
  struct timeval tr, tb;
  
  tb.tv_sec=timeout; tb.tv_usec=0;
  tr = tb;

  for (;;) 
    {
#ifdef FD_SETSIZE
      readfds = svc_fdset;
#else
      readfds = svc_fds;
#endif /* def FD_SETSIZE */
      switch (select(_rpc_dtablesize(), &readfds, (void *)0, (void *)0, &tr)) 
	{
	case -1:
	  if (errno == EINTR) 
	    {
	      tr = tb; /* Read the Linux select.2 manpage ! */
	      continue;
	    }
	  yp_msg("svc_run: - select failed (%s)",strerror(errno));
	  return;
	case 0:
	  yp_msg( "%s->%s: Callback timed out\n",current_map,target);
	  exit(0);
	default:
	  svc_getreqset(&readfds);
	  break;
	}
    }
}

/*
 *	Compare 2 hostnames.
 */
static int hostcmp(char *h1, char *h2)
{
  char buf1[MAXHOSTNAMELEN+1], buf2[MAXHOSTNAMELEN+1];
  char *p, *s;
  
  strncpy(buf1, h1, sizeof(buf1));
  strncpy(buf2, h2, sizeof(buf2));
  s = strchr(buf1, '.');
  p = strchr(buf2, '.');
  if (s && !p) *s = 0;
  if (p && !s) *p = 0;
  
  return strcmp(buf1, buf2);
}

static char *get_dbm_entry(char *key)
{
  static char mappath[MAXPATHLEN+2];
  char *val;
  datum dkey,dval;
  GDBM_FILE dbm;

#if defined(HAVE_SNPRINTF)  
  if(snprintf(mappath, MAXPATHLEN, "%s/%s/%s", YPMAPDIR, DomainName, current_map)==MAXPATHLEN)
#else
  if(strlen(YPMAPDIR)+strlen(DomainName)+strlen(current_map)+3 < MAXPATHLEN)
    sprintf(mappath, "%s/%s/%s", YPMAPDIR, DomainName, current_map);
  else
#endif
    {
      yp_msg("ERROR: Path to long: %s/%s/%s\n",YPMAPDIR, DomainName,current_map);
      exit(-1);
    }

  if ((dbm=gdbm_open(mappath, 0, GDBM_READER, 0600, NULL))==NULL) 
    {
      yp_msg("YPPUSH: Cannot open %s\n", mappath);
      exit(-1);
    }	

  dkey.dptr=key; dkey.dsize=strlen(dkey.dptr);
  dval=gdbm_fetch(dbm,dkey);
  gdbm_close(dbm);
  if (dval.dptr==NULL)
    return NULL;
  else
    {
      val = malloc(dval.dsize+1);
      strncpy(val,dval.dptr,dval.dsize);
      val[dval.dsize] = 0;
      
      return val;
    }
} 

static u_int getordernum( void)
{
  char *val;
  u_int i; 
  
  val = get_dbm_entry("YP_LAST_MODIFIED");

  if (val==NULL)
    {
      if (verbose_flag >1)
	yp_msg("YPPUSH: %s: Cannot determine order number\n", current_map);
      return -1;
    }

  for (i=0; i<strlen(val); i++) 
    {
      if (!isdigit(val[i])) 
	{
	  yp_msg("YPPUSH: %s: Invalid order number '%s'\n",
		 current_map, val);
	  return -1;
	}
    }
  
  i = atoi(val);

  free(val);

  return i;
}

/*
** Create with the ypservers or slaves.hostname map a list with all
** slave servers we should send the new map
*/
/* NetBSD has a different prototype in struct ypall_callback */
#if defined(__NetBSD__)
static int add_slave_server(u_long status, char *key, int keylen,
			    char *val, int vallen, void *data)
#else
static int add_slave_server(int status, const char *key, int keylen,
			    const char *val, int vallen, const char *data)
#endif
{
  char host[YPMAXPEER+2];
  struct hostlist *tmp;
  
  if(verbose_flag>1)
    yp_msg("add_slave_server: Key=%.*s, Val=%.*s, status=%d\n",keylen,key,vallen,val,status);
  
  if (status!=YP_TRUE) 
    return status;
  
#if defined(HAVE_SNPRINTF)
  if(snprintf(host,YPMAXPEER,"%.*s", vallen, val)==YPMAXPEER)
#else
  if(vallen < YPMAXPEER)
    sprintf(host, "%.*s", vallen, val);
  else
#endif
    {
      yp_msg("ERROR: add_slave_server: %.*s to long\n",vallen,val);
      exit(-1);
    }

  /* Do not add ourself! */
  if (hostcmp(local_hostname, host) == 0) 
    {
      if (verbose_flag > 1) 
	yp_msg("add_slave_server: skipping %s\n", host);
      return 0;
    }
  
  if ((tmp = (struct hostlist *)malloc(sizeof(struct hostlist))) == NULL) 
    {
      yp_msg("malloc() failed: %s", strerror(errno));
      return -1;
    }
  tmp->hostname = strdup(host);
  tmp->next = hostliste;
  hostliste = tmp;

  return 0;
}

static void child_sig_int(int sig)
{
  if (CallbackProg!=0)
    svc_unregister(CallbackProg, 1);
  exit(-1);
}

static int yppush_foreach(const char *host)
{
  SVCXPRT *CallbackXprt;  
  CLIENT *PushClient=NULL;
  struct ypreq_xfr req;
  struct timeval tv={10,0};
  u_int transid;
  char server[YPMAXPEER+2];
  int sock;
  struct sigaction sa;
#if HAVE_WAIT4
  struct rusage r;
#endif

  if(verbose_flag>1)
    yp_msg("yppush_foreach: host=%s\n",host);

  sa.sa_handler=child_sig_int;
  sigemptyset(&sa.sa_mask);
#if defined(linux) || (defined(sun) && defined(__srv4__))
  sa.sa_flags=SA_NOMASK; 
  /* Do  not  prevent  the  signal   from   being
     received from within its own signal handler. */
#endif
  sigaction(SIGINT, &sa, NULL);
  
#if defined(HAVE_SNPRINTF)
  if(snprintf(server,YPMAXPEER,"%s", host)==YPMAXPEER)
#else
  if(strlen(host) < YPMAXPEER)
    sprintf(server, "%s",host );
  else
#endif
    {
      yp_msg("ERROR: yppush_foreach: %.*s to long\n",host);
      exit(-1);
    }

  PushClient=clnt_create(server, YPPROG, YPVERS, "tcp");
  if (PushClient==NULL) 
    {
      yp_msg("%s",host);
      clnt_pcreateerror("");
      return 1;
    }

  sock=RPC_ANYSOCK;
  CallbackXprt=svcudp_create(sock);
  if (CallbackXprt==NULL) 
    {
      yp_msg("YPPUSH: Cannot create callback transport.\n");
      return 1;
    }
  for (CallbackProg=0x40000000; CallbackProg<0x5fffffff; CallbackProg++) 
    {
      if (svc_register(CallbackXprt, CallbackProg, 1,
		       yppush_xfrrespprog_1, IPPROTO_UDP))
	break;
    }
  
  switch (transid=fork()) 
    {
    case -1:
      perror("Cannot fork");
      exit(-1);
    case 0:
      yppush_svc_run(server);
      exit(0);
    default:
      close(CallbackXprt->xp_sock);
      req.map_parms.domain=(char *)DomainName;
      req.map_parms.map=(char *)current_map;
      /* local_hostname is correct since we have compared it with YP_MASTER_NAME */
      req.map_parms.peer=local_hostname;
      req.map_parms.ordernum=MapOrderNum;
      req.transid=transid;
      req.prog=CallbackProg;
      req.port=CallbackXprt->xp_port;
      
      if (verbose_flag)
	{
	  yp_msg("%s has been called.\n",server);
	  if (verbose_flag>1)
	    {
	      yp_msg("\t->target: %s\n",server);
	      yp_msg("\t->domain: %s\n",req.map_parms.domain);      
	      yp_msg("\t->map: %s\n",req.map_parms.map);
	      yp_msg("\t->tarnsid: %d\n",req.transid);
	      yp_msg("\t->prog: %d\n",req.prog);
	      yp_msg("\t->master: %s\n",req.map_parms.peer);
	      yp_msg("\t->ordernum: %d\n",req.map_parms.ordernum);
	    }
	}
      
      if(clnt_call(PushClient, YPPROC_XFR, (xdrproc_t) xdr_ypreq_xfr,  
		   (void *) &req, (xdrproc_t) xdr_void, NULL, tv) 
	 != RPC_SUCCESS) 
	{
	  clnt_perror(PushClient, "YPPUSH: Cannot call YPPROC_XFR");
	  kill(transid, SIGTERM);
	}
#if HAVE_WAIT4
      wait4(transid, &sock, 0, &r);
#else
      waitpid(transid,&sock, 0);
#endif
      svc_unregister(CallbackProg, 1);
      CallbackProg=0;
      if (PushClient!=NULL) 
	{
	  clnt_destroy(PushClient);
	  PushClient=NULL;
	}
    }

  return 0;
}
	
static void 
sig_child (int sig)
{
   int status;

#ifdef HAVE_WAIT3   
   while (wait3(&status, WNOHANG, NULL) > 0)
#else 
   while (waitpid(-1, &status, WNOHANG) > 0)
#endif
     {
       if(verbose_flag > 1)
	 yp_msg("Child %d exists\n",WEXITSTATUS(status));
       children--;
     }
}

static inline void 
Usage (int exit_code)
{
  yp_msg("Usage: yppush [-d domain] [-t timeout] [-p #] [-h host] [-v] mapname ...\n");
  yp_msg("       yppush --version\n");
  exit(exit_code);
}

int main(int argc, char **argv)
{
  struct hostlist *tmp;

  struct ypall_callback f;
  enum ypstat y;
  struct sigaction a;
  
  a.sa_handler=sig_child;
  sigemptyset(&a.sa_mask);
#if defined(linux) || (defined(sun) && defined(__srv4__))
  a.sa_flags=SA_NOMASK; 
  /* Do  not  prevent  the  signal   from   being
     received from within its own signal handler. */
#endif
  sigaction(SIGCHLD, &a, NULL);
  
  while(1) 
    {
      int c;
      int option_index = 0;
      static struct option long_options[] =
      {
        {"version", no_argument, NULL, '\255'},
	{"verbose", no_argument, NULL, 'v'},
	{"host", required_argument, NULL, 'h'},
        {"help", no_argument, NULL, 'u'},
        {"usage", no_argument, NULL, 'u'},
	{"parallel", required_argument, NULL, 'p'},
	{"timeout", required_argument, NULL, 't'},
        {NULL, 0, NULL, '\0'}
      };
      
      c=getopt_long(argc, argv, "d:vh:ut:p:j:",long_options, &option_index);
      if (c==EOF) break;
      switch (c) 
	{
	case 'd':
	  DomainName=optarg;
	  break;
	case 'v':
	  verbose_flag++;
	  break;
	case 't':
	  timeout=atoi(optarg);
	  break;
	case 'j':
	case 'p':
	  maxchildren=atoi(optarg);
	  break;
	case 'h':
	  /* we can handle multiple hosts */
	  if ((tmp = (struct hostlist *)malloc(sizeof(struct hostlist))) == NULL) 
	    {
	      yp_msg("malloc() failed: %s", strerror(errno));
	      return 1;
	    }
	  tmp->hostname = strdup(optarg);
	  tmp->next = hostliste;
	  hostliste = tmp;
	  break;
	case 'u':
	  Usage(0);
	  break;
	case '\255':
          fprintf(stderr,"yppush - NYS YP Server version %s\n",version);
          exit(0);
	default:
	  Usage(1);
	}
    }
  argc-=optind;
  argv+=optind;
  
  if (argc<1) Usage(1);
  
  if (DomainName==NULL) 
    {
      if (yp_get_default_domain(&DomainName)!=0) 
	{
	  yp_msg("YPPUSH: Cannot get default domain\n");
	  return 1;
	}
    }
  
  if (gethostname(local_hostname, MAXHOSTNAMELEN)!=0) 
    {
      perror("YPPUSH: gethostname");
      yp_msg("YPPUSH: Cannot determine local hostname\n");
      return 1;
    }

  if(hostliste == NULL)
    {
      f.foreach=add_slave_server;
#ifdef OSF_KLUDGE
      y = yp_all (DomainName, "ypservers", f);
#else
      y = yp_all (DomainName, "ypservers", &f);
#endif
      if (y && y!=YP_NOMORE) 
	{
	  yp_msg("Could not read ypservers: %d %s\n",
		 y, yperr_string(y));
	}
    }
  
  while(*argv)
    {
      char *val;

      current_map=*argv++;
      val = get_dbm_entry("YP_MASTER_NAME");
      if(val && strcmp(val,local_hostname) != 0)
	{
	  yp_msg("YPPUSH: %s is not the master for %s, try it from %s.\n",
		 local_hostname, current_map, val);
	  free(val);
	  continue;
	}
      else
	if(val) free(val);

      MapOrderNum=getordernum();
#if 0
      if (MapOrderNum==0xffffffff)
	continue;
#endif
      tmp = hostliste;
      while(tmp != NULL)
	{
	  while(children >= maxchildren)
	    sleep(1);
	  children++;
	  switch(fork())
	    {
	    case -1:
	      perror("YPPUSH: Cannot fork");
	      exit(1);
	    case 0:
	      yppush_foreach(tmp->hostname);
	      exit(children);
	    default:
	      if(verbose_flag>1)
		yp_msg("Start new child (%d)\n",children);
	      break;
	    }
	  tmp = tmp->next;
	}
      while(children != 0) 
	{
	  sleep(10);
	  if(verbose_flag>1)
	    yp_msg("Running Children: %d\n",children);
	}
    }

  if(verbose_flag>1)
    yp_msg("all done (%d running childs)\n",children);

  return 0;
}
