mirror of
https://gitee.com/acl-dev/acl.git
synced 2024-12-01 11:27:40 +08:00
868 lines
25 KiB
C
868 lines
25 KiB
C
/*++
|
|
* NAME
|
|
* tls_misc 3
|
|
* SUMMARY
|
|
* miscellaneous TLS support routines
|
|
* SYNOPSIS
|
|
* #include <tls.h>
|
|
* #include <tls_private.h>
|
|
*
|
|
* char *var_tls_high_clist;
|
|
* char *var_tls_medium_clist;
|
|
* char *var_tls_low_clist;
|
|
* char *var_tls_export_clist;
|
|
* char *var_tls_null_clist;
|
|
* char *var_tls_eecdh_strong;
|
|
* char *var_tls_eecdh_ultra;
|
|
* int var_tls_daemon_rand_bytes;
|
|
*
|
|
* TLS_APPL_STATE *tls_alloc_app_context(ssl_ctx)
|
|
* SSL_CTX *ssl_ctx;
|
|
*
|
|
* void tls_free_app_context(app_ctx)
|
|
* void *app_ctx;
|
|
*
|
|
* TLS_SESS_STATE *tls_alloc_sess_context(log_level, namaddr)
|
|
* int log_level;
|
|
* const char *namaddr;
|
|
*
|
|
* void tls_free_context(TLScontext)
|
|
* TLS_SESS_STATE *TLScontext;
|
|
*
|
|
* void tls_check_version()
|
|
*
|
|
* long tls_bug_bits()
|
|
*
|
|
* void tls_param_init()
|
|
*
|
|
* int tls_protocol_mask(plist)
|
|
* const char *plist;
|
|
*
|
|
* int tls_cipher_grade(name)
|
|
* const char *name;
|
|
*
|
|
* const char *str_tls_cipher_grade(grade)
|
|
* int grade;
|
|
*
|
|
* const char *tls_set_ciphers(app_ctx, context, grade, exclusions)
|
|
* TLS_APPL_STATE *app_ctx;
|
|
* const char *context;
|
|
* int grade;
|
|
* const char *exclusions;
|
|
*
|
|
* void tls_print_errors()
|
|
*
|
|
* void tls_info_callback(ssl, where, ret)
|
|
* const SSL *ssl; #unused
|
|
* int where;
|
|
* int ret;
|
|
*
|
|
* long tls_bio_dump_cb(bio, cmd, argp, argi, argl, ret)
|
|
* BIO *bio;
|
|
* int cmd;
|
|
* const char *argp;
|
|
* int argi;
|
|
* long argl; #unused
|
|
* long ret;
|
|
* DESCRIPTION
|
|
* This module implements routines that support the TLS client
|
|
* and server internals.
|
|
*
|
|
* tls_alloc_app_context() creates an application context that
|
|
* holds the SSL context for the application and related cached state.
|
|
*
|
|
* tls_free_app_context() deallocates the application context and its
|
|
* contents (the application context is stored outside the TLS library).
|
|
*
|
|
* tls_alloc_sess_context() creates an initialized TLS session context
|
|
* structure with the specified log mask and peer name[addr].
|
|
*
|
|
* tls_free_context() destroys a TLScontext structure
|
|
* together with OpenSSL structures that are attached to it.
|
|
*
|
|
* tls_check_version() logs a warning when the run-time OpenSSL
|
|
* library differs in its major, minor or micro number from
|
|
* the compile-time OpenSSL headers.
|
|
*
|
|
* tls_bug_bits() returns the bug compatibility mask appropriate
|
|
* for the run-time library. Some of the bug work-arounds are
|
|
* not appropriate for some library versions.
|
|
*
|
|
* tls_param_init() loads main.cf parameters used internally in
|
|
* TLS library. Any errors are fatal.
|
|
*
|
|
* tls_protocol_mask() returns a bitmask of excluded protocols, given
|
|
* a list (plist) of protocols to include or (preceded by a '!') exclude.
|
|
* If "plist" contains invalid protocol names, TLS_PROTOCOL_INVALID is
|
|
* returned and no warning is logged.
|
|
*
|
|
* tls_cipher_grade() converts a case-insensitive cipher grade
|
|
* name (high, medium, low, export, null) to the corresponding
|
|
* TLS_CIPHER_ constant. When the input specifies an unrecognized
|
|
* grade, tls_cipher_grade() logs no warning, and returns
|
|
* TLS_CIPHER_NONE.
|
|
*
|
|
* str_tls_cipher_grade() converts a cipher grade to a name.
|
|
* When the input specifies an undefined grade, str_tls_cipher_grade()
|
|
* logs no warning, returns a null pointer.
|
|
*
|
|
* tls_set_ciphers() generates a cipher list from the specified
|
|
* grade, minus any ciphers specified via a list of exclusions.
|
|
* The cipherlist is applied to the supplied SSL context if it
|
|
* is different from the most recently applied value. The return
|
|
* value is the cipherlist used and is overwritten upon each call.
|
|
* When the input is invalid, tls_set_ciphers() logs a warning with
|
|
* the specified context, and returns a null pointer result.
|
|
*
|
|
* tls_print_errors() queries the OpenSSL error stack,
|
|
* logs the error messages, and clears the error stack.
|
|
*
|
|
* tls_info_callback() is a call-back routine for the
|
|
* SSL_CTX_set_info_callback() routine. It logs SSL events
|
|
* to the Postfix logfile.
|
|
*
|
|
* tls_bio_dump_cb() is a call-back routine for the
|
|
* BIO_set_callback() routine. It logs SSL content to the
|
|
* Postfix logfile.
|
|
* LICENSE
|
|
* .ad
|
|
* .fi
|
|
* This software is free. You can do with it whatever you want.
|
|
* The original author kindly requests that you acknowledge
|
|
* the use of his software.
|
|
* AUTHOR(S)
|
|
* Originally written by:
|
|
* Lutz Jaenicke
|
|
* BTU Cottbus
|
|
* Allgemeine Elektrotechnik
|
|
* Universitaetsplatz 3-4
|
|
* D-03044 Cottbus, Germany
|
|
*
|
|
* Updated by:
|
|
* Wietse Venema
|
|
* IBM T.J. Watson Research
|
|
* P.O. Box 704
|
|
* Yorktown Heights, NY 10598, USA
|
|
*
|
|
* Victor Duchovni
|
|
* Morgan Stanley
|
|
*--*/
|
|
|
|
/* System library. */
|
|
|
|
#include "StdAfx.h"
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
|
|
#ifdef USE_TLS
|
|
|
|
/*
|
|
* TLS library.
|
|
*/
|
|
#include "tls.h"
|
|
#include "tls_params.h"
|
|
#include "tls_private.h"
|
|
|
|
/* Application-specific. */
|
|
|
|
/*
|
|
* Index to attach TLScontext pointers to SSL objects, so that they can be
|
|
* accessed by call-back routines.
|
|
*/
|
|
int TLScontext_index = -1;
|
|
|
|
/*
|
|
* Protocol name <=> mask conversion.
|
|
*/
|
|
static const NAME_CODE protocol_table[] = {
|
|
{ SSL_TXT_SSLV2, TLS_PROTOCOL_SSLv2 },
|
|
{ SSL_TXT_SSLV3, TLS_PROTOCOL_SSLv3 },
|
|
{ SSL_TXT_TLSV1, TLS_PROTOCOL_TLSv1 },
|
|
{ 0, TLS_PROTOCOL_INVALID },
|
|
};
|
|
|
|
/*
|
|
* Ciphersuite name <=> code conversion.
|
|
*/
|
|
const NAME_CODE tls_cipher_grade_table[] = {
|
|
{ "high", TLS_CIPHER_HIGH },
|
|
{ "medium", TLS_CIPHER_MEDIUM },
|
|
{ "low", TLS_CIPHER_LOW },
|
|
{ "export", TLS_CIPHER_EXPORT },
|
|
{ "null", TLS_CIPHER_NULL },
|
|
{ "invalid", TLS_CIPHER_NONE },
|
|
{ 0, TLS_CIPHER_NONE },
|
|
};
|
|
|
|
/*
|
|
* Parsed OpenSSL version number.
|
|
*/
|
|
typedef struct {
|
|
int major;
|
|
int minor;
|
|
int micro;
|
|
int patch;
|
|
int status;
|
|
} TLS_VINFO;
|
|
|
|
/*
|
|
* OpenSSL adopted the cipher selection patch, so we don't expect any more
|
|
* broken ciphers other than AES and CAMELLIA.
|
|
*/
|
|
typedef struct {
|
|
const char *ssl_name;
|
|
const int alg_bits;
|
|
const char *evp_name;
|
|
} cipher_probe_t;
|
|
|
|
static const cipher_probe_t cipher_probes[] = {
|
|
{ "AES", 256, "AES-256-CBC" },
|
|
{ "CAMELLIA", 256, "CAMELLIA-256-CBC" },
|
|
{ 0, 0, 0 },
|
|
};
|
|
|
|
static void free_argv_fn(void *arg)
|
|
{
|
|
ACL_ARGV *argv = (ACL_ARGV*) arg;
|
|
acl_argv_free(argv);
|
|
}
|
|
|
|
/* tls_exclude_missing - Append exclusions for missing ciphers */
|
|
|
|
static const char *tls_exclude_missing(SSL_CTX *ctx, ACL_VSTRING *buf)
|
|
{
|
|
const char *myname = "tls_exclude_missing";
|
|
static __thread ACL_ARGV *exclude; /* Cached */
|
|
SSL *s = 0;
|
|
|
|
STACK_OF(SSL_CIPHER) * ciphers;
|
|
SSL_CIPHER *c;
|
|
const cipher_probe_t *probe;
|
|
int alg_bits;
|
|
int num;
|
|
int i;
|
|
|
|
/*
|
|
* Process a list of probes which specify:
|
|
*
|
|
* An SSL cipher-suite name for a family of ciphers that use the same
|
|
* symmetric algorithm at two or more key sizes, typically 128/256 bits.
|
|
*
|
|
* The key size (typically 256) that OpenSSL fails to check, and assumes
|
|
* available when another key size (typically 128) is usable.
|
|
*
|
|
* The OpenSSL name of the symmetric algorithm associated with the SSL
|
|
* cipher-suite. Typically, this is MUMBLE-256-CBC, where "MUMBLE" is the
|
|
* name of the SSL cipher-suite that use the MUMBLE symmetric algorithm.
|
|
* On systems that support the required encryption algorithm, the name is
|
|
* listed in the output of "openssl list-cipher-algorithms".
|
|
*
|
|
* When an encryption algorithm is not available at the given key size but
|
|
* the corresponding OpenSSL cipher-suite contains ciphers that have have
|
|
* this key size, the problem ciphers are explicitly disabled in Postfix.
|
|
* The list is cached in the static "exclude" array.
|
|
*/
|
|
if (exclude == 0) {
|
|
exclude = acl_argv_alloc(1);
|
|
acl_pthread_atexit_add(exclude, free_argv_fn);
|
|
|
|
/*
|
|
* Iterate over the probe list
|
|
*/
|
|
for (probe = cipher_probes; probe->ssl_name; ++probe) {
|
|
/* No exclusions if evp_name is a valid algorithm */
|
|
if (EVP_get_cipherbyname(probe->evp_name))
|
|
continue;
|
|
|
|
/*
|
|
* Sadly there is no SSL_CTX_get_ciphers() interface, so we are
|
|
* forced to allocate and free an SSL object. Fatal error if we
|
|
* can't allocate the SSL object.
|
|
*/
|
|
ERR_clear_error();
|
|
if (s == 0 && (s = SSL_new(ctx)) == 0) {
|
|
tls_print_errors();
|
|
acl_msg_fatal("%s: error allocating SSL object", myname);
|
|
}
|
|
|
|
/*
|
|
* Cipher is not supported by libcrypto, nothing to do if also
|
|
* not supported by libssl. Flush the OpenSSL error stack.
|
|
*
|
|
* XXX: There may be additional places in pre-existing code where
|
|
* SSL errors are generated and ignored, that require a similar
|
|
* "flush". Better yet, is to always flush before calls that run
|
|
* tls_print_errors() on failure.
|
|
*
|
|
* Contrary to documentation, on SunOS 5.10 SSL_set_cipher_list()
|
|
* returns success with no ciphers selected, when this happens
|
|
* SSL_get_ciphers() produces a stack with 0 elements!
|
|
*/
|
|
if (SSL_set_cipher_list(s, probe->ssl_name) == 0
|
|
|| (ciphers = SSL_get_ciphers(s)) == 0
|
|
|| (num = sk_SSL_CIPHER_num(ciphers)) == 0) {
|
|
ERR_clear_error(); /* flush any generated errors */
|
|
continue;
|
|
}
|
|
for (i = 0; i < num; ++i) {
|
|
c = sk_SSL_CIPHER_value(ciphers, i);
|
|
(void) SSL_CIPHER_get_bits(c, &alg_bits);
|
|
if (alg_bits == probe->alg_bits)
|
|
acl_argv_add(exclude, SSL_CIPHER_get_name(c), ACL_ARGV_END);
|
|
}
|
|
}
|
|
if (s != 0)
|
|
SSL_free(s);
|
|
}
|
|
for (i = 0; i < exclude->argc; ++i)
|
|
acl_vstring_sprintf_append(buf, ":!%s", exclude->argv[i]);
|
|
return (acl_vstring_str(buf));
|
|
}
|
|
|
|
/* tls_apply_cipher_list - update SSL_CTX cipher list */
|
|
|
|
static const char *tls_apply_cipher_list(TLS_APPL_STATE *app_ctx,
|
|
const char *context, ACL_VSTRING *spec)
|
|
{
|
|
const char *new = tls_exclude_missing(app_ctx->ssl_ctx, spec);
|
|
|
|
ERR_clear_error();
|
|
if (SSL_CTX_set_cipher_list(app_ctx->ssl_ctx, new) == 0) {
|
|
tls_print_errors();
|
|
acl_vstring_sprintf(app_ctx->why, "invalid %s cipher list: \"%s\"",
|
|
context, new);
|
|
return (0);
|
|
}
|
|
return (new);
|
|
}
|
|
|
|
/* tls_protocol_mask - Bitmask of protocols to exclude */
|
|
|
|
int tls_protocol_mask(const char *plist)
|
|
{
|
|
char *save;
|
|
char *tok;
|
|
char *cp;
|
|
int code;
|
|
int exclude = 0;
|
|
int include = 0;
|
|
|
|
save = cp = acl_mystrdup(plist);
|
|
while ((tok = acl_mystrtok(&cp, "\t\n\r ,:")) != 0) {
|
|
if (*tok == '!')
|
|
exclude |= code =
|
|
name_code(protocol_table, NAME_CODE_FLAG_NONE, ++tok);
|
|
else
|
|
include |= code =
|
|
name_code(protocol_table, NAME_CODE_FLAG_NONE, tok);
|
|
if (code == TLS_PROTOCOL_INVALID)
|
|
return TLS_PROTOCOL_INVALID;
|
|
}
|
|
acl_myfree(save);
|
|
|
|
/*
|
|
* When the include list is empty, use only the explicit exclusions.
|
|
* Otherwise, also exclude the complement of the include list from the
|
|
* built-in list of known protocols. There is no way to exclude protocols
|
|
* we don't know about at compile time, and this is unavoidable because
|
|
* the OpenSSL API works with compile-time *exclusion* bit-masks.
|
|
*/
|
|
return (include ? (exclude | (TLS_KNOWN_PROTOCOLS & ~include)) : exclude);
|
|
}
|
|
|
|
static void free_vstring_fn(void *arg)
|
|
{
|
|
ACL_VSTRING *s = (ACL_VSTRING*) arg;
|
|
|
|
acl_vstring_free(s);
|
|
}
|
|
|
|
/* tls_set_ciphers - Set SSL context cipher list */
|
|
|
|
const char *tls_set_ciphers(TLS_APPL_STATE *app_ctx, const char *context,
|
|
const char *grade, const char *exclusions)
|
|
{
|
|
const char *myname = "tls_set_ciphers";
|
|
static __thread ACL_VSTRING *buf;
|
|
int new_grade;
|
|
char *save;
|
|
char *cp;
|
|
char *tok;
|
|
const char *new_list;
|
|
|
|
new_grade = tls_cipher_grade(grade);
|
|
if (new_grade == TLS_CIPHER_NONE) {
|
|
acl_vstring_sprintf(app_ctx->why, "invalid %s cipher grade: \"%s\"",
|
|
context, grade);
|
|
return (0);
|
|
}
|
|
if (buf == 0) {
|
|
buf = acl_vstring_alloc(10);
|
|
acl_pthread_atexit_add(buf, free_vstring_fn);
|
|
}
|
|
ACL_VSTRING_RESET(buf);
|
|
|
|
/*
|
|
* Given cached state and identical input, we return the same result.
|
|
*/
|
|
if (app_ctx->cipher_list) {
|
|
if (new_grade == app_ctx->cipher_grade
|
|
&& strcmp(app_ctx->cipher_exclusions, exclusions) == 0)
|
|
return (app_ctx->cipher_list);
|
|
|
|
/* Change required, flush cached state */
|
|
app_ctx->cipher_grade = TLS_CIPHER_NONE;
|
|
|
|
acl_myfree(app_ctx->cipher_exclusions);
|
|
app_ctx->cipher_exclusions = 0;
|
|
|
|
acl_myfree(app_ctx->cipher_list);
|
|
app_ctx->cipher_list = 0;
|
|
}
|
|
switch (new_grade) {
|
|
case TLS_CIPHER_HIGH:
|
|
acl_vstring_strcpy(buf, var_tls_high_clist);
|
|
break;
|
|
case TLS_CIPHER_MEDIUM:
|
|
acl_vstring_strcpy(buf, var_tls_medium_clist);
|
|
break;
|
|
case TLS_CIPHER_LOW:
|
|
acl_vstring_strcpy(buf, var_tls_low_clist);
|
|
break;
|
|
case TLS_CIPHER_EXPORT:
|
|
acl_vstring_strcpy(buf, var_tls_export_clist);
|
|
break;
|
|
case TLS_CIPHER_NULL:
|
|
acl_vstring_strcpy(buf, var_tls_null_clist);
|
|
break;
|
|
default:
|
|
|
|
/*
|
|
* The caller MUST provide a valid cipher grade
|
|
*/
|
|
acl_msg_panic("invalid %s cipher grade: %d", context, new_grade);
|
|
}
|
|
|
|
/*
|
|
* The base lists for each grade can't be empty.
|
|
*/
|
|
if (ACL_VSTRING_LEN(buf) == 0)
|
|
acl_msg_panic("%s: empty \"%s\" cipherlist", myname, grade);
|
|
|
|
/*
|
|
* Apply locally-specified exclusions.
|
|
*/
|
|
#define CIPHER_SEP "\t\n\r ,:"
|
|
if (exclusions != 0) {
|
|
cp = save = acl_mystrdup(exclusions);
|
|
while ((tok = acl_mystrtok(&cp, CIPHER_SEP)) != 0) {
|
|
|
|
/*
|
|
* Can't exclude ciphers that start with modifiers.
|
|
*/
|
|
if (strchr("!+-@", *tok)) {
|
|
acl_vstring_sprintf(app_ctx->why,
|
|
"invalid unary '!+-@' in %s cipher "
|
|
"exclusion: \"%s\"", context, tok);
|
|
return (0);
|
|
}
|
|
acl_vstring_sprintf_append(buf, ":!%s", tok);
|
|
}
|
|
acl_myfree(save);
|
|
}
|
|
if ((new_list = tls_apply_cipher_list(app_ctx, context, buf)) == 0)
|
|
return (0);
|
|
|
|
/* Cache new state */
|
|
app_ctx->cipher_grade = new_grade;
|
|
app_ctx->cipher_exclusions = acl_mystrdup(exclusions);
|
|
|
|
return (app_ctx->cipher_list = acl_mystrdup(new_list));
|
|
}
|
|
|
|
/* tls_alloc_app_context - allocate TLS application context */
|
|
|
|
TLS_APPL_STATE *tls_alloc_app_context(SSL_CTX *ssl_ctx)
|
|
{
|
|
TLS_APPL_STATE *app_ctx;
|
|
|
|
app_ctx = (TLS_APPL_STATE *) acl_mymalloc(sizeof(*app_ctx));
|
|
|
|
memset((char *) app_ctx, 0, sizeof(*app_ctx));
|
|
app_ctx->ssl_ctx = ssl_ctx;
|
|
|
|
/* See also: cache purging code in tls_set_ciphers(). */
|
|
app_ctx->cipher_grade = TLS_CIPHER_NONE;
|
|
app_ctx->cipher_exclusions = 0;
|
|
app_ctx->cipher_list = 0;
|
|
app_ctx->cache_type = 0;
|
|
app_ctx->why = acl_vstring_alloc(1);
|
|
|
|
return (app_ctx);
|
|
}
|
|
|
|
/* tls_free_app_context - Free TLS application context */
|
|
|
|
void tls_free_app_context(TLS_APPL_STATE *app_ctx)
|
|
{
|
|
if (app_ctx->ssl_ctx)
|
|
SSL_CTX_free(app_ctx->ssl_ctx);
|
|
if (app_ctx->cache_type)
|
|
acl_myfree(app_ctx->cache_type);
|
|
/* See also: cache purging code in tls_set_ciphers(). */
|
|
if (app_ctx->cipher_exclusions)
|
|
acl_myfree(app_ctx->cipher_exclusions);
|
|
if (app_ctx->cipher_list)
|
|
acl_myfree(app_ctx->cipher_list);
|
|
if (app_ctx->why)
|
|
acl_vstring_free(app_ctx->why);
|
|
acl_myfree(app_ctx);
|
|
}
|
|
|
|
/* tls_alloc_sess_context - allocate TLS session context */
|
|
|
|
TLS_SESS_STATE *tls_alloc_sess_context(int log_level, const char *namaddr)
|
|
{
|
|
TLS_SESS_STATE *TLScontext;
|
|
|
|
/*
|
|
* PORTABILITY: Do not assume that null pointers are all-zero bits. Use
|
|
* explicit assignments to initialize pointers.
|
|
*
|
|
* See the C language FAQ item 5.17, or if you have time to burn,
|
|
* http://www.google.com/search?q=zero+bit+null+pointer
|
|
*
|
|
* However, it's OK to use memset() to zero integer values.
|
|
*/
|
|
TLScontext = (TLS_SESS_STATE *) acl_mymalloc(sizeof(TLS_SESS_STATE));
|
|
memset((char *) TLScontext, 0, sizeof(*TLScontext));
|
|
TLScontext->con = 0;
|
|
TLScontext->internal_bio = 0;
|
|
TLScontext->network_bio = 0;
|
|
TLScontext->cache_type = 0;
|
|
TLScontext->serverid = 0;
|
|
TLScontext->peer_CN = 0;
|
|
TLScontext->issuer_CN = 0;
|
|
TLScontext->peer_fingerprint = 0;
|
|
TLScontext->protocol = 0;
|
|
TLScontext->cipher_name = 0;
|
|
TLScontext->log_level = log_level;
|
|
TLScontext->namaddr = acl_lowercase(acl_mystrdup(namaddr));
|
|
|
|
return (TLScontext);
|
|
}
|
|
|
|
/* tls_free_context - deallocate TLScontext and members */
|
|
|
|
void tls_free_context(TLS_SESS_STATE *TLScontext)
|
|
{
|
|
/*
|
|
* Free the SSL structure and the BIOs. Warning: the internal_bio is
|
|
* connected to the SSL structure and is automatically freed with it. Do
|
|
* not free it again (core dump)!! Only free the network_bio.
|
|
*/
|
|
if (TLScontext->con != 0)
|
|
SSL_free(TLScontext->con);
|
|
if (TLScontext->network_bio)
|
|
BIO_free(TLScontext->network_bio);
|
|
|
|
if (TLScontext->namaddr)
|
|
acl_myfree(TLScontext->namaddr);
|
|
if (TLScontext->serverid)
|
|
acl_myfree(TLScontext->serverid);
|
|
|
|
if (TLScontext->peer_CN)
|
|
acl_myfree(TLScontext->peer_CN);
|
|
if (TLScontext->issuer_CN)
|
|
acl_myfree(TLScontext->issuer_CN);
|
|
if (TLScontext->peer_fingerprint)
|
|
acl_myfree(TLScontext->peer_fingerprint);
|
|
|
|
acl_myfree(TLScontext);
|
|
}
|
|
|
|
/* tls_version_split - Split OpenSSL version number into major, minor, ... */
|
|
|
|
static void tls_version_split(long version, TLS_VINFO *info)
|
|
{
|
|
|
|
/*
|
|
* OPENSSL_VERSION_NUMBER(3):
|
|
*
|
|
* OPENSSL_VERSION_NUMBER is a numeric release version identifier:
|
|
*
|
|
* MMNNFFPPS: major minor fix patch status
|
|
*
|
|
* The status nibble has one of the values 0 for development, 1 to e for
|
|
* betas 1 to 14, and f for release. Parsed OpenSSL version number. for
|
|
* example
|
|
*
|
|
* 0x000906000 == 0.9.6 dev 0x000906023 == 0.9.6b beta 3 0x00090605f ==
|
|
* 0.9.6e release
|
|
*
|
|
* Versions prior to 0.9.3 have identifiers < 0x0930. Versions between
|
|
* 0.9.3 and 0.9.5 had a version identifier with this interpretation:
|
|
*
|
|
* MMNNFFRBB major minor fix final beta/patch
|
|
*
|
|
* for example
|
|
*
|
|
* 0x000904100 == 0.9.4 release 0x000905000 == 0.9.5 dev
|
|
*
|
|
* Version 0.9.5a had an interim interpretation that is like the current
|
|
* one, except the patch level got the highest bit set, to keep continu-
|
|
* ity. The number was therefore 0x0090581f.
|
|
*/
|
|
|
|
if (version < 0x0930) {
|
|
info->status = 0;
|
|
info->patch = version & 0x0f;
|
|
version >>= 4;
|
|
info->micro = version & 0x0f;
|
|
version >>= 4;
|
|
info->minor = version & 0x0f;
|
|
version >>= 4;
|
|
info->major = version & 0x0f;
|
|
} else if (version < 0x00905800L) {
|
|
info->patch = version & 0xff;
|
|
version >>= 8;
|
|
info->status = version & 0xf;
|
|
version >>= 4;
|
|
info->micro = version & 0xff;
|
|
version >>= 8;
|
|
info->minor = version & 0xff;
|
|
version >>= 8;
|
|
info->major = version & 0xff;
|
|
} else {
|
|
info->status = version & 0xf;
|
|
version >>= 4;
|
|
info->patch = version & 0xff;
|
|
version >>= 8;
|
|
info->micro = version & 0xff;
|
|
version >>= 8;
|
|
info->minor = version & 0xff;
|
|
version >>= 8;
|
|
info->major = version & 0xff;
|
|
if (version < 0x00906000L)
|
|
info->patch &= ~0x80;
|
|
}
|
|
}
|
|
|
|
/* tls_check_version - Detect mismatch between headers and library. */
|
|
|
|
void tls_check_version(void)
|
|
{
|
|
const char *myname = "tls_check_version";
|
|
TLS_VINFO hdr_info;
|
|
TLS_VINFO lib_info;
|
|
|
|
tls_version_split(OPENSSL_VERSION_NUMBER, &hdr_info);
|
|
tls_version_split(SSLeay(), &lib_info);
|
|
|
|
if (lib_info.major != hdr_info.major
|
|
|| lib_info.minor != hdr_info.minor
|
|
|| lib_info.micro != hdr_info.micro)
|
|
acl_msg_warn("%s: run-time library vs. compile-time header version mismatch: "
|
|
"OpenSSL %d.%d.%d may not be compatible with OpenSSL %d.%d.%d",
|
|
myname, lib_info.major, lib_info.minor, lib_info.micro,
|
|
hdr_info.major, hdr_info.minor, hdr_info.micro);
|
|
}
|
|
|
|
/* tls_bug_bits - SSL bug compatibility bits for this OpenSSL version */
|
|
|
|
long tls_bug_bits(void)
|
|
{
|
|
long bits = SSL_OP_ALL; /* Work around all known bugs */
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x00908000L
|
|
long lib_version = SSLeay();
|
|
|
|
/*
|
|
* In OpenSSL 0.9.8[ab], enabling zlib compression breaks the padding bug
|
|
* work-around, leading to false positives and failed connections. We may
|
|
* not interoperate with systems with the bug, but this is better than
|
|
* breaking on all 0.9.8[ab] systems that have zlib support enabled.
|
|
*/
|
|
if (lib_version >= 0x00908000L && lib_version <= 0x0090802fL) {
|
|
STACK_OF(SSL_COMP) * comp_methods;
|
|
|
|
comp_methods = SSL_COMP_get_compression_methods();
|
|
if (comp_methods != 0 && sk_SSL_COMP_num(comp_methods) > 0)
|
|
bits &= ~SSL_OP_TLS_BLOCK_PADDING_BUG;
|
|
}
|
|
#endif
|
|
return (bits);
|
|
}
|
|
|
|
/* tls_print_errors - print and clear the error stack */
|
|
|
|
void tls_print_errors(void)
|
|
{
|
|
const char *myname = "tls_print_errors";
|
|
unsigned long err;
|
|
char buffer[1024]; /* XXX */
|
|
const char *file;
|
|
const char *data;
|
|
int line;
|
|
int flags;
|
|
unsigned long thread;
|
|
|
|
/*
|
|
while ((err = ERR_get_error()) != 0) {
|
|
ERR_error_string(err, buffer);
|
|
acl_msg_warn("%s: TLS library warning: %lu|%s", __FUNCTION__, err, buffer);
|
|
}
|
|
*/
|
|
|
|
thread = CRYPTO_thread_id();
|
|
while ((err = ERR_get_error_line_data(&file, &line, &data, &flags)) != 0) {
|
|
ERR_error_string_n(err, buffer, sizeof(buffer));
|
|
if (flags & ERR_TXT_STRING)
|
|
acl_msg_warn("%s: TLS library problem: %lu|%s|%s|%d|%s|",
|
|
myname, thread, buffer, file, line, data);
|
|
else
|
|
acl_msg_warn("%s: TLS library problem: %lu|%s|%s|%d|",
|
|
myname, thread, buffer, file, line);
|
|
}
|
|
}
|
|
|
|
/* tls_info_callback - callback for logging SSL events via Postfix */
|
|
|
|
void tls_info_callback(const SSL *s, int where, int ret)
|
|
{
|
|
char *str;
|
|
int w;
|
|
|
|
/* Adapted from OpenSSL apps/s_cb.c. */
|
|
|
|
w = where & ~SSL_ST_MASK;
|
|
|
|
if (w & SSL_ST_CONNECT)
|
|
str = "SSL_connect";
|
|
else if (w & SSL_ST_ACCEPT)
|
|
str = "SSL_accept";
|
|
else
|
|
str = "unknown";
|
|
|
|
if (where & SSL_CB_LOOP) {
|
|
acl_msg_info("%s:%s", str, SSL_state_string_long((const SSL *) s));
|
|
} else if (where & SSL_CB_ALERT) {
|
|
str = (where & SSL_CB_READ) ? "read" : "write";
|
|
if ((ret & 0xff) != SSL3_AD_CLOSE_NOTIFY)
|
|
acl_msg_info("SSL3 alert %s:%s:%s", str,
|
|
SSL_alert_type_string_long(ret),
|
|
SSL_alert_desc_string_long(ret));
|
|
} else if (where & SSL_CB_EXIT) {
|
|
if (ret == 0)
|
|
acl_msg_info("%s:failed in %s",
|
|
str, SSL_state_string_long((const SSL *) s));
|
|
else if (ret < 0) {
|
|
#ifndef LOG_NON_ERROR_STATES
|
|
switch (SSL_get_error((SSL *) s, ret)) {
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_WRITE:
|
|
/* Don't log non-error states. */
|
|
break;
|
|
default:
|
|
#endif
|
|
acl_msg_info("%s:error in %s",
|
|
str, SSL_state_string_long((SSL *) s));
|
|
#ifndef LOG_NON_ERROR_STATES
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* taken from OpenSSL crypto/bio/b_dump.c.
|
|
*
|
|
* Modified to save a lot of strcpy and strcat by Matti Aarnio.
|
|
*
|
|
* Rewritten by Wietse to elimate fixed-size stack buffer, array index
|
|
* multiplication and division, sprintf() and strcpy(), and lots of strlen()
|
|
* calls. We could make it a little faster by using a fixed-size stack-based
|
|
* buffer.
|
|
*
|
|
* 200412 - use %lx to print pointers, after casting them to unsigned long.
|
|
*/
|
|
|
|
#define TRUNCATE_SPACE_NULL
|
|
#define DUMP_WIDTH 16
|
|
#define VERT_SPLIT 7
|
|
|
|
static void tls_dump_buffer(const unsigned char *start, int len)
|
|
{
|
|
ACL_VSTRING *buf = acl_vstring_alloc(100);
|
|
const unsigned char *last = start + len - 1;
|
|
const unsigned char *row;
|
|
const unsigned char *col;
|
|
int ch;
|
|
|
|
#ifdef TRUNCATE_SPACE_NULL
|
|
while (last >= start && (*last == ' ' || *last == 0))
|
|
last--;
|
|
#endif
|
|
|
|
for (row = start; row <= last; row += DUMP_WIDTH) {
|
|
ACL_VSTRING_RESET(buf);
|
|
acl_vstring_sprintf(buf, "%04lx ", (unsigned long) (row - start));
|
|
for (col = row; col < row + DUMP_WIDTH; col++) {
|
|
if (col > last) {
|
|
acl_vstring_strcat(buf, " ");
|
|
} else {
|
|
ch = *col;
|
|
acl_vstring_sprintf_append(buf, "%02x%c",
|
|
ch, col - row == VERT_SPLIT ? '|' : ' ');
|
|
}
|
|
}
|
|
ACL_VSTRING_ADDCH(buf, ' ');
|
|
for (col = row; col < row + DUMP_WIDTH; col++) {
|
|
if (col > last)
|
|
break;
|
|
ch = *col;
|
|
if (!ACL_ISPRINT(ch))
|
|
ch = '.';
|
|
ACL_VSTRING_ADDCH(buf, ch);
|
|
if (col - row == VERT_SPLIT)
|
|
ACL_VSTRING_ADDCH(buf, ' ');
|
|
}
|
|
ACL_VSTRING_TERMINATE(buf);
|
|
acl_msg_info("%s", acl_vstring_str(buf));
|
|
}
|
|
#ifdef TRUNCATE_SPACE_NULL
|
|
if ((last + 1) - start < len)
|
|
acl_msg_info("%04lx - <SPACES/NULLS>",
|
|
(unsigned long) ((last + 1) - start));
|
|
#endif
|
|
acl_vstring_free(buf);
|
|
}
|
|
|
|
/* taken from OpenSSL apps/s_cb.c */
|
|
|
|
long tls_bio_dump_cb(BIO *bio, int cmd, const char *argp, int argi,
|
|
long unused_argl acl_unused, long ret)
|
|
{
|
|
if (cmd == (BIO_CB_READ | BIO_CB_RETURN)) {
|
|
acl_msg_info("read from %08lX [%08lX] (%d bytes => %ld (0x%lX))",
|
|
(unsigned long) bio, (unsigned long) argp, argi,
|
|
ret, (unsigned long) ret);
|
|
tls_dump_buffer((const unsigned char *) argp, (int) ret);
|
|
} else if (cmd == (BIO_CB_WRITE | BIO_CB_RETURN)) {
|
|
acl_msg_info("write to %08lX [%08lX] (%d bytes => %ld (0x%lX))",
|
|
(unsigned long) bio, (unsigned long) argp, argi,
|
|
ret, (unsigned long) ret);
|
|
tls_dump_buffer((const unsigned char *) argp, (int) ret);
|
|
}
|
|
return (ret);
|
|
}
|
|
|
|
#else
|
|
|
|
/*
|
|
* Broken linker workaround.
|
|
*/
|
|
int tls_dummy_for_broken_linkers;
|
|
|
|
#endif
|