/*
 * Copyright (c) 2002, The EROS Group, LLC and Johns Hopkins
 * University. All rights reserved.
 * 
 * This software was developed to support the EROS secure operating
 * system project (http://www.eros-os.org). The latest version of
 * the OpenCM software can be found at http://www.opencm.org.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 * 
 * 3. Neither the name of the The EROS Group, LLC nor the name of
 *    Johns Hopkins University, nor the names of its contributors
 *    may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <opencm.h>

static void sigpipe_handle(int x);

static void
sigpipe_handle(int x)
{
  log_trace(TRC_COMM, "In sigpipe handler!\n");
  THROW(ExConnLost, "Connection to server lost");
}

static int
verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx)
{
  return 1;
}

SSL_CTX *
ssl_init_ctx(const char *keyfile, const char *certfile)
{
  SSL_METHOD *meth;
  SSL_CTX *context;
  const char *cfile = certfile?certfile:keyfile;

  /* paranoia checks: */
  if (keyfile == NULL)
    THROW(ExBadValue, "No private key file specified.");
  else if (!path_exists(keyfile))
    THROW(ExNoObject, format("Couldn't find file: %s.\n", keyfile));

  if (!path_exists(cfile))
    THROW(ExNoObject, format("Couldn't find file: %s.\n", cfile));

  /* Set up a SIGPIPE handler */
  signal(SIGPIPE,sigpipe_handle);

  /* Create the context */
  meth=SSLv3_method();
  context=SSL_CTX_new(meth);

  /* Load the keys and certs */
  if (!(SSL_CTX_use_certificate_file(context, cfile, SSL_FILETYPE_PEM)))
    THROW(ExBadValue,
	  format("Error in certificate file: %s.\n%s.\n",
		 cfile, ERR_reason_error_string(ERR_get_error())));

  if(!(SSL_CTX_use_PrivateKey_file(context, keyfile, SSL_FILETYPE_PEM)))
    THROW(ExBadValue,
	  format("Error in key file: %s\n%s.\n", 
		 keyfile, ERR_reason_error_string(ERR_get_error())));

  /* Set verify depth to zero, which checks self-signing only */
  SSL_CTX_set_verify_depth(context, 0);

  /* Make sure we do authentication both ways */
  SSL_CTX_set_verify(context, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, 
		     verify_callback);

  return context;
}

void
ssl_destroy_ctx(SSL_CTX *ctx)
{
  SSL_CTX_free(ctx);
}

X509 *
ssl_read_cert_file(const char *keyfile)
{
  FILE *cf =  NULL;
  X509 *cert;

  if (!path_exists(keyfile)) {
    THROW(ExNoObject, format("Unable to read certificate file \"%s\"",
			     keyfile));
  }

  TRY {
    /* Create the repository unique ID -- this is just the repos's
       public key taken from the supplied certificate */
    cf = fopen(keyfile, "r");

    cert = PEM_read_X509(cf, NULL, NULL, NULL);
    if (cert == NULL)
      THROW(ExNoAccess,
	    format("Error reading cert from file: %s [%s]", 
		   keyfile, ERR_reason_error_string(ERR_get_error())));

    xfclose(cf);
  }
  DEFAULT(ex) {
    xfclose(cf);

    RETHROW(ex);			/* re-throw */
  }
  END_CATCH;

  return cert;
}

void
ssl_write_cert_file(const char *keyfile, X509 *cert)
{
  FILE *cf = NULL;

  if (path_exists(keyfile))
    THROW(ExObjectExists, 
	  format("Attempt to overwrite existing keyfile %s", keyfile));

  TRY {
    cf = xfopen(keyfile, 'w', 't');

    PEM_write_X509(cf, cert);

    xfclose(cf);
  }
  DEFAULT(ex) {
    xfclose(cf);

    RETHROW(ex);		/* re-throw */
  }
  END_CATCH;
}
