#ifdef _AIX
#define _ALL_SOURCE
#endif

#include "lclient.h"
#include <errno.h>
#include <fcntl.h>
#ifdef SOLARIS
#define _POSIX_SOURCE
#include <signal.h>
#undef _POSIX_SOURCE
#else
#include <signal.h>
#endif
#include <sys/wait.h>
#include <sys/param.h>

#include <stdio.h>
#include <sys/time.h>
#include "Connect.h"
#include "inet-udp.h"
#include "Code.h"
#include "literal.h"
#include "machdefs.h"
#include "slw.h"

char pname[100];
int gothup = 0;

void takehup(int sig)
{
  gothup = 1;
}

void printError(die)
     int die;
{
  int fatal = 0;

  while (Error_Exists)
    {
#ifdef DEBUG
      if (1)
#else
      if (Error_Severity == S_FATAL)
#endif
	{
	  fprintf(stderr, "%s: ", pname);
	  fprintf(stderr, Error_String(Error), Error_Info);
	  fputc('\n', stderr);
	}
      if (Error_Severity == S_FATAL)
	fatal++;
      Error_Pop();
    }

  if (fatal && die)
    exit(1);
}

#define E(x) if (x) printError(1)
#define ED(x) if (x) printError(0)

void nlstrip(string)
     char *string;
{
  int i;

  i = strlen(string) - 1;
  if (i < 0)
    return;
  if (string[i] == '\n')
    string[i] = '\0';
}

main(argc, argv)
     char **argv;
{
  FILE *config;
  Addr local;
  char *message;
  int i;
  Packet req, repl;	/* XXX should call ConnectInitPacket or somesuch */
  CodeBlock *request, *reply;
  char name[50];
  char from[50];
  char program[100], version[100], vendor[100], restriction[100],
       path[MAXPATHLEN], arg0[MAXPATHLEN];
  char *scratch;
  struct timeval t;
  Card16 ptype, err;
  char *keyptr;
  int keysize;
  Trap retval;

  int fd, pid1, pid2;
  char **pargv;

  /*
   * Random initialization
   */
  sprintf(pname, "wrapper(%s)", argv[0] ? argv[0] : "null");

  if (argc < 2)
    {
      fprintf(stderr, "usage: %s configfile <args>\n",
	      pname);
      exit(1);
    }

  config = fopen(argv[1], "r");
  if (config == NULL)
    {
      fprintf(stderr, "%s: couldn't open file %s\n", pname, argv[1]);
      exit(1);
    }

  if (NULL == fgets(program, sizeof(program), config) || /* shell line */
      NULL == fgets(program, sizeof(program), config) ||
      NULL == fgets(version, sizeof(version), config) ||
      NULL == fgets(vendor, sizeof(vendor), config) ||
      NULL == fgets(restriction, sizeof(restriction), config) ||
      NULL == fgets(path, sizeof(path), config) ||
      NULL == fgets(arg0, sizeof(arg0), config))
    {
      fprintf(stderr, "%s: insufficient fields in file %s\n", pname, argv[1]);
      exit(1);
    }

  fclose(config);
  nlstrip(program);
  nlstrip(version);
  nlstrip(vendor);
  nlstrip(restriction);
  nlstrip(path);

  sprintf(pname, "wrapper(%s)", program);

  E(Connect_Initialize());
  E(Connect_RegisterDomain(&inetudp));

  E(Code_Initialize());
  E(Code_RegisterDomain(&literal));

  sprintf(from, "%s:.", PORTDOMAIN);
  E(Connect_NameToAddress(from, &local));

  /*
   * Create the request packet we want to send.
   */
  gettimeofday(&t, NULL);

  E(Code_CreateBlock(&request, CODEDOMAIN, NULL, 0));
  E(Code_PutCard16(request, PROTOVERSION0));	/* protocol version */
  E(Code_PutCard16(request, REQUEST_KEY));	/* request type */

  E(Code_PutString(request, SYSTEM));		/* key identifier */
  E(Code_PutString(request, program));
  E(Code_PutString(request, version));
  E(Code_PutString(request, vendor));
  E(Code_PutString(request, restriction));	/* also restriction type */

  E(Code_PutString(request, path));		/* so we can find it */
  E(Code_PutCard32(request, t.tv_sec));		/* partial key generator */

  req.Source = local;
  req.packet = Code_BlockData(request);
  req.length = Code_BlockLength(request);

  if (askQuestionOfServer(&req, &repl))
    {
      fprintf(stderr, "%s: Could not contact any servers.\n", pname);
      exit(1);
    }

  E(Code_CreateBlock(&reply, CODEDOMAIN, repl.packet, repl.length));

#ifdef DEBUG
  Connect_AddressToName(repl.Source, name, sizeof(name));
  fprintf(stdout, "From: %d - %s\n", repl.Source, name);
  fprintf(stdout, "length: %d\n", repl.length);
#endif

  E(Code_GetCard16(reply, &ptype));

#ifdef DEBUG
  fprintf(stdout, "type: %d\n", ptype);
#endif

  switch(ptype)
    {
    case REPLY_KEY:
      E(Code_GetMemory(reply, &keyptr, &keysize));
      if (keysize != 8)
	{
	  fprintf(stderr, "%s: server returned keysize %d != 8\n",
		  pname, keysize);
	  exit(1);
	}
      else
	{
#ifdef DEBUG
	  for (i = 0; i < 8; i++)
	    fprintf(stdout, "%02x", keyptr[i]&0xff);
	  fputc('\n', stdout);
#endif
	  decrypt_key(keyptr, t.tv_sec);
#ifdef DEBUG
	  for (i = 0; i < 8; i++)
	    fprintf(stdout, "%02x", keyptr[i]&0xff);
	  fputc('\n', stdout);
#endif
	}
      break;

    case REPLY_ERROR:
      E(Code_GetCard16(reply, &err));
      E(Code_GetString(reply, &scratch));
      fprintf(stderr, "%s: server returned error: %s\n",
	      pname, errors[err]);
      if (*scratch != '\0')
	fprintf(stderr, "%s\n", scratch);
      exit(1);
      break;

    default:
      fprintf(stderr, "%s: unexpected packet type (%s) returned",
	      pname, replies[ptype]);
      exit(1);
      break;
    }

  pargv = &argv[1];

  fd = open(path, O_RDONLY);
  if (fd == -1)
    {
      perror("run: unable to open input file");
      exit(2);
    }

  pid2 = decrypt_exec("run", pargv, fd, keyptr);

  if (pid2 == 0)
    exit(4);

  /* decrypt_exec closes std{in,out,err} unless DEBUG is defined,
     so error output won't go anywhere interesting after this. */

#ifdef DEBUG
  printf("pid %d\n", pid2);
#endif

  /* Let the server know we're up & running. This is merely
     auditing info, so we don't care too much that the server
     actually gets it. */
  Code_ResetBlock(request);
  E(Code_PutCard16(request, PROTOVERSION0));
  E(Code_PutCard16(request, REQUEST_OPEN));
  /* req.{Source,Destination} remain from former request. We tell
     the same server that we requested the key from that we're
     actually using it successfully. */
  req.length = Code_BlockLength(request);
  E(Connect_SendPacket(&req));

  /* Clean up incoming packet info in case we use it more. */
  Code_ResetBlock(reply);
  free(repl.packet);

  while (1)
    {
      if (retval = Connect_Wait(&repl, 10 * 1000))
	{
	  if ((Error == CONNECT_TIMEOUT) ||
	      ((Error == CONNECT_SELECT) && ((int)Error_Info == EINTR) &&
	      gothup))
	    Error_Pop();
	  else
	    E(retval);
	}
      else /* We got a packet. Likely a server response to ignore. */
	{
	  free(repl.packet);
	}

      if (gothup || (-1 == kill(pid2, 0)))
	{
	  if (gothup || (errno == ESRCH) || (errno == EPERM))
	    {
	      Code_ResetBlock(request);
	      E(Code_PutCard16(request, PROTOVERSION0));
	      E(Code_PutCard16(request, REQUEST_CLOSE));
	      req.length = Code_BlockLength(request);
	      E(Connect_SendPacket(&req)); /* && HUP */
	      exit(0);
	    }

	  fprintf(stderr, "%s: errno: %d\n", pname, errno);
	  exit(1);
	}
    }
}
