
#include <unistd.h>
#include <stdio.h>
#include <sys/select.h>
#include <errno.h>
#include <signal.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>		/* for chmod() */
#include <sys/un.h>
#include <stdlib.h>
#include <errno.h>

#include <rpc/rpc.h>

//#define NEED_RPC_TLI
#define HAVE_STROPTS_H
#define NEED_SVCFD_CREATE_DEFN

#ifdef NEED_RPC_TLI
#ifdef HAVE_STROPTS_H
#include <stropts.h>
#endif
#endif

typedef int ShibSocket;
#ifdef USE_PF_INET
typedef int ShibSockName;
#define SHIB_SHAR_SOCKET 12345

#else
typedef char* ShibSockName;
#define SHIB_SHAR_SOCKET "/tmp/test-socket-name"

#endif



#ifndef UNIX_PATH_MAX
#define UNIX_PATH_MAX 100
#endif

#define SHIBRPC_PROG 123456
#define SHIBRPC_VERS_1 1

#define shibrpc_ping 0

/* Create a ShibSocket -- return 0 on success; non-zero on error */
int
shib_sock_create (ShibSocket *sock)
{
  if (!sock) return EINVAL;

  *sock = socket (
#ifdef USE_PF_INET
		  PF_INET,
#else
		  PF_UNIX,
#endif
		  SOCK_STREAM, 0);
  if (*sock < 0) {
    perror ("socket");
    return EINVAL;
  }
    
  return 0;
}

/* Bind the socket to the name. 
 *
 * NOTE: This will close the socket on failure 
 */
int
shib_sock_bind (ShibSocket s, ShibSockName name)
{
#ifdef USE_PF_INET
  struct sockaddr_in sunaddr;

  memset (&sunaddr, 0, sizeof (sunaddr));
  sunaddr.sin_family = AF_INET;
  sunaddr.sin_addr.s_addr = INADDR_LOOPBACK;
  sunaddr.sin_port = name;
#else
  struct sockaddr_un sunaddr;

  memset (&sunaddr, 0, sizeof (sunaddr));
  sunaddr.sun_family = AF_UNIX;
  strncpy (sunaddr.sun_path, name, UNIX_PATH_MAX);
#endif

  if (bind (s, (struct sockaddr *)&sunaddr, sizeof (sunaddr)) < 0) {
    perror ("bind");
    close (s);
    return EINVAL;
  }

#ifndef USE_PF_INET
  /* Make sure that only the creator can read -- we don't want just
   * anyone connecting, do we?
   */
  if (chmod (name, 0777) < 0) {
    perror("chmod");
    close (s);
    unlink (name);
    return EINVAL;
  }
#endif

  listen (s, 3);

  return 0;
}

/* Connect the socket to the local host and "port name" */
int
shib_sock_connect (ShibSocket s, ShibSockName name)
{
#ifdef USE_PF_INET
  struct sockaddr_in sunaddr;

  memset (&sunaddr, 0, sizeof (sunaddr));
  sunaddr.sin_family = AF_INET;
  sunaddr.sin_addr.s_addr = INADDR_LOOPBACK;
  sunaddr.sin_port = name;
#else
  struct sockaddr_un sunaddr;

  memset (&sunaddr, 0, sizeof (sunaddr));
  sunaddr.sun_family = AF_UNIX;
  strncpy (sunaddr.sun_path, name, UNIX_PATH_MAX);
#endif

  if (connect (s, (struct sockaddr *)&sunaddr, sizeof (sunaddr)) < 0) {
    perror ("connect");
    return 1;
  }

  return 0;
}

/* close the socket (and remove the file) */
void
shib_sock_close (ShibSocket s, ShibSockName name)
{
  if (name) {
#ifndef USE_PF_INET
    if (unlink (name))
      perror ("unlink");
#endif
  }
  close (s);
}

int shib_sock_accept (ShibSocket listener, ShibSocket* s)
{
  if (!s) return EINVAL;

  *s = accept (listener, NULL, NULL);
  if (*s < 0)
    return errno;

  return 0;
}

#ifdef TEST_CLIENT
static struct timeval TIMEOUT = { 25, 0 };

static CLIENT *
shibrpc_client_create (ShibSocket sock, u_long program, u_long version)
{
  struct sockaddr_in sin;

  memset (&sin, 0, sizeof (sin));
  sin.sin_addr.s_addr = INADDR_LOOPBACK;
#ifdef USE_AF_INET
  sin.sin_port = SHIB_SHAR_SOCKET;
#else
  sin.sin_port = 1;
#endif

#ifdef NEED_RPC_TLI
  /*
   * there's an undocumented restriction that the fd you pass in
   * needs to support the TLI operations.
   */
  {
    void* handlep;
    struct netconfig* nc;
    CLIENT *cl = NULL;

#if 0
    handlep = setnetconfig();
    if (handlep == NULL)
      return NULL;

    while (nc = getnetconfig (handlep)) {
      if ((nc->nc_semantics == NC_TPI_COTS) &&
	  (strcmp( nc->nc_protofmly, NC_INET ) == 0) &&
	  (strcmp( nc->nc_proto, NC_TCP ) == 0))
	break;
    }
#else
    nc = getnetconfigent("ticots");
#endif
    if (nc == NULL) {
      fprintf (stderr, "no netconfig found\n");
      goto err;
    }

#if 0
    if (t_bind (sock, NULL, NULL) == -1) {
      errno = t_errno;
      perror ("t_bind");
      return NULL;
    }
#endif

    if (ioctl (sock, I_PUSH, "timod") < 0) {
      perror("I_PUSH: timod");
      close (sock);
      return NULL;
    } 

    if (ioctl (sock, I_PUSH, "timod") < 0) {
      perror("I_PUSH: timod(2)");
      close (sock);
      return NULL;
    } 

    fprintf (stderr, "creating client...\n");

    cl = clnttcp_create (&sin, program, version, &sock, 0, 0);
    //    cl = clnt_tli_create (sock, nc, NULL, program, version, 1024, 1024);
  err:
#if 0
    endnetconfig (handlep);
#endif
    fprintf (stderr, "returning %p\n", cl);
    return cl;
  }
#else
  return clnttcp_create (&sin, program, version, &sock, 0, 0);
#endif
}

static enum clnt_stat 
shibrpc_ping_1(int *argp, int *clnt_res, CLIENT *clnt)
{
	return (clnt_call(clnt, shibrpc_ping,
		(xdrproc_t) xdr_int, (caddr_t) argp,
		(xdrproc_t) xdr_int, (caddr_t) clnt_res,
		TIMEOUT));
}


int
main (int argc, char *argv[])
{
  int sock, res;
  CLIENT *clnt;
  enum clnt_stat clnt_stat;

#if defined(USE_AF_INET) && defined(TEST_CORERPC)
  sock = RPC_ANYSOCK;
#else
  if (shib_sock_create (&sock) != 0)
    return -1;

  if (shib_sock_connect (sock, SHIB_SHAR_SOCKET) != 0)
    return -2;

  sleep(1);
#endif

#if 0
  if (write (sock, "test", 5) != 5)
    perror ("write");
#endif

  clnt = shibrpc_client_create (sock, SHIBRPC_PROG, SHIBRPC_VERS_1);
  if (!clnt) {
    clnt_pcreateerror ("shibrpc_client_create");
    sleep(1);
    return -3;
  }

  res = 0;
  clnt_stat = shibrpc_ping_1 (&sock, &res, clnt);

  if (clnt_stat != RPC_SUCCESS) {
    clnt_perror (clnt, "client rpc");
    return -4;
  }

  printf("%d -> %d\n", sock, res);
  clnt_destroy (clnt);

  return 0;
}

#else

typedef struct {
  u_long prog;
  u_long vers;
  void (*dispatch)();
} ShibRPCProtocols;

#ifdef NEED_SVCFD_CREATE_DEFN
extern SVCXPRT* svcfd_create ();
#endif

static int shar_run = 1;

static int
new_connection (ShibSocket listener, const ShibRPCProtocols protos[], int numproto)
{
  int i;
  SVCXPRT *svc;
  ShibSocket sock;

  /* Accept the connection */
  if (shib_sock_accept (listener, &sock)) {
    fprintf (stderr, "ACCEPT failed\n");
    return -1;
  }

#ifdef NEED_RPC_TLI
  /*
   * there's an undocumented restriction that the fd you pass in
   * needs to support the TLI operations.
   */
  if (ioctl (sock, I_PUSH, "timod") < 0) {
    perror("I_PUSH");
    close (sock);
    return -2;
  }
#endif

  /* Wrap an RPC Service around the new connection socket */
  svc = svcfd_create (sock, 0, 0);
  if (!svc) {
    fprintf (stderr, "Cannot create RPC Listener\n");
    return -2;
  }

  /* Register the SHIBRPC RPC Program */
  for (i = 0; i < numproto; i++) {
    if (!svc_register (svc, protos[i].prog, protos[i].vers,
		       protos[i].dispatch, 0)) {
      svc_destroy(svc);
      close (sock);
      fprintf (stderr, "Cannot register RPC Program\n");
      return -3;
    }
  }

  fprintf (stderr, "New Connection created...(%d)\n", sock);
  return 0;
}

static void
shar_svc_run (ShibSocket listener, const ShibRPCProtocols protos[], int numproto)
{
  while (shar_run) {
    fd_set readfds = svc_fdset;
    struct timeval tv = { 0, 0 };

    FD_SET(listener, &readfds);
    tv.tv_sec = 5;

    switch (select (getdtablesize(), &readfds, 0, 0, &tv)) {

    case -1:
      if (errno == EINTR) continue;
      perror ("shar_svc_run: - select failed");
      return;

    case 0:
      continue;

    default:
      if (FD_ISSET (listener, &readfds)) {
	new_connection (listener, protos, numproto);
	FD_CLR (listener, &readfds);
      } else
	fprintf (stderr, "Data received... processing...\n");

      svc_getreqset (&readfds);
    }
  }
}

static void term_handler (int arg)
{
  shar_run = 0;
}

static int setup_signals (void)
{
  struct sigaction sa;

  memset(&sa, 0, sizeof (sa));
  sa.sa_handler = SIG_IGN;
  sa.sa_flags = SA_RESTART;

  if (sigaction(SIGPIPE, &sa, NULL) < 0) {
    perror ("sigaction SIGPIPE");
    return -1;
  }

  memset(&sa, 0, sizeof (sa));
  sa.sa_handler = term_handler;
  sa.sa_flags = SA_RESTART;

  if (sigaction(SIGHUP, &sa, NULL) < 0) {
    perror ("sigaction SIGHUP");
    return -1;
  }
  if (sigaction(SIGINT, &sa, NULL) < 0) {
    perror ("sigaction SIGINT");
    return -1;
  }
  if (sigaction(SIGQUIT, &sa, NULL) < 0) {
    perror ("sigaction SIGQUIT");
    return -1;
  }
  if (sigaction(SIGTERM, &sa, NULL) < 0) {
    perror ("sigaction SIGTERM");
    return -1;
  }

  return 0;
}

#ifndef SIG_PF
#define SIG_PF void(*)(int)
#endif

static bool_t
shibrpc_ping_1_svc(int *argp, int *result, struct svc_req *rqstp)
{
  *result = (*argp)+1;
  fprintf(stderr, "shibrpc_ping_1_svc called...\n");
  return TRUE;
}

static int
shibrpc_prog_1_freeresult (SVCXPRT *transp, xdrproc_t xdr_result, caddr_t result)
{
	xdr_free (xdr_result, result);
	return 1;
}

static void
shibrpc_prog_1(struct svc_req *rqstp, register SVCXPRT *transp)
{
	union {
		int shibrpc_ping_1_arg;
	} argument;
	union {
		int shibrpc_ping_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 shibrpc_ping:
		_xdr_argument = (xdrproc_t) xdr_int;
		_xdr_result = (xdrproc_t) xdr_int;
		local = (bool_t (*) (char *, void *,  struct svc_req *))shibrpc_ping_1_svc;
		break;

	default:
		svcerr_noproc (transp);
		return;
	}
	memset ((char *)&argument, 0, sizeof (argument));
	if (!svc_getargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) {
		svcerr_decode (transp);
		return;
	}
	retval = (bool_t) (*local)((char *)&argument, (void *)&result, rqstp);
	if (retval > 0 && !svc_sendreply(transp, (xdrproc_t) _xdr_result, (char *)&result)) {
		svcerr_systemerr (transp);
	}
	if (!svc_freeargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) {
		fprintf (stderr, "%s", "unable to free arguments");
		exit (1);
	}
	if (!shibrpc_prog_1_freeresult (transp, _xdr_result, (caddr_t) &result))
		fprintf (stderr, "%s", "unable to free results");

	return;
}

int
main (int argc, char *argv[])
{
  ShibSocket sock;
  char* config = getenv("SHIBCONFIG");
  ShibRPCProtocols protos[] = {
    { SHIBRPC_PROG, SHIBRPC_VERS_1, shibrpc_prog_1 }
  };

  if (setup_signals() != 0)
    return -1;

  /* Create the SHAR listener socket */
  if (shib_sock_create (&sock) != 0)
    return -3;

  /* Bind to the proper port */
  if (shib_sock_bind (sock, SHIB_SHAR_SOCKET) != 0)
    return -4;

  shar_svc_run(sock, protos, 1);

  shib_sock_close(sock, SHIB_SHAR_SOCKET);
  fprintf (stderr, "shar_svc_run returned.\n");
  return 0;
}
#endif
