/* A program to proxy SSL for Lynx using SSLeay */
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <netdb.h>
#include <ctype.h>

#define XBUFSIZ 16384
static char xferbuf[XBUFSIZ];
static char goodcerts[128] = "goodcerts";
static int debugflag = 0;
static char hostname[256] = "";
static char thisuser[256];
static char error[256] = "";
static FILE *errlog = stderr;

/* 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));
}

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

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;

  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 && !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)
    return (ok);

  ERR_load_crypto_strings(), SSL_load_error_strings();

  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);
}

/* convert string (dns or dotted decimal) to address */
int getaddr(char *toaddr, struct sockaddr_in *sin)
{
  long 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[])
{
  SSL_CTX *ssl_ctx = NULL;
  SSL *ssl_conn = NULL;
  char mycert[128] = "mycert.pem";
  size_t n;
  int m;
  int lstn, acpt = -1, rnet = -1;
  int snewsflag;
  fd_set fds;
  struct sockaddr_in sin, rsin;
  FILE *tmpfd;
  char tmpbuf[256];
  char tmpbuf2[512];
  char *cp, *pp = NULL;
  int localport = 5010;

  tmpfd = fopen(argc > 1 ? argv[1] : "eassl.conf", "r");
  if (tmpfd != NULL) {
    while (!feof(tmpfd)) {
      if (fgets(xferbuf, 255, tmpfd) == NULL)
        break;
      n = sscanf(xferbuf, "%63s %127s", tmpbuf2, tmpbuf);
      if (!strcmp(tmpbuf2, "debug"))
        debugflag = 1;
      else if (!strcmp(tmpbuf2, "mycert"))
        strncpy(mycert, tmpbuf, 128);
      else if (!strcmp(tmpbuf2, "goodcerts"))
        strncpy(goodcerts, tmpbuf, 128);
      else if (!strcmp(tmpbuf2, "localport"))
        localport = atoi(tmpbuf);
    }
    fclose(tmpfd);
  }
  memset((char *) &sin, 0, sizeof(sin));
  sin.sin_family = AF_INET;
  rsin = sin;
  sin.sin_port = htons(localport);
  sin.sin_addr.s_addr = inet_addr("127.0.0.1");  /* chg to 0.0.0.0 for all */
  if ((lstn = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    return lstn;
  m = -1;
  if ((m = setsockopt(lstn, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(int))) < 0)
      return m;

  if ((m = bind(lstn, (struct sockaddr *) &sin, sizeof(sin))) < 0)
    return m;

  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);

  if ((m = listen(lstn, 3)) < 0)
    return m;
  for (;;) {
    close(acpt);
    close(rnet);
    FD_ZERO(&fds);
    FD_SET(lstn, &fds);
    if ((m = select(lstn + 1, &fds, NULL, NULL, NULL)) < 0)
      break;
    n = sizeof(sin);
    acpt = accept(lstn, (struct sockaddr *) &sin, &n);
    if (debugflag)
      fprintf(stderr, "From %s:%u (%d)\n",
              inet_ntoa(sin.sin_addr), htons(sin.sin_port), acpt);
    m = 0;
    do {                        /* get entire http header */
      if ((n = read(acpt, &xferbuf[m], XBUFSIZ - m)) < 0)
        break;
      m += n;
      xferbuf[m] = 0;
    } while (!strstr(xferbuf, "\r\n\r\n"));
    if (n < 0)
      continue;
    if (!(cp = strchr(xferbuf, ' ')))  /* point at url */
      continue;
    if (!strncasecmp(++cp, "https://", 8))
      rsin.sin_port = htons(443),
        snewsflag = 0;
    else if (!strncasecmp(cp, "snews://", 8))
      rsin.sin_port = htons(563),
        snewsflag = 1;
    else
      continue;
    cp += 8;
    if ((pp = strchr(cp, '/')) || (pp = strchr(cp, ' '))
        || (pp = strchr(cp, '\r')))
      *pp++ = 0;                /* isolate hostname */
    strncpy(hostname, cp, 128);
    cp = hostname;
    if ((cp = strchr(cp, ':')))
      *cp++ = 0,
        rsin.sin_port = htons(atoi(cp));
    if (debugflag)
      fprintf(stderr, "Secure proxy to %s:%d\n", hostname,
              htons(rsin.sin_port));

    if (getaddr(hostname, &rsin))
      continue;
    if ((rnet = socket(AF_INET, SOCK_STREAM, 0)) < 0)
      continue;
    m = connect(rnet, (struct sockaddr *) &(rsin), sizeof(sin));
    if (debugflag)
      fprintf(stderr, "(to) %s:%u (%d)\n",
              inet_ntoa(rsin.sin_addr), htons(rsin.sin_port), m);
    if (m < 0)
      continue;
    ssl_conn = SSL_new(ssl_ctx);

    SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, verify_callback);
    SSL_use_certificate_file(ssl_conn, mycert, SSL_FILETYPE_PEM);
    SSL_use_RSAPrivateKey_file(ssl_conn, mycert, SSL_FILETYPE_PEM);
    SSL_set_fd(ssl_conn, rnet);

    if ((m = SSL_connect(ssl_conn)) < 0) {
      fprintf(stderr, "SSL connection error: %d %s\n",
              m, ERR_error_string(ERR_get_error(), tmpbuf));
      continue;
    }
    if (debugflag)
      fprintf(stderr, "Using cipher %s to server\n", SSL_get_cipher(ssl_conn));
    if (!snewsflag) {           /* for lynx proxy https, forward edited hdr */
      cp = strstr(xferbuf, "https://");
      *cp++ = '/';
      strcpy(cp, pp);           /* delete https://x.y.z:p/ */
      SSL_write(ssl_conn, xferbuf, strlen(xferbuf));
    }
    for (;;) {
      FD_ZERO(&fds);
      FD_SET(rnet, &fds);
      FD_SET(acpt, &fds);
      n = select(1 + (rnet > acpt ? rnet : acpt), &fds, NULL, NULL, NULL);
      if (n <= 0)
        break;
      if (FD_ISSET(rnet, &fds)) {
        if ((n = SSL_read(ssl_conn, xferbuf, XBUFSIZ)) <= 0)
          break;
        if ((n = write(acpt, xferbuf, n)) <= 0)
          break;
      }
      if (FD_ISSET(acpt, &fds)) {
        if ((n = read(acpt, xferbuf, XBUFSIZ)) <= 0)
          break;
        if ((n = SSL_write(ssl_conn, xferbuf, n)) <= 0)
          break;
      }
    }
    SSL_free(ssl_conn);
  }
  return (0);
}
