/*
 * NAME:
 *	ssl - routines useful for SSL servers and clients
 *
 * DESCRIPTION:
 *	These routines provide for client authentication using
 *	Eric Young's SSLeay library.
 *
 * SEE ALSO:
 *	sslfd.h, sslc.c, ssld.c, rwnet.c
 *
 * AUTHOR:
 *	Simon J. Gerraty <sjg@quick.com.au>
 */
/*
 *      @(#)Copyright (c) 1996 Simon J. Gerraty.
 *      
 *      This is free software.  It comes with NO WARRANTY.
 *      Permission to use, modify and distribute this source code 
 *      is granted subject to the following conditions.
 *      1/ that the above copyright notice and this notice 
 *      are preserved in all copies and that due credit be given 
 *      to the author.  
 *      2/ that any changes to this code are clearly commented 
 *      as such so that the author does not get blamed for bugs 
 *      other than his own.
 *      
 *      Please send copies of changes and bug-fixes to:
 *      sjg@quick.com.au
 */
#ifndef lint
static char RCSid[] = "$Id: ssl.c,v 1.18 1997/02/08 07:13:04 sjg Exp $";
#endif

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <syslog.h>
#include <string.h>


#include "x509.h"
#include "ssl.h"

#define _EXTERN
#include "sslfd.h"

/* make sure we get cipher's we trust */
#ifndef SSL_CIPHER_LIST
# define SSL_CIPHER_LIST "RC4-MD5:IDEA-CBC-MD5:DES-CBC3-MD5:DES-CBC-MD5"
#endif
#ifndef SSL_VERIFY_FLAG
# define SSL_VERIFY_FLAG SSL_VERIFY_PEER
#endif
#ifndef NUMFDS
# define NUMFDS 64
#endif

SSL_CTX	*ssl_ctx;
SSL	*ssl_con;
char x509_name[256];

static char *ssl_key_file = 0;
static SSL *ssl_cons[NUMFDS];

int
set_sslcon(fd, con)
	int fd;
	SSL *con;
{
	static int once = 1;	

	if (once) {
		memset((char *)ssl_cons, 0, sizeof(ssl_cons));
		once = 0;
	}
	if (fd >= 0 && fd < NUMFDS) {
		ssl_cons[fd] = con;
		return 1;
	}
	return 0;
}

SSL *
get_sslcon(fd)
	int fd;
{
	set_sslcon(-1, 0);			/* ensure initialized */
	if (fd >= 0 && fd < NUMFDS) {
		return ssl_cons[fd];
	}
	return 0;
}

int
ssl_pending(fd)
	int fd;
{
	SSL *con;
	
	if (con = get_sslcon(fd)) {
		return SSL_pending(con);
	}
	return 0;
}

int
ssl_close(fd)
	int fd;
{
	SSL *con;

	if (con = ssl_cons[fd]) {
		if (con != ssl_con)		/* want to keep this one */
			SSL_free(con);
		set_sslcon(fd, 0);
	}
	return close(fd);
}

void
ssl_err(log_lev, fmt)
	int log_lev;
	char *fmt;
{
	if (log_lev > 0) {
		syslog(log_lev, fmt, ERR_error_string(ERR_get_error(), NULL));
	} else if (log_lev < 0) {
		fprintf(stderr, fmt, ERR_error_string(ERR_get_error(), NULL));
	}
}

/*
 * this is mainly here for telnet's use
 */
int
ssl_get_pw(key_file, log_lev)
	const char *key_file;
	int log_lev;
{
	if (!key_file)
		key_file = ssl_key_file;
	
	if (key_file && *key_file) {
		if (SSL_use_RSAPrivateKey_file(ssl_con,
					       (char *) key_file,
					       X509_FILETYPE_PEM)<=0) {
			ssl_err(log_lev, "SSL_use_RSAPrivateKey_file error %s\n");
			return (-1);
		}
		if (key_file == ssl_key_file) {
			free(ssl_key_file);
			ssl_key_file = 0;
		}
	}
	return 0;
}

int
ssl_init(cert_file, key_file, cipher_list, verify_flag, verify_cb, log_lev, flags)
	const char *cert_file, *key_file, *cipher_list;
	int verify_flag;
	int (*verify_cb)();
	int log_lev;
	int flags;
{
	int sts = -1;
	char *err = 0;
	
	if (!cert_file)
		cert_file = getenv("SSL_CERT");

	if (!key_file)
		key_file = getenv("SSL_KEY");
	if (!cipher_list)
		cipher_list = getenv("SSL_CIPHER");
#ifdef SSL_CIPHER_LIST
	if (!cipher_list)
		cipher_list = SSL_CIPHER_LIST;
#endif
	if (verify_flag >= 0)
		ssl_verify_flag = verify_flag;
	else if (ssl_verify_flag < 0)
		ssl_verify_flag = SSL_VERIFY_FLAG;

	if (ssl_key_file) {
		free(ssl_key_file);
		ssl_key_file = 0;
	}
	
	SSL_load_error_strings();
	if ((ssl_ctx = SSL_CTX_new()) == 0) {
		ssl_err(log_lev, "cannot create SSL context: %s\n");
		return (-1);
	}
		
	if (!X509_set_default_verify_paths(ssl_ctx->cert)) {
		ssl_err(log_lev, "cannot load certificates via X509_set_default_verify_paths %s\n");
		return (-1);
	}
	ssl_con = SSL_new(ssl_ctx);
	if (cert_file && *cert_file) {
		if (!(key_file && *key_file))
			key_file = cert_file;

		if (SSL_use_certificate_file(ssl_con, (char *) cert_file,
					     X509_FILETYPE_PEM)<=0) {
			ssl_err(log_lev, "SSL_use_certificate_file error %s\n");
			return (-1);
		}
		if (key_file) {
			if (flags & 2)		/* defer key read */
				ssl_key_file = strdup(key_file);
			else {
				if (ssl_get_pw(key_file, log_lev) < 0) {
					return (-1);
				}
			}
		}
	}
	SSL_set_verify(ssl_con, ssl_verify_flag, verify_cb);
	if (cipher_list && *cipher_list)
		SSL_set_cipher_list(ssl_con,(char *) cipher_list);
	return (cert_file != 0);
}

static int
ssl_start1(s, log_lev, active, con)
	int s;
	int log_lev;
	int active;
	SSL	*con;
{
	int sts = -1;
	
	if (con == 0)
		return -1;
	
	SSL_set_fd(con,s);
	/*
	 * <sjg> we defer reading key until actually needed
	 */
	if (ssl_key_file) {
		/*
		 * NOTE: ssl_get_pw() assumes con==ssl_con
		 */
		if (ssl_get_pw(ssl_key_file, log_lev) < 0) {
			return (-1);
		}
	}
	/*
	 * <sjg> this is confusing, but the acceptor always provides a
	 * cert, where as it is optional for the connector.
	 * In cases  were the application only cares about client
	 * authentication, we can reverse the SSL roles to cut down
	 * the startup overheads.
	 * Mutual auth with 512bit keys takes up to 4s on a i486DX33
	 */
	if (active) {
		sts = SSL_connect(con);
	} else {
		sts = SSL_accept(con);
	}
	if (sts <= 0) {
		if (log_lev > 0) {
			syslog(log_lev, "SSL_%s error %s",
			       (active) ? "connect" : "accept",
			       ERR_error_string(ERR_get_error(), NULL));
		} else if (log_lev < 0) {
			fprintf(stderr, "SSL_%s error %s\n",
				(active) ? "connect" : "accept",
				ERR_error_string(ERR_get_error(), NULL));
		}
		return (-1);
	}
	set_sslcon(s, con);
	return sts;
}

int
ssl_start(s, log_lev, active)
	int s;
	int log_lev;
	int active;
{
	int sts;
	
	if ((sts = ssl_start1(s, log_lev, active, ssl_con)) > 0)
		ssl_fd = s;
	return sts;
}

int
ssl_start2(s, log_lev, active)
	int s;
	int log_lev;
	int active;
{
	SSL *ssl_con2 = SSL_new(ssl_ctx);
	int sts = -1;
	
	if (ssl_con2) {
		SSL_copy_session_id(ssl_con2, ssl_con);
		SSL_set_fd(ssl_con2,s);
		sts = ssl_start1(s, log_lev, active, ssl_con2);
	}
	return sts;
}

char *
get_x509_name(buf, bufsz)
	char *buf;
	int bufsz;
{
	char *p, *e;
	int n = 0;

	if (buf == 0) {
		buf = x509_name;
		bufsz = sizeof(x509_name);
	}
	if (ssl_auth_cert) {
		if (p = strstr(ssl_auth_cert, "/CN=")) {
			p += 4;
			if (e = strpbrk(p, "/\r\n")) {
				n = e - p;
				if (n >= bufsz)
					n = bufsz - 1;
			} else
				n = bufsz - 1;
			(void) strncpy(buf, p, n);
			buf[n] = '\0';
		}
		return buf;
	}
	return 0;
}
