/****************************************************************************************
 *  sslinit.c 
 *  This file contains all the necessary initialization routines to set up the ssl server
 *
 ***************************************************************************************/
#include "stdafx.h"
#include <stdio.h>
#include "openssl/lhash.h"
#include <sys/stat.h>

#include <errno.h>
#include <stdlib.h>
#include <string.h>
#define NO_RSA

void initialize_PRNG(void);
void context_init(void);
void context_free(void);
#ifndef NO_RSA
	static RSA *make_temp_key(int keylen)
	static RSA *tmp_rsa_cb(SSL *s, int export, int keylen);
#endif
static int verify_callback(int state, X509_STORE_CTX *ctx);
static void info_callback(SSL *s, int where, int ret);
void set_options();
//static void sslerror(char *txt);

#define KEY_CACHE_LENGTH 2049
#define KEY_CACHE_TIME 3600
#ifndef CERT_DIR
#define CERT_DIR	"cert"
#endif
#ifndef CERT_FILE
#define CERT_FILE	"cert\\certfile.pem"
#endif
#ifndef PEM_DIR
#define PEM_DIR	CERT_DIR
#endif
#ifndef PEM_FILE
#define PEM_FILE	"cert\\certkey.pem"
#endif

#define safecopy(dst, src) \
    (dst[MAX_PATH-1]='\0', strncpy((dst), (src), MAX_PATH-1))
#define safeconcat(dst, src) \
    (dst[MAX_PATH-1]='\0', strncat((dst), (src), MAX_PATH-strlen(dst)-1))

#define OPT_CERT        0x02
#define SSL_CERT_DEFAULTS	1

typedef struct {
    char pem[MAX_PATH];  		/* pem (priv key/cert) filename */
    char cert_dir[MAX_PATH];	/* directory for hashed certs */
    char cert_file[MAX_PATH];	/* file containing bunches of certs */
    char pidfile[MAX_PATH];
    unsigned long dpid;
    int clients;
    int option;
    int foreground;         /* force messages to stderr */
    unsigned short localport, remoteport;
    unsigned int *localnames, *remotenames;
    char *execname, **execargs; /* program name and arguments for local mode */
    char servname[MAX_PATH];  /* service name for loggin & permission checking */
    int verify_level;
    int verify_use_only_my;
    int debug_level;		/* debug level for syslog */
    int facility;		/* debug facility for syslog */
    long session_timeout;
    char *cipher_list;
    char *username;
    char *protocol;
    char *setuid_user;
    char *setgid_group;
    char *egd_sock;	/* entropy gathering daemon socket */
    char *rand_file;	/* file with random data */
    int rand_write;	/* overwrite rand_file with new rand data when PRNG seeded */
    int random_bytes;	/* how many random bytes to read */
    char *pid_dir;
    int cert_defaults;
} server_options;

SSL_CTX *ctx;
server_options options;

void initialize_PRNG()
{
	char buf[1024];

	RAND_screen();
	if (!RAND_status())
	{
		RAND_seed(buf,1024);
		if (!RAND_status())
		{
			TRACE("Cannot Seed PRNG");
			exit(1);
		}
	}
}

void context_init()
{
	static DH *dh = NULL;
	BIO *bio = NULL;

	initialize_PRNG();
	
	SSLeay_add_ssl_algorithms();

	ctx = SSL_CTX_new(SSLv23_server_method());		
#ifndef NO_RSA
	SSL_CTX_set_tmp_rsa_callback(ctx,tmp_rsa_cb);
#endif

	if (bio = BIO_new_file("cert\\certkey.pem", "r"))
		if (dh=PEM_read_bio_DHparams(bio,NULL,NULL,NULL))
			SSL_CTX_set_tmp_dh(ctx,dh);

	if (bio)
		BIO_free(bio);
	if (dh)
		DH_free(dh);

	SSL_CTX_set_session_cache_mode(ctx,SSL_SESS_CACHE_BOTH);
	SSL_CTX_set_timeout(ctx,options.session_timeout);

	if (options.option & OPT_CERT)
	{
		if (!SSL_CTX_use_certificate_file(ctx,"cert\\certfile.pem",SSL_FILETYPE_PEM))
		{
			TRACE("Failed to load Certificate File\n\r");
			context_free();
			ERR_remove_state(0);
			exit(1);
		}
#ifdef NO_RSA
		if (!SSL_CTX_use_PrivateKey_file(ctx,"cert\\certkey.pem",SSL_FILETYPE_PEM))
		{
			TRACE("Failed to load Private Key File\n\r");
			context_free();
			ERR_remove_state(0);
			exit(1);
		}
#else /* NO_RSA */
		if (!SSL_CTX_use_RSAPrivateKey_file(ctx,"cert\\certkey.pem",SSL_FILETYPE_PEM))
		{
			TRACE("Failed to load RSA Private Key File\n\r");
			context_free();
			ERR_remove_state(0);
			exit(1);
		}
#endif /* NO_RSA */

		if (!SSL_CTX_check_private_key(ctx))
		{
			TRACE("Private Key doesn't match certificate\n\r");
			context_free();
			ERR_remove_state(0);
			exit(1);
		}
	}

	if (options.cert_defaults & SSL_VERIFY_NONE)
	{
		if ((!SSL_CTX_set_default_verify_paths(ctx)))
		{
			TRACE("Cannot set default verify paths.\n\r");
			context_free();
			ERR_remove_state(0);
			exit(1);
		}
		
		if (options.cert_file[0])
		{
			if (!SSL_CTX_load_verify_locations(ctx, options.cert_file,NULL))
			{
				TRACE("Cannot verify location of certificate file.");
				context_free();
				ERR_remove_state(0);
				exit(1);
			}
			SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(options.cert_file));
			// loaded verify certificates
		}
		
		if (options.cert_dir[0])
		{
			if (!SSL_CTX_load_verify_locations(ctx, options.cert_dir,NULL))
			{
				TRACE("Cannot verify certificate directory location.");
				context_free();
				ERR_remove_state(0);
				exit(1);
			}
		}
		
		SSL_CTX_set_verify(ctx, options.verify_level, verify_callback);
		
		if (options.verify_use_only_my)
		{
			TRACE("Using Peer Verify locations.");
		}
	}

//	SSL_CTX_set_info_callback(ctx, info_callback);
	if (options.cipher_list)
	{
		if (!SSL_CTX_set_cipher_list(ctx, options.cipher_list))
		{
			TRACE("Unable to set cipher list.");
			context_free();
			ERR_remove_state(0);
			exit(1);
		}
	}

	SSL_CTX_set_mode(ctx,SSL_MODE_AUTO_RETRY);
}

void context_free()
{
	SSL_CTX_free(ctx);
	ctx = 0;
}

#ifndef NO_RSA
static RSA *make_temp_key(int keylen) 
{
    RSA *result;

    TRACE("Generating %d bit temporary RSA key...", keylen);
    result=RSA_generate_key(keylen, RSA_F4, NULL, NULL);
    TRACE("Temporary RSA key created");
    return result;
}


static RSA* tmp_rsa_cb(SSL *s, int export, int keylen)
{
    static int initialized=0;
    static struct keytabstruct 
	{
        RSA *key;
        time_t timeout;
    } keytable[KEY_CACHE_LENGTH];

    static RSA *longkey;
    static int longlen;
    static time_t longtime;
    RSA *oldkey;
    time_t now;
    int i;

    if(!initialized) 
	{
        for(i=0; i<KEY_CACHE_LENGTH; i++) 
		{
            keytable[i].key=NULL;
            keytable[i].timeout=0;
        }
        longkey=NULL;
        longlen=0;
        longtime=0;
        initialized=1;
    }

    /* TODO: make it fully mt-safe */
    time(&now);
    if(keylen<KEY_CACHE_LENGTH) 
	{
        enter_critical_section(0);
        if(keytable[keylen].timeout<now) 
		{
            oldkey=keytable[keylen].key;
            keytable[keylen].key=make_temp_key(keylen);
            keytable[keylen].timeout=now+KEY_CACHE_TIME;
            if(oldkey)
                RSA_free(oldkey);
        }
        leave_critical_section(0);
        return keytable[keylen].key;
    } 
	else 
	{ /* Temp key > 2048 bits.  Is it possible? */
        enter_critical_section(1);
        if(longtime<now || longlen!=keylen) 
		{
            oldkey=longkey;
            longkey=make_temp_key(keylen);
            longtime=now+KEY_CACHE_TIME;
            longlen=keylen;
            if(oldkey)
                RSA_free(oldkey);
        }
        leave_critical_section(1);
        return longkey;
    }

}
#endif

static int verify_callback(int state, X509_STORE_CTX *ctx)
{ /* our verify callback function */
    char txt[256];
    X509_OBJECT ret;

    X509_NAME_oneline(X509_get_subject_name(ctx->current_cert),
        txt, sizeof(txt));
    if(!state) 
	{
        /* Remote site specified a certificate, but it's not correct */
        TRACE("VERIFY ERROR: depth=%d error=%s: %s",
            ctx->error_depth,
            X509_verify_cert_error_string (ctx->error), txt);
        return 0; /* Reject connection */
    }
    if(options.verify_use_only_my && ctx->error_depth==0 
		&& X509_STORE_get_by_subject(ctx, X509_LU_X509,
			X509_get_subject_name(ctx->current_cert), &ret)!=1) 
	{
        TRACE("VERIFY ERROR ONLY MY: no cert for: %s", txt);
        return 0; /* Reject connection */
    }
    TRACE("VERIFY OK: depth=%d: %s", ctx->error_depth, txt);
    return 1; /* Accept connection */
}

static void info_callback(SSL *s, int where, int ret)
{
    TRACE("%s", SSL_state_string_long(s));
//   if(where==SSL_CB_HANDSHAKE_DONE)
//       print_stats();
}

void set_options()
{
    /* set options */
    options.option |= OPT_CERT;
    options.verify_level=0x00; /* SSL_VERIFY_NONE */
    options.verify_use_only_my=0;
    options.debug_level=5;
    options.facility=1;
    options.session_timeout=300;
    options.cipher_list=NULL;
    options.username=NULL;
    options.protocol=NULL;
    options.setuid_user=NULL;
    options.setgid_group=NULL;
    options.egd_sock=NULL;
    options.rand_file=NULL;
    options.rand_write=1;
    options.random_bytes=1024;
   	safecopy(options.cert_file,CERT_FILE);
    safecopy(options.cert_dir, CERT_DIR);
    safecopy(options.pem, PEM_FILE);
    options.verify_level |= 0x01; /* SSL_VERIFY_PEER */
//	    	options.egd_sock=optarg;
}