/* * stunnel Universal SSL tunnel * Copyright (C) 1998-2012 Michal Trojnara * * 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, see . * * Linking stunnel statically or dynamically with other modules is making * a combined work based on stunnel. Thus, the terms and conditions of * the GNU General Public License cover the whole combination. * * In addition, as a special exception, the copyright holder of stunnel * gives you permission to combine stunnel with free software programs or * libraries that are released under the GNU LGPL and with code included * in the standard release of OpenSSL under the OpenSSL License (or * modified versions of such code, with unchanged license). You may copy * and distribute such a system following the terms of the GNU GPL for * stunnel and the licenses of the other code concerned. * * Note that people who make modified versions of stunnel are not obligated * to grant this special exception for their modified versions; it is their * choice whether to do so. The GNU General Public License gives permission * to release a modified version without this exception; this exception * also makes it possible to release a modified version which carries * forward this exception. */ #include "common.h" #include "prototypes.h" /**************************************** prototypes */ /* SNI */ #ifndef OPENSSL_NO_TLSEXT static int servername_cb(SSL *, int *, void *); #endif /* DH/ECDH initialization */ #ifndef OPENSSL_NO_DH static int init_dh(SERVICE_OPTIONS *); static DH *read_dh(char *); static DH *get_dh2048(void); #endif /* OPENSSL_NO_DH */ #ifndef OPENSSL_NO_ECDH static int init_ecdh(SERVICE_OPTIONS *); #endif /* USE_ECDH */ /* loading certificate */ static int load_certificate(SERVICE_OPTIONS *); #if defined(USE_WIN32) || OPENSSL_VERSION_NUMBER>=0x0090700fL static int password_cb(char *, int, int, void *); #endif /* session cache callbacks */ static int sess_new_cb(SSL *, SSL_SESSION *); static SSL_SESSION *sess_get_cb(SSL *, unsigned char *, int, int *); static void sess_remove_cb(SSL_CTX *, SSL_SESSION *); static void cache_transfer(SSL_CTX *, const unsigned int, const unsigned, const unsigned char *, const unsigned int, const unsigned char *, const unsigned int, unsigned char **, unsigned int *); /* info callbacks */ static void info_callback( #if OPENSSL_VERSION_NUMBER>=0x0090700fL const #endif SSL *, int, int); static void sslerror_queue(void); static void sslerror_log(unsigned long, char *); /**************************************** initialize section->ctx */ int context_init(SERVICE_OPTIONS *section) { /* init SSL context */ /* create SSL context */ if(section->option.client) section->ctx=SSL_CTX_new(section->client_method); else /* server mode */ section->ctx=SSL_CTX_new(section->server_method); if(!section->ctx) { sslerror("SSL_CTX_new"); return 1; /* FAILED */ } SSL_CTX_set_ex_data(section->ctx, opt_index, section); /* for callbacks */ /* initialize certificate verification */ if(load_certificate(section)) return 1; /* FAILED */ if(verify_init(section)) return 1; /* FAILED */ /* initialize DH/ECDH server mode */ if(!section->option.client) { #ifndef OPENSSL_NO_TLSEXT SSL_CTX_set_tlsext_servername_arg(section->ctx, section); SSL_CTX_set_tlsext_servername_callback(section->ctx, servername_cb); #endif /* OPENSSL_NO_TLSEXT */ #ifndef OPENSSL_NO_DH init_dh(section); /* ignore the result (errors are not critical) */ #endif /* OPENSSL_NO_DH */ #ifndef OPENSSL_NO_ECDH init_ecdh(section); /* ignore the result (errors are not critical) */ #endif /* OPENSSL_NO_ECDH */ } /* setup session cache */ if(!section->option.client) { unsigned int servname_len=strlen(section->servname); if(servname_len>SSL_MAX_SSL_SESSION_ID_LENGTH) servname_len=SSL_MAX_SSL_SESSION_ID_LENGTH; if(!SSL_CTX_set_session_id_context(section->ctx, (unsigned char *)section->servname, servname_len)) { sslerror("SSL_CTX_set_session_id_context"); return 1; /* FAILED */ } } SSL_CTX_set_session_cache_mode(section->ctx, SSL_SESS_CACHE_BOTH); SSL_CTX_set_timeout(section->ctx, section->session_timeout); if(section->option.sessiond) { SSL_CTX_sess_set_new_cb(section->ctx, sess_new_cb); SSL_CTX_sess_set_get_cb(section->ctx, sess_get_cb); SSL_CTX_sess_set_remove_cb(section->ctx, sess_remove_cb); } /* set info callback */ if(global_options.debug_level==LOG_DEBUG) /* performance optimization */ SSL_CTX_set_info_callback(section->ctx, info_callback); /* ciphers, options, mode */ if(section->cipher_list) if(!SSL_CTX_set_cipher_list(section->ctx, section->cipher_list)) { sslerror("SSL_CTX_set_cipher_list"); return 1; /* FAILED */ } s_log(LOG_DEBUG, "SSL options set: 0x%08lX", SSL_CTX_set_options(section->ctx, section->ssl_options)); #ifdef SSL_MODE_RELEASE_BUFFERS SSL_CTX_set_mode(section->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_RELEASE_BUFFERS); #else SSL_CTX_set_mode(section->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); #endif return 0; /* OK */ } /**************************************** SNI callback */ #ifndef OPENSSL_NO_TLSEXT static int servername_cb(SSL *ssl, int *ad, void *arg) { SERVICE_OPTIONS *section=(SERVICE_OPTIONS *)arg; const char *servername=SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); SERVERNAME_LIST *list; CLI *c; #ifdef USE_LIBWRAP char *accepted_address; #endif /* USE_LIBWRAP */ /* leave the alert type at SSL_AD_UNRECOGNIZED_NAME */ (void)ad; /* skip warning about unused parameter */ if(!section->servername_list_head) /* no virtual services defined */ return SSL_TLSEXT_ERR_OK; if(!servername) /* no SNI extension received from the client */ return SSL_TLSEXT_ERR_NOACK; for(list=section->servername_list_head; list; list=list->next) if(!strcasecmp(servername, list->servername)) { c=SSL_get_ex_data(ssl, cli_index); c->opt=list->opt; SSL_set_SSL_CTX(ssl, c->opt->ctx); SSL_set_verify(ssl, SSL_CTX_get_verify_mode(c->opt->ctx), SSL_CTX_get_verify_callback(c->opt->ctx)); s_log(LOG_NOTICE, "SNI: switched to section %s", c->opt->servname); #ifdef USE_LIBWRAP accepted_address=s_ntop(&c->peer_addr, c->peer_addr_len); libwrap_auth(c, accepted_address); /* retry on a service switch */ str_free(accepted_address); #endif /* USE_LIBWRAP */ return SSL_TLSEXT_ERR_OK; } s_log(LOG_ERR, "SNI: no service defined for server %s", servername); return SSL_TLSEXT_ERR_ALERT_FATAL; } /* TLSEXT callback return codes: * - SSL_TLSEXT_ERR_OK * - SSL_TLSEXT_ERR_ALERT_WARNING * - SSL_TLSEXT_ERR_ALERT_FATAL * - SSL_TLSEXT_ERR_NOACK */ #endif /* OPENSSL_NO_TLSEXT */ /**************************************** DH initialization */ #ifndef OPENSSL_NO_DH static int init_dh(SERVICE_OPTIONS *section) { DH *dh; dh=read_dh(section->cert); if(!dh) dh=get_dh2048(); if(!dh) { s_log(LOG_NOTICE, "DH initialization failed"); return 1; /* FAILED */ } SSL_CTX_set_tmp_dh(section->ctx, dh); s_log(LOG_DEBUG, "DH initialized with %d-bit key", 8*DH_size(dh)); DH_free(dh); return 0; /* OK */ } static DH *read_dh(char *cert) { DH *dh; BIO *bio; if(!cert) { s_log(LOG_DEBUG, "No certificate available to load DH parameters"); return NULL; /* FAILED */ } bio=BIO_new_file(cert, "r"); if(!bio) { sslerror("BIO_new_file"); return NULL; /* FAILED */ } dh=PEM_read_bio_DHparams(bio, NULL, NULL, NULL); BIO_free(bio); if(!dh) { while(ERR_get_error()) ; /* OpenSSL error queue cleanup */ s_log(LOG_DEBUG, "Could not load DH parameters from %s", cert); return NULL; /* FAILED */ } s_log(LOG_DEBUG, "Using DH parameters from %s", cert); return dh; } static DH *get_dh2048() { static unsigned char dh2048_p[]={ /* OpenSSL DH parameters */ 0xED,0x92,0x89,0x35,0x82,0x45,0x55,0xCB,0x3B,0xFB,0xA2,0x76, 0x5A,0x69,0x04,0x61,0xBF,0x21,0xF3,0xAB,0x53,0xD2,0xCD,0x21, 0xDA,0xFF,0x78,0x19,0x11,0x52,0xF1,0x0E,0xC1,0xE2,0x55,0xBD, 0x68,0x6F,0x68,0x00,0x53,0xB9,0x22,0x6A,0x2F,0xE4,0x9A,0x34, 0x1F,0x65,0xCC,0x59,0x32,0x8A,0xBD,0xB1,0xDB,0x49,0xED,0xDF, 0xA7,0x12,0x66,0xC3,0xFD,0x21,0x04,0x70,0x18,0xF0,0x7F,0xD6, 0xF7,0x58,0x51,0x19,0x72,0x82,0x7B,0x22,0xA9,0x34,0x18,0x1D, 0x2F,0xCB,0x21,0xCF,0x6D,0x92,0xAE,0x43,0xB6,0xA8,0x29,0xC7, 0x27,0xA3,0xCB,0x00,0xC5,0xF2,0xE5,0xFB,0x0A,0xA4,0x59,0x85, 0xA2,0xBD,0xAD,0x45,0xF0,0xB3,0xAD,0xF9,0xE0,0x81,0x35,0xEE, 0xD9,0x83,0xB3,0xCC,0xAE,0xEA,0xEB,0x66,0xE6,0xA9,0x57,0x66, 0xB9,0xF1,0x28,0xA5,0x3F,0x22,0x80,0xD7,0x0B,0xA6,0xF6,0x71, 0x93,0x9B,0x81,0x0E,0xF8,0x5A,0x90,0xE6,0xCC,0xCA,0x6F,0x66, 0x5F,0x7A,0xC0,0x10,0x1A,0x1E,0xF0,0xFC,0x2D,0xB6,0x08,0x0C, 0x62,0x28,0xB0,0xEC,0xDB,0x89,0x28,0xEE,0x0C,0xA8,0x3D,0x65, 0x94,0x69,0x16,0x69,0x53,0x3C,0x53,0x60,0x13,0xB0,0x2B,0xA7, 0xD4,0x82,0x87,0xAD,0x1C,0x72,0x9E,0x41,0x35,0xFC,0xC2,0x7C, 0xE9,0x51,0xDE,0x61,0x85,0xFC,0x19,0x9B,0x76,0x60,0x0F,0x33, 0xF8,0x6B,0xB3,0xCA,0x52,0x0E,0x29,0xC3,0x07,0xE8,0x90,0x16, 0xCC,0xCC,0x00,0x19,0xB6,0xAD,0xC3,0xA4,0x30,0x8B,0x33,0xA1, 0xAF,0xD8,0x8C,0x8D,0x9D,0x01,0xDB,0xA4,0xC4,0xDD,0x7F,0x0B, 0xBD,0x6F,0x38,0xC3,}; static unsigned char dh2048_g[]={0x02,}; DH *dh; dh=DH_new(); if(!dh) return NULL; dh->p=BN_bin2bn(dh2048_p, sizeof dh2048_p, NULL); dh->g=BN_bin2bn(dh2048_g, sizeof dh2048_g, NULL); if(!dh->p || !dh->g) { DH_free(dh); return NULL; } s_log(LOG_DEBUG, "Using hardcoded DH parameters"); return dh; } #endif /* OPENSSL_NO_DH */ /**************************************** ECDH initialization */ #ifndef OPENSSL_NO_ECDH static int init_ecdh(SERVICE_OPTIONS *section) { EC_KEY *ecdh; ecdh=EC_KEY_new_by_curve_name(section->curve); if(!ecdh) { s_log(LOG_ERR, "Unable to create curve %s", OBJ_nid2ln(section->curve)); return 1; /* FAILED */ } SSL_CTX_set_tmp_ecdh(section->ctx, ecdh); EC_KEY_free(ecdh); s_log(LOG_DEBUG, "ECDH initialized with curve %s", OBJ_nid2ln(section->curve)); return 0; /* OK */ } #endif /* OPENSSL_NO_ECDH */ /**************************************** loading certificate */ static int cache_initialized=0; static int load_certificate(SERVICE_OPTIONS *section) { int i, reason; UI_DATA ui_data; #ifdef HAVE_OSSL_ENGINE_H EVP_PKEY *pkey; UI_METHOD *ui_method; #endif struct stat st; /* buffer for stat */ /* check if certificate exists */ if(!section->key) /* key file not specified */ section->key=section->cert; #ifdef HAVE_OSSL_ENGINE_H if(!section->engine) #endif if(section->key) { if(stat(section->key, &st)) { ioerror(section->key); return 1; /* FAILED */ } #if !defined(USE_WIN32) && !defined(USE_OS2) if(st.st_mode & 7) s_log(LOG_WARNING, "Insecure file permissions on %s", section->key); #endif /* defined USE_WIN32 */ } if(!section->cert) /* no certificate specified */ return 0; /* OK */ ui_data.section=section; /* setup current section for callbacks */ s_log(LOG_DEBUG, "Certificate: %s", section->cert); if(!SSL_CTX_use_certificate_chain_file(section->ctx, section->cert)) { s_log(LOG_ERR, "Error reading certificate file: %s", section->cert); sslerror("SSL_CTX_use_certificate_chain_file"); return 1; /* FAILED */ } s_log(LOG_DEBUG, "Certificate loaded"); s_log(LOG_DEBUG, "Key file: %s", section->key); #if defined(USE_WIN32) || OPENSSL_VERSION_NUMBER>=0x0090700fL SSL_CTX_set_default_passwd_cb(section->ctx, password_cb); #endif #ifdef HAVE_OSSL_ENGINE_H #ifdef USE_WIN32 ui_method=UI_create_method("stunnel WIN32 UI"); UI_method_set_reader(ui_method, pin_cb); #else /* USE_WIN32 */ ui_method=UI_OpenSSL(); #endif /* USE_WIN32 */ if(section->engine) for(i=1; i<=3; i++) { pkey=ENGINE_load_private_key(section->engine, section->key, ui_method, &ui_data); if(!pkey) { reason=ERR_GET_REASON(ERR_peek_error()); if(i<=2 && (reason==7 || reason==160)) { /* wrong PIN */ sslerror_queue(); /* dump the error queue */ s_log(LOG_ERR, "Wrong PIN: retrying"); continue; } sslerror("ENGINE_load_private_key"); return 1; /* FAILED */ } if(SSL_CTX_use_PrivateKey(section->ctx, pkey)) break; /* success */ sslerror("SSL_CTX_use_PrivateKey"); return 1; /* FAILED */ } else #endif /* HAVE_OSSL_ENGINE_H */ for(i=0; i<=3; i++) { if(!i && !cache_initialized) continue; /* there is no cached value */ SSL_CTX_set_default_passwd_cb_userdata(section->ctx, i ? &ui_data : NULL); /* try the cached password first */ if(SSL_CTX_use_PrivateKey_file(section->ctx, section->key, SSL_FILETYPE_PEM)) break; reason=ERR_GET_REASON(ERR_peek_error()); if(i<=2 && reason==EVP_R_BAD_DECRYPT) { sslerror_queue(); /* dump the error queue */ s_log(LOG_ERR, "Wrong pass phrase: retrying"); continue; } sslerror("SSL_CTX_use_PrivateKey_file"); return 1; /* FAILED */ } if(!SSL_CTX_check_private_key(section->ctx)) { sslerror("Private key does not match the certificate"); return 1; /* FAILED */ } s_log(LOG_DEBUG, "Private key loaded"); return 0; /* OK */ } #if defined(USE_WIN32) || OPENSSL_VERSION_NUMBER>=0x0090700fL static int password_cb(char *buf, int size, int rwflag, void *userdata) { static char cache[PEM_BUFSIZE]; int len; if(size>PEM_BUFSIZE) size=PEM_BUFSIZE; if(userdata) { /* prompt the user */ #ifdef USE_WIN32 len=passwd_cb(buf, size, rwflag, userdata); #else /* PEM_def_callback is defined in OpenSSL 0.9.7 and later */ len=PEM_def_callback(buf, size, rwflag, NULL); #endif memcpy(cache, buf, size); /* save in cache */ cache_initialized=1; } else { /* try the cached value */ strncpy(buf, cache, size); buf[size-1]='\0'; len=strlen(buf); } return len; } #endif /**************************************** session cache callbacks */ #define CACHE_CMD_NEW 0x00 #define CACHE_CMD_GET 0x01 #define CACHE_CMD_REMOVE 0x02 #define CACHE_RESP_ERR 0x80 #define CACHE_RESP_OK 0x81 static int sess_new_cb(SSL *ssl, SSL_SESSION *sess) { unsigned char *val, *val_tmp; int val_len; val_len=i2d_SSL_SESSION(sess, NULL); val_tmp=val=str_alloc(val_len); i2d_SSL_SESSION(sess, &val_tmp); cache_transfer(ssl->ctx, CACHE_CMD_NEW, SSL_SESSION_get_timeout(sess), sess->session_id, sess->session_id_length, val, val_len, NULL, NULL); str_free(val); return 1; /* leave the session in local cache for reuse */ } static SSL_SESSION *sess_get_cb(SSL *ssl, unsigned char *key, int key_len, int *do_copy) { unsigned char *val, *val_tmp=NULL; unsigned int val_len=0; SSL_SESSION *sess; *do_copy = 0; /* allow the session to be freed autmatically */ cache_transfer(ssl->ctx, CACHE_CMD_GET, 0, key, key_len, NULL, 0, &val, &val_len); if(!val) return NULL; val_tmp=val; sess=d2i_SSL_SESSION(NULL, #if OPENSSL_VERSION_NUMBER>=0x0090800fL (const unsigned char **) #endif /* OpenSSL version >= 0.8.0 */ &val_tmp, val_len); str_free(val); return sess; } static void sess_remove_cb(SSL_CTX *ctx, SSL_SESSION *sess) { cache_transfer(ctx, CACHE_CMD_REMOVE, 0, sess->session_id, sess->session_id_length, NULL, 0, NULL, NULL); } #define MAX_VAL_LEN 512 typedef struct { u_char version, type; u_short timeout; u_char key[SSL_MAX_SSL_SESSION_ID_LENGTH]; u_char val[MAX_VAL_LEN]; } CACHE_PACKET; static void cache_transfer(SSL_CTX *ctx, const unsigned int type, const unsigned int timeout, const unsigned char *key, const unsigned int key_len, const unsigned char *val, const unsigned int val_len, unsigned char **ret, unsigned int *ret_len) { char session_id_txt[2*SSL_MAX_SSL_SESSION_ID_LENGTH+1]; const char hex[16]="0123456789ABCDEF"; const char *type_description[]={"new", "get", "remove"}; unsigned int i; int s, len; struct timeval t; CACHE_PACKET *packet; SERVICE_OPTIONS *section; if(ret) /* set error as the default result if required */ *ret=NULL; /* log the request information */ for(i=0; i>4]; session_id_txt[2*i+1]=hex[key[i]&0x0f]; } session_id_txt[2*i]='\0'; s_log(LOG_INFO, "cache_transfer: request=%s, timeout=%u, id=%s, length=%d", type_description[type], timeout, session_id_txt, val_len); /* allocate UDP packet buffer */ if(key_len>SSL_MAX_SSL_SESSION_ID_LENGTH) { s_log(LOG_ERR, "cache_transfer: session id too big (%d bytes)", key_len); return; } if(val_len>MAX_VAL_LEN) { s_log(LOG_ERR, "cache_transfer: encoded session too big (%d bytes)", key_len); return; } packet=str_alloc(sizeof(CACHE_PACKET)); /* setup packet */ packet->version=1; packet->type=type; packet->timeout=htons((u_short)(timeout<64800?timeout:64800));/* 18 hours */ memcpy(packet->key, key, key_len); memcpy(packet->val, val, val_len); /* create the socket */ s=s_socket(AF_INET, SOCK_DGRAM, 0, 0, "cache_transfer: socket"); if(s<0) { str_free(packet); return; } /* retrieve pointer to the section structure of this ctx */ section=SSL_CTX_get_ex_data(ctx, opt_index); if(sendto(s, (void *)packet, sizeof(CACHE_PACKET)-MAX_VAL_LEN+val_len, 0, §ion->sessiond_addr.sa, addr_len(§ion->sessiond_addr))<0) { sockerror("cache_transfer: sendto"); closesocket(s); str_free(packet); return; } if(!ret || !ret_len) { /* no response is required */ closesocket(s); str_free(packet); return; } /* set recvfrom timeout to 200ms */ t.tv_sec=0; t.tv_usec=200; if(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (void *)&t, sizeof t)<0) { sockerror("cache_transfer: setsockopt SO_RCVTIMEO"); closesocket(s); str_free(packet); return; } /* retrieve response */ len=recv(s, (void *)packet, sizeof(CACHE_PACKET), 0); closesocket(s); if(len<0) { if(get_last_socket_error()==S_EWOULDBLOCK || get_last_socket_error()==S_EAGAIN) s_log(LOG_INFO, "cache_transfer: recv timeout"); else sockerror("cache_transfer: recv"); str_free(packet); return; } /* parse results */ if(len<(int)sizeof(CACHE_PACKET)-MAX_VAL_LEN || /* too short */ packet->version!=1 || /* wrong version */ memcmp(packet->key, key, key_len)) { /* wrong session id */ s_log(LOG_DEBUG, "cache_transfer: malformed packet received"); str_free(packet); return; } if(packet->type!=CACHE_RESP_OK) { s_log(LOG_INFO, "cache_transfer: session not found"); str_free(packet); return; } *ret_len=len-(sizeof(CACHE_PACKET)-MAX_VAL_LEN); *ret=str_alloc(*ret_len); s_log(LOG_INFO, "cache_transfer: session found"); memcpy(*ret, packet->val, *ret_len); str_free(packet); } /**************************************** informational callback */ static void info_callback( #if OPENSSL_VERSION_NUMBER>=0x0090700fL const #endif SSL *ssl, int where, int ret) { if(where & SSL_CB_LOOP) { s_log(LOG_DEBUG, "SSL state (%s): %s", where & SSL_ST_CONNECT ? "connect" : where & SSL_ST_ACCEPT ? "accept" : "undefined", SSL_state_string_long(ssl)); } else if(where & SSL_CB_ALERT) { s_log(LOG_DEBUG, "SSL alert (%s): %s: %s", where & SSL_CB_READ ? "read" : "write", SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); } else if(where==SSL_CB_HANDSHAKE_DONE) { s_log(LOG_DEBUG, "%4ld items in the session cache", SSL_CTX_sess_number(ssl->ctx)); s_log(LOG_DEBUG, "%4ld client connects (SSL_connect())", SSL_CTX_sess_connect(ssl->ctx)); s_log(LOG_DEBUG, "%4ld client connects that finished", SSL_CTX_sess_connect_good(ssl->ctx)); s_log(LOG_DEBUG, "%4ld client renegotiations requested", SSL_CTX_sess_connect_renegotiate(ssl->ctx)); s_log(LOG_DEBUG, "%4ld server connects (SSL_accept())", SSL_CTX_sess_accept(ssl->ctx)); s_log(LOG_DEBUG, "%4ld server connects that finished", SSL_CTX_sess_accept_good(ssl->ctx)); s_log(LOG_DEBUG, "%4ld server renegotiations requested", SSL_CTX_sess_accept_renegotiate(ssl->ctx)); s_log(LOG_DEBUG, "%4ld session cache hits", SSL_CTX_sess_hits(ssl->ctx)); s_log(LOG_DEBUG, "%4ld external session cache hits", SSL_CTX_sess_cb_hits(ssl->ctx)); s_log(LOG_DEBUG, "%4ld session cache misses", SSL_CTX_sess_misses(ssl->ctx)); s_log(LOG_DEBUG, "%4ld session cache timeouts", SSL_CTX_sess_timeouts(ssl->ctx)); } } /**************************************** SSL error reporting */ void sslerror(char *txt) { /* OpenSSL error handler */ unsigned long err; err=ERR_get_error(); if(err) { sslerror_queue(); sslerror_log(err, txt); } else { s_log(LOG_ERR, "%s: Peer suddenly disconnected", txt); } } static void sslerror_queue(void) { /* recursive dump of the error queue */ unsigned long err; err=ERR_get_error(); if(err) { sslerror_queue(); sslerror_log(err, "error queue"); } } static void sslerror_log(unsigned long err, char *txt) { char *error_string; error_string=str_alloc(120); ERR_error_string(err, error_string); s_log(LOG_ERR, "%s: %lX: %s", txt, err, error_string); str_free(error_string); } /* end of ctx.c */