#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 <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"

static void alarm_handler(int sig)
{
}


char pname[100];

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)

main(argc, argv)
     char **argv;
{
  Addr local;
  char *message;
  int i;
  Packet req, repl;
  CodeBlock *request, *reply;
  char name[50];
  char from[50];
  char *program, *version, *vendor, *path;
  struct timeval t;
  Card16 ptype;
  char *keyptr;
  int keysize;

  int fd, pid1, pid2;
  char **pargv;
#ifdef __ultrix
  union wait status;
#else
  int status;
#endif
  struct sigaction action;

  /*
   * Random initialization
   */
  if (argc < 6)
    {
      fprintf(stderr, "usage: %s program version vendor executable [args]\n",
	      argv[0]);
      exit(2);
    }

  sprintf(pname, "%s(%s)", argv[0], argv[5]);

  program = argv[1];
  version = argv[2];
  vendor = argv[3];
  path = argv[4];

  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, 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(3);
    }

  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, "keysize = %d\n");
      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;

    default:
      fprintf(stderr, "ptype = %d\n", ptype);
      break;
    }

  pargv = &argv[5];

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

  pid1 = fork();
  if (pid1 > 0)
    {
      /* parent process should exit when child does, not when ^C pressed */
      signal(SIGINT, SIG_IGN);
      if (waitpid(pid1, &status, 0) == -1)
	perror("waitpid");
      exit(4);
    }
  if (pid1 == -1)
    {
      perror("run: fork");
      exit(6);
    }

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

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

  /* No need for terminal any more.  */
  close(0);
#ifndef DEBUG
  close(1);
#endif
#ifdef DEBUG
  printf("pid %d\n", pid2);
#endif
  signal(SIGTSTP, SIG_IGN);
  signal(SIGINT, SIG_IGN);

  /* Make SIGALRM interrupt the waitpid call.  The alternative method (making
     calls from the SIGALRM handler) could cause problems with single-threaded
     libraries.  */
  action.sa_handler = alarm_handler;
  sigemptyset(&action.sa_mask);

#ifndef SOLARIS /* XXX POSIX might be right */
  action.sa_flags = SV_INTERRUPT;
  sigaction(SIGALRM, &action, (struct sigaction *)0);
#endif

  while (1)
    {
      alarm(8);
      if (waitpid(pid2, &status, WUNTRACED) == -1)
	if (errno == EINTR)
	  {
	    /* send status report */
#ifdef DEBUG
	    puts("child running");
#endif
	    continue;
	  }
	else
	  {
	    perror("wait");
	    return 1;
	  }
      if (WIFSTOPPED(status))
	{
#ifdef DEBUG
	  puts("child stopped");
#endif
	  continue;
	}
      if (WIFEXITED(status))
	{
	  return WEXITSTATUS(status);
	}
      if (WIFSIGNALED(status))
	{
#ifdef DEBUG
	  printf("child died of signal %d\n", WTERMSIG(status));
#endif
	  return -WTERMSIG(status);
	}
    }
}
