/*
 * @(#) deslogingw.c  RCS: $Revision: 1.5 $ $Date: 95/03/02 12:54:10 $
 *
 * Gateway for deslogin to deslogind
 *
 * Actually, this program is much more general.  Any remote user who knows
 * the correct gateway passphrase may use this gateway to connect to any
 * port number on any machine.  The authentication is secure, but the 
 * data flowing through the connection made by this program is not changed
 * in any way.  It is up to the remote clients to perform any additional
 * authentication or channel encryption (as deslogin/deslogind do).
 *
 * Copyright 1994 David A Barrett.
 */
#define _POSIX_SOURCE
#include <unistd.h>	/* close getlogin */
#include <stdlib.h>	/* atexit exit */
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>	/* for fcntl.h */
#include <fcntl.h>
#include <termios.h>
#include <sys/time.h>	/* time asctime */
#include <sys/wait.h>	/* WNOHANG */
#include "posignal.h"
#include "log.h"
#include "tty.h"
#include "auth.h"
#include "txfr.h"
#include "socket.h"
#include "deslogin.h"
#include "log.h"

#define MAX_HOSTNAME	255

#if !defined(GW_LOG_FILE)
#define GW_LOG_FILE	"/usr/adm/deslogingw.log"
#endif

extern int  optind;
extern char *optarg;

#if defined(__STDC__) || defined(__cplusplus)
extern int askTty(char *, char *, unsigned, int);
#else
extern int askTty();
#endif

static char ident[] = 
" @(#) deslogingw.c version 1.01 09-Apr-94 Copyright 1994 by Dave Barrett(barrett@asgard.cs.Colorado.EDU)\n";

char mainProgName[255];

char *progName, *argcName;
int  debug = 0;
int  verbose = 0;

unsigned long inBytes = 0;		/* for txfr */
unsigned long outBytes = 0;

void setProgName() {
   pid_t pid = getpid();				/* can't fail */
   sprintf(mainProgName, "%s[%lu]", argcName, (unsigned long) pid);
   progName = mainProgName;
}

void sigHandler(sig)
   int sig;
{
   switch (sig) {
   case SIGHUP:
   case SIGTERM:
   case SIGINT:
      log("%s: Terminated by signal %d\n", progName, sig);
      exit(1);			/* make sure cleanup occurs on interrupt */
      break;
   case SIGCHLD: 			/* child died */
      if (debug) {
	 log("%s(main): SIGCHLD\n", progName);
      }
      break;
   default:
      break;
   }
}

int main(argc, argv)
   int argc;
   char *argv[];
{
   int ch, count, res, chldStat;
   unsigned bufSize = 127;
   char *chp, *logName = GW_LOG_FILE;
   int sfd, dfd;
   unsigned rport, dport = DESLOGIN_PORT, sport = DESLOGIN_GW_PORT;
   char passPhrase[PHRASE_SIZE], *tp = (char *) 0;
   char rhostName[MAX_HOSTNAME], hostName[MAX_HOSTNAME], aport[16];
   char protover[VERS_SIZE];
   unsigned loginTimeout = 1000 * (LOGIN_TIMEOUT + 1);	/* login timer */
   pid_t pid;
   keyType key;

   argcName = *argv;
   if ((chp = strrchr(argcName, '/')) != (char *) 0) argcName = chp + 1;
   progName = argcName;

   while ((ch = getopt(argc, argv, "dvp:h:l:")) != EOF) switch (ch) {
   case 'h':
      tp = optarg;
      break;
   case 'p':
      count = sscanf(optarg, "%d", &sport);
      if (count != 1) goto usage;
      break;
   case 'v':
      verbose++;
      break;
   case 'd':
      debug++;
      break;
   case 'l':
      logName = optarg;
      break;
   default:
usage:
      fprintf(stderr, 
	 "usage: %s [-dv] [-p serverport ] [-l logFile ]\n", 
	 progName);
      exit(1);
   }
   argc -= optind;
   argv += optind;

   res = askTty("Gateway Pass phrase: ", passPhrase, PHRASE_SIZE-1, 0);
   if (res < 0) {
      fprintf(stderr, "%s: couldn't get pass phrase\n", progName);
      exit(1);
   }

   res = openLog(logName);
   if (res < 0) {
      fprintf(stderr, "%s: couldn't open logfile \"%s\"\n", progName, logName);
      exit(1);
   }

   if (!debug) {
       /* Disassociate ourselves with any controlling terminal or session */
      close(0);
      close(1);
      close(2);
      pid = fork();
      if (pid < 0) {
	 log("%s: unable to fork as daemon--%s\n", 
	    progName, ERRMSG);
	 exit(1);
      }
      if (pid > 0) {		/* the parent is finished */
	 exit(0);
      }
      pid = setsid();	/* break control terminal affiliation */
      if (pid == -1) {
	 log("%s: setsid failed--%s\n", progName, ERRMSG);
	 exit(1);
      }
   }
   setProgName();
   log("%s: Starting pid %d as daemon\n", progName, getpid());

   /*
    * To make sure we cleanup utmp with atexit if we're aborted
    */
   chp = (char *) posignal(SIGINT, sigHandler);
   if (chp == (char *) SIG_ERR) {
      log("%s: sigaction SIGINT failed--%s\n", progName, ERRMSG);
      return 1;
   }
   chp = (char *) posignal(SIGTERM, sigHandler); 
   if (chp == (char *) SIG_ERR) {
      log("%s: sigaction SIGTERM failed--%s\n", progName, ERRMSG);
      return 1;
   }
   chp = (char *) posignal(SIGHUP, sigHandler); 
   if (chp == (char *) SIG_ERR) {
      log("%s: sigaction SIGHUP failed--%s\n", progName, ERRMSG);
      return 1;
   }

   sfd = openServer(sport, rhostName, sizeof rhostName, &rport, !debug);
   if (sfd < 0) {
      log("%s: open network port %d failed--%s\n", 
	 progName, sport, ERRMSG);
      return 1;
   }
   if (verbose) {
      log("%s: connect from %s:%u\n", progName, rhostName, rport);
   }

   key = challenge(sfd, passPhrase, loginTimeout);
   if (key == (keyType) 0) {
      log("%s: BADLOGIN %s:%u\n", progName, rhostName, rport);
      exit(1);
   }
   memset(passPhrase, '\0', PHRASE_SIZE);
   destroyKey(&key);

   res = write(sfd, PROTOCOL_VERS, strlen(PROTOCOL_VERS)+1);
   if (res < 0) {
      log("%s: write (protover) failed--%s\n", 
	  progName, ERRMSG);
      exit(1);
   }
   count = getString(sfd, protover, (sizeof protover) - 1, SETUP_TIMEOUT);
   if (count == 0) {
      log("%s: NOVERSION  %s:%d\n", progName, rhostName, rport);
      exit(1);
   }
   if (protover[0] != PROTOCOL_VERS[0]) {
      log("%s: BADPROTO(%s) %s:%d\n",
         progName, protover, rhostName, rport);
      exit(1);
   }
   count = getString(sfd, hostName, (sizeof hostName) - 1, SETUP_TIMEOUT);
   if (count <= 0) {
      log("%s: NOHOST   %s:%d\n", progName, rhostName, rport);
      return 1;
   }
   if (verbose) {
      log("%s: requested host:\"%s\"\n", progName, hostName);
   }
   count = getString(sfd, aport, (sizeof aport) - 1, SETUP_TIMEOUT);
   if (count <= 0) {
      log("%s: NOPORT   %s:%u\n", progName, rhostName, rport);
      return 1;
   }
   if (verbose) {
      log("%s: requested port:\"%s\"\n", progName, aport);
   }
   count = sscanf(aport, "%u", &dport);
   if (count != 1) {
      log("%s: BADPORT  \"%s\" %s:%u \"%s\"\n", 
	 progName, aport, rhostName, rport);
      return 1;
   }

   if (verbose) {
      log("%s: openClient %s:%d\n", progName, hostName, dport);
   }
   dfd = openClient(hostName, dport);
   if (dfd < 0) {
      log("%s: NOCLIENT %s:%u -> %s:%u\n", progName,
	 rhostName, rport, hostName, dport);
      return 1;
   }

   log("%s: LOGIN  %s:%u -> %s:%u\n", progName,
      rhostName, rport, hostName, dport);

   res = txfr(dfd, sfd, sfd, bufSize, 0, (keyType) 0);

   log("%s: LOGOUT %s:%u -> %s:%u\n", progName,
      rhostName, rport, hostName, dport);

   /*
    * Reap all terminated children so we don't leave zombie processes
    * and make sure our child CPU times are updated.  Waitpid returns
    * -1 when all children have been reaped, and 0 if still have 
    * unterminated children.
    */
   do {
      pid = waitpid(-1, &chldStat, WNOHANG);
      if (debug) {
	 log("%s: waitpid returned %d\n", progName, pid);
      }
   } while (pid > 0);

   return 0;
}
