#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <unistd.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <signal.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <netdb.h>
#include <ctype.h>

#undef INADDR_NONE
#define INADDR_NONE -1

#define MAXPROXY 16
#define XBUFSIZ 16384

static char xferbuf[XBUFSIZ];
static int childleft = 0;
static int debugflag;
static int sigflag = 0;
static int curcon = -1;
static int askuser[MAXPROXY];
static char thisuser[256];
static int nlstn;
static int lstn[MAXPROXY];
static char conncert[MAXPROXY][128];
static int reverse[MAXPROXY];
static int forcessl[MAXPROXY];
static char hostname[256] = "";
static char error[256] = "";
static FILE *errlog = stderr;

static char pidpath[128] = "/var/run";
static char logfile[128] = "/var/log/edssl";
static char usercerts[128] = "/var/log/usercerts.pem";
static char usersfile[128] = "/etc/edssl.users";
static char goodcerts[128] = "/etc/goodcerts";
static char certbrwsr[128] = "/usr/local/ssl/certs/universal.pem";
static char mycert[128] = "/usr/local/ssl/certs/edssl.pem";

static int dossl;

/* match common names using shell expressions */
int shellexp(char *t, char *m)
{
  static char *lastx;
  int good;
  char *lastt, *tref;

  tref = t;
  lastt = t;
  while (*m) {
    good = 0;
    switch (*m) {
    case '~':
      return (!shellexp(tref, ++m));
    case '$':
      return (!(*t));
#ifdef ALLOWILD
    case '*':
      while (*m && *m == '*')
        m++;
      if (!*m)
        return 1;
      while (*t)
        if (shellexp(t++, m))
          return 1;
      return 0;
    case '[':
      {
        int revflag;

        if ((revflag = (*++m == '^')))
          m++;
        while (!good && *m && *m != ']') {
          switch (*m) {
          case '-':
            if (*t >= m[-1] && *t <= m[1])
              good = 1;
            m++;
            m++;
            break;
          case '\\':
            if (!*++m)
              return 0;
          default:
            if (*++m == *t)
              good = 1;
            break;
          }
        }
        if (good == revflag)
          return 0;
        while (*m != ']')
          if (!*++m)
            return 0;
      }
    case '?':                  /* with fallthrough */
      m++, t++;
      break;
#endif
    case '(':
      do {
        m++;
        if (shellexp(t, m))
          good = 1, lastt = lastx;
        while (*m && *m != ')' && *m != '|')
          m++;
      } while (*m == '|');
      if (!good || *m++ != ')')
        return 0;
      t = lastt;
      break;
    case '|':
    case ')':
      lastx = t;
      return 1;
    case '\\':
      if (!*++m)
        return 0;
    default:                   /* with fallthrough */
      if (tolower(*m++) != tolower(*t++))
        return 0;
    }
  }
  return (!(*t));
}

/* override bad cert */
int goodover(char *tstcname)
{
  char cbuf[255];
  int n;
  FILE *tmpfd;

  /* look for good cert override: file format is oneline-space-hostname */
  cbuf[0] = 0;
  n = strlen(tstcname);
  tmpfd = fopen(goodcerts, "r");
  if (tmpfd != NULL) {
    while (!feof(tmpfd)) {
      fgets(cbuf, 255, tmpfd);
      if (!strncasecmp(tstcname, cbuf, n)
          && !strncasecmp(hostname, &cbuf[n + 1], strlen(hostname)))
        break;
    }
    fclose(tmpfd);
  }
  return (!strncasecmp(tstcname, cbuf, n)
          && !strncasecmp(hostname, &cbuf[n + 1], strlen(hostname)));
}

/* match cert oneline v.s. hostname */
int namematch(char *tstcname, char *hostname)
{
  char *cp;
  char cbuf[255];
  int n;

  if (goodover(tstcname))
    return 1;
  cp = strstr(tstcname, "/CN=");  /* move to common name */
  if (cp == NULL)
    return 0;
  cbuf[0] = '\0';
  cp += 4;
  n = strlen(hostname);
  strcpy(cbuf, cp);
  if ((cp = strchr(cbuf, '/')))
    *cp = '\0';
  return (shellexp(hostname, cbuf));
}

/* look up user cert oneline */
int usermatch(char *tstcname)
{
  char cbuf[255];
  int n;
  FILE *tmpfd;

  cbuf[0] = 0;
  n = strlen(tstcname);
  tmpfd = fopen(usersfile, "r");
  if (tmpfd == NULL)
    return 0;
  while (!feof(tmpfd)) {
    fgets(cbuf, 255, tmpfd);
    if (!strncasecmp(tstcname, cbuf, n))
      break;
  }
  fclose(tmpfd);

  if (strncasecmp(tstcname, cbuf, n)) {
    sprintf(error, "User Cert not in file");
    return 0;
  }
  return 1;
}

#include "buffer.h"
#include "ssl.h"
#include "err.h"
#include "pem.h"
#include "x509.h"

SSL_CTX *ssl_ctx = NULL;
SSL *ssl_conn = NULL;
SSL *ssl_con1 = NULL;

static struct hostent *hostres;

static int verify_depth = 0;
static int verify_error = X509_V_OK;

/* should be X509 * but we can just have them as char *. */
int verify_callback(ok, ctx)
     int ok;
     X509_STORE_CTX *ctx;
{
  char buf[256];
  X509 *err_cert;
  int err, depth;
  FILE *tmpfd;

  BIO *bio_err = BIO_new(BIO_s_file());

  BIO_set_fp(bio_err, errlog, BIO_NOCLOSE);

  err_cert = X509_STORE_CTX_get_current_cert(ctx);
  err = X509_STORE_CTX_get_error(ctx);
  depth = X509_STORE_CTX_get_error_depth(ctx);

  X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256);

  if (depth == 0)
    strncpy(thisuser, buf, 255);
  if (ok && depth == 0) {
    if (askuser[curcon]) {
      if (askuser[curcon] == 2 && (tmpfd = fopen(usercerts, "a"))) {
        PEM_write_X509(tmpfd, err_cert);
        fclose(tmpfd);
      } else if (askuser[curcon] == 3 && !usermatch(thisuser))
/* verify user cert against file */
        ok = 0;
    }
    if (reverse[curcon] && !goodover(thisuser)
        && !namematch(thisuser, hostname)) {
/* proxy SSL to remote secure server - verify server host */
      sprintf(error, "Host:%s != Cert:%s", hostname, thisuser);
      BIO_printf(bio_err, "ERROR: Hostname [%s] != Name in cert\n", hostname);
      ok = 0;
    }
  }
  if (!ok && goodover(thisuser))
    ok = 1;

  if (!debugflag && ok == 1 && (err < 2 || err == 18))
    return (ok);
  if (hostres)
    BIO_printf(bio_err, "Reverse DNS hostname: %s\n", hostres->h_name);
  BIO_printf(bio_err, "SSL CERTIFICATE STATUS: ok=%d depth=%d err=%s(%d)\n",
             ok, depth, X509_verify_cert_error_string(err), err);
  BIO_printf(bio_err, "Subject Cert Oneline:\n%s %s\n", thisuser, hostname);
  if (!ok) {
    if (verify_depth > depth)
      verify_error = X509_V_ERR_CERT_CHAIN_TOO_LONG;
    else
      ok++, verify_error = X509_V_OK;
  }
  switch (ctx->error) {
  case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
    X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), buf, 256);
    BIO_printf(bio_err, "unknown issuer= %s\n", buf);
    break;
  case X509_V_ERR_CERT_NOT_YET_VALID:
  case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
    BIO_printf(bio_err, "notBefore=");
    ASN1_UTCTIME_print(bio_err, X509_get_notBefore(ctx->current_cert));
    BIO_printf(bio_err, "\n");
    break;
  case X509_V_ERR_CERT_HAS_EXPIRED:
  case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
    BIO_printf(bio_err, "notAfter=");
    ASN1_UTCTIME_print(bio_err, X509_get_notAfter(ctx->current_cert));
    BIO_printf(bio_err, "\n");
    break;
  }
  return (ok);
}

void prefix()
{
  if (ssl_ctx != NULL)
    return;
  ERR_load_crypto_strings();
  SSL_load_error_strings();
  SSLeay_add_ssl_algorithms();
  ssl_ctx = SSL_CTX_new(SSLv23_method());
  SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL);
  SSL_CTX_set_default_verify_paths(ssl_ctx);
  SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, verify_callback);
}

static int savconn;

int fixconn(int conn, char *certfile, int fd, int verflag, int conacpt)
{
  SSL *scon;
  char ebuf[256];
  int n;

  if (!dossl) {
    savconn = fd;
    n = forcessl[curcon];
    if (n != -1)
      return n;

    dossl = 1;
    fprintf(errlog, "forcing ssl on fd %d\n", savconn);
  }
  SSL_CTX_set_verify(ssl_ctx, verflag | conacpt ?
                     SSL_VERIFY_PEER : SSL_VERIFY_NONE, verify_callback);
  scon = SSL_new(ssl_ctx);
  SSL_use_certificate_file(scon, certfile, SSL_FILETYPE_PEM);
  SSL_use_RSAPrivateKey_file(scon, certfile, SSL_FILETYPE_PEM);

  SSL_set_fd(scon, fd);
  n = (conacpt ? SSL_connect(scon) : SSL_accept(scon));

  if (n < 0)
    sprintf(&error[strlen(error)], "SSL connection error: %d %s\n",
            n, ERR_error_string(ERR_get_error(), ebuf));
  else if (debugflag)
    fprintf(errlog, "Using cipher %s to %s\n", SSL_get_cipher(scon), conn ?
            "browser" : "server");
  if (conn)
    ssl_con1 = scon;
  else
    ssl_conn = scon;
  return (n < 0);
}

int remote_write(int a, void *b, int c)
{
  if (dossl)
    return SSL_write(a ? ssl_con1 : ssl_conn, b, c);
  return write(savconn, b, c);
}

int remote_read(int a, void *b, int c)
{
  if (dossl)
    return SSL_read(a ? ssl_con1 : ssl_conn, b, c);
  return read(savconn, b, c);
}

/* signal handling */
/* LINUX may apply elsewhere */
#ifdef LINUX
__sighandler_t sighandle(int signum, __sighandler_t h)
#else
#define __sighandler_t void *
void *sighandle(int signum)
#endif
{
  int status, n;

  if (signum == SIGCHLD)
    do {
      n = waitpid(-1, &status, WNOHANG);
      if (n > 0) {
        childleft--;
        signal(SIGCHLD, (__sighandler_t) sighandle);
      }
    } while (n > 0);
  else
    sigflag = signum;

  return SIG_IGN;
}

/* convert string (dns or dotted decimal) to address */
int getaddr(char *toaddr, struct sockaddr_in *sin)
{
  int n;

  n = inet_addr(toaddr);
  if (n != INADDR_NONE)
    memcpy(&sin->sin_addr, &n, sizeof(n));
  else {
    hostres = gethostbyname(toaddr);
    if (hostres == NULL)
      return 1;
    memcpy(&sin->sin_addr, hostres->h_addr, hostres->h_length);
  }
  hostres = gethostbyaddr((char *) &sin->sin_addr, sizeof(sin->sin_addr),
                          AF_INET);
  return 0;
}

int main(int argc, char *argv[])
{
  size_t nt;
  int on = 1;
  int n, m;
  int acpt, rnet, rfd, wfd, maxfd;
  int snewsflag = 0;
  fd_set fds;
  struct sockaddr_in sin, rsin[MAXPROXY];
  FILE *tmpfd;
  char errfile[256];
  char pidfile[256];
  char logline[256];
  char option[64], param[128], toaddr[64], toport[64];
  char *cp, *pp = NULL;
  char lyproxcmd[32];
  struct timeval tv;
  int timeout = 90;
  struct servent *serv;

#ifdef SO_LINGER
  struct linger linger;

#endif /* SO_LINGER */

  while (sigflag != SIGINT) {
    debugflag = 0;
    nlstn = 0;
    maxfd = 0;
    errfile[0] = 0;
    if (argc > 1)
      tmpfd = fopen(argv[1], "r");
    else
      tmpfd = fopen("/etc/edssl.conf", "r");
    if (tmpfd == NULL)
      exit(-1);
    while (!feof(tmpfd)) {
      if (fgets(xferbuf, 255, tmpfd) == NULL)
        break;
      n = sscanf(xferbuf, "%63s %127s %63s %63s",
                 option, param, toaddr, toport);
      if (n <= 0)
        continue;
      if (option[0] == '#')
        continue;
      if (n == 1) {
        if (!strcmp(option, "debug"))
          debugflag = 1;
        continue;
      }
      if (n == 2) {
        if (!strcmp(option, "brcert"))
          strncpy(certbrwsr, param, 128);
        else if (!strcmp(option, "cert"))
          strncpy(mycert, param, 128);
        else if (!strcmp(option, "usercerts"))
          strncpy(usercerts, param, 128);
        else if (!strcmp(option, "users"))
          strncpy(usersfile, param, 128);
        else if (!strcmp(option, "goodcerts"))
          strncpy(goodcerts, param, 128);
        else if (!strcmp(option, "pidpath"))
          strncpy(pidpath, param, 128);
        else if (!strcmp(option, "logfile"))
          strncpy(logfile, param, 128);
        else if (!strcmp(option, "errlog"))
          strncpy(errfile, param, 128);
        else if (!strcmp(option, "timeout"))
          timeout = atoi(param);
        continue;
      }
      askuser[nlstn] = 0;
      reverse[nlstn] = 0;
      forcessl[nlstn] = 0;
      strncpy(conncert[nlstn], mycert, 64);
      if (n >= 3) {
        if (!strcmp(option, "ask"))
          askuser[nlstn] = 1;
        else if (!strcmp(option, "grab"))
          askuser[nlstn] = 2;
        else if (!strcmp(option, "verify"))
          askuser[nlstn] = 3;
        else if (!strcmp(option, "reverse"))
          reverse[nlstn] = 1;
        else if (!strcmp(option, "lynxproxy"))
          reverse[nlstn] = 2;
        else if (!strcmp(option, "netscape"))
          reverse[nlstn] = 3;
        else if (!strcmp(option, "secure"))
          forcessl[nlstn] = -1;
        else if (strcmp(option, "ssl"))
          continue;
      }
      memset((char *) &sin, 0, sizeof(sin));
      sin.sin_family = AF_INET;
      rsin[nlstn] = sin;
      rsin[nlstn].sin_addr.s_addr = inet_addr("127.0.0.1");
      serv = getservbyname(param, "tcp");
      if (serv != NULL)
        sin.sin_port = serv->s_port;
      else
        sin.sin_port = htons(atoi(param));
      sin.sin_addr.s_addr = INADDR_ANY;
      if (getaddr(toaddr, &rsin[nlstn])) {
        fprintf(errlog, "Bad Hostname %s\n", toaddr);
        exit(-1);
      }
      if (reverse[nlstn] >= 2)
        sin.sin_addr.s_addr = rsin[nlstn].sin_addr.s_addr;
      else {
        serv = getservbyname(toport, "tcp");
        if (serv != NULL)
          rsin[nlstn].sin_port = serv->s_port;
        else
          rsin[nlstn].sin_port = htons(atoi(toport));
      }
      if ((lstn[nlstn] = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        return lstn[nlstn];
      if (lstn[nlstn] > maxfd)
        maxfd = lstn[nlstn];
      n = -1;
      if ((n = setsockopt(lstn[nlstn], SOL_SOCKET, SO_REUSEADDR, (void *) &on,
                          sizeof(on))) < 0)
        return n;
#ifdef SO_LINGER
      linger.l_onoff = 1;
      linger.l_linger = 120;
      setsockopt(lstn[nlstn], SOL_SOCKET, SO_LINGER, (void *) &linger,
                 sizeof(linger));
#endif /* SO_LINGER */

      if ((n = bind(lstn[nlstn], (struct sockaddr *) &sin, sizeof(sin))) < 0) {
        fprintf(stderr, "Can not bind to port %d (%s)\n",
                lstn[nlstn], strerror(errno));
        shutdown(lstn[nlstn], 2);
        close(lstn[nlstn]);
        return n;
      }
      nlstn++;
    }
    fclose(tmpfd);
    if (!nlstn)
      exit(-1);
    if (strlen(errfile))
      errlog = fopen(errfile, "a");
    if (errlog == NULL)
      exit(-1);

    /* listen on all ports */
    for (n = 0; n < nlstn; n++)
      if ((m = listen(lstn[n], 10)) < 0)
        return m;
    maxfd++;
    if (!debugflag && fork()) /* *** if !debug, fork into the background */
      exit(0);

    n = getpid();
    sprintf(pidfile, "%s/edssl.pid", pidpath);
    tmpfd = fopen(pidfile, "w");
    fprintf(tmpfd, "%d\n", n);
    fclose(tmpfd);

    prefix();
    signal(SIGINT, (__sighandler_t) sighandle);
    signal(SIGHUP, (__sighandler_t) sighandle);
    signal(SIGCHLD, (__sighandler_t) sighandle);
    sigflag = 0;
    while (!sigflag) {
      FD_ZERO(&fds);
      for (n = 0; n < nlstn; n++)
        FD_SET(lstn[n], &fds);
      if ((n = select(maxfd, &fds, NULL, NULL, NULL)) < 0) {
        if (errno != EINTR)
          break;                /* exit(-1); */
        continue;
      }
      for (curcon = 0; curcon < nlstn; curcon++) {
        if (!FD_ISSET(lstn[curcon], &fds))
          continue;
        /* accept connection and fork to process */
        nt = sizeof(sin);
        acpt = accept(lstn[curcon], (struct sockaddr *) &sin, &nt);
        if (acpt < 0) {
          if (errno != EINTR)
            fprintf(errlog, "accept: %.100s\n", strerror(errno));
          continue;
        }
        if ((n = fork())) {
          /* another child */
          childleft++;
          close(acpt);
          continue;
        }
        if (debugflag)
          fprintf(errlog, "Connect from %15s:%u (%d)\n",
                  inet_ntoa(sin.sin_addr), htons(sin.sin_port), acpt);
        if (reverse[curcon] >= 2) {  /* proxy handling */
          m = 0;
          for (;;) {            /* get entire http header */
            tv.tv_sec = timeout;
            tv.tv_usec = 0;
            FD_ZERO(&fds);
            FD_SET(acpt, &fds);
            if (select(acpt + 1, &fds, NULL, NULL, &tv) <= 0)
              exit(-1);         /* timeout */
            if ((n = read(acpt, &xferbuf[m], XBUFSIZ - m)) < 0)
              break;
            m += n;
            xferbuf[m] = 0;
            if (strstr(xferbuf, "\r\n\r\n"))
              break;
          }
          if (n < 0)
            exit(-1);
          if (!(cp = strchr(xferbuf, ' '))) {  /* point at site or url */
            fprintf(errlog, "xferbuf: %.100s\n", strerror(errno));
            exit(-1);
          }
          /* cp points to URL - isolate URL */
          if ((pp = strchr(++cp, ' ')))
            *pp = 0;
          strncpy(lyproxcmd, xferbuf, 16);
          if ((pp = strchr(lyproxcmd, ' ')))
            *pp = '\0';
          if (strncmp(lyproxcmd, "CONNECT", 7)) {  /* not netscape, do lynx */
            if (!strncasecmp(cp, "https://", 8))
              rsin[curcon].sin_port = htons(443);
            else if (!strncasecmp(cp, "snews://", 8)) {
              rsin[curcon].sin_port = htons(563);
              snewsflag = 1;
              if ((pp = strchr(cp, '\r')))  /* isolate hostname */
                *pp++ = 0;
            } else {
              fprintf(errlog, "wrong service: %.100s\n", strerror(errno));
              exit(-1);
            }
            cp += 8;
          }
          if ((pp = strchr(cp, ':'))) {  /* find port - getservbyname? */
            *pp++ = ':';
            rsin[curcon].sin_port = htons(atoi(pp));
          }
          if ((pp = strchr(cp, '/')))  /* isolate hostname */
            *pp++ = 0;
          else
            pp = cp + strlen(cp);  /* URL has no path */
          strncpy(hostname, cp, 128);
          /* restore and correct header for lynx proxies */
          pp[strlen(pp)] = ' '; /* end of URL */
          pp -= 3 + strlen(lyproxcmd);
	  *pp++ = 0;
          strcpy(pp, lyproxcmd);  /* command */
          pp[strlen(pp)] = ' ';
          pp[strlen(pp)] = '/';
          if ((cp = strchr(hostname, ':')))  /* nuke port */
            *cp++ = 0;
          if (debugflag)
            fprintf(errlog, "Secure proxy to %s:%d\n", hostname,
                    htons(rsin[curcon].sin_port));
          if (getaddr(hostname, &rsin[curcon])) {
            fprintf(errlog, "getaddr: %.100s\n", strerror(errno));
            exit(-1);
          }
        }
        /* open remote connection */
        if ((rnet = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
          fprintf(errlog, "rnet socket: %.100s\n", strerror(errno));
          exit(-1);
        }
        n = connect(rnet, (struct sockaddr *) &(rsin[curcon]), sizeof(sin));
        if (debugflag)
          fprintf(errlog, "          to %15s:%u (%d)\n",
                  inet_ntoa(rsin[curcon].sin_addr),
                  htons(rsin[curcon].sin_port), n);
        if (n < 0) {
          fprintf(errlog, "rnet connect: %.100s\n", strerror(errno));
          close(rnet);
          exit(-1);
        }
        dossl = 1;
        if (!askuser[curcon] && !reverse[curcon]) {
          FD_ZERO(&fds);
          FD_SET(acpt, &fds);
          tv.tv_sec = 2;
          tv.tv_usec = 0;
          dossl = 0;
          if (select(acpt + 1, &fds, NULL, NULL, &tv) == 1) {
            n = recv(acpt, logline, 3, MSG_PEEK);
            if (n >= 3 && logline[0] == '\200' && logline[2] == 1)
              dossl = 1;
          }
        }
        /* get connection stats */
        sprintf(logline, "%s:%u ", inet_ntoa(sin.sin_addr),
                htons(sin.sin_port));
        /* local connection */
        nt = sizeof(sin);
        /* memset(&sin, 0, nt); */
        getsockname(acpt, (struct sockaddr *) &sin, &nt);
        sprintf(&logline[strlen(logline)], "%s:%u ", inet_ntoa(sin.sin_addr),
                htons(sin.sin_port));
        /* remote connection */
        nt = sizeof(sin);
        /* memset(&sin, 0, nt); */
        getsockname(rnet, (struct sockaddr *) &sin, &nt);
        sprintf(&logline[strlen(logline)], "%s:%u ", inet_ntoa(sin.sin_addr),
                htons(sin.sin_port));
        sprintf(&logline[strlen(logline)], "%s:%u ",
              inet_ntoa(rsin[curcon].sin_addr), htons(rsin[curcon].sin_port));
        /* if bidirectional, open browser session */
        if (reverse[curcon] == 3) {
          write(acpt, "HTTP/1.0 200 Ok\n\n", 17);  /* tell netscape continue */
          fixconn(1, certbrwsr, acpt, 0, 0);
        }
        /* correct for direction */
        rfd = acpt,
          wfd = rnet;
        if (reverse[curcon])
          rfd = rnet,
            wfd = acpt;
        /* set up secure connection */
        thisuser[0] = 0;

        if (!error[0])
          if ((n = fixconn(0, conncert[curcon], rfd, askuser[curcon],
                           reverse[curcon])) != 0) {
            fprintf(errlog, "fixconn caused an exit: %d\n", n);
            exit(-1);
          }
        sprintf(&logline[strlen(logline)], "%s", thisuser);
        /* append stats to log file */
        tmpfd = fopen(logfile, "a");
        n = time(NULL);
        strcpy(pidfile, ctime((time_t *) & n));
        pidfile[strlen(pidfile) - 6] = 0;
        fprintf(tmpfd, "%s %s %s\n", &pidfile[4], logline, error);
        fclose(tmpfd);
        if (error[0]) {
          fprintf(errlog, "error: %.100s\n", strerror(errno));
          exit(-1);
        }
        /* create pid file for spawned task */
        n = getpid();
        sprintf(pidfile, "%s/edssl.%d", pidpath, n);
        tmpfd = fopen(pidfile, "a");
        fprintf(tmpfd, "%s\n", logline);
        fclose(tmpfd);
        /* for lynx proxy https, forward edited header */
        if (reverse[curcon] == 2 && !snewsflag)
          n = remote_write(0, pp, strlen(pp));
        /* copy data between connections */
        for (;;) {
          FD_ZERO(&fds);
          FD_SET(rfd, &fds);
          FD_SET(wfd, &fds);
          n = rfd > wfd ? rfd : wfd;
          tv.tv_sec = timeout;
          tv.tv_usec = 0;
          if ((n = select(++n, &fds, NULL, NULL, &tv)) <= 0)
            break;              /* timeout */
          if (FD_ISSET(rfd, &fds)) {
            if ((n = remote_read(0, xferbuf, XBUFSIZ)) <= 0)
              break;
            if (reverse[curcon] == 3)
              n = remote_write(1, xferbuf, n);
            else
              n = write(wfd, xferbuf, n);
            if (n <= 0)
              break;
          }
          if (FD_ISSET(wfd, &fds)) {
            if (reverse[curcon] == 3)
              n = remote_read(1, xferbuf, XBUFSIZ);
            else
              n = read(wfd, xferbuf, XBUFSIZ);
            if (n <= 0)
              break;
            if ((n = remote_write(0, xferbuf, n)) <= 0)
              break;
          }
        }
        if (!debugflag)
          unlink(pidfile);
        exit(0);
      }
    }
    for (n = 0; n < nlstn; n++)
      close(lstn[n]);
    while (childleft) {
      fprintf(errlog, "Waiting for %d child procs to complete\n", childleft);
      sleep(3);
    }
  }
  unlink(pidfile);
  fprintf(errlog, "return(0): %.100s\n", strerror(errno));
  return (0);
}
