/*
 * Copyright (c) 2002-2007, OpenFWTK Development Group
 * All rights reserved. See LICENSE.
 */

/*
 * OpenFWTK SSL wrapper functions
 *
 * (C) Copyright 2002 ArkanoiD <ark@eltex.ru>
 *
 */

#include	<stdio.h>
#include	<string.h>
#include	<syslog.h>
#include	<sys/types.h>
#include	<sys/ioctl.h>
#include	<sys/time.h>
#include	<sys/socket.h>
#include	<unistd.h>
#include	<stdlib.h>
#include	<netdb.h>
#include	<netinet/in.h>
#include	<errno.h>
#include	<time.h>
#include	<sys/stat.h>

#ifdef	OpenBSD
#include 	<ssl/x509.h>
#include 	<ssl/err.h>
#include 	<ssl/ssl.h>
#else
#include 	<openssl/x509.h>
#include 	<openssl/err.h>
#include 	<openssl/ssl.h>
#endif

#include	"firewall.h"
#include	"auth.h"

#include	"fwfunc.h"
#include	"firewall2.h"


SSL_CTX			*proxy_SSLCTX;
STACK_OF(X509_NAME) 	*proxy_SSLcertnames;

int
proxy_check_cert(ssl,host,vrfy)
SSL	*ssl;
char	*host;
char	*vrfy;
{
	X509	*peer;
	char	peer_CN[256];
	int	err;

	if (!strcasecmp(vrfy,"none"))
		return(0);

	if ((err = SSL_get_verify_result(ssl)) != X509_V_OK) {
		syslog (LLEV,"SSL certificate does not verify, %d", err);
		return(1);
	}

	if (strcasecmp(vrfy,"checkaddr"))
		return(0);

	peer = SSL_get_peer_certificate(ssl);
	X509_NAME_get_text_by_NID(X509_get_subject_name(peer),
				  NID_commonName,peer_CN,256);
	return(!hostmatch(peer_CN,host));
}

void
proxy_SSLinit()
{
	char		*sslkeyfile = NULL;
	char		*sslcertfile = NULL;
	char		*sslCAfile = NULL;

	sslkeyfile = proxy_conf_string(proxy_confp,"private-key");
	sslcertfile = proxy_conf_string(proxy_confp,"certificate");
	sslCAfile = proxy_conf_string(proxy_confp,"CAfile");

	SSL_library_init();
	SSL_load_error_strings();
	if (!sslcertfile) {
		syslog(LLEV,"fwtkcfgerr: certifcate file not specified");
		exit(1);
	}
	if (!sslkeyfile) {
		syslog(LLEV,"fwtkcfgerr: key file not specified");
		exit(1);
	}
	if (!(proxy_SSLCTX = SSL_CTX_new(SSLv23_method()))) {
		syslog(LLEV,"fwtksyserr: cannot create SSL context, %s",
				ERR_error_string(ERR_get_error(), NULL));
		exit(1);
	}
	if (!(SSL_CTX_use_certificate_chain_file(proxy_SSLCTX, sslcertfile))) {
		syslog(LLEV,"fwtksyserr: cannot read certificate chain file %s:%s",
		sslcertfile,ERR_error_string(ERR_get_error(), NULL));
		exit(1);
	}
	if (!(SSL_CTX_use_PrivateKey_file(proxy_SSLCTX,sslkeyfile,
						SSL_FILETYPE_PEM))) {
		syslog(LLEV,"fwtksyserr: error loading private key %s:%s",
				sslkeyfile,
				ERR_error_string(ERR_get_error(), NULL));
		exit(1);
	}
	if (sslCAfile) {
		if (!(SSL_CTX_load_verify_locations(proxy_SSLCTX,
						    sslCAfile,NULL))) {
			syslog(LLEV,"fwtksyserr: error setting verify locations %s:%s",
					sslCAfile,
					ERR_error_string(ERR_get_error(), NULL));
			exit(1);
		}
		if (!(proxy_SSLcertnames =SSL_load_client_CA_file(sslCAfile))) {
			syslog(LLEV,"fwtksyserr: error loading CA file %s:%s",
					sslCAfile,
					ERR_error_string(ERR_get_error(), NULL));
			exit(1);
		}
	}
}

SSL*
proxy_SSLaccept(int fd, char *rladdr, char *riaddr, char *cvrfy)
{
	SSL	*cconn;
	cconn = SSL_new(proxy_SSLCTX);
	SSL_set_fd(cconn,fd);
	SSL_set_client_CA_list(cconn,proxy_SSLcertnames);  

	if (strcasecmp(cvrfy,"none"))
		SSL_set_verify(cconn,SSL_VERIFY_PEER,NULL);
	else
		SSL_set_verify(cconn,SSL_VERIFY_NONE,NULL);

	if (SSL_accept(cconn) == -1) {
		syslog(LLEV,"fwtksyserr: error accepting SSL connection: %s", 
				ERR_error_string(ERR_get_error(), NULL));
		exit(1);
	}

	if (proxy_check_cert(cconn,riaddr,cvrfy)) {
		syslog(LLEV,"deny host=%s/%s certificate does not match host identification",
		rladdr,riaddr);
		exit(1);
	}
	return(cconn);
}
	

SSL*
proxy_SSLconnect(int fd, char *dst, char *svrfy)
{
	SSL	*sconn;
	sconn = SSL_new(proxy_SSLCTX);
	SSL_set_fd(sconn,fd);

	if (strcasecmp(svrfy,"none"))
		SSL_set_verify(sconn,SSL_VERIFY_PEER,NULL);
	else
		SSL_set_verify(sconn,SSL_VERIFY_NONE,NULL);

	if (SSL_connect(sconn) == -1) {
		syslog(LLEV,"fwtksyserr: error initiating SSL connection: %s", 
			ERR_error_string(ERR_get_error(), NULL));
		exit(1);
	}

	if (proxy_check_cert(sconn,dst,svrfy)) {
		syslog(LLEV,"deny host=%s/%s destination=%s certificate does not match destination identification",
		       proxy_stats.rladdr,proxy_stats.riaddr,dst);
		exit(1);
	}
	return(sconn);
}

int
proxy_SSLread(SSL *conn, char *buf, int buflen)
{
	int	x;
	if ((x = SSL_read(conn,buf,buflen) > 0))
		return(x);
	switch (SSL_get_error(conn,x)) {
	case SSL_ERROR_NONE:
	case SSL_ERROR_ZERO_RETURN:
	case SSL_ERROR_WANT_WRITE:
	case SSL_ERROR_WANT_READ:
	case SSL_ERROR_WANT_X509_LOOKUP:
		return(0);
	case SSL_ERROR_SYSCALL:
	case SSL_ERROR_SSL:
		syslog(LLEV,"SSL read error: %.128s",
				ERR_error_string(ERR_get_error(), NULL));
		return(0);
	default:
		return(0);
	}
}

int
proxy_SSLwrite(SSL *conn, char *buf, int buflen)
{
	unsigned int 	nbytes = 0;
	int		j;

	while (nbytes < buflen) {
		j = SSL_write(conn,buf,buflen-nbytes);
		if (j <= 0) {
			switch (SSL_get_error(conn,j)) {
			case SSL_ERROR_NONE:
			case SSL_ERROR_ZERO_RETURN:
				return (nbytes ? nbytes : j);
			case SSL_ERROR_WANT_WRITE:
			case SSL_ERROR_WANT_READ:
			case SSL_ERROR_WANT_X509_LOOKUP:
				continue;
			case SSL_ERROR_SYSCALL:
			case SSL_ERROR_SSL:
				syslog(LLEV,"SSL write error: %.128s",
				ERR_error_string(ERR_get_error(), NULL));
				return (nbytes ? nbytes : j);
			}
		}
		buf += j;
		nbytes += j;
	}
	return nbytes;
}

