/* $Id: sslgetpage.C,v 1.2 2000/04/24 00:42:51 fubob Exp $ */

/*
 *
 * Copyright (C) 2000 Michael Kaminsky (kaminsky@lcs.mit.edu)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

#include "async.h"
#include "parseopt.h"
#include "rxx.h"
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <dirent.h>

#define PublicKey_digest(data, type, md, len) \
	ASN1_digest ((int (*) ()) i2d_PublicKey, type, (char *) data, md, len)

const int SSL_VERSION = 3;

const int FORMAT_UNDEF = 0;
const int FORMAT_ASN1 = 1;
const int FORMAT_TEXT = 2;
const int FORMAT_PEM = 3;
const int FORMAT_NETSCAPE = 4;

const char CERT_HDR[] = "certificate";

str hostname;
str hostkey;
int hostport;
bool opt_verbose = false;
in_addr ia;

static void
usage ()
{
  fatal << "usage: " << progname << " [-v] https-server-name\n";
}

static const str SSLrx = "^([\\w\\.-]+)(:\\d{1,6})?:([A-KMNP-Za-kmnp-z2-9]{32})\\S*";

static bool
parseurl (str arg, str *name, int *port, str *key)
{
  rxx r (SSLrx);

  if (!r.match (arg))
    return false;

  *name = r[1];
  if (r[2])
    convertint (r[2].cstr () + 1, port); // skip colon
  else
    *port = 443;
  *key = r[3];

  return true;
}

void make_ssl_connection (int sock);

void connectagain ()
{
  tcpconnect (ia, hostport, wrap (make_ssl_connection));
}

void
make_ssl_connection (int sock)
{
  if (sock < 0) {
    timecb (timenow +1, wrap (connectagain));
    return;
  }

  make_sync (sock);

  SSL_METHOD *req_method;
  SSL_CTX* ctx;
  SSL* ssl;
  int err;

#if 0
  X509* server_cert;
  char *s;
#endif

  /* global setup */
  SSL_load_error_strings ();

  /* Setup all the global SSL stuff */
  SSLeay_add_ssl_algorithms ();

  switch (SSL_VERSION) {
  default:
    req_method = SSLv23_client_method ();
    break;
  case 2:
    req_method = SSLv2_client_method ();
    break;
  case 3:
    req_method = SSLv3_client_method ();
    break;
  }

  ctx = SSL_CTX_new (req_method);
  if (!ctx)
    fatal ("SSL: couldn't create a context!\n");

  /* Lets make an SSL structure */
  ssl = SSL_new (ctx);
  if (!ssl)
    fatal ("SSL_new: failed\n");
  SSL_set_connect_state (ssl);

  /* pass the raw socket into the SSL layers */
  SSL_set_fd (ssl, sock);
  err = SSL_connect (ssl);

  if (err == -1) {
    err = ERR_get_error (); 
    fatal ("SSL: %s\n", ERR_error_string (err, NULL));
  }

  if (opt_verbose)
    warnx ("SSL connection using %s\n", SSL_get_cipher (ssl));
  
  /* Get server's certificate (note: beware of dynamic allocation) - opt */
  /* major serious hack alert -- we should check certificates
   * to authenticate the server; otherwise we risk man-in-the-middle
   * attack
   */

#if 0  
  server_cert = SSL_get_peer_certificate (ssl);
  if (!server_cert)
    fatal ("SSL: couldn't get peer certificate!\n");
  if (opt_verbose)
    warnx << "Server certificate:\n";


  s = X509_NAME_oneline (X509_get_subject_name (server_cert), 0, 0);
  if (!server_cert)
    fatal ("SSL: couldn't get X509-subject!\n");
  if (opt_verbose)
    warnx ("\t subject: %s\n", s);
  free (s);

  s = X509_NAME_oneline (X509_get_issuer_name (server_cert), 0, 0);
  if (!server_cert)
    fatal ("SSL: couldn't get X509-issuer name!\n");
  if (opt_verbose)
    warnx ("\t issuer: %s\n", s);
  free (s);

  /* We could do all sorts of certificate verification stuff here before
     deallocating the certificate. */

  unsigned int n;
  unsigned char md[EVP_MAX_MD_SIZE];

  EVP_PKEY *pk = X509_get_pubkey (server_cert);
  if (!PublicKey_digest (pk, EVP_sha1 (), md, &n))
    fatal << "PublicKey digest: out of memory\n";
  str pk_sha1 = armor32 (md, n);
  if (opt_verbose)
    warnx << "\t SHA1 Public Key Fingerprint = " << pk_sha1 << "\n";

  EVP_PKEY_free (pk);
  X509_free (server_cert);

  if (pk_sha1 != hostkey)
    fatal << "Given hostkey does not match one returned by server\n";
#endif

  /* XXX: write header to server and fetch the page */
  char buf[4096];

  strcpy (buf, "HEAD / HTTP/1.0\r\n\r\n");
  err = strlen (buf);

#if 0
  while ((err = read (0, buf, sizeof (buf))) > 0) {
    if (err == -1)
      fatal << "read: " << strerror (errno) << "\n";
    warn << str(buf, err) << err << "\n";
#endif
    
    err = SSL_write (ssl, buf, err);
    if (err == -1) {
      err = ERR_get_error (); 
      fatal ("SSL_write: %s\n", ERR_error_string (err, NULL));
    }
    
    //  }
    
    while ((err = SSL_read (ssl, buf, sizeof (buf))) > 0) {
    if (err == -1) {
      err = ERR_get_error (); 
      fatal ("SSL_read: %s\n", ERR_error_string (err, NULL));
    }
    err = write (1, buf, err);
    if (err == -1)
      fatal << "write: " << strerror (errno) << "\n";
  }

  SSL_shutdown (ssl);  /* send SSL/TLS close_notify */
  /* Clean up. */

  close (sock);
  SSL_free (ssl);
  SSL_CTX_free (ctx);
  exit (0);
}

int
main (int argc, char **argv)
{
  setprogname (argv[0]);

  int ch;
  while ((ch = getopt (argc, argv, "v")) != -1)
    switch (ch) {
    case 'v':
      opt_verbose = true;
      break;
    default:
      usage ();
    }
  argc -= optind;
  argv += optind;

  if (argc < 1)
    usage ();

  //  warn << argv[0] << "\n";
  if (!parseurl (argv[0], &hostname, &hostport, &hostkey))
    fatal << "Could not parse argument--should be of the form\n"
	  << "\thostname:sslkeyhash.ssl\n";

  hostport = 8443;

#if 0
  struct hostent *h;
  if ((h = gethostbyname (hostname)) == NULL) {
    warn << "gethostbyname failed\n";
    exit(-1);
  }
  ia = (struct in_addr *) h->h_addr;
#endif

  ia.s_addr = inet_addr("18.26.4.31");

  tcpconnect (ia, hostport, wrap (make_ssl_connection));

  amain ();
}
