/* ssl/ssl_sess.c */
/* Copyright (C) 1995-1996 Eric Young (eay@mincom.oz.au)
 * All rights reserved.
 * 
 * This file is part of an SSL implementation written
 * by Eric Young (eay@mincom.oz.au).
 * The implementation was written so as to conform with Netscapes SSL
 * specification.  This library and applications are
 * FREE FOR COMMERCIAL AND NON-COMMERCIAL USE
 * as long as the following conditions are aheared to.
 * 
 * Copyright remains Eric Young's, and as such any Copyright notices in
 * the code are not to be removed.  If this code is used in a product,
 * Eric Young should be given attribution as the author of the parts used.
 * This can be in the form of a textual message at program startup or
 * in documentation (online or textual) provided with the package.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    This product includes software developed by Eric Young (eay@mincom.oz.au)
 * 
 * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * 
 * The licence and distribution terms for any publically available version or
 * derivative of this code cannot be changed.  i.e. this code cannot simply be
 * copied and put under another distribution licence
 * [including the GNU Public Licence.]
 */

#include <stdio.h>
#ifdef NO_MD5
#include <md5.h>
#else
#include "md5.h"
#endif
#include "lhash.h"
#include "rand.h"
#include "ssl_locl.h"

SSL_SESSION *SSL_SESSION_new()
	{
	SSL_SESSION *ss;
	int i;

	ss=(SSL_SESSION *)Malloc(sizeof(SSL_SESSION));
	if (ss == NULL)
		{
		SSLerr(SSL_F_SSL_SESSION_NEW,ERR_R_MALLOC_FAILURE);
		return(0);
		}
	ss->num_ciphers=0;
	for (i=0; i<SSL_NUM_CIPHERS; i++) ss->ciphers[i]=NULL;
	ss->cipher=NULL;
	ss->key_arg_length=0;
	ss->key_arg=NULL;
	ss->master_key_length=0;
	ss->master_key=NULL;
	ss->session_id_length=0;
	ss->session_id=NULL;

	ss->cert=NULL;
	ss->peer=NULL;
	ss->references=1;
	ss->timeout=60*5; /* 5 minute timeout by default */
	ss->time=time(NULL);
	return(ss);
	}

int ssl_get_new_session(s, session)
SSL *s;
int session;
	{
	SSL_SESSION *ss=NULL;

	if ((ss=SSL_SESSION_new()) == NULL) return(0);

	/* If the context has a default timeout, use it */
	if (s->ctx->session_timeout != 0)
		ss->timeout=s->ctx->session_timeout;

	if (s->session != NULL)
		{
		SSL_SESSION_free(s->session);
		s->session=NULL;
		}

	if (session)
		{
		ss->session_id_length=SSL_SESSION_ID_LENGTH;
		ss->session_id=(unsigned char *)Malloc(SSL_SESSION_ID_LENGTH);
		if (ss->session_id == NULL)
			{
			SSLerr(SSL_F_SSL_GET_NEW_SESSION,ERR_R_MALLOC_FAILURE);
			SSL_SESSION_free(ss);
			return(0);
			}

		for (;;)
			{
			SSL_SESSION *r;

			RAND_bytes(ss->session_id,SSL_SESSION_ID_LENGTH);
			CRYPTO_r_lock(CRYPTO_LOCK_SSL_CTX);
			r=(SSL_SESSION *)lh_retrieve(s->ctx->sessions,
				(char *)ss);
			CRYPTO_r_unlock(CRYPTO_LOCK_SSL_CTX);
			if (r == NULL) break;
			/* else - woops a session_id match */
			}
		}
	else
		{
		ss->session_id_length=0;
		}

	s->session=ss;
	return(1);
	}

int ssl_get_prev_session(s, len, session)
SSL *s;
int len;
unsigned char *session;
	{
	SSL_SESSION *ret,data;

	/* conn_init();*/
	data.session_id_length=len;
	data.session_id=session;

	CRYPTO_r_lock(CRYPTO_LOCK_SSL_CTX);
	ret=(SSL_SESSION *)lh_retrieve(s->ctx->sessions,(char *)&data);
	CRYPTO_r_unlock(CRYPTO_LOCK_SSL_CTX);

	if (ret == NULL)
		{
		int copy=1;

		s->ctx->sess_miss++;
		ret=NULL;
		if ((s->ctx->get_session_cb != NULL) &&
			((ret=s->ctx->get_session_cb(s,session,len,&copy))
				!= NULL))
			{
			s->ctx->sess_cb_hit++;

			/* The following should not return 1, otherwise,
			 * things are very strange */
			SSL_add_session(s->ctx,ret);
			/* auto free it */
			if (!copy)
				SSL_SESSION_free(ret);
			}
		if (ret == NULL) return(0);
		}
#ifdef SSL_DEBUG
	SSL_TRACE(SSL_ERR,"GOT ONE\n");
#endif

	/* If a thread got the session, then 'swaped', and another got
	 * it and then due to a time-out decided to 'Free' it we could
	 * be in trouble.  So I'll increment it now, then double decrement
	 * later - am I speaking rubbish?. */
	CRYPTO_add(&ret->references,1,CRYPTO_LOCK_SSL_SESSION);

	if ((long)(ret->time+ret->timeout) < (long)time(NULL)) /* timeout */
		{
#ifdef SSL_DEBUG
		SSL_TRACE(SSL_ERR,"TIMEOUT ON SSL_SESSION\n");
#endif
		s->ctx->sess_timeout++;
		SSL_remove_session(s->ctx,ret); /* remove it from the cache */
		SSL_SESSION_free(ret);		/* again to actually Free it */
		return(0);
		}

	s->ctx->sess_hit++;

	/* ret->time=time(NULL); */ /* rezero timeout? */
	/* again, just leave the session 
	 * if it is the same session, we have just incremented and
	 * then decremented the reference count :-) */
	if (s->session != NULL)
		SSL_SESSION_free(s->session);
	s->session=ret;
	return(1);
	}

int SSL_add_session(ctx,c)
SSL_CTX *ctx;
SSL_SESSION *c;
	{
	SSL_SESSION *s;

	/* conn_init(); */
	CRYPTO_add(&c->references,1,CRYPTO_LOCK_SSL_SESSION);

	CRYPTO_w_lock(CRYPTO_LOCK_SSL_CTX);
	s=(SSL_SESSION *)lh_insert(ctx->sessions,(char *)c);
	CRYPTO_w_unlock(CRYPTO_LOCK_SSL_CTX);

	/* If the same session if is being 're-added', Free the old
	 * one when the last person stops using it.
	 * This will also work if it is alread in the cache.
	 * The references will go up and then down :-) */
	if (s != NULL)
		{
		SSL_SESSION_free(s);
		return(0);
		}
	else
		return(1);
	}

void SSL_remove_session(ctx,c)
SSL_CTX *ctx;
SSL_SESSION *c;
	{
	SSL_SESSION *r;

	if ((c->session_id != NULL) && (c != NULL))
		{
		CRYPTO_w_lock(CRYPTO_LOCK_SSL_CTX);
		r=(SSL_SESSION *)lh_delete(ctx->sessions,(char *)c);
		if (r == NULL)
			{
#ifdef SSL_DEBUG
			SSL_TRACE(SSL_ERR,"Freeing a session that is not hashed\n");
#endif
			return;
			}
		else
			SSL_SESSION_free(r);
		CRYPTO_w_unlock(CRYPTO_LOCK_SSL_CTX);
		}
	}

void SSL_SESSION_free(ss)
SSL_SESSION *ss;
	{
	int i;

	i=CRYPTO_add(&ss->references,-1,CRYPTO_LOCK_SSL_SESSION);
	if (i > 0) return;
#ifdef REF_CHECK
	if (i < 0)
		{
		fprintf(stderr,"SSL_SESSION_free, bad reference count\n");
		abort();
		}
#endif

	if (ss->key_arg != NULL)
		{
		memset(ss->key_arg,0,ss->key_arg_length);
		Free(ss->key_arg);
		}
	if (ss->master_key != NULL)
		{
		memset(ss->master_key,0,ss->master_key_length);
		Free(ss->master_key);
		}
	if (ss->session_id != NULL)
		{
		memset(ss->session_id,0,ss->session_id_length);
		Free(ss->session_id);
		}
	if (ss->cert != NULL) ssl_cert_free(ss->cert);
	if (ss->peer != NULL) X509_free(ss->peer);
	Free(ss);
	}

int SSL_set_session(s, session)
SSL *s;
SSL_SESSION *session;
	{
	int ret=0;

	if (session != NULL)
		{
		/* CRYPTO_w_lock(CRYPTO_LOCK_SSL);*/
		CRYPTO_add(&session->references,1,CRYPTO_LOCK_SSL_SESSION);
		if (s->session != NULL)
			SSL_SESSION_free(s->session);
		s->session=session;
		/* CRYPTO_w_unlock(CRYPTO_LOCK_SSL);*/
		ret=1;
		}
	return(ret);
	}

long SSL_set_timeout(s,t)
SSL_SESSION *s;
long t;
	{
	if (s == NULL) return(0);
	s->timeout=t;
	return(1);
	}

long SSL_get_timeout(s)
SSL_SESSION *s;
	{
	if (s == NULL) return(0);
	return(s->timeout);
	}

long SSL_get_time(s)
SSL_SESSION *s;
	{
	if (s == NULL) return(0);
	return(s->time);
	}

long SSL_set_time(s,t)
SSL_SESSION *s;
long t;
	{
	if (s == NULL) return(0);
	s->time=t;
	return(t);
	}

typedef struct timeout_param_st
	{
	unsigned long time;
	LHASH *cache;
	} TIMEOUT_PARAM;

static void timeout(s,p)
SSL_SESSION *s;
TIMEOUT_PARAM *p;
	{
	if ((p->time == 0) || (p->time > (s->time+s->timeout))) /* timeout */
		{
		lh_delete(p->cache,(char *)s);
		SSL_SESSION_free(s);
		}
	}

void SSL_flush_sessions(s,t)
SSL_CTX *s;
long t;
	{
	unsigned long i;
	TIMEOUT_PARAM tp;

	tp.cache=SSL_CTX_sessions(s);
	if (tp.cache == NULL) return;
	tp.time=t;
	CRYPTO_w_lock(CRYPTO_LOCK_SSL_CTX);
	i=tp.cache->down_load;
	tp.cache->down_load=0;
	lh_doall_arg(tp.cache,(void (*)())timeout,(char *)&tp);
	tp.cache->down_load=i;
	CRYPTO_w_unlock(CRYPTO_LOCK_SSL_CTX);
	}

