/*
 * This is sample code generated by rpcgen.
 * These are only templates and you can use them
 * as a guideline for developing your own functions.
 */

#include <rpcsvc/yp_prot.h>
#include <rpcsvc/ypclnt.h>
#include <stdio.h>
#include <stdlib.h> /* getenv, exit */
#include <rpc/pmap_clnt.h> /* for pmap_unset */
#include <string.h> /* strcmp */
#include <ctype.h>
#include <memory.h>
#include <unistd.h> /* setsid */
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <sys/file.h>
#include "ypbind.h"

extern int putenv(const char *);

static SVCXPRT *udptransp;
static domainname mydomain;
static int use_broadcast = 1;
static int ypsetmode = YPSET_NO;
static struct _dom_binding *ypbindlist;

static bool_t eachresult(bool_t *, struct sockaddr_in *);
static bool_t ypconf_check(char *);
static void broadcast(char *);
static void checkwork(void);
static void my_svc_run(void);
static void rpc_received(char *, struct sockaddr_in *, int);
static void terminate(int);
static void ypconf_free(struct ypservers *);

void *
ypbindproc_null_2_svc(struct svc_req *rqstp)
{
  static char * result;
  bzero((char *)&result, sizeof(result));
  return((void *) &result);
}

struct ypbind_resp *
ypbindproc_domain_2_svc(domainname arg1, struct svc_req *rqstp)
{
  static struct ypbind_resp  result;
  struct _dom_binding *ypdb;
#if USE_BINDINGDIR
  char path[MAXPATHLEN];
#endif
  
  bzero((char *) &result, sizeof(result));
  result.ypbind_status = YPBIND_FAIL_VAL;
  for (ypdb = ypbindlist; NULL != ypdb; ypdb = ypdb->dom_pnext)
    if (0 == strcmp(ypdb->dom_domain, arg1))
      break;
  
  if (NULL == ypdb)
    {
      ypdb = (struct _dom_binding *) malloc(sizeof *ypdb);
      bzero((char *) ypdb, sizeof *ypdb);
      strncpy(ypdb->dom_domain, arg1, sizeof ypdb->dom_domain);
      ypdb->dom_vers = YPVERS;
      ypdb->dom_alive = 0;
      ypdb->dom_lockfd = -1;
#if USE_BINDINGDIR
      sprintf(path, "%s/%s.%ld", BINDINGDIR, ypdb->dom_domain, ypdb->dom_vers);
      unlink(path);
#endif
      ypdb->dom_pnext = ypbindlist;
      ypbindlist = ypdb;
      return NULL;
    }
  if (0 == ypdb->dom_alive)
    return NULL;
  
#if 0
  delta = ypdb->dom_check_t - ypdb->dom_ask_t;
  if (!(0 == ypdb->dom_ask_t || delta > 5)) {
    ypdb->dom_ask_t = time(NULL);
        /*
         * Hmm. More than 2 requests in 5 seconds have indicated that
         * my binding is possibly incorrect. Ok, make myself unalive,
         * and find out what the actual state is.
         */
    if (-1 != ypdb->dom_lockfd)
      close(ypdb->dom_lockfd);
    ypdb->dom_lockfd = -1;
    ypdb->dom_alive = 0;
    ypdb->dom_lockfd = -1;
#if USE_BINDINGDIR
    sprintf(path, "%s/%s.%ld", BINDINGDIR, ypdb->dom_domain, ypdb->dom_vers);
    unlink(path);
#endif
    return NULL;
  }
#endif
  
  result.ypbind_status = YPBIND_SUCC_VAL;
  result.ypbind_respbody.ypbind_bindinfo.ypbind_binding_addr.s_addr =
    ypdb->dom_server_addr.sin_addr.s_addr;
  result.ypbind_respbody.ypbind_bindinfo.ypbind_binding_port =
    ypdb->dom_server_port;
      /*
       * printf("domain %s at %s/%d\n", ypdb->dom_domain,
       * inet_ntoa(ypdb->dom_server_addr.sin_addr),
       * ntohs(ypdb->dom_server_addr.sin_port));
       */
  
  return (&result);
}

void *
ypbindproc_setdom_2_svc(struct ypbind_setdom arg1, struct svc_req *rqstp)
{
  static char * result;
  struct sockaddr_in *fromsin, bindsin;
  
  bzero((char *) &result, sizeof(result));
  fromsin = svc_getcaller(rqstp->rq_xprt);
  
  switch (ypsetmode)
    {
    case YPSET_LOCAL:
      if (fromsin->sin_addr.s_addr != htonl(INADDR_LOOPBACK))
        return (void *) NULL;
      break;
    case YPSET_ALL:
      break;
    case YPSET_NO:
    default:
      return (void *) NULL;
    }
  
  if (ntohs(fromsin->sin_port) >= IPPORT_RESERVED)
    return (void *) &result;
  
  if (YPVERS != arg1.ypsetdom_vers)
    return (void *) &result;
  
  bzero((char *) &bindsin, sizeof bindsin);
  bindsin.sin_family = AF_INET;
  bindsin.sin_addr.s_addr = arg1.ypsetdom_addr.s_addr;
  bindsin.sin_port = arg1.ypsetdom_port;
  rpc_received(arg1.ypsetdom_domain, &bindsin, 1);
  return((void *) &result);
}

static void *
_ypbindproc_null_2(void  *argp, struct svc_req *rqstp)
{
  return (ypbindproc_null_2_svc(rqstp));
}

static struct ypbind_resp *
_ypbindproc_domain_2(domainname argp, struct svc_req *rqstp)
{
  return (ypbindproc_domain_2_svc(argp, rqstp));
}

static void *
_ypbindproc_setdom_2(struct ypbind_setdom  *argp, struct svc_req *rqstp)
{
  return (ypbindproc_setdom_2_svc(*argp, rqstp));
}

static void
ypbindprog_2(struct svc_req *rqstp, register SVCXPRT *transp)
{
  union {
    char ypbindproc_domain_2_arg[YPMAXDOMAIN];
    struct ypbind_setdom ypbindproc_setdom_2_arg;
  } argument;
  
  struct authunix_parms *creds;
  char *result;
  xdrproc_t xdr_argument, xdr_result;
  char *(*local)(char *, struct svc_req *);
  
  switch (rqstp->rq_proc)
    {
    case YPBINDPROC_NULL:
      xdr_argument = (xdrproc_t) xdr_void;
      xdr_result = (xdrproc_t) xdr_void;
      local = (char *(*)(char *, struct svc_req *)) _ypbindproc_null_2;
      break;
      
    case YPBINDPROC_DOMAIN:
      xdr_argument = (xdrproc_t) xdr_domainname;
      xdr_result = (xdrproc_t) xdr_ypbind_resp;
      local = (char *(*)(char *, struct svc_req *)) _ypbindproc_domain_2;
      break;
      
    case YPBINDPROC_SETDOM:
      switch (rqstp->rq_cred.oa_flavor)
        {
        case AUTH_UNIX:
          creds = (struct authunix_parms *) rqstp->rq_clntcred;
          if (0 != creds->aup_uid)
            {
              svcerr_auth(transp, AUTH_BADCRED);
              return;
            }
          break;
        default:
          svcerr_weakauth(transp);
          return;
        }
      xdr_argument = (xdrproc_t) xdr_ypbind_setdom;
      xdr_result = (xdrproc_t) xdr_void;
      local = (char *(*)(char *, struct svc_req *)) _ypbindproc_setdom_2;
      break;
      
    default:
      svcerr_noproc(transp);
      return;
    }
  bzero((char *)&argument, 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);
    }
  return;
}

void
rpc_received(char *dom, struct sockaddr_in *raddrp, int force)
{
  struct _dom_binding *ypdb;
#if USE_BINDINGDIR
  struct iovec iov[2];
  struct ypbind_resp ybr;
  char path[MAXPATHLEN];
  int fd;
#endif
  
      /*
       * printf("returned from %s about %s\n", inet_ntoa(raddrp->sin_addr),
       * dom);
       */
  
  if (NULL == dom)
    return;
  
      /* Find entry for domain dom */
  for (ypdb = ypbindlist; ypdb; ypdb = ypdb->dom_pnext)
    if (0 == strcmp(ypdb->dom_domain, dom))
      break;
  
  if (NULL == ypdb) /* no entry for domain dom */
    {
      if (0 == force)
        return;
          /* create entry for domain dom */
      ypdb = (struct _dom_binding *) malloc(sizeof *ypdb);
      bzero((char *) ypdb, sizeof *ypdb);
      strncpy(ypdb->dom_domain, dom, sizeof ypdb->dom_domain);
      ypdb->dom_lockfd = -1;
      ypdb->dom_pnext = ypbindlist;
      ypbindlist = ypdb;
    }
      /* Now update the entry
       * soft update, alive, less than 30 seconds old */
  if (1 == ypdb->dom_alive &&
      0 == force &&
      ypdb->dom_check_t < time(NULL) + 30)
    return;
  
  bcopy((char *) raddrp,
        (char *) &ypdb->dom_server_addr,
        sizeof ypdb->dom_server_addr);
  ypdb->dom_check_t = time(NULL) + CHECK_INTERVAL;	/* recheck binding in 60
                                                     * seconds */
  ypdb->dom_vers = YPVERS;
  ypdb->dom_alive = 1;
  
#if USE_BINDINGDIR
  if (-1 != ypdb->dom_lockfd)
    close(ypdb->dom_lockfd);
  sprintf(path, "%s/%s.%ld", BINDINGDIR,
          ypdb->dom_domain, ypdb->dom_vers);
#ifdef O_SHLOCK
  if ((fd = open(path, O_CREAT | O_SHLOCK | O_RDWR | O_TRUNC, 0644)) == -1)
    {
      (void) mkdir(BINDINGDIR, 0755);
      if ((fd = open(path, O_CREAT | O_SHLOCK | O_RDWR | O_TRUNC, 0644)) == -1)
        return;
    }
#else
  if ((fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644)) == -1)
    {
      (void) mkdir(BINDINGDIR, 0755);
      if ((fd = open(path, O_CREAT | O_RDWR | O_TRUNC, 0644)) == -1)
        return;
    }
  flock(fd, LOCK_SH);
#endif
  
      /*
       * ok, if BINDINGDIR exists, and we can create the binding file, then
       * write to it..
       */
  ypdb->dom_lockfd = fd;
  
  iov[0].iov_base = (caddr_t) & (udptransp->xp_port);
  iov[0].iov_len = sizeof udptransp->xp_port;
  iov[1].iov_base = (caddr_t) & ybr;
  iov[1].iov_len = sizeof ybr;
  
  bzero(&ybr, sizeof ybr);
  ybr.ypbind_status = YPBIND_SUCC_VAL;
  ybr.ypbind_respbody.ypbind_bindinfo.ypbind_binding_addr = raddrp->sin_addr;
  ybr.ypbind_respbody.ypbind_bindinfo.ypbind_binding_port = raddrp->sin_port;
  
  if (writev(ypdb->dom_lockfd, iov, 2) != iov[0].iov_len + iov[1].iov_len)
    {
      err_ret("write");
      close(ypdb->dom_lockfd);
      ypdb->dom_lockfd = -1;
      return;
    }
#endif
}

bool_t
eachresult(bool_t *out, struct sockaddr_in *addr)
{
  if (*out)
    {
#ifndef DAEMON
      struct hostent *hostentp;
      hostentp = gethostbyaddr((char *) &addr->sin_addr.s_addr,
                               sizeof(addr->sin_addr.s_addr), AF_INET);
      err_ret("Answer from server %s\n", hostentp->h_name);
#endif
      rpc_received(ypbindlist->dom_domain, addr, 0);
      return (1);
    }
  else
    {
      return (0);
    }
}

void
broadcast(char *dom)
{
  bool_t out;
  enum clnt_stat  stat;
  
  stat = clnt_broadcast(YPPROG, YPVERS, YPPROC_DOMAIN_NONACK,
                        xdr_domainname, dom,
                        xdr_bool, &out,
                        eachresult);
  if (RPC_SUCCESS != stat)
    {
      err_ret("broadcast: %s\n", clnt_sperrno(stat));
    }
}

void
checkwork(void)
{
  struct _dom_binding *ypdb;
  time_t t;
  
  time(&t);
  for (ypdb = ypbindlist; ypdb; ypdb = ypdb->dom_pnext)
    {
      if (0 == ypdb->dom_alive || ypdb->dom_check_t < t)
        {
          broadcast(ypdb->dom_domain);
          time(&t);
          ypdb->dom_check_t = t + CHECK_INTERVAL;
        }
    }
}

static void
my_svc_run(void)
{
  fd_set readfds;
  int dtblsize = _rpc_dtablesize();
  struct timeval timeout;
  extern int errno;
  
  while (1)
    {
      readfds = svc_fdset;
      timeout.tv_sec = 3;
      timeout.tv_usec = 0;
      
      switch (select(dtblsize, &readfds, (fd_set *)0, (fd_set *)0, &timeout))
        {
        case 0:
          checkwork();
          continue;
        case -1:
          if (EBADF != errno)
            {
              continue;
            }
          err_ret("svc_run: select failed\n");
          return;
        default:
          svc_getreqset(&readfds);
          break;
        }
    }
}

int
main(int argc, char **argv)
{
  register SVCXPRT *transp;
  int i;
#if USE_BINDINGDIR
  char path[MAXPATHLEN];
#endif
  
  err_init("ypbind");
  yp_get_default_domain(&mydomain);
  if ('\0' == mydomain[0])
    {
      err_quit("domainname not set. Aborting.\n");
    }
  
  for (i = 1; i < argc; i++)
    {
      if (0 == strcmp("-ypset", argv[i]))
        ypsetmode = YPSET_ALL;
      else if (0 == strcmp("-ypsetme", argv[i]))
        ypsetmode = YPSET_LOCAL;
    }
  
#ifdef DAEMON
  daemon_start(1);
#endif
  
  (void) pmap_unset(YPBINDPROG, YPBINDVERS);
  signal(SIGTERM, terminate);
  signal(SIGINT, terminate); 
      /* build initial domain binding, make it "unsuccessful" */
  ypbindlist = (struct _dom_binding *) malloc(sizeof *ypbindlist);
  bzero((char *) ypbindlist, sizeof *ypbindlist);
  strncpy(ypbindlist->dom_domain, mydomain, sizeof ypbindlist->dom_domain);
  ypbindlist->dom_vers = YPVERS;
  ypbindlist->dom_alive = 0;
  ypbindlist->dom_lockfd = -1;
#if USE_BINDINGDIR
  sprintf(path, "%s/%s.%ld", BINDINGDIR, ypbindlist->dom_domain,
          ypbindlist->dom_vers);
  (void) unlink(path);
#endif

  use_broadcast = ypconf_check(mydomain);
  
  transp = svcudp_create(RPC_ANYSOCK);
  if (transp == NULL)
    {
      err_quit("cannot create udp service.\n");
    }
  if (!svc_register(transp, YPBINDPROG, YPBINDVERS, ypbindprog_2, IPPROTO_UDP))
    {
      err_quit("unable to register (YPBINDPROG, YPBINDVERS, udp).\n");
    }
  
  transp = svctcp_create(RPC_ANYSOCK, 0, 0);
  if (transp == NULL)
    {
      err_quit("cannot create tcp service.\n");
    }
  if (!svc_register(transp, YPBINDPROG, YPBINDVERS, ypbindprog_2, IPPROTO_TCP))
    {
      err_quit("unable to register (YPBINDPROG, YPBINDVERS, tcp).\n");
    }
  
  if(use_broadcast)
    {
      my_svc_run();
    }
  else
    {
      svc_run();
    }
  err_quit("svc_run returned\n");
      /* NOTREACHED */
  return 1;
}

/*
 * Routines for parsing the config file ( /etc/yp.conf )
 *
 */

struct ypservers *
ypconf_read(char *path)
      /* parse the config file, check bindings */
{
  struct ypservers *ncptmp, *ncp = NULL;
  FILE *fp;
  char buf[1024];
  char *cp, *tmp;
  char tmp2[81];
  int count;
  
  fp = fopen(path, "r");
  if (NULL == fp)
    {
      ypconf_free(ncp);
      return NULL;
    }
  
  while ((cp = fgets(buf, sizeof(buf), fp)) != NULL)
    {
      tmp = strchr(cp, '#');  /* Kommentare ausblenden */
      if (tmp)
        *tmp = '\0';
      while (isspace(*cp))    /* Leerraum ueberlesen */
        cp++;
      if (*cp == '\0')        /* Leerzeile ignorieren */
        continue;


      count = sscanf(cp, "domainname %80s", tmp2);
      if (1 == count)
        err_ret("domainname setting not supported (yet)\n");
      count = sscanf(cp, "ypserver %80s", tmp2);
      if (1 == count)
        {
          ncptmp = (struct ypservers *) malloc(sizeof(struct ypservers));
          if (NULL == ncptmp)
            {
              err_ret("malloc failed\n");
              return ncp;
            }
          ncptmp->next_server = ncp;
          ncptmp->server = strdup(tmp2);
          ncp = ncptmp;
        }
    }
  fclose(fp);
  return ncp;
}

bool_t
ypconf_check(char *ypdomain)
{
  extern int h_errno;
  struct ypservers *ncp = NULL;
  struct hostent *host;
  struct sockaddr_in server_addr;
  int socket, stat;
  bool_t out, broadcast = TRUE;
  enum clnt_stat status;
  struct timeval timeout;
  CLIENT *clnt_handlep = NULL;
  
  ncp = ypconf_read(_PATH_YPCONF);
  if (NULL == ncp)
    {
      return broadcast;
    }

  while (NULL != ncp)
    {
    /*
     * FIXME: gethostbyname may in turn ask ypbind (entry in 
     * /etc/host.conf)!!! 
     * so much for shooting yourself in the foot.
     * Using RESOLV_SERV_ORDER may be a kludge.
     */
      stat = putenv(strdup("RESOLV_SERV_ORDER=hosts bind"));
      if (0 != stat)
        err_ret("putenv failed\n");
      host = gethostbyname(ncp->server);
      if (NULL == host)
        {
          switch (h_errno)
            {
            case HOST_NOT_FOUND:
              err_ret("Unknown host: %s\n", ncp->server);
              break;
            case TRY_AGAIN:
              err_ret("Host name lookup failure\n");
              break;
            case NO_DATA:
              err_ret("No address associated with name: %s\n", ncp->server);
              break;
            case NO_RECOVERY:
              err_ret("Unknown server error\n");
              break;
            default:
              err_ret("gethostbyname: Unknown error\n");
              break;
            }
        }
      else
        {
          server_addr.sin_family = host->h_addrtype;
          server_addr.sin_port = 0;
          socket = RPC_ANYSOCK;
          while(NULL != host->h_addr_list)
            {
              bcopy(*host->h_addr_list, (char *)&server_addr.sin_addr,
                    host->h_length);
              
              timeout.tv_sec = 5;
              timeout.tv_usec = 0;
              clnt_handlep = clntudp_create(&server_addr, YPPROG, YPVERS,
                                            timeout, &socket);
              if (NULL != clnt_handlep)
                break;
              host->h_addr_list++;
            }
          
          if (NULL == clnt_handlep)
            {
              err_ret("clnt_create for server %s failed\n", host->h_name);
              ncp = ncp->next_server;
              continue;
            }
          
          timeout.tv_sec = 5;
          timeout.tv_usec = 0;
          status = clnt_call(clnt_handlep, YPPROC_DOMAIN_NONACK,
                             xdr_domainname, ypdomain,
                             xdr_bool, &out,
                             timeout);
          if (RPC_SUCCESS != status)
            {
              err_ret(clnt_sperror(clnt_handlep, host->h_name));
            }
          else
            {
              rpc_received(ypdomain, &server_addr, 0);
              broadcast = FALSE;
            }
        }
      ncp = ncp->next_server;
    }
  return broadcast;
}

void
ypconf_free(struct ypservers *ncp)
{
  if (NULL == ncp)
    return;
  if (NULL == ncp->next_server)
    {
      free(ncp);
      return;
    }
  ypconf_free(ncp->next_server);
}

static void
terminate(int sig)
{
  (void) pmap_unset(YPBINDPROG, YPBINDVERS);
  exit(0);
}
