/*
Example usage for creating private key/certificate. : On startup do this:

#include <openssl/rand.h>

	SSL_library_init();
#ifdef WIN32
	RAND_screen();
#endif

	if (!lib_exists(private_key_file)) {
		int bits=1024;
		if (RAND_status()!=1) {
			wmsg("Warning - insufficient random data available. Private key may not be secure\n");
		}
		pmsg("Creating %d bit private key...\n",bits);
		encrypt_lib_create_private_key(private_key_file,bits,NULL);
		pmsg("Done creating key\n");
	}
	if (!lib_exists(certificate_file)) {
		encrypt_lib_create_certificate(certificate_file,private_key_file,NULL,"matt.netwin.co.nz",365*10);
	}


RAND_status() will not return 1 on unix systems such as solaris that don't have /dev/urandom

before creating private key file, may want to add code like this:

  	file_root2(rand_data_name,"random_data.dat");
	if (lib_exists(rand_data_name)) {
		char *data;
		int data_len;
		if (lib_file_read(rand_data_name,&data,&data_len)) {
			pmsg("Using %d bytes of random data from %s\n",data_len,rand_data_name);
			RAND_seed(data,data_len);
			free(data);
		}
	}



*/
#ifdef HAS_SSL

#ifdef __XCODE__
#include "xcode_quiet.h"
#endif

#ifdef XINCLUDE_PRECOMPC
#include "precompc.h"
#pragma hdrstop
#endif

#ifdef X_SAM_INCLUDE_TCP_H
#include "../tcp.h"
#endif

#include <string.h>
#ifdef WIN32
#include <windows.h>
#include <direct.h>
#else
#include <unistd.h>
#endif

#define DMID 14
#include "lprintf.h"
#include "lib.h"
#include "debug.h"
#include "encrypt_lib.h"
#include <sys/stat.h>

#include <openssl/rand.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <openssl/err.h>

#include <openssl/rsa.h>       /* SSLeay stuff */
#include <openssl/ssl.h>
#include <openssl/rand.h>
#include <openssl/crypto.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/pem.h>


#include <openssl/x509v3.h>
const EVP_MD *EVP_sha256(void);

#ifdef SURGEMAIL
// Use dmalloc - I Think this is safe, if not it will die fairly quickly :-)
#define DMID 14
#include "debug.h"
#endif

//#define BFSZ 1024 (chrisp changed, must always match incase we mix stuff up)
#define BFSZ 1000

#ifndef FALSE
#define FALSE 0
#endif

#ifndef TRUE 
#define TRUE (!FALSE)
#endif


#include "../sam/ssltrack.h" 



static int more_secure_certs=FALSE;
int more_secure_certs_init(int value)
{
	more_secure_certs=value;
	return TRUE;
}
char *add_dns_list(char *list, char *alt)
{
	char *upto;
	char bf[BFSZ];
	char *s;
	ncpy(bf,list,BFSZ-1);
	strcpy(alt,"");
	// CHRISP, REFORMAT SO IT HAS dns: on hte start...
	for (s=mystrtok(bf,", \r\n",&upto);s!=NULL;s = mystrtok(NULL,", \r\n",&upto)) {
			if (strlen(s)>0) {
				if (strlen(alt)>0) lpcat(alt,BFSZ-1,",");
				lpcat(alt,BFSZ-1,"DNS:%s",s);
			}
	}
	return alt;
}

int encrypt_lib_create_certificate2(char *certificate_file, EVP_PKEY *pk, char *data_domain, long days)
{
	return encrypt_lib_create_certificate3(certificate_file,pk,data_domain,NULL,days);
}

int add_ext(X509 *cert, int nid, char *value)
{
	X509_EXTENSION *ex;
	X509V3_CTX ctx;
	/* This sets the 'context' of the extensions. */
	/* No configuration database */
	X509V3_set_ctx_nodb(&ctx);
	/* Issuer and subject certs: both the target since it is self signed,
	 * no request and no CRL
	 */
	X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0);
	ex = X509V3_EXT_conf_nid(NULL, &ctx, nid, value);
	if (!ex)
		return 0;

	X509_add_ext(cert,ex,-1);
	X509_EXTENSION_free(ex);
	return 1;
}
int add_ext_stk(STACK_OF(X509_EXTENSION) *sk, int nid, char *value) 
{
  X509_EXTENSION *ex;
  ex = X509V3_EXT_conf_nid(NULL, NULL, nid, value);
  if (!ex)
    return 0;
  sk_X509_EXTENSION_push(sk, ex);
  return 1;
}
int add_extr(X509_REQ *cert, int nid, char *value)
{	
	STACK_OF(X509_EXTENSION) *exts;
	exts = sk_X509_EXTENSION_new_null();
	add_ext_stk(exts,nid,value);
	X509_REQ_add_extensions(cert, exts);
	sk_X509_EXTENSION_pop_free(exts, X509_EXTENSION_free);
	return 1;
}

int encrypt_lib_create_certificate3(char *certificate_file, EVP_PKEY *pk, char *data_domain, char *data_email, long days)
{
	/*FILE *f;*/
	X509 *x=NULL;
	int serial=0;
	X509_NAME *name=NULL;
	/*X509_NAME_ENTRY *ne=NULL;*/
//	X509_EXTENSION *ex=NULL;

	x=X509_new();

	X509_set_version(x,2);
	RAND_bytes((unsigned char *)&serial,4);
	ASN1_INTEGER_set(X509_get_serialNumber(x),serial);
	X509_gmtime_adj(X509_get_notBefore(x),-60*60*24*2);

	/* Go back a bit from now to account for borland complaining recently generated certificates are not yet valid
	(probably due to Borland and VC using different daylight savings handling)
	*/

	X509_gmtime_adj(X509_get_notAfter(x),(long)60*60*24*days);
	X509_set_pubkey(x,pk);

	name=X509_get_subject_name(x);

	/* This function creates and adds the entry, working out the
	 * correct string type and performing checks on its length.
	 * Normally we'd check the return value for errors...
	 */
	/*X509_NAME_add_entry_by_txt(name,"C",
				MBSTRING_ASC, "UK", -1, -1, 0);*/
	X509_NAME_add_entry_by_txt(name,"CN",
				MBSTRING_ASC, (unsigned char *)  data_domain, -1, -1, 0);
	if (data_email) {
		X509_NAME_add_entry_by_txt(name,"emailAddress",
					MBSTRING_ASC, (unsigned char *)  data_email, -1, -1, 0);
	}

	X509_set_issuer_name(x,name);

	/*add_ext(x, NID_basic_constraints, "critical,CA:TRUE");
	add_ext(x, NID_key_usage, "critical,keyCertSign,cRLSign");

	add_ext(x, NID_subject_key_identifier, "hash");*/

	/* Some Netscape specific extensions */

	if (data_domain) {
		add_ext(x, NID_netscape_cert_type, "server");
		add_ext(x, NID_netscape_ssl_server_name, data_domain);
	}


	//add_ext(x, NID_subject_alt_name, "DNS:testbox,DNS:www.test.com,DNS:*.land10web.com,DNS:*.etc.com");


	/* Add extension using V3 code: we can set the config file as NULL
	 * because we wont reference any other sections. We can also set
         * the context to NULL because none of these extensions below will need
	 * to access it.
	 */

/*
	ex = X509V3_EXT_conf_nid(NULL, NULL, NID_netscape_cert_type, "server");
	X509_add_ext(x,ex,-1);
	X509_EXTENSION_free(ex);

	ex = X509V3_EXT_conf_nid(NULL, NULL, NID_netscape_comment,
						"example comment extension");
	X509_add_ext(x,ex,-1);
	X509_EXTENSION_free(ex);


	ex = X509V3_EXT_conf_nid(NULL, NULL, NID_netscape_ssl_server_name,
						data_domain);
	X509_add_ext(x,ex,-1);
	X509_EXTENSION_free(ex);
*/
	more_secure_certs = TRUE;
	if(more_secure_certs){
		if (!X509_sign(x,pk,EVP_sha256())) // chrisp canging from sha1
			return 0;
	}else{
		if (!X509_sign(x,pk,EVP_md5()))
			return 0;
	}

	encrypt_lib_write_certificate(certificate_file,x);
	/*f=fopen(certificate_file,"w");
	if (!f) return 0;
	PEM_write_X509(f,x);
	fclose(f);*/
	return 1;
}


int encrypt_lib_create_certificate(char *certificate_file, char *private_key_file, char *password, char *data_domain, long days)
/*
Create a self-signed certificate based on given private_key_file and given domain name
and given number of days until expires.
Be careful not to make days too large (it gets converted to seconds and will crash if invalid)
*/
{
	int ret;
	FILE *f;
	/*X509 *x=NULL;*/
	EVP_PKEY *pk=NULL;
	/*int serial=0;*/
	/*X509_NAME *name=NULL;*/
	/*X509_NAME_ENTRY *ne=NULL;*/
	/*X509_EXTENSION *ex=NULL;*/

	f=fopen(private_key_file,"r");
	if (!f) return 0;
	if (password==NULL) password="";/* So it doesn't prompt for password in stdin if file is encrypted and a password is not supplied*/

	pk=PEM_read_PrivateKey(f,NULL,NULL,password);
	fclose(f);
	if (!pk) {
		return 0;
	}
	ret=encrypt_lib_create_certificate2(certificate_file,pk,data_domain,days);
	EVP_PKEY_free(pk);
	return ret;
}

int encrypt_lib_create_certificate_for_email(char *certificate_file, char *private_key_file, char *password, char *data_name, char *data_email, long days)
{
	int ret;
	FILE *f;
	EVP_PKEY *pk=NULL;

	f=fopen(private_key_file,"r");
	if (!f) return 0;
	if (password==NULL) password="";/* So it doesn't prompt for password in stdin if file is encrypted and a password is not supplied*/

	pk=PEM_read_PrivateKey(f,NULL,NULL,password);
	fclose(f);
	if (!pk) {
		return 0;
	}
	ret=encrypt_lib_create_certificate3(certificate_file,pk,data_name,data_email,days);
	EVP_PKEY_free(pk);
	return ret;
}

#ifndef WIN32
#include <sys/time.h>
#endif

void encrypt_lib_reseed()
/*
On systems that support it, sleep briefly and take the actualy time slept
as a random seed.
*/
{
#ifndef WIN32
#ifndef NO_GETTIMEOFDAY
	int diff;
	struct timeval tv,tv2;
	struct timezone tz;
	gettimeofday(&tv, &tz);
	usleep(100);
	gettimeofday(&tv2, &tz);
	diff=tv.tv_usec-tv2.tv_usec;
	if (diff<0) diff=-diff;
	srand(myrand()+diff);
#endif
#endif
}
int encrypt_lib_seed(void)
{
	int x;
	int lc=0;
	if (RAND_status()==1) return 1;
	/*
RAND_status() returns 1 on linux and Windows.

Will not return 1 on unix systems such as
solaris that don't have /dev/urandom

RAND_status() must return 1 before private key creation calls will work.
	*/

	x= (int) time(NULL);
	srand(x);
	while(RAND_status()!=1) {
		char cc;
		int c=rand();
		if (c<0) c=-c;
		c=c%256;
		cc=(char) c;
		RAND_seed(&cc,1);
		lc++;
		encrypt_lib_reseed();
		if (lc>1000) return 0;
	}
	return 1;
}

int encrypt_lib_create_private_key(char *fname, int bits, char *password)
{
	RSA *r;
	FILE *f;
	encrypt_lib_seed();

	if (password && *password==0) password=NULL;
//	printf("RSA_generate_key\n");
	r=RSA_generate_key(bits,65537,NULL,NULL);
//	printf("RSA_generate_key done\n");

	if (!r) {
		return 0;
	}

	f=fopen(fname,"w");
	if (!f) {
		RSA_free(r);
		return 0;
	}

	if (!PEM_write_RSAPrivateKey(f,r,password?EVP_des_ede3_cbc():NULL,NULL,0,NULL,password)) {
		fclose(f);
		RSA_free(r);
		return 0;
	}

	fclose(f);
	RSA_free(r);
	return 1;
}


#ifdef LIB_INCLUDE_FILE
#include LIB_INCLUDE_FILE
#endif

#ifndef MAX_PATH
#define MAX_PATH 10000
#endif

char *encrypt_lib_bio_strdup_and_free(BIO *b);

int encrypt_lib_file_size(char *fname)
{
	struct stat bf;
	if (stat(fname,&bf)!=0) return 0;
	return bf.st_size;
}

int encrypt_lib_delete_file(char *fname)
/*
Delete a file by first overwriting it with 0's to remove any data.
USeful if file used to be an un-encrypted private key.
*/
{
	FILE *f;
	char *bf="";
	int i=encrypt_lib_file_size(fname);
	int j;
	if (i<=0) return 1;
	f=fopen(fname,"rb+");
	if (!f) return 0;
	for(j=0;j<i;j++) {
		fwrite(bf,1,1,f);
	}
	fclose(f);
	remove(fname);
	return 1;
}
void myssl_cleanup(void);
int encrypt_lib_write_private_key(char *fname, EVP_PKEY *pk, char *password)
/*
Done in a rather round-about way since default write doesn't use \r's, even when writing in text mode.
*/
{
	FILE *f;
	int ok;
	BIO *out;
	char *data;

	encrypt_lib_delete_file(fname);

	out=BIO_new(BIO_s_mem());
	if (!out) return 0;

	if (password && *password==0) password=NULL;

	ok=PEM_write_bio_PrivateKey(out,pk,password?EVP_des_ede3_cbc():NULL ,NULL,0,NULL, password);
	if (!ok) {
		BIO_free(out);
		myssl_cleanup();

		return 0;
	}
	data=encrypt_lib_bio_strdup_and_free(out);

	f=fopen(fname,"w");
	if (!f) {
		free(data);
		return 0;
	}
	fprintf(f,"%s",data);

	fclose(f);
	free(data);
	return 1;
}


EVP_PKEY *encrypt_lib_read_private_key(char *fname, char *password)
{
	EVP_PKEY *pkey;
	FILE *f;
	f=fopen(fname,"r");
	if (!f) return NULL;
	if (!password) password="";
	pkey = PEM_read_PrivateKey(f, NULL,NULL,password);
	fclose(f);
	return pkey;
}

X509 *encrypt_lib_read_certificate(char *fname)
{
	return encrypt_lib_read_certificate2(fname,0);
}

X509 *encrypt_lib_read_certificate2(char *fname, int return_no_cert_on_empty_file)
{
	X509 *x;
	FILE *f;
	f=fopen(fname,"r");
	if (!f) return NULL;
	x = PEM_read_X509(f, NULL,NULL, NULL);
	fclose(f);
	if (!x && return_no_cert_on_empty_file) return CERT_NO_CERT;
	return x;

}

int encrypt_lib_write_certificate(char *fname, X509 *cert)
{
/*
Done in a rather round-about way since default write doesn't use \r's, even when writing in text mode.
*/
	FILE *f;
	/*f=fopen(fname,"w");
	if (!f) return 0;
	PEM_write_X509(f,cert);
	fclose(f);
	return 1;*/

	int ok;
	BIO *out;
	char *data;
	out=BIO_new(BIO_s_mem());
	if (!out) return 0;

	ok=PEM_write_bio_X509(out,cert);
	if (!ok) {
		BIO_free(out);
		myssl_cleanup();
		return 0;
	}
	data=encrypt_lib_bio_strdup_and_free(out);

	f=fopen(fname,"w");
	if (!f) {
		free(data);
		return 0;
	}
	fprintf(f,"%s",data);

	fclose(f);
	free(data);
	return 1;

}

/* Add customer details to the CSR or certificate */
int add_cert_info (X509_NAME *name, cert_info *data)
{
	if (lib_strlen(data->domain)>0)
		X509_NAME_add_entry_by_txt(name,"CN", MBSTRING_ASC, (unsigned char *)  data->domain, -1, -1, 0);
	if (lib_strlen(data->organisation)>0)
		X509_NAME_add_entry_by_txt(name,"O", MBSTRING_ASC, (unsigned char *)  data->organisation, -1, -1, 0);
	if (lib_strlen(data->unit)>0)
		X509_NAME_add_entry_by_txt(name,"OU", MBSTRING_ASC, (unsigned char *)  data->unit, -1, -1, 0);
	if (lib_strlen(data->state)>0)
		X509_NAME_add_entry_by_txt(name,"ST", MBSTRING_ASC, (unsigned char *)  data->state, -1, -1, 0);
	if (lib_strlen(data->city)>0)
		X509_NAME_add_entry_by_txt(name,"L", MBSTRING_ASC, (unsigned char *)  data->city, -1, -1, 0);
	if (lib_strlen(data->country)>0)
		X509_NAME_add_entry_by_txt(name,"C", MBSTRING_ASC, (unsigned char *)  data->country, -1, -1, 0);
	if (lib_strlen(data->email)>0)
		X509_NAME_add_entry_by_txt(name,"Email", MBSTRING_ASC, (unsigned char *)  data->email, -1, -1, 0);



	return 1;
}

/* Create a CSR based on a full set of customer information and private key*/
int encrypt_lib_create_csr2(char *csr_file, EVP_PKEY *pk, long days, cert_info *data)
{
	FILE *f;
	X509_REQ *x=NULL;
	X509_NAME *name=NULL;

	x=X509_REQ_new();

	X509_REQ_set_pubkey(x,pk);

	/* Next line added to fix problem with microsoft CA rejecting with
	"Error Parsing Request ASN1 bad tag value met. 0x8009310b (ASN: 267)".*/
	X509_REQ_set_version(x,0L); /* version 1*/

	name=X509_REQ_get_subject_name(x);
	add_cert_info(name,data);



	if (data->altname && (strlen(data->altname)>0) ) {
		char bf[BFSZ];
		add_extr(x, NID_subject_alt_name, add_dns_list(data->altname,bf));
	} 

 
	more_secure_certs = TRUE;
	if(more_secure_certs){
		if (!X509_REQ_sign(x,pk,EVP_sha256())) // chrisp canging from sha1   chrisp added cast, hopefully this is ok??? 
//		if (!X509_REQ_sign(x,pk,EVP_sha1()))
			return 0;
	}else{
		if (!X509_REQ_sign(x,pk,EVP_md5()))
			return 0;
	}
	f=fopen(csr_file,"w");
	if (!f) return 0;
	PEM_write_X509_REQ(f,x);
	fclose(f);

	return 1;
}

/* Create a CSR based on a full set of customer information and private key file*/
int encrypt_lib_create_csr(char *csr_file, char *private_key_file, char *password, long days, cert_info *ci, char *reason)
{
	int ret;
	FILE *f;
	EVP_PKEY *pk=NULL;

	f=fopen(private_key_file,"r");
	if (!f) {
		strcpy(reason,"Unable to open private key file");
		return 0;
	}
	if (password==NULL) password="";/* So it doesn't prompt for password in stdin if file is encrypted and a password is not supplied*/

	pk=PEM_read_PrivateKey(f,NULL,NULL,password);
	fclose(f);
	if (!pk) {
		strcpy(reason,"Unable to read existing private key file");
		return 0;
	}
	ret=encrypt_lib_create_csr2(csr_file,pk,days,ci);
	EVP_PKEY_free(pk);
	if (!ret) strcpy(reason,"Creating CSR failed for some reason");
	return ret;
}


/* Create a self signed certificate based on a full set of customer information and private key */
int encrypt_lib_create_fullcert(char *fname, EVP_PKEY *pk, long days, cert_info *data)
{
	X509 *x=NULL;
	int serial=0;
	X509_NAME *name=NULL;
//	char bf[BFSZ];

	x=X509_new();

	X509_set_version(x,3);
	RAND_bytes((unsigned char *)&serial,4);
	ASN1_INTEGER_set(X509_get_serialNumber(x),serial);
	X509_gmtime_adj(X509_get_notBefore(x),-60*60*24*2);
	X509_gmtime_adj(X509_get_notAfter(x),(long)60*60*24*days);

	X509_set_pubkey(x,pk);

	name=X509_get_subject_name(x);
	add_cert_info(name,data);

	X509_set_issuer_name(x,name);

	if (data->altname && (strlen(data->altname)>0)) {
		char bf[BFSZ];
		add_ext(x, NID_subject_alt_name, add_dns_list(data->altname,bf));
	} 
	

	more_secure_certs = TRUE;
	if(more_secure_certs){
		if (!X509_sign(x,pk,EVP_sha256())) // chrisp canging from sha1
//		if (!X509_sign(x,pk,EVP_sha1()))
			return 0;
	}else{
		if (!X509_sign(x,pk,EVP_md5()))
			return 0;
	}

	encrypt_lib_write_certificate(fname,x);
	return 1;
}

/* Create a certificate based on a full set of customer information and private key file*/
int encrypt_lib_create_cert(char *certificate_file, char *private_key_file, char *password, long days, cert_info *ci)
{
	int ret;
	FILE *f;
	EVP_PKEY *pk=NULL;

	f=fopen(private_key_file,"r");
	if (!f) return 0;
	if (password==NULL) password="";/* So it doesn't prompt for password in stdin if file is encrypted and a password is not supplied*/

	pk=PEM_read_PrivateKey(f,NULL,NULL,password);
	fclose(f);
	if (!pk) {
		return 0;
	}
	ret=encrypt_lib_create_fullcert(certificate_file,pk,days,ci);
	EVP_PKEY_free(pk);
	return ret;
}

EVP_PKEY *encrypt_lib_read_public_key(char *fname)
{
	EVP_PKEY *pkey;
	X509 *x=encrypt_lib_read_certificate(fname);
	if (!x) return NULL;
	pkey=X509_get_pubkey(x);
	X509_free(x);
	return pkey;
}


char *encpy(char *dst, char *src, int len)
{
        char *xdst = dst;
        int xlen = len;
		if (len<0) lib_abort();

		if (src==NULL) lib_abort();
        for (;(*src !=0) && (len>0); ) {*dst++  = *src++; len--;}
        *dst++ = 0;
        xdst[xlen] = 0;
        return xdst;
}

char *encrypt_lib_bio_strdup(BIO *b)
{
	char *ret;
	char *ret2;
	int len;
	len=BIO_get_mem_data(b,&ret);
	if (len<0) {
		return NULL;
	}
	ret2=(char *) malloc(len+2);
	encpy(ret2,ret,len);
	return ret2;
}
char *encrypt_lib_bio_strdup_and_free(BIO *b)
{
	char *ret=encrypt_lib_bio_strdup(b);
	BIO_free(b);
	myssl_cleanup();
	return ret;
}


char *encrypt_lib_pkcs7_strdup(PKCS7 *p7)
{
	BIO *out;
	if (!p7) return NULL;
	out=BIO_new(BIO_s_mem());
	PEM_write_bio_PKCS7(out, p7);
	return encrypt_lib_bio_strdup_and_free(out);
}
char *encrypt_lib_pkcs7_strdup_and_free(PKCS7 *p7)
{
	char *ret=encrypt_lib_pkcs7_strdup(p7);
	PKCS7_free(p7);
	return ret;
}

BIO *encrypt_lib_bio_from_data(char *data, int data_len)
{
	return BIO_new_mem_buf(data,data_len);
/*
	BIO *b;
	b=BIO_new(BIO_s_mem());
	BIO_write(b,data,data_len);
	return b;*/
}



char *encrypt_lib_encrypt(X509 **certs, int num_certs, char *msg, int msg_len)
{
	BIO *bio;
	int i;
	PKCS7 *p7 = NULL;
	STACK_OF(X509) *encerts = NULL;
	const EVP_CIPHER *cipher = EVP_des_ede3_cbc();
	int flags = PKCS7_DETACHED | PKCS7_BINARY;

	encerts = sk_X509_new_null();
	for(i=0;i<num_certs;i++) {
		sk_X509_push(encerts, certs[i]);
	}
	bio=encrypt_lib_bio_from_data(msg,msg_len);
	p7 = PKCS7_encrypt(encerts, bio, cipher, flags);
	BIO_free(bio);
	myssl_cleanup();
	return encrypt_lib_pkcs7_strdup_and_free(p7);
}


int encrypt_lib_decrypt(char *in_data, X509 *recipient_certificate, EVP_PKEY *private_key, char **out_data, int *out_data_len)
{
	PKCS7 *p7 = NULL;
	BIO *in;
	BIO *out;
	int iret=0;
	char *ret;
	int ret_len;
	char *ret2;
	int flags = PKCS7_DETACHED | PKCS7_BINARY;
	in=encrypt_lib_bio_from_data(in_data,lib_strlen(in_data));
	p7 = PEM_read_bio_PKCS7(in, NULL, NULL, NULL);
	BIO_free(in);
	myssl_cleanup();
	if (!p7) {
		return 0;
	}
	out=BIO_new(BIO_s_mem());
	if(!PKCS7_decrypt(p7, private_key, recipient_certificate, out, flags)) {
		PKCS7_free(p7);
		BIO_free(out);
	myssl_cleanup();
		return 0;
	}
	PKCS7_free(p7);
	ret_len=BIO_get_mem_data(out,&ret);
	if (ret_len<0) {
		BIO_free(out);
	myssl_cleanup();
		return 0;
	}
	*out_data_len=ret_len;
	ret2=(char *)malloc(ret_len+1);
	if (ret2) {
		if (ret_len<0) lib_abort();
		memcpy(ret2,ret,ret_len);
		ret2[ret_len]=0;
		*out_data=ret2;
		iret=1;
	}
	else {
		*out_data="";
		iret=0;
	}
	BIO_free(out);
	myssl_cleanup();
	return iret;
}


char *encrypt_lib_sign(char *data, int data_len, X509 *cert, EVP_PKEY *private_key)
{
	int flags = PKCS7_DETACHED | PKCS7_BINARY;
	PKCS7 *p7 = NULL;
	BIO *in;

	in=encrypt_lib_bio_from_data(data,data_len);
	flags = flags | PKCS7_BINARY;

	p7 = PKCS7_sign(cert, private_key, NULL, in, flags);
	BIO_free(in);
	myssl_cleanup();
	return encrypt_lib_pkcs7_strdup_and_free(p7);
}


X509 *encrypt_lib_verify_and_get_cert(char *data, int data_len, char *signature)
/*
If signature is valid for given data, return the x509 signature.
No checking is done for if signature is actually a signed by a trusted CA.
(since this is written for an app using self signed certs - i.e. user accepts cert first
time and is notified if it ever changes. )
*/
{
	STACK_OF(X509) *signers;
	int flags = PKCS7_DETACHED | PKCS7_BINARY;
	PKCS7 *p7 = NULL;
	X509 *ret=NULL;


	BIO *data_in=encrypt_lib_bio_from_data(data,data_len);
	BIO *sig=encrypt_lib_bio_from_data(signature,lib_strlen(signature));
	BIO *out=BIO_new(BIO_s_mem());

	p7 = PEM_read_bio_PKCS7(sig, NULL, NULL, NULL);

	flags |= PKCS7_NOVERIFY;
	if(PKCS7_verify(p7, NULL, NULL , data_in, out, flags)) {
		signers = PKCS7_get0_signers(p7, NULL, flags);
		if (sk_X509_num(signers)>=1) {
			ret=X509_dup(sk_X509_value(signers, 0));
		}
		sk_X509_free(signers);
	}
	else {
		/*
		BIO *berr = BIO_new_fd(fileno(stdout), BIO_NOCLOSE);
 		ERR_print_errors(berr);
		*/
	}

	BIO_free(data_in);
	BIO_free(sig);
	BIO_free(out);
	myssl_cleanup();
	PKCS7_free(p7);

	return ret;
}

char *encrypt_lib_cert_to_bf(X509 *cert)
/*
Allocates memory for and returns certificate in text format.
*/
{

	BIO *b=BIO_new(BIO_s_mem());

	if (!PEM_write_bio_X509(b,cert)) {
		BIO_free(b);
	myssl_cleanup();
		return NULL;
	}
	return encrypt_lib_bio_strdup_and_free(b);
}

X509 *encrypt_lib_bf_to_cert(char *bf)
{
	X509 *ret;
	BIO *b=encrypt_lib_bio_from_data(bf,lib_strlen(bf));
	if (!b) return NULL;
	ret=PEM_read_bio_X509(b,NULL,NULL,NULL);
	BIO_free(b);
	myssl_cleanup();
	return ret;
}

/*
certificates/matt.pem etc
*/

static char *certificate_dir=NULL;

int encrypt_lib_set_certificate_dir(char *dir)
/*
eg "c:\certificates"
*/
{
	if (certificate_dir) free(certificate_dir);
	certificate_dir=strdup(dir);
#ifdef WIN32
	_mkdir(dir);
#else
	mkdir(dir,0500);
#endif
	return 1;
}

int encrypt_lib_free()
{
	if (certificate_dir) free(certificate_dir);
	certificate_dir=NULL;

	return 1;
}

#ifdef WIN32
#define PATH_SEP '\\'
#else
#define PATH_SEP '/'
#endif

char *encrypt_lib_file_name(char *bf, int bfsz, char *user)
{
	if (!certificate_dir) return NULL;
	if (strstr(user,"..")) return NULL;
	lprintf(bf,bfsz-1,"%s%c%s.txt",certificate_dir,PATH_SEP,user);
	return bf;

}

char *encrypt_lib_expect_file_name(char *bf, int bfsz, char *user)
{
	if (!certificate_dir) return NULL;
	if (strstr(user,"..")) return NULL;
	lprintf(bf,bfsz-1,"%s%c%s.expect_cert",certificate_dir,PATH_SEP,user);
	return bf;

}

/*
When receiving signed message:
UNDECIDED : Set to yes

Non-signed message:

*/
int encrypt_lib_expect_sig(char *user)
{
	char fname[MAX_PATH];
	char bf[5];
	FILE *f;
	if (!encrypt_lib_expect_file_name(fname,MAX_PATH,user)) return EXPECT_SIG_UNDECIDED;
	f=fopen(fname,"r");
	if (!f) return EXPECT_SIG_UNDECIDED;
	if (!fgets(bf,4,f)) {
		fclose(f);
		return EXPECT_SIG_UNDECIDED;
	}
	fclose(f);
	if (bf[0]=='1') return EXPECT_SIG_YES;
	return EXPECT_SIG_NO;
}

int encrypt_lib_set_expect_sig(char *user, int expect_it)
{
	char fname[MAX_PATH];
	FILE *f;
	if (!encrypt_lib_expect_file_name(fname,MAX_PATH,user)) return 0;
	if (expect_it==EXPECT_SIG_UNDECIDED) {
		remove(fname);
	}
	else {
		f=fopen(fname,"w");
		if (!f) return 0;
		fprintf(f,(expect_it==EXPECT_SIG_YES)?"11":"0");
		fclose(f);
	}
	return 1;
}


X509 *encrypt_lib_get_certificate(char *user)
{
	char fname[MAX_PATH];
	if (!encrypt_lib_file_name(fname,MAX_PATH,user)) return NULL;
	return encrypt_lib_read_certificate2(fname,1);
}

int encrypt_lib_save_certificate(char *user, X509 *cert)
{
	char fname[MAX_PATH];
	if (!encrypt_lib_file_name(fname,MAX_PATH,user)) return 0;
	if (cert==CERT_NO_CERT) {
		FILE *f;
		f=fopen(fname,"w");
		if (!f) return 0;
		fclose(f);
		return 1;
	}
	return encrypt_lib_write_certificate(fname,cert);
}


void encrypt_lib_test2(char *private_key_file, char *certificate_file)
{
	char msg[]="Testing Message Stuff!\nMore";
	char *ret;
	int ok;
	char *ret2;
	char *sig;
	int ret2_len;
	EVP_PKEY *pkey;
	X509 *x=encrypt_lib_read_certificate(certificate_file);
	X509 *x2;
	ret=encrypt_lib_encrypt(&x,1,msg,lib_strlen(msg));
	printf("enc=%d:[%s]\n",(int) lib_strlen(ret),ret);

	pkey = encrypt_lib_read_private_key(private_key_file,NULL);
	ok=encrypt_lib_decrypt(ret,x,pkey,&ret2,&ret2_len);
	printf("dec=%d\n",ok);
	if (ok) {
		printf("rslt=%d:[%s]\n",ret2_len,ret2);
	}


	sig=encrypt_lib_sign(msg,lib_strlen(msg),x,pkey);

	x2=encrypt_lib_verify_and_get_cert(msg,lib_strlen(msg),sig);
	if (x2) {
		if (X509_cmp(x,x2)==0) {
			printf("verify same as original cert\n");
		}
		else {
			printf("verify different from original cert\n");
		}
	}
	else {
		printf("verify failed\n");
	}
	exit(99);
}

int encrypt_cert_checksum(char *cert_bf)
/*
Used for quickly deciding whether entire certificate is probably same as local copy.
*/
{
	char *valid_chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
	int csum=0;
	int did=0;
	while(*cert_bf) {
		char *p=strchr(valid_chars,*cert_bf);
		if (p) {
			int val=(int)(p-valid_chars);
			csum=csum*133+val;
			did=1;
		}
		cert_bf++;
	}
	if (csum<0) csum=-csum;
	csum=csum%1000000000;
	if (!did) return -1;
	return csum;
}


int encrypt_lib_extract_signature(char *data)
/*
If it beings with a sig, return the length in bytes of the signature.
*/
{
	int len;
	char *start="-----BEGIN PKCS7-----";
	char *end="-----END PKCS7-----\n";
	char *p;
	int start_len=lib_strlen(start);
	int end_len=lib_strlen(end);
	if (strncmp(data,start,start_len)!=0) return 0;
	p=strstr(data,end);
	if (!p) return 0;
	len=(int)(p-data+end_len);
	return len;
}


int encrypt_lib_create_pubkey(char *outfile, char* file)
{
	BIO *key=NULL;
	BIO *out=NULL;
	EVP_PKEY *pkey=NULL;
	RSA *rsa=NULL;
	char result[BFSZ];

	key=BIO_new(BIO_s_file());
	if (key == NULL){
		lprintf(result,BFSZ-1,"file io error 1");
		goto error;
	}

	if (BIO_read_filename(key,file) <= 0){
		lprintf(result,BFSZ-1,"file io error 2 (%s)",file);
		goto error;
	}

	pkey=PEM_read_bio_PrivateKey(key,NULL,NULL,"");
	BIO_free(key);
	myssl_cleanup();
	
	if (pkey == NULL){
		lprintf(result,BFSZ-1,"Private key invalid 1");
		goto error;
	}

	rsa = EVP_PKEY_get1_RSA(pkey);
	if (rsa == NULL){
		lprintf(result,BFSZ-1,"Private key invalid 2");
		goto error;
	}
	EVP_PKEY_free(pkey);

	out=BIO_new(BIO_s_file());
	if (out == NULL){
		lprintf(result,BFSZ-1,"file io error 3 ");
		goto error;
	}

	if (BIO_write_filename(out,outfile) <= 0){
		lprintf(result,BFSZ-1,"file io error 4 (%s)",outfile);
		goto error;
	}
	
	if (!PEM_write_bio_RSA_PUBKEY(out,rsa)){		
		lprintf(result,BFSZ-1,"file io error 5 ");
		goto error;
	}
	BIO_flush(out);
	BIO_free_all(out);	
	RSA_free(rsa);
	myssl_cleanup();

	return TRUE;

error:
	myssl_cleanup();
	return FALSE;
}


int aes_init(unsigned char *key_data, int key_data_len, unsigned char *salt, EVP_CIPHER_CTX *e_ctx, EVP_CIPHER_CTX *d_ctx)
{
  int i, nrounds = 5;
  unsigned char key[32], iv[32];

  nrounds=1; // to match openssl command line...
//  printf("aes_init salt=(%s) key_data (%s) key_len %d\n",salt,key_data,key_data_len);
  i = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_md5(), salt, key_data, key_data_len, nrounds, key, iv);
  if (i != 32) {
    //printf("Key size is %d bits - should be 256 bits\n", i);
    return -1;
  }

  EVP_CIPHER_CTX_init(e_ctx);
  //printf("aes_init key=(%s) \n",lib_bin_to_hex(key,key_data_len,tmp,BFSZ-1));
 // printf("aes_init iv=(%s) \n",lib_bin_to_hex(iv,32,tmp,BFSZ-1));

  EVP_EncryptInit_ex(e_ctx, EVP_aes_256_cbc(), NULL, key, iv);
  EVP_CIPHER_CTX_init(d_ctx);
  EVP_DecryptInit_ex(d_ctx, EVP_aes_256_cbc(), NULL, key, iv);

  return 0;
}

#include "thread.h"
// test code...	
int encrypt_lib_salt(char *salt)
{
	int x;
	strcpy(salt,"12345678");
#ifdef DO_THREADS
	srand((int) time(NULL)+th_id()*100);
#else 
	srand((int)time(NULL)*100+getpid());
#endif

	x = myrand();		// Random salt.... 
	memcpy(salt,&x,4);
	x = myrand();
	memcpy(salt+4,&x,4);
	return TRUE;
}

#ifndef AES_BLOCK_SIZE
#define AES_BLOCK_SIZE 16
#endif

int encrypt_lib_encrypt_file_byname(char *fin, char *fout, unsigned char *key_data,char *reason)
{
	FILE *f1;
	FILE *f2;
	int r = FALSE;
	strcpy(reason,"ok");
	f1 = fopen(fin,"rb");
	if (f1==NULL){ lprintf(reason,BFSZ-1,"Could not open input %s %s",fin,strerror(errno)); return FALSE;}
	f2 = fopen(fout,"wb");
	if (f2==NULL){fclose(f1); lprintf(reason,BFSZ-1,"Could not open output %s %s",fout,strerror(errno)); return FALSE;}
	if (f1 && f2) r = encrypt_lib_encrypt_file(f1,f2,key_data,reason);
	if (f1) fclose(f1);
	if (f2) fclose(f2);
	return r;
}
int encrypt_lib_encrypt_file(FILE *fin, FILE *fout, unsigned char *key_data,char *reason)
{
	static const char magic[]="Salted__";
	unsigned char ciphertext[BFSZ+AES_BLOCK_SIZE+1];
	int key_data_len = (int) lib_strlen(key_data);
	EVP_CIPHER_CTX *e=NULL,*de=NULL;
	int ret=FALSE;
	char bf[BFSZ];
	int c_len=0;
	char salt[BFSZ];

	strcpy(reason,"");
	encrypt_lib_salt(salt);
	salt[8] = 0; 
	memset(&e,0,sizeof(e));
	e = EVP_CIPHER_CTX_new();
	de = EVP_CIPHER_CTX_new();

	if (aes_init(key_data, key_data_len, salt, e, de)) {
		lprintf(reason,BFSZ-1,"Couldn't initialize AES cipher");
		goto failed;
	}
	EVP_EncryptInit_ex(e, NULL, NULL, NULL, NULL);
	fwrite(magic,1,8,fout); // First 8 bytes are the magic word 'salted..." ...
	fwrite(salt,1,8,fout); // Next 8 bytes are the salt...
	for (;!feof(fin);) {
		int got;
		c_len = BFSZ + AES_BLOCK_SIZE - 1;
		got = (int)fread(bf,1,BFSZ-1,fin);
		if (got<=0) break;
		EVP_EncryptUpdate(e, ciphertext, &c_len, bf, got);
		fwrite(ciphertext,1,c_len,fout);
	}
	c_len = BFSZ + AES_BLOCK_SIZE - 1;
	EVP_EncryptFinal_ex(e, ciphertext, &c_len);
	fwrite(ciphertext,1,c_len,fout);
	ret = TRUE;
failed:
	if (e!=NULL) EVP_CIPHER_CTX_free(e); e = NULL;
	if (de!=NULL) EVP_CIPHER_CTX_free(de); de = NULL;

	if (!ret) lprintf(reason,BFSZ-1,"Encrypte FAILED for some reason");
	return ret;
}

int enc_file_osize(FILE *f)
{
	struct stat bf;
	if (fstat(fileno(f),&bf)!=0) {return -1;} /* Its nice to know when a file does not exist  */
	return bf.st_size;
}
int encrypt_lib_decrypt_file_byname(char *fin, char *fout, unsigned char *key_data,char *reason)
{
	FILE *f1;
	FILE *f2;
	int len;
	int r;
	r = FALSE;
//printf("encrypt file %s %s\n",fin,fout);
	f1 = fopen(fin,"rb");
	f2 = fopen(fout,"wb");
	if (f1) len = enc_file_osize(f1);
	if (f1 && f2) r = encrypt_lib_decrypt_file(f1,len,f2,key_data,reason);
	if (f1) fclose(f1);
	if (f2) fclose(f2);
	return r;
}

int encrypt_lib_decrypt_file(FILE *fin, int inlen, FILE *fout, char *secret, char *reason)
{
	EVP_CIPHER_CTX *enot=NULL, *de=NULL;
	unsigned char salt[BFSZ];
	unsigned char *key_data;
	char magic[BFSZ];
	int key_data_len;
	int c_len;
	int n;
	unsigned char ciphertext[BFSZ+AES_BLOCK_SIZE+1];
	int f_len = 0;
	char bf[BFSZ];
	
	strcpy(reason,"");
	key_data = secret;
	key_data_len = lib_strlen(key_data);

	fread(magic,1,8,fin); // First 8 bytes are the salt...
	fread(salt,1,8,fin); // First 8 bytes are the salt...
	salt[8] = 0;

	enot = EVP_CIPHER_CTX_new();
	de = EVP_CIPHER_CTX_new();

	/* gen key and iv. init the cipher ctx object */
	if (aes_init(key_data, key_data_len, (unsigned char *)&salt, enot, de)) {
		lprintf(reason,BFSZ-1,"Couldn't initialize AES cipher");
		goto failed;
	}

	/* allows reusing of 'e' for multiple encryption cycles */
	EVP_DecryptInit_ex(de, NULL, NULL, NULL, NULL);

	for (;!feof(fin);) {
		c_len = BFSZ + AES_BLOCK_SIZE - 1;
		if (c_len>inlen) c_len = inlen;
		if (inlen<=0) break;
		n = (int)fread(bf,1,BFSZ-1,fin);
		if (n<=0) break;
		inlen -= n;
		/* update ciphertext, c_len is filled with the length of ciphertext generated,  *len is the size of plaintext in bytes */
		EVP_DecryptUpdate(de, ciphertext, &c_len, bf, n);
		fwrite(ciphertext,1,c_len,fout);
	}

	c_len = BFSZ + AES_BLOCK_SIZE - 1;
	EVP_DecryptFinal_ex(de, ciphertext, &c_len);
	fwrite(ciphertext,1,c_len,fout);
	if (enot!=NULL) EVP_CIPHER_CTX_free(enot); enot = NULL;
	if (de!=NULL) EVP_CIPHER_CTX_free(de); de = NULL;
	return TRUE;
failed:
	if (enot!=NULL) EVP_CIPHER_CTX_free(enot); enot = NULL;
	if (de!=NULL) EVP_CIPHER_CTX_free(de); de = NULL;
	return FALSE;
}


#else
#include <string.h>
#endif

