
#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_port = 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 NULL;
  } else {
    static struct netconfig* nc = NULL;

    if (!nc) {
      nc = getnetconfigent("ticots");
      fprintf (stderr, "Acquired netconfig: %p\n", nc);
    }

    return clnt_tli_create (sock, nc, NULL, program, version, 0, 0);
  }
#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 (shib_sock_create (&sock) != 0)
    return -1;

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

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

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

  if (clnt_stat != RPC_SUCCESS) {
    clnt_perror (clnt, "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);
      }

      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
