/*
 * ssl.c
 * Copyright (C) 2000  --  DaP <profeta@freemail.c3.hu>
 *
 * 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 of the License, 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 <openssl/ssl.h>		  /* SSL_() */
#include <openssl/err.h>		  /* ERR_() */

/* globals */
SSL_CTX *ctx;						  /* SSL context */
char cipher_buf[256];			  /* static buffer for _SSL_get_cipher_string() */
char stats_buf[1024];			  /* static buffer for _SSL_get_stats() */
char cert_buf[1024];				  /* static buffer for _SSL_get_cert_string() */
char err_buf[256];				  /* generic error string buffer */


void
__SSL_fill_err_buf (char *funcname)
{
	int err;
	char buf[256];


	err = ERR_get_error ();
	ERR_error_string (err, buf);
	snprintf (err_buf, sizeof (err_buf), "%s: %s (%d)\n", funcname, buf, err);
}


void
__SSL_critical_error (char *funcname)
{
	__SSL_fill_err_buf(funcname);
	fprintf(stderr, "%s\n", err_buf);

	exit (1);
}


char *
_SSL_get_stats ()
{
	snprintf (stats_buf, sizeof (stats_buf),
"%4ld items in the session cache\n"
"%4ld client connects (SSL_connect())\n"
"%4ld client connects that finished\n"
"%4ld client renegotiatations requested\n"
"%4ld server connects (SSL_accept())\n"
"%4ld server connects that finished\n"
"%4ld server renegotiatiations requested\n"
"%4ld session cache hits\n"
"%4ld session cache misses\n"
"%4ld session cache timeouts\n"
, SSL_CTX_sess_number (ctx), SSL_CTX_sess_connect (ctx), SSL_CTX_sess_connect_good (ctx), SSL_CTX_sess_connect_renegotiate (ctx), SSL_CTX_sess_accept (ctx), SSL_CTX_sess_accept_good (ctx), SSL_CTX_sess_accept_renegotiate (ctx), SSL_CTX_sess_hits (ctx), SSL_CTX_sess_misses (ctx), SSL_CTX_sess_timeouts (ctx));

	return (stats_buf);
}


void
_SSL_context_init (void (*info_cb_func))
{
/*
#ifndef NO_DH
    static DH *dh=NULL;
    BIO *bio=NULL;
#endif
*/


	SSLeay_add_ssl_algorithms ();
	SSL_load_error_strings ();

	/* client */
	ctx = SSL_CTX_new (SSLv3_client_method ());
	/* ctx = SSL_CTX_new(SSLv3_server_method()); */

	/* server */
/*
#ifndef NO_RSA
        log(LOG_DEBUG, "Generating %d bit temporary RSA key...", KEYLENGTH);
        rsa_tmp=RSA_generate_key(KEYLENGTH, RSA_F4, NULL, NULL);
        if(!rsa_tmp) {
            sslerror("tmp_rsa_cb");
            exit(1);
        }
        log(LOG_DEBUG, "Temporary RSA key generated");
        SSL_CTX_set_tmp_rsa_callback(ctx, tmp_rsa_cb);
#endif
#ifndef NO_DH
        if(!(bio=BIO_new_file(options.certfile, "r"))) {
            log(LOG_ERR, "DH: Could not read %s: %s", options.certfile,
                strerror(errno));
            goto dh_failed;
        }
        if(!(dh=PEM_read_bio_DHparams(bio, NULL, NULL , NULL))) {
            log(LOG_ERR, "Could not load DH parameters from %s",
                options.certfile);
            goto dh_failed;
        }
        SSL_CTX_set_tmp_dh(ctx, dh);
        log(LOG_DEBUG, "Diffie-Hellman initialized with %d bit key",
            8*DH_size(dh));
        goto dh_done;
dh_failed:
        log(LOG_WARNING, "Diffie-Hellman initialization failed");
dh_done:
        if(bio)
            BIO_free(bio);
        if(dh)
            DH_free(dh);
#endif
*/

	SSL_CTX_set_session_cache_mode (ctx, SSL_SESS_CACHE_BOTH);
	SSL_CTX_set_timeout (ctx, 300);

	/* used in SSL_connect(), SSL_accept() */
	SSL_CTX_set_info_callback (ctx, info_cb_func);
}


void
_SSL_context_free ()
{
	SSL_CTX_free (ctx);
}


char *
_SSL_add_keypair (char *privkey, char *cert)
{
	if (SSL_CTX_use_PrivateKey_file (ctx, privkey, SSL_FILETYPE_PEM) <= 0)
	{
		__SSL_fill_err_buf ("SSL_CTX_use_PrivateKey_file");
		return (err_buf);
	}
	if (SSL_CTX_use_certificate_file (ctx, cert, SSL_FILETYPE_PEM) <= 0)
	{
		__SSL_fill_err_buf ("SSL_CTX_use_certificate_file");
		return (err_buf);
	}

	if (!SSL_CTX_check_private_key (ctx))
	{
		__SSL_fill_err_buf
			("Private key does not match the certificate public key\n");
		return (err_buf);
	}

	return (NULL);
}


/*
int
ASN1_TIME_print(char *buf, ASN1_TIME *tm)
{
	if(tm->type == V_ASN1_UTCTIME) return ASN1_UTCTIME_print(bp, tm);
	if(tm->type == V_ASN1_GENERALIZEDTIME)
				return ASN1_GENERALIZEDTIME_print(bp, tm);
	return(0);
}

static const char *mon[12]=
    {
    "Jan","Feb","Mar","Apr","May","Jun",
    "Jul","Aug","Sep","Oct","Nov","Dec"
    };

int ASN1_GENERALIZEDTIME_print(BIO *bp, ASN1_GENERALIZEDTIME *tm)
	{
	char *v;
	int gmt=0;
	int i;
	int y=0,M=0,d=0,h=0,m=0,s=0;

	i=tm->length;
	v=(char *)tm->data;

	if (i < 12) goto err;
	if (v[i-1] == 'Z') gmt=1;
	for (i=0; i<12; i++)
		if ((v[i] > '9') || (v[i] < '0')) goto err;
	y= (v[0]-'0')*1000+(v[1]-'0')*100 + (v[2]-'0')*10+(v[3]-'0');
	M= (v[4]-'0')*10+(v[5]-'0');
	if ((M > 12) || (M < 1)) goto err;
	d= (v[6]-'0')*10+(v[7]-'0');
	h= (v[8]-'0')*10+(v[9]-'0');
	m=  (v[10]-'0')*10+(v[11]-'0');
	if (	(v[12] >= '0') && (v[12] <= '9') &&
		(v[13] >= '0') && (v[13] <= '9'))
		s=  (v[12]-'0')*10+(v[13]-'0');

	if (BIO_printf(bp,"%s %2d %02d:%02d:%02d %d%s",
		mon[M-1],d,h,m,s,y,(gmt)?" GMT":"") <= 0)
		return(0);
	else
		return(1);
err:
	BIO_write(bp,"Bad time value",14);
	return(0);
	}

int ASN1_UTCTIME_print(BIO *bp, ASN1_UTCTIME *tm)
	{
	char *v;
	int gmt=0;
	int i;
	int y=0,M=0,d=0,h=0,m=0,s=0;

	i=tm->length;
	v=(char *)tm->data;

	if (i < 10) goto err;
	if (v[i-1] == 'Z') gmt=1;
	for (i=0; i<10; i++)
		if ((v[i] > '9') || (v[i] < '0')) goto err;
	y= (v[0]-'0')*10+(v[1]-'0');
	if (y < 50) y+=100;
	M= (v[2]-'0')*10+(v[3]-'0');
	if ((M > 12) || (M < 1)) goto err;
	d= (v[4]-'0')*10+(v[5]-'0');
	h= (v[6]-'0')*10+(v[7]-'0');
	m=  (v[8]-'0')*10+(v[9]-'0');
	if (	(v[10] >= '0') && (v[10] <= '9') &&
		(v[11] >= '0') && (v[11] <= '9'))
		s=  (v[10]-'0')*10+(v[11]-'0');

	if (BIO_printf(bp,"%s %2d %02d:%02d:%02d %d%s",
		mon[M-1],d,h,m,s,y+1900,(gmt)?" GMT":"") <= 0)
		return(0);
	else
		return(1);
err:
	BIO_write(bp,"Bad time value",14);
	return(0);
	}
*/


/*
    FIXME: Session-ID, Master-Key, Extensions, CA bits
	    (openssl x509 -text -in servcert.pem)
*/
char *
_SSL_get_cert_string (SSL * ssl)
{
	X509 *peer_cert;
	EVP_PKEY *peer_pkey;
	EVP_PKEY *ca_pkey;
	ASN1_TIME *notBefore;
	ASN1_TIME *notAfter;
	int alg;
	int sign_alg;


	if (!(peer_cert = SSL_get_peer_certificate (ssl)))
		return (NULL);				  /* FATAL? */

	alg = OBJ_obj2nid (peer_cert->cert_info->key->algor->algorithm);
	sign_alg = OBJ_obj2nid (peer_cert->sig_alg->algorithm);
	notBefore = X509_get_notBefore (peer_cert);
	notAfter = X509_get_notAfter (peer_cert);

	peer_pkey = X509_get_pubkey (peer_cert);

	snprintf (cert_buf, sizeof (cert_buf),
				 "Peer's public key alg. is %s (%d bits), sign %s (%d bits)",
				 (alg == NID_undef) ? "UNKNOWN" : OBJ_nid2ln (alg),
				 EVP_PKEY_bits (peer_pkey),
				 (sign_alg == NID_undef) ? "UNKNOWN" : OBJ_nid2ln (sign_alg), 0);
/*    EVP_PKEY_bits(ca_pkey));*/

	EVP_PKEY_free (peer_pkey);

	/* SSL_SESSION_print_fp(stdout, SSL_get_session(ssl)); */

	X509_free (peer_cert);

	return (cert_buf);
}


char *
_SSL_get_cipher_string (SSL * ssl)
{
	SSL_CIPHER *c;
	int bits;


	c = SSL_get_current_cipher (ssl);
	SSL_CIPHER_get_bits (c, &bits);
	snprintf (cipher_buf, sizeof (cipher_buf),
				 "SSL opened with %s, cipher %s (%u bits)",
				 SSL_CIPHER_get_version (c), SSL_CIPHER_get_name (c), bits);

	return (cipher_buf);
}


int
_SSL_send (SSL * ssl, char *buf, int len)
{
	int num;


	num = SSL_write (ssl, buf, len);

	switch (SSL_get_error (ssl, num))
	{
	case SSL_ERROR_SSL:			  /* setup errno! */
		/* ??? */
		__SSL_fill_err_buf ("SSL_write");
		fprintf (stderr, "%s\n", err_buf);
		break;
	case SSL_ERROR_SYSCALL:
		/* ??? */
		perror ("SSL_write/write");
		break;
	case SSL_ERROR_ZERO_RETURN:
		/* fprintf(stderr, "SSL closed on write\n"); */
		break;
	}

	return (num);
}


int
_SSL_recv (SSL * ssl, char *buf, int len)
{
	int num;


	num = SSL_read (ssl, buf, len);

	switch (SSL_get_error (ssl, num))
	{
	case SSL_ERROR_SSL:
		/* ??? */
		__SSL_fill_err_buf ("SSL_read");
		fprintf (stderr, "%s\n", err_buf);
		break;
	case SSL_ERROR_SYSCALL:
		/* ??? */
		perror ("SSL_read/read");
		break;
	case SSL_ERROR_ZERO_RETURN:
		/* fprintf(stdeerr, "SSL closed on read\n"); */
		break;
	}

	return (num);
}


SSL *
_SSL_socket (int sd)
{
	SSL *ssl;


	if (!(ssl = SSL_new (ctx)))
		/* FATAL */
		__SSL_critical_error ("SSL_new");

	SSL_set_fd (ssl, sd);
	SSL_set_connect_state (ssl);
	/* SSL_set_accept_state (ssl); */

	return (ssl);
}


char *
_SSL_set_verify (void *verify_callback, char *cacert)
{
	if (!SSL_CTX_set_default_verify_paths (ctx))
	{
		__SSL_fill_err_buf ("SSL_CTX_set_default_verify_paths");
		return (err_buf);
	}

	if (cacert)
	{
		if (!SSL_CTX_load_verify_locations (ctx, cacert, NULL))
		{
			__SSL_fill_err_buf ("SSL_CTX_load_verify_locations");
			return (err_buf);
		}
	}

	SSL_CTX_set_verify (ctx, SSL_VERIFY_PEER, verify_callback);

	return (NULL);
}


void
_SSL_close (SSL * ssl)
{
	SSL_set_shutdown (ssl, SSL_SENT_SHUTDOWN | SSL_RECEIVED_SHUTDOWN);
	SSL_free (ssl);
	ERR_remove_state (0);		  /* free state buffer */
}
