/** OpenSSL test by Shawn Bayern, based on OpenSSL demonstration code. */

#include <stdio.h>
#include <string.h>
#include <memory.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include "cas.h"

#define END(x) { ret = (x); goto end; }
#define FAIL END(0)
#define SUCCEED END(1)

static char *trusted_ca[] = {
    "/src/web/wind/aux/sslvalidator/CrenCA.pem", /* HARD CODED PATH to Root Certificate Authority */
    "/src/web/wind/aux/sslvalidator/CrenSignedColumbiaCA.pem",
    NULL
};

int CAS_validate(char *ticket, char *service, char *outbuf, int outbuflen);
static X509 *get_cert_from_file(char *filename);
static int valid_cert(X509 *cert, char *hostname);

/** Returns status of certification:  0 for invalid, 1 for valid. */
static int valid_cert(X509 *cert, char *hostname)
{
  int i;
  char buf[4096];
  X509_STORE *store = X509_STORE_new();
  X509_STORE_CTX *ctx = X509_STORE_CTX_new();
  for (i = 0; trusted_ca[i] != NULL; i++) {
    X509 *cacert = get_cert_from_file(trusted_ca[i]);
    if (cacert)
      X509_STORE_add_cert(store, cacert);
  }
  X509_STORE_CTX_init(ctx, store, cert, sk_X509_new_null());
  if (X509_verify_cert(ctx) == 0)
    return 0;
  X509_NAME_get_text_by_NID(X509_get_subject_name(cert), NID_commonName,
    buf, sizeof(buf) - 1);

  // anal-retentive:  make sure the hostname isn't as long as the
  // buffer, since we don't want to match only because of truncation
  if (strlen(hostname) >= sizeof(buf) - 1)
    return 0;

  return (!strcmp(buf, hostname));
}

/** Returns status of ticket by filling 'buf' with a NetID if the ticket
 *  is valid and buf is large enough and returning 1.  If not, 0 is
 *  returned.
 */
int CAS_validate(char *ticket, char *service, char *outbuf, int outbuflen)
{
  int s, err, b, ret, total;
  struct sockaddr_in sa;
  struct hostent h, *hp2;
  SSL_CTX *ctx;
  SSL *ssl;
  X509 *s_cert;
  char buf[4096];
  SSL_METHOD *method;
  char *full_request, *str, *tmp;
  SSL_library_init();
  method = SSLv2_client_method();
  SSL_load_error_strings();
  ctx = SSL_CTX_new(method);
  if (!ctx)
    {
      printf("SSL_CTX_new error!\n");
      ERR_print_errors_fp(stderr);
      FAIL;
    }
  if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    FAIL;

  memset(&sa, 0, sizeof(sa));
  sa.sin_family = AF_INET;

  hp2 = gethostbyname(CAS_HOST);
  memcpy(&h, hp2, sizeof(h));
//  gethostbyname_r(CAS_HOST, &h, buf, sizeof(buf), &hp2, &b);

  memcpy(&(sa.sin_addr.s_addr), h.h_addr_list[0], sizeof(long));
  sa.sin_port = htons(CAS_PORT);
  if (connect(s, (struct sockaddr*) &sa, sizeof(sa)) == -1)
    FAIL;
  if (!(ssl = SSL_new(ctx)))
    FAIL;
  SSL_set_fd(ssl, s);
  if (!SSL_connect(ssl))
    FAIL;
  if (!(s_cert = SSL_get_peer_certificate(ssl))) {
    printf("No peer certificate.\n");
    FAIL;
  }
  if (!valid_cert(s_cert, CAS_HOST)) {
    printf("Peer cert not valid.\n");
    FAIL;
  }
  X509_free(s_cert);
  
  full_request = malloc(strlen(CAS_METHOD) + strlen(" ")
    + strlen(CAS_VALIDATE) + strlen("?ticketid=") + strlen(ticket) + 
    + strlen("&service=") + strlen(service) + strlen(" ") 
    + strlen(CAS_PROT) + strlen("\n\n") + 1);
  sprintf(full_request, "%s %s?ticketid=%s&service=%s %s\n\n",
    CAS_METHOD, CAS_VALIDATE, ticket, service, CAS_PROT);

  printf("full request is to: %s %s:%d/%s?ticketid=%s&service=%s %s\n\n",
    CAS_METHOD, CAS_HOST, CAS_PORT, CAS_VALIDATE, ticket, service, CAS_PROT);

  if (!SSL_write(ssl, full_request, strlen(full_request)))
    FAIL;

  total = 0;
  do {
    b = SSL_read(ssl, buf + total, (sizeof(buf) - 1) - total);
    total += b;
  } while (b > 0);
  buf[total] = '\0';

  if (b != 0 || total >= sizeof(buf) - 1) {
    printf("unexpected read error or response too large\n");
    FAIL;		// unexpected read error or response too large
  }
  /*
   * The following segment of code is spelled out in excruciating detail
   * since it's worth the extra care.
   */
  /* printf("RESPONSE: %s", buf); */
  str = strstr(buf, "\r\n\r\n");  // find the end of the header
  if (!str)
    FAIL;			// no header
  str += 4;			// safe because we just found "\r\n\r\n"
  if (strncmp(str, "yes", 3)) {
    printf ("No affirmative response.\n");
    FAIL;			// no affirmative response
  }
  str = strstr(str, "\n");	// find the end of the current line
  if (!str) {
    printf ("Unexectedly short body while finding end of current line.\n");
    FAIL;			// unexpectedly short body
  }
  str++;			// safe because we just found "\n"
  
  // now, we've got the NetID and a bunch of crap after it.  Find the next
  // 'n' and end the string there
  {
    char *str2 = strchr(str, '\n');
    if (!str2) {
      printf ("Unexectedly short body.");
      FAIL;			// unexpectedly short body
    }
    *str2 = '\0';
  }

  /*
   * without enough space, fail entirely, since a partial NetID could
   * be dangerous
   */
  if (outbuflen < strlen(str) + 1) {
    printf ("Not enough space in output buffer.\n");
    FAIL;
  }
  strcpy(outbuf, str);
  SUCCEED;

   /* cleanup and return */

end:
  if (ssl)
    SSL_shutdown(ssl);
  if (s > 0)
    close(s);
  if (ssl)
    SSL_free(ssl);
  if (ctx)
    SSL_CTX_free(ctx);
  return ret;
}

static X509 *get_cert_from_file(char *filename) {
    FILE *f = fopen(filename, "r");
    X509 *c = PEM_read_X509(f, NULL, NULL, NULL);
    fclose(f);
    return c;
}
