#include "StdAfx.h"
#include <sys/stat.h>
#include <stdlib.h>
#ifdef	ACL_UNIX
# include <unistd.h>
# include <sys/time.h>			/* gettimeofday, not POSIX */
#elif defined(WIN32)
# include <process.h>
#endif

#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <limits.h>

/* OpenSSL library. */

#ifdef USE_TLS
#include <openssl/rand.h>		/* For the PRNG */
#endif

#include "../attr/attr.h"

#ifndef UCHAR_MAX
#define UCHAR_MAX 0xff
#endif

/* TLS library. */

#ifdef USE_TLS
#include "tls.h"			/* TLS_MGR_SCACHE_<type> */
#include "tls_private.h"
#include "tls_mgr.h"
#include "tls_prng.h"
#include "tls_scache.h"
#include "tls_params.h"

/* Application-specific. */

 /*
  * Tunables.
  */
char   *var_tls_rand_source;
int     var_tls_rand_bytes;
int     var_tls_reseed_period;
int     var_tls_prng_exch_period;
int     var_server_tls_loglevel;
char   *var_server_tls_scache_db;
int     var_server_tls_scache_timeout;
int     var_client_tls_loglevel;
char   *var_client_tls_scache_db;
int     var_client_tls_scache_timeout;
char   *var_tls_rand_exch_name;
int     var_tlsmgr_stand_alone = 0;

 /*
  * Bound the time that we are willing to wait for an I/O operation. This
  * produces better error messages than waiting until the watchdog timer
  * kills the process.
  */
#define TLS_MGR_TIMEOUT	10

 /*
  * State for updating the PRNG exchange file.
  */
static TLS_PRNG_SRC *rand_exch;

 /*
  * State for seeding the internal PRNG from external source.
  */
static TLS_PRNG_SRC *rand_source_dev;
static TLS_PRNG_SRC *rand_source_egd;
static TLS_PRNG_SRC *rand_source_file;

 /*
  * The external entropy source type is encoded in the source name. The
  * obvious alternative is to have separate configuration parameters per
  * source type, so that one process can query multiple external sources.
  */
#define DEV_PREF "dev:"
#define DEV_PREF_LEN (sizeof((DEV_PREF)) - 1)
#define DEV_PATH(dev) ((dev) + EGD_PREF_LEN)

#define EGD_PREF "egd:"
#define EGD_PREF_LEN (sizeof((EGD_PREF)) - 1)
#define EGD_PATH(egd) ((egd) + EGD_PREF_LEN)

 /*
  * State for TLS session caches.
  */
typedef struct {
    char   *cache_label;		/* cache short-hand name */
    TLS_SCACHE *cache_info;		/* cache handle */
    int     cache_active;		/* cache status */
    char  **cache_db;			/* main.cf parameter value */
    int    *cache_loglevel;		/* main.cf parameter value */
    int    *cache_timeout;		/* main.cf parameter value */
} TLSMGR_SCACHE;

TLSMGR_SCACHE cache_table[] = {
    { TLS_MGR_SCACHE_SERVER, 0, 0, &var_server_tls_scache_db,
	&var_server_tls_loglevel, &var_server_tls_scache_timeout },
    { TLS_MGR_SCACHE_CLIENT, 0, 0, &var_client_tls_scache_db,
	&var_client_tls_loglevel, &var_client_tls_scache_timeout },
    { 0, 0, 0, 0, 0, 0 },
};

static ACL_EVENT *__eventp;
 /*
  * SLMs.
  */
#define STR(x)		acl_vstring_str(x)
#define LEN(x)		ACL_VSTRING_LEN(x)
#define STREQ(x, y)	(strcmp((x), (y)) == 0)

/* tlsmgr_prng_exch_event - update PRNG exchange file */

static void tlsmgr_prng_exch_event(int event_type acl_unused,
	ACL_EVENT *event acl_unused, void *dummy)
{
    const char *myname = "tlsmgr_prng_exch_event";
    unsigned char randbyte;
    int     next_period;
    struct acl_stat st;

    if (acl_msg_verbose)
	acl_msg_info("%s: update PRNG exchange file", myname);

    /*
     * Sanity check. If the PRNG exchange file was removed, there is no point
     * updating it further. Restart the process and update the new file.
     */
    if (acl_fstat(rand_exch->fd.file, &st) < 0)
	acl_msg_fatal("%s: cannot fstat() the PRNG exchange file: %s", myname, acl_last_serror());
    if (st.st_nlink == 0) {
	acl_msg_warn("%s: PRNG exchange file was removed -- exiting to reopen", myname);
	sleep(1);
	exit(0);
    }
    tls_prng_exch_update(rand_exch);

    /*
     * Make prediction difficult for outsiders and calculate the time for the
     * next execution randomly.
     */
    RAND_bytes(&randbyte, 1);
    next_period = (var_tls_prng_exch_period * randbyte) / UCHAR_MAX;
    acl_event_request_timer(__eventp, tlsmgr_prng_exch_event, dummy, next_period, 0);
}

/* tlsmgr_reseed_event - re-seed the internal PRNG pool */

static void tlsmgr_reseed_event(int event_type acl_unused,
	ACL_EVENT *event acl_unused, void *dummy)
{
    const char *myname = "tlsmgr_reseed_event";
    int     next_period;
    unsigned char randbyte;
    int     must_exit = 0;

    /*
     * Reseed the internal PRNG from external source. Errors are recoverable.
     * We simply restart and reconnect without making a fuss. This is OK
     * because we do require that exchange file updates succeed. The exchange
     * file is the only entropy source that really matters in the long term.
     * 
     * If the administrator specifies an external randomness source that we
     * could not open upon start-up, restart to see if we can open it now
     * (and log a nagging warning if we can't).
     */
    if (*var_tls_rand_source) {

	/*
	 * Source is a random device.
	 */
	if (rand_source_dev) {
	    if (tls_prng_dev_read(rand_source_dev, var_tls_rand_bytes) <= 0) {
		acl_msg_info("%s: cannot read from entropy device %s: %s -- "
			 "exiting to reopen", myname, DEV_PATH(var_tls_rand_source),
			 acl_last_serror());
		must_exit = 1;
	    }
	}

	/*
	 * Source is an EGD compatible socket.
	 */
	else if (rand_source_egd) {
	    if (tls_prng_egd_read(rand_source_egd, var_tls_rand_bytes) <= 0) {
		acl_msg_info("%s: lost connection to EGD server %s -- "
		     "exiting to reconnect", myname, EGD_PATH(var_tls_rand_source));
		must_exit = 1;
	    }
	}

	/*
	 * Source is a regular file. Read the content once and close the
	 * file.
	 */
	else if (rand_source_file) {
	    if (tls_prng_file_read(rand_source_file, var_tls_rand_bytes) <= 0)
		acl_msg_warn("%s: cannot read from entropy file %s: %s",
			 myname, var_tls_rand_source, acl_last_serror());
	    tls_prng_file_close(rand_source_file);
	    rand_source_file = 0;
	    var_tls_rand_source[0] = 0;
	}

	/*
	 * Could not open the external source upon start-up. See if we can
	 * open it this time. Save PRNG state before we exit.
	 */
	else {
	    acl_msg_info("exiting to reopen external entropy source %s",
		     var_tls_rand_source);
	    must_exit = 1;
	}
    }

    /*
     * Save PRNG state in case we must exit.
     */
    if (must_exit) {
	if (rand_exch)
	    tls_prng_exch_update(rand_exch);
	sleep(1);
	acl_msg_info("exit now");
	exit(0);
    }

    /*
     * Make prediction difficult for outsiders and calculate the time for the
     * next execution randomly.
     */
    RAND_bytes(&randbyte, 1);
    next_period = (var_tls_reseed_period * randbyte) / UCHAR_MAX;
    acl_event_request_timer(__eventp, tlsmgr_reseed_event, dummy, next_period, 0);
}

/* tlsmgr_cache_run_event - start TLS session cache scan */

static void tlsmgr_cache_run_event(int event_type acl_unused,
	ACL_EVENT *event acl_unused, void *ctx)
{
    const char *myname = "tlsmgr_cache_run_event";
    TLSMGR_SCACHE *cache = (TLSMGR_SCACHE *) ctx;

    /*
     * This routine runs when it is time for another TLS session cache scan.
     * Make sure this routine gets called again in the future.
     * 
     * Don't start a new scan when the timer goes off while cache cleanup is
     * still in progress.
     */
    if (cache->cache_info->verbose)
	acl_msg_info("%s: start TLS %s session cache cleanup",
		 myname, cache->cache_label);

    if (cache->cache_active == 0)
	cache->cache_active =
	    tls_scache_sequence(cache->cache_info, DICT_SEQ_FUN_FIRST,
				TLS_SCACHE_SEQUENCE_NOTHING);

    acl_event_request_timer(__eventp, tlsmgr_cache_run_event, (char *) cache,
			cache->cache_info->timeout, 0);
}

/* tlsmgr_loop - TLS manager main loop */

static int tlsmgr_loop(char *unused_name acl_unused, char **unused_argv acl_unused)
{
    struct timeval tv;
    int     active = 0;
    TLSMGR_SCACHE *ent;

    /*
     * Update the PRNG pool with the time of day. We do it here after every
     * event (including internal timer events and external client request
     * events), instead of doing it in individual event call-back routines.
     */
    gettimeofday(&tv, 0);
    RAND_seed(&tv, sizeof(struct timeval));

    /*
     * This routine runs as part of the event handling loop, after the event
     * manager has delivered a timer or I/O event, or after it has waited for
     * a specified amount of time. The result value of tlsmgr_loop()
     * specifies how long the event manager should wait for the next event.
     * 
     * We use this loop to interleave TLS session cache cleanup with other
     * activity. Interleaved processing is needed when we use a client-server
     * protocol for entropy and session state exchange with client(8) and
     * server(8) processes.
     */
#define DONT_WAIT	0
#define WAIT_FOR_EVENT	(-1)

    for (ent = cache_table; ent->cache_label; ++ent) {
	if (ent->cache_info && ent->cache_active)
	    active |= ent->cache_active =
		tls_scache_sequence(ent->cache_info, DICT_SEQ_FUN_NEXT,
				    TLS_SCACHE_SEQUENCE_NOTHING);
    }

    return (active ? DONT_WAIT : WAIT_FOR_EVENT);
}

/* tlsmgr_request_receive - receive request */

static int tlsmgr_request_receive(ACL_VSTREAM *client_stream, ACL_VSTRING *request)
{
    const char *myname = "tlsmgr_request_receive";
    int     count;

    /*
     * Kluge: choose the protocol depending on the request size.
     */
    if (acl_read_wait(ACL_VSTREAM_SOCK(client_stream), var_ipc_timeout) < 0) {
	acl_msg_warn("%s: timeout while waiting for data from %s",
		 myname, ACL_VSTREAM_PATH(client_stream));
	return (-1);
    }
    if ((count = acl_peekfd(ACL_VSTREAM_SOCK(client_stream))) < 0) {
	acl_msg_warn("%s: cannot examine read buffer of %s: %s",
		 myname, ACL_VSTREAM_PATH(client_stream), acl_last_serror());
	return (-1);
    }

    /*
     * Short request: master trigger. Use the string+null protocol.
     */
    if (count <= 2) {
	if (acl_vstring_gets_null(request, client_stream) == ACL_VSTREAM_EOF) {
	    acl_msg_warn("%s: end-of-input while reading request from %s: %s",
		     myname, ACL_VSTREAM_PATH(client_stream), acl_last_serror());
	    return (-1);
	}
    }

    /*
     * Long request: real tlsmgr client. Use the attribute list protocol.
     */
    else {
	if (attr_scan(client_stream,
		      ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
		      ATTR_TYPE_STR, TLS_MGR_ATTR_REQ, request,
		      ATTR_TYPE_END) != 1) {
	    return (-1);
	}
    }
    return (0);
}

/* tlsmgr_service - respond to external request */

static void tlsmgr_service(ACL_VSTREAM *client_stream,
	char *unused_service acl_unused, char **argv acl_unused)
{
    const char *myname = "tlsmgr_service";
    static ACL_VSTRING *request = 0;
    static ACL_VSTRING *cache_type = 0;
    static ACL_VSTRING *cache_id = 0;
    static ACL_VSTRING *buffer = 0;
    int     len;
    static char wakeup[] = {            /* master wakeup request */
	TRIGGER_REQ_WAKEUP,
	0,
    };
    TLSMGR_SCACHE *ent;
    int     status = TLS_MGR_STAT_FAIL;

    /*
     * Sanity check. This service takes no command-line arguments.
     */
    if (var_tlsmgr_stand_alone && argv[0])
        acl_msg_fatal("unexpected command-line argument: %s", argv[0]);

    /*
     * Initialize. We're select threaded, so we can use static buffers.
     */
    if (request == 0) {
	request = acl_vstring_alloc(10);
	cache_type = acl_vstring_alloc(10);
	cache_id = acl_vstring_alloc(10);
	buffer = acl_vstring_alloc(10);
    }

    /*
     * This routine runs whenever a client connects to the socket dedicated
     * to the tlsmgr service (including wake up events sent by the master).
     * All connection-management stuff is handled by the common code in
     * multi_server.c.
     */
    if (tlsmgr_request_receive(client_stream, request) == 0) {

	/*
	 * Load session from cache.
	 */
	if (STREQ(STR(request), TLS_MGR_REQ_LOOKUP)) {
	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type,
			  ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_ID, cache_id,
			  ATTR_TYPE_END) == 2) {
		for (ent = cache_table; ent->cache_label; ++ent)
		    if (strcmp(ent->cache_label, STR(cache_type)) == 0)
			break;
		if (ent->cache_label == 0) {
		    acl_msg_warn("%s: bogus cache type \"%s\" in \"%s\" request",
			     myname, STR(cache_type), TLS_MGR_REQ_LOOKUP);
		    ACL_VSTRING_RESET(buffer);
		} else if (ent->cache_info == 0) {

		    /*
		     * Cache type valid, but not enabled
		     */
		    ACL_VSTRING_RESET(buffer);
		} else {
		    status = tls_scache_lookup(ent->cache_info,
					       STR(cache_id), buffer) ?
			TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR;
		}
	    }
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       ATTR_TYPE_INT, TLS_ATTR_STATUS, status,
		       ATTR_TYPE_DATA, TLS_MGR_ATTR_SESSION,
		       LEN(buffer), STR(buffer),
		       ATTR_TYPE_END);
	}

	/*
	 * Save session to cache.
	 */
	else if (STREQ(STR(request), TLS_MGR_REQ_UPDATE)) {
	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type,
			  ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_ID, cache_id,
			  ATTR_TYPE_DATA, TLS_MGR_ATTR_SESSION, buffer,
			  ATTR_TYPE_END) == 3) {
		for (ent = cache_table; ent->cache_label; ++ent)
		    if (strcmp(ent->cache_label, STR(cache_type)) == 0)
			break;
		if (ent->cache_label == 0) {
		    acl_msg_warn("%s: bogus cache type \"%s\" in \"%s\" request",
			     myname, STR(cache_type), TLS_MGR_REQ_UPDATE);
		} else if (ent->cache_info != 0) {
		    status = 0;
			tls_scache_update(ent->cache_info, STR(cache_id),
					  STR(buffer), LEN(buffer)) ?
			TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR;
		}
	    }
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       ATTR_TYPE_INT, TLS_ATTR_STATUS, status,
		       ATTR_TYPE_END);
	}

	/*
	 * Delete session from cache.
	 */
	else if (STREQ(STR(request), TLS_MGR_REQ_DELETE)) {
	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type,
			  ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_ID, cache_id,
			  ATTR_TYPE_END) == 2) {
		for (ent = cache_table; ent->cache_label; ++ent)
		    if (strcmp(ent->cache_label, STR(cache_type)) == 0)
			break;
		if (ent->cache_label == 0) {
		    acl_msg_warn("%s: bogus cache type \"%s\" in \"%s\" request",
			     myname, STR(cache_type), TLS_MGR_REQ_DELETE);
		} else if (ent->cache_info != 0) {
		    status = tls_scache_delete(ent->cache_info,
					       STR(cache_id)) ?
			TLS_MGR_STAT_OK : TLS_MGR_STAT_ERR;
		}
	    }
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       ATTR_TYPE_INT, TLS_ATTR_STATUS, status,
		       ATTR_TYPE_END);
	}

	/*
	 * Entropy request.
	 */
	else if (STREQ(STR(request), TLS_MGR_REQ_SEED)) {
	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  ATTR_TYPE_INT, TLS_MGR_ATTR_SIZE, &len,
			  ATTR_TYPE_END) == 1) {
		ACL_VSTRING_RESET(buffer);
		if (len <= 0 || len > 255) {
		    acl_msg_warn("%s: bogus seed length \"%d\" in \"%s\" request",
			     myname, len, TLS_MGR_REQ_SEED);
		} else {
		    ACL_VSTRING_SPACE(buffer, len);
		    RAND_bytes((unsigned char *) STR(buffer), len);
		    ACL_VSTRING_AT_OFFSET(buffer, len);	/* XXX not part of the
							 * official interface */
		    status = TLS_MGR_STAT_OK;
		}
	    }
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       ATTR_TYPE_INT, TLS_ATTR_STATUS, status,
		       ATTR_TYPE_DATA, TLS_MGR_ATTR_SEED,
		       LEN(buffer), STR(buffer),
		       ATTR_TYPE_END);
	}

	/*
	 * Caching policy request.
	 */
	else if (STREQ(STR(request), TLS_MGR_REQ_POLICY)) {
	    int     cachable = 0;

	    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
			  ATTR_TYPE_STR, TLS_MGR_ATTR_CACHE_TYPE, cache_type,
			  ATTR_TYPE_END) == 1) {
		for (ent = cache_table; ent->cache_label; ++ent)
		    if (strcmp(ent->cache_label, STR(cache_type)) == 0)
			break;
		if (ent->cache_label == 0) {
		    acl_msg_warn("%s: bogus cache type \"%s\" in \"%s\" request",
			     myname, STR(cache_type), TLS_MGR_REQ_POLICY);
		} else {
		    cachable = (ent->cache_info != 0) ? 1 : 0;
		    status = TLS_MGR_STAT_OK;
		}
	    }
	    attr_print(client_stream, ATTR_FLAG_NONE,
		       ATTR_TYPE_INT, TLS_ATTR_STATUS, status,
		       ATTR_TYPE_INT, TLS_MGR_ATTR_CACHABLE, cachable,
		       ATTR_TYPE_END);
	}
        
	/*
	 * Master trigger. Normally, these triggers arrive only after some
	 * other process requested the tlsmgr's service. The purpose is to
	 * restart the tlsmgr after it aborted due to a fatal run-time error,
	 * so that it can continue its housekeeping even while nothing is
	 * using TLS.
	 * 
	 * XXX Which begs the question, if TLS isn't used often, do we need a
	 * tlsmgr background process? It could terminate when the session
	 * caches are empty.
	 */
	else if (var_tlsmgr_stand_alone && STREQ(STR(request), wakeup)) {
	    if (acl_msg_verbose)
		acl_msg_info("received master trigger");
#ifdef ACL_UNIX
	    acl_multi_server_disconnect(client_stream);
#endif
	    return;                             /* NOT: acl_vstream_fflush */
	}

	/*
	 * protocol error
	 */
	else {
	    attr_print(client_stream, ATTR_FLAG_NONE,
			ATTR_TYPE_INT, TLS_ATTR_STATUS, TLS_MGR_STAT_FAIL,
			ATTR_TYPE_END);
	}
    }

    /*
     * Protocol error.
     */
    else {
	attr_print(client_stream, ATTR_FLAG_NONE,
		   ATTR_TYPE_INT, TLS_ATTR_STATUS, TLS_MGR_STAT_FAIL,
		   ATTR_TYPE_END);
    }
    acl_vstream_fflush(client_stream);
}

/* tlsmgr_pre_init - pre-jail initialization */

static void tlsmgr_pre_init(char *unused_name acl_unused, char **unused_argv acl_unused)
{
    const char *myname = "tlsmgr_pre_init";
    char   *path;
    struct timeval tv;
    TLSMGR_SCACHE *ent;
    ACL_HTABLE *dup_filter;
    const char *dup_label;

    /*
     * If nothing else works then at least this will get us a few bits of
     * entropy.
     * 
     * XXX This is our first call into the OpenSSL library. We should find out
     * if this can be moved to the post-jail initialization phase, without
     * breaking compatibility with existing installations.
     */
#ifdef WIN32
# define getpid _getpid
#endif
    gettimeofday(&tv, 0);
    tv.tv_sec ^= getpid();
    RAND_seed(&tv, sizeof(struct timeval));

    /*
     * Open the external entropy source. We will not be able to open it again
     * after we are sent to chroot jail, so we keep it open. Errors are not
     * fatal. The exchange file (see below) is the only entropy source that
     * really matters in the long run.
     * 
     * Security note: we open the entropy source while privileged, but we don't
     * access the source until after we release privileges. This way, none of
     * the OpenSSL code gets to execute while we are privileged.
     */
    if (*var_tls_rand_source) {

	/*
	 * Source is a random device.
	 */
	if (!strncmp(var_tls_rand_source, DEV_PREF, DEV_PREF_LEN)) {
	    path = DEV_PATH(var_tls_rand_source);
	    rand_source_dev = tls_prng_dev_open(path, TLS_MGR_TIMEOUT);
	    if (rand_source_dev == 0)
		acl_msg_warn("%s: cannot open entropy device %s: %s",
			myname, path, acl_last_serror());
	}

	/*
	 * Source is an EGD compatible socket.
	 */
	else if (!strncmp(var_tls_rand_source, EGD_PREF, EGD_PREF_LEN)) {
	    path = EGD_PATH(var_tls_rand_source);
	    rand_source_egd = tls_prng_egd_open(path, TLS_MGR_TIMEOUT);
	    if (rand_source_egd == 0)
		acl_msg_warn("%s: cannot connect to EGD server %s: %s",
			myname, path, acl_last_serror());
	}

	/*
	 * Source is regular file. We read this only once.
	 */
	else {
	    rand_source_file =
		tls_prng_file_open(var_tls_rand_source, TLS_MGR_TIMEOUT);
	}
    } else {
	acl_msg_warn("%s: no entropy source specified with parameter %s",
		 myname, VAR_TLS_RAND_SOURCE);
	acl_msg_warn("%s: encryption keys etc. may be predictable", myname);
    }

    /*
     * Open the PRNG exchange file before going to jail, but don't use root
     * privileges. Start the exchange file read/update pseudo thread after
     * dropping privileges.
     */
    if (*var_tls_rand_exch_name) {
	rand_exch = tls_prng_exch_open(var_tls_rand_exch_name);
	if (rand_exch == 0)
	    acl_msg_fatal("cannot open PRNG exchange file %s: %s",
		      var_tls_rand_exch_name, acl_last_serror());
    }

    /*
     * Open the session cache files and discard old information before going
     * to jail, but don't use root privilege. Start the cache maintenance
     * pseudo threads after dropping privileges.
     */

    if (!var_tlsmgr_stand_alone)
	tls_scache_init();

    dup_filter = acl_htable_create(sizeof(cache_table) / sizeof(cache_table[0]), 0);
    for (ent = cache_table; ent->cache_label; ++ent) {
	if (**ent->cache_db) {
	    if ((dup_label = acl_htable_find(dup_filter, *ent->cache_db)) != 0)
		acl_msg_fatal("do not use the same TLS cache file %s for %s and %s",
			  *ent->cache_db, dup_label, ent->cache_label);
	    acl_htable_enter(dup_filter, *ent->cache_db, ent->cache_label);
	    ent->cache_info = tls_scache_open(*ent->cache_db,
					ent->cache_label,
					*ent->cache_loglevel >= 2,
					*ent->cache_timeout);
	}
    }
    acl_htable_free(dup_filter, NULL);
}

/* tlsmgr_post_init - post-jail initialization */

static void tlsmgr_post_init(char *unused_name acl_unused,
	char **unused_argv acl_unused)
{
    const char *myname = "tlsmgr_post_init";
    TLSMGR_SCACHE *ent;

#define NULL_EVENT	(0)
#define NULL_CONTEXT	((char *) 0)

#ifdef ACL_UNIX
    if (var_tlsmgr_stand_alone)
	__eventp = acl_multi_server_event();
    else
#endif
	__eventp = acl_event_new_select(1, 0);

    if (__eventp == NULL)
	acl_msg_fatal("%s: __eventp null", myname);

    if (var_tlsmgr_stand_alone) {
	/*
	 * This routine runs after the skeleton code has entered the chroot jail,
	 * but before any client requests are serviced. Prevent automatic process
	 * suicide after a limited number of client requests or after a limited
	 * amount of idle time.
	 */
#ifdef ACL_UNIX
	acl_var_multi_use_limit = 0;
	acl_var_multi_idle_limit = 0;
#endif
    }

    /*
     * Start the internal PRNG re-seeding pseudo thread first.
     */
    if (*var_tls_rand_source) {
	if (var_tls_reseed_period > INT_MAX / UCHAR_MAX)
	    var_tls_reseed_period = INT_MAX / UCHAR_MAX;
	tlsmgr_reseed_event(NULL_EVENT, NULL, NULL_CONTEXT);
    }

    /*
     * Start the exchange file read/update pseudo thread.
     */
    if (*var_tls_rand_exch_name) {
	if (var_tls_prng_exch_period > INT_MAX / UCHAR_MAX)
	    var_tls_prng_exch_period = INT_MAX / UCHAR_MAX;
	tlsmgr_prng_exch_event(NULL_EVENT, NULL, NULL_CONTEXT);
    }

    /*
     * Start the cache maintenance pseudo threads last. Strictly speaking
     * there is nothing to clean up after we truncate the database to zero
     * length, but early cleanup makes verbose logging more informative (we
     * get positive confirmation that the cleanup threads are running).
     */
    for (ent = cache_table; ent->cache_label; ++ent)
	if (ent->cache_info)
	    tlsmgr_cache_run_event(NULL_EVENT, NULL, (char *) ent);
}

/* tlsmgr_before_exit - save PRNG state before exit */

static void tlsmgr_before_exit(char *unused_service_name acl_unused,
	char **unused_argv acl_unused)
{

    /*
     * Save state before we exit after "postfix reload".
     */
    if (rand_exch)
	tls_prng_exch_update(rand_exch);
}

#ifdef ACL_UNIX

void   tlsmgr_alone_start(int argc, char *argv[])
{
    static ACL_CONFIG_STR_TABLE str_table[] = {
	{ VAR_TLS_RAND_SOURCE, DEF_TLS_RAND_SOURCE, &var_tls_rand_source },
	{ VAR_TLS_RAND_EXCH_NAME, DEF_TLS_RAND_EXCH_NAME, &var_tls_rand_exch_name },
	{ VAR_SERVER_TLS_SCACHE_DB, DEF_SERVER_TLS_SCACHE_DB, &var_server_tls_scache_db },
	{ VAR_CLIENT_TLS_SCACHE_DB, DEF_CLIENT_TLS_SCACHE_DB, &var_client_tls_scache_db },
	{ 0, 0, 0 },
    };
    static ACL_CONFIG_INT_TABLE int_table[] = {
	{ VAR_TLS_RAND_BYTES, DEF_TLS_RAND_BYTES, &var_tls_rand_bytes, 1, 0 },
	{ VAR_SERVER_TLS_LOGLEVEL, DEF_SERVER_TLS_LOGLEVEL, &var_server_tls_loglevel, 0, 0 },
	{ VAR_CLIENT_TLS_LOGLEVEL, DEF_CLIENT_TLS_LOGLEVEL, &var_client_tls_loglevel, 0, 0 },

	{ VAR_TLS_RESEED_PERIOD, DEF_TLS_RESEED_PERIOD, &var_tls_reseed_period, 1, 0 },
	{ VAR_TLS_PRNG_UPD_PERIOD, DEF_TLS_PRNG_UPD_PERIOD, &var_tls_prng_exch_period, 1, 0 },
	{ VAR_SERVER_TLS_SCACHTIME, DEF_SERVER_TLS_SCACHTIME, &var_server_tls_scache_timeout, 0, 0 },
	{ VAR_CLIENT_TLS_SCACHTIME, DEF_CLIENT_TLS_SCACHTIME, &var_client_tls_scache_timeout, 0, 0 },
	{ 0, 0, 0, 0, 0 },
    };

    /*
     * Use the multi service skeleton, and require that no-one else is
     * monitoring our service port while this process runs.
     */
    acl_multi_server_main(argc, argv, tlsmgr_service,
	    ACL_MASTER_SERVER_INT_TABLE, int_table,
	    ACL_MASTER_SERVER_STR_TABLE, str_table,
	    ACL_MASTER_SERVER_PRE_INIT, tlsmgr_pre_init,
	    ACL_MASTER_SERVER_POST_INIT, tlsmgr_post_init,
	    ACL_MASTER_SERVER_EXIT, tlsmgr_before_exit,
	    ACL_MASTER_SERVER_LOOP, tlsmgr_loop,
	    ACL_MASTER_SERVER_SOLITARY,
	    0);
}

#endif  /* ACL_UNIX */

static void client_ready_callback(int event_type acl_unused,
	ACL_EVENT *event, ACL_VSTREAM *stream acl_unused, void *ctx)
{
    ACL_VSTREAM *client_stream = (ACL_VSTREAM*) ctx;

    tlsmgr_service(client_stream, NULL, NULL);
    acl_event_enable_read(event, client_stream,
	0, client_ready_callback, client_stream);
}

static void listen_callback(int event_type acl_unused, ACL_EVENT *event,
	ACL_VSTREAM *stream, void *ctx acl_unused)
{
    const char *myname = "listen_callback";
    ACL_VSTREAM *client_stream;

    client_stream = acl_vstream_accept(stream, NULL, 0);
    if (client_stream == NULL) {
	acl_msg_warn("%s(%d): accept error(%s)",
		myname, __LINE__, acl_last_serror());
	return;
    }
    acl_event_enable_read(event, client_stream,
	0, client_ready_callback, client_stream);
}

static void *tlsmgr_thread_main(void *arg)
{
    ACL_VSTREAM *server_stream = (ACL_VSTREAM*) arg;
    int   delay_sec;

    acl_event_enable_listen(__eventp, server_stream,
	    0, listen_callback, server_stream);
    while (1) {
	delay_sec = tlsmgr_loop(NULL, NULL);
	acl_event_set_delay_sec(__eventp, delay_sec);
	acl_event_loop(__eventp);
    }

    acl_vstream_close(server_stream);
    tlsmgr_before_exit(NULL, NULL);
    return (NULL);
}

void   tlsmgr_local_start(const char *addr)
{
    const char *myname = "tlsmgr_local_start";
    static ACL_CONFIG_STR_TABLE str_table[] = {
	{ VAR_TLS_RAND_SOURCE, DEF_TLS_RAND_SOURCE, &var_tls_rand_source },
	{ VAR_TLS_RAND_EXCH_NAME, DEF_TLS_RAND_EXCH_NAME, &var_tls_rand_exch_name },
	{ VAR_SERVER_TLS_SCACHE_DB, DEF_SERVER_TLS_SCACHE_DB, &var_server_tls_scache_db },
	{ VAR_CLIENT_TLS_SCACHE_DB, DEF_CLIENT_TLS_SCACHE_DB, &var_client_tls_scache_db },
	{ 0, 0, 0 },
    };
    static ACL_CONFIG_INT_TABLE int_table[] = {
	{ VAR_TLS_RAND_BYTES, DEF_TLS_RAND_BYTES, &var_tls_rand_bytes, 1, 0 },
	{ VAR_SERVER_TLS_LOGLEVEL, DEF_SERVER_TLS_LOGLEVEL, &var_server_tls_loglevel, 0, 0 },
	{ VAR_CLIENT_TLS_LOGLEVEL, DEF_CLIENT_TLS_LOGLEVEL, &var_client_tls_loglevel, 0, 0 },

	{ VAR_TLS_RESEED_PERIOD, DEF_TLS_RESEED_PERIOD, &var_tls_reseed_period, 1, 0 },
	{ VAR_TLS_PRNG_UPD_PERIOD, DEF_TLS_PRNG_UPD_PERIOD, &var_tls_prng_exch_period, 1, 0 },
	{ VAR_SERVER_TLS_SCACHTIME, DEF_SERVER_TLS_SCACHTIME, &var_server_tls_scache_timeout, 0, 0 },
	{ VAR_CLIENT_TLS_SCACHTIME, DEF_CLIENT_TLS_SCACHTIME, &var_client_tls_scache_timeout, 0, 0 },
	{ 0, 0, 0, 0, 0 },
    };
    acl_pthread_t tid;
    acl_pthread_attr_t attr;
    char  def_addr[] = "127.0.0.1:0";
    ACL_VSTREAM *server_stream;

    acl_get_app_conf_str_table(str_table);
    acl_get_app_conf_int_table(int_table);

    server_stream = acl_vstream_listen((addr == NULL || *addr == 0) ? def_addr : addr, 128);
    if (server_stream == NULL) {
    	tlsmgr_before_exit(NULL, NULL);
	acl_msg_error("%s(%d): listen addr(%s) error(%s)",
		myname, __LINE__, addr, acl_last_serror());
	return;
    }
    snprintf(var_tlsmgr_service, sizeof(var_tlsmgr_service),
	"inet:%s", ACL_VSTREAM_LOCAL(server_stream));

    tlsmgr_pre_init(NULL, NULL);
    tlsmgr_post_init(NULL, NULL);

    acl_pthread_attr_init(&attr);
#ifdef ACL_UNIX
    acl_pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
#else
    acl_pthread_attr_setdetachstate(&attr, ACL_PTHREAD_CREATE_DETACHED);
#endif

    acl_pthread_create(&tid, &attr, tlsmgr_thread_main, server_stream);
}

#endif  /* USE_TLS */

