shellinabox/libhttp/ssl.c
zodiac ca18a5346f Run-time testing for availability of libpthread functions does not
work reliably on some platforms. So, avoid doing so on anything other
than Linux/i386. For all other platforms, assume that the code is not
linked against libpthread. For ShellInABox, this is always the correct
assumption. But if the code gets embedded into other projects, this
might have to be changed.


git-svn-id: https://shellinabox.googlecode.com/svn/trunk@141 0da03de8-d603-11dd-86c2-0f8696b7b6f9
2009-07-07 00:40:51 +00:00

920 lines
29 KiB
C

// ssl.c -- Support functions that find and load SSL support, if available
// Copyright (C) 2008-2009 Markus Gutschke <markus@shellinabox.com>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
//
// 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, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// In addition to these license terms, the author grants the following
// additional rights:
//
// If you modify this program, or any covered work, by linking or
// combining it with the OpenSSL project's OpenSSL library (or a
// modified version of that library), containing parts covered by the
// terms of the OpenSSL or SSLeay licenses, the author
// grants you additional permission to convey the resulting work.
// Corresponding Source for a non-source form of such a combination
// shall include the source code for the parts of OpenSSL used as well
// as that of the covered work.
//
// You may at your option choose to remove this additional permission from
// the work, or from any part of it.
//
// It is possible to build this program in a way that it loads OpenSSL
// libraries at run-time. If doing so, the following notices are required
// by the OpenSSL and SSLeay licenses:
//
// This product includes software developed by the OpenSSL Project
// for use in the OpenSSL Toolkit. (http://www.openssl.org/)
//
// This product includes cryptographic software written by Eric Young
// (eay@cryptsoft.com)
//
//
// The most up-to-date version of this program is always available from
// http://shellinabox.com
#define _GNU_SOURCE
#include "config.h"
#define pthread_once x_pthread_once
#define pthread_sigmask x_pthread_sigmask
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "libhttp/ssl.h"
#include "libhttp/httpconnection.h"
#include "logging/logging.h"
#undef pthread_once
#undef pthread_sigmask
#if defined(HAVE_OPENSSL) && !defined(OPENSSL_NO_TLSEXT) && \
defined(TLSEXT_NAMETYPE_host_name) && defined(SSL_TLSEXT_ERR_OK)
#define HAVE_TLSEXT
#endif
#if defined(HAVE_PTHREAD_H)
// Pthread support is optional. Only enable it, if the library has been
// linked into the program
#include <pthread.h>
#if defined(__linux__)
extern int pthread_once(pthread_once_t *, void (*)(void))__attribute__((weak));
#endif
extern int pthread_sigmask(int, const sigset_t *, sigset_t *)
__attribute__((weak));
#endif
#if defined(HAVE_DLOPEN)
// SSL support is optional. Only enable it, if the library can be loaded.
long (*BIO_ctrl)(BIO *, int, long, void *);
BIO_METHOD * (*BIO_f_buffer)(void);
void (*BIO_free_all)(BIO *);
BIO * (*BIO_new)(BIO_METHOD *);
BIO * (*BIO_new_socket)(int, int);
BIO * (*BIO_pop)(BIO *);
BIO * (*BIO_push)(BIO *, BIO *);
void (*ERR_clear_error)(void);
void (*ERR_clear_error)(void);
unsigned long (*ERR_peek_error)(void);
unsigned long (*ERR_peek_error)(void);
long (*SSL_CTX_callback_ctrl)(SSL_CTX *, int, void (*)(void));
int (*SSL_CTX_check_private_key)(const SSL_CTX *);
long (*SSL_CTX_ctrl)(SSL_CTX *, int, long, void *);
void (*SSL_CTX_free)(SSL_CTX *);
SSL_CTX * (*SSL_CTX_new)(SSL_METHOD *);
int (*SSL_CTX_use_PrivateKey_file)(SSL_CTX *, const char *, int);
int (*SSL_CTX_use_PrivateKey_ASN1)(int, SSL_CTX *,
const unsigned char *, long);
int (*SSL_CTX_use_certificate_file)(SSL_CTX *, const char *, int);
int (*SSL_CTX_use_certificate_ASN1)(SSL_CTX *, long,
const unsigned char *);
long (*SSL_ctrl)(SSL *, int, long, void *);
void (*SSL_free)(SSL *);
int (*SSL_get_error)(const SSL *, int);
void * (*SSL_get_ex_data)(const SSL *, int);
BIO * (*SSL_get_rbio)(const SSL *);
const char * (*SSL_get_servername)(const SSL *, int);
BIO * (*SSL_get_wbio)(const SSL *);
int (*SSL_library_init)(void);
SSL * (*SSL_new)(SSL_CTX *);
int (*SSL_read)(SSL *, void *, int);
SSL_CTX * (*SSL_set_SSL_CTX)(SSL *, SSL_CTX *);
void (*SSL_set_accept_state)(SSL *);
void (*SSL_set_bio)(SSL *, BIO *, BIO *);
int (*SSL_set_ex_data)(SSL *, int, void *);
int (*SSL_shutdown)(SSL *);
int (*SSL_write)(SSL *, const void *, int);
SSL_METHOD * (*SSLv23_server_method)(void);
X509 * (*d2i_X509)(X509 **px, const unsigned char **in, int len);
void (*X509_free)(X509 *a);
#endif
static void sslDestroyCachedContext(void *ssl_, char *context_) {
struct SSLSupport *ssl = (struct SSLSupport *)ssl_;
SSL_CTX *context = (SSL_CTX *)context_;
#if defined(HAVE_OPENSSL)
if (context != ssl->sslContext) {
SSL_CTX_free(context);
}
#else
check(!context);
check(!ssl->sslContext);
#endif
}
struct SSLSupport *newSSL(void) {
struct SSLSupport *ssl;
check(ssl = malloc(sizeof(struct SSLSupport)));
initSSL(ssl);
return ssl;
}
void initSSL(struct SSLSupport *ssl) {
ssl->enabled = serverSupportsSSL();
ssl->sslContext = NULL;
ssl->sniCertificatePattern = NULL;
ssl->generateMissing = 0;
initTrie(&ssl->sniContexts, sslDestroyCachedContext, ssl);
}
void destroySSL(struct SSLSupport *ssl) {
if (ssl) {
free(ssl->sniCertificatePattern);
destroyTrie(&ssl->sniContexts);
#if defined(HAVE_OPENSSL)
if (ssl->sslContext) {
dcheck(!ERR_peek_error());
SSL_CTX_free(ssl->sslContext);
}
#else
check(!ssl->sslContext);
#endif
}
}
void deleteSSL(struct SSLSupport *ssl) {
destroySSL(ssl);
free(ssl);
}
#if defined(HAVE_OPENSSL) && defined(HAVE_DLOPEN)
static int maybeLoadCrypto(void) {
// Some operating systems cannot automatically load dependent dynamic
// libraries. As libssl.so can depend on libcrypto.so, we try to load
// it, iff we haven't tried loading it before and iff libssl.so does not
// work by itself.
static int crypto;
if (!crypto++) {
#ifdef RTLD_NOLOAD
if (dlopen("libcrypto.so", RTLD_LAZY|RTLD_GLOBAL|RTLD_NOLOAD))
return 1;
else
#endif
if (dlopen("libcrypto.so", RTLD_LAZY|RTLD_GLOBAL))
return 1;
}
return 0;
}
static void *loadSymbol(const char *lib, const char *fn) {
int err = NOINTR(dup(2));
if (err > 2) {
int null = NOINTR(open("/dev/null", O_WRONLY));
if (null >= 0) {
NOINTR(dup2(null, 2));
NOINTR(close(null));
}
}
void *dl = RTLD_DEFAULT;
void *rc = dlsym(dl, fn);
if (!rc) {
for (int i = 0; i < 2; i++) {
#ifdef RTLD_NOLOAD
dl = dlopen(lib, RTLD_LAZY|RTLD_GLOBAL|RTLD_NOLOAD);
#else
dl = NULL;
#endif
if (dl == NULL) {
dl = dlopen(lib, RTLD_LAZY|RTLD_GLOBAL);
}
if (dl != NULL || !maybeLoadCrypto()) {
break;
}
}
if (dl != NULL) {
rc = dlsym(RTLD_DEFAULT, fn);
if (rc == NULL && maybeLoadCrypto()) {
rc = dlsym(RTLD_DEFAULT, fn);
}
}
}
if (err > 2) {
NOINTR(dup2(err, 2));
}
NOINTR(close(err));
return rc;
}
static void loadSSL(void) {
check(!SSL_library_init);
struct {
union {
void *avoid_gcc_warning_about_type_punning;
void **var;
};
const char *fn;
} symbols[] = {
{ { &BIO_ctrl }, "BIO_ctrl" },
{ { &BIO_f_buffer }, "BIO_f_buffer" },
{ { &BIO_free_all }, "BIO_free_all" },
{ { &BIO_new }, "BIO_new" },
{ { &BIO_new_socket }, "BIO_new_socket" },
{ { &BIO_pop }, "BIO_pop" },
{ { &BIO_push }, "BIO_push" },
{ { &ERR_clear_error }, "ERR_clear_error" },
{ { &ERR_clear_error }, "ERR_clear_error" },
{ { &ERR_peek_error }, "ERR_peek_error" },
{ { &ERR_peek_error }, "ERR_peek_error" },
{ { &SSL_CTX_callback_ctrl }, "SSL_CTX_callback_ctrl" },
{ { &SSL_CTX_check_private_key }, "SSL_CTX_check_private_key" },
{ { &SSL_CTX_ctrl }, "SSL_CTX_ctrl" },
{ { &SSL_CTX_free }, "SSL_CTX_free" },
{ { &SSL_CTX_new }, "SSL_CTX_new" },
{ { &SSL_CTX_use_PrivateKey_file }, "SSL_CTX_use_PrivateKey_file" },
{ { &SSL_CTX_use_PrivateKey_ASN1 }, "SSL_CTX_use_PrivateKey_ASN1" },
{ { &SSL_CTX_use_certificate_file },"SSL_CTX_use_certificate_file"},
{ { &SSL_CTX_use_certificate_ASN1 },"SSL_CTX_use_certificate_ASN1"},
{ { &SSL_ctrl }, "SSL_ctrl" },
{ { &SSL_free }, "SSL_free" },
{ { &SSL_get_error }, "SSL_get_error" },
{ { &SSL_get_ex_data }, "SSL_get_ex_data" },
{ { &SSL_get_rbio }, "SSL_get_rbio" },
#ifdef HAVE_TLSEXT
{ { &SSL_get_servername }, "SSL_get_servername" },
#endif
{ { &SSL_get_wbio }, "SSL_get_wbio" },
{ { &SSL_library_init }, "SSL_library_init" },
{ { &SSL_new }, "SSL_new" },
{ { &SSL_read }, "SSL_read" },
#ifdef HAVE_TLSEXT
{ { &SSL_set_SSL_CTX }, "SSL_set_SSL_CTX" },
#endif
{ { &SSL_set_accept_state }, "SSL_set_accept_state" },
{ { &SSL_set_bio }, "SSL_set_bio" },
{ { &SSL_set_ex_data }, "SSL_set_ex_data" },
{ { &SSL_shutdown }, "SSL_shutdown" },
{ { &SSL_write }, "SSL_write" },
{ { &SSLv23_server_method }, "SSLv23_server_method" },
{ { &d2i_X509 }, "d2i_X509" },
{ { &X509_free }, "X509_free" }
};
for (int i = 0; i < sizeof(symbols)/sizeof(symbols[0]); i++) {
if (!(*symbols[i].var = loadSymbol("libssl.so", symbols[i].fn))) {
debug("Failed to load SSL support. Could not find \"%s\"",
symbols[i].fn);
for (int j = 0; j < sizeof(symbols)/sizeof(symbols[0]); j++) {
*symbols[j].var = NULL;
}
return;
}
}
SSL_library_init();
dcheck(!ERR_peek_error());
debug("Loaded SSL suppport");
}
#endif
int serverSupportsSSL(void) {
#if defined(HAVE_OPENSSL) && !defined(HAVE_DLOPEN)
return SSL_library_init();
#else
#if defined(HAVE_OPENSSL)
// We want to call loadSSL() exactly once. For single-threaded applications,
// this is straight-forward. For threaded applications, we need to call
// pthread_once(), instead. We perform run-time checks for whether we are
// single- or multi-threaded, so that the same code can be used.
// This currently only works on Linux.
#if defined(HAVE_PTHREAD_H) && defined(__linux__) && defined(__i386__)
if (!!&pthread_once) {
static pthread_once_t once = PTHREAD_ONCE_INIT;
pthread_once(&once, loadSSL);
} else
#endif
{
static int initialized;
if (!initialized) {
initialized = 1;
loadSSL();
}
}
return !!SSL_library_init;
#else
return 0;
#endif
#endif
}
#if defined(HAVE_OPENSSL)
static void sslGenerateCertificate(const char *certificate,
const char *serverName) {
debug("Auto-generating missing certificate \"%s\" for \"%s\"",
certificate, serverName);
char *cmd = stringPrintf(NULL,
"set -e; "
"exec 2>/dev/null </dev/null; "
"umask 0377; "
"PATH=/usr/bin:/usr/sbin "
"openssl req -x509 -nodes -days 7300 -newkey rsa:1024 -keyout /dev/stdout "
"-out /dev/stdout -subj '/CN=%s/' | cat>'%s'",
serverName, certificate);
if (system(cmd)) {
warn("Failed to generate self-signed certificate \"%s\"", certificate);
}
free(cmd);
}
static const unsigned char *sslSecureReadASCIIFileToMem(int fd) {
size_t inc = 16384;
size_t bufSize = inc;
size_t len = 0;
unsigned char *buf;
check((buf = malloc(bufSize)) != NULL);
for (;;) {
check(len < bufSize - 1);
size_t readLen = bufSize - len - 1;
ssize_t bytesRead = NOINTR(read(fd, buf + len, readLen));
if (bytesRead > 0) {
len += bytesRead;
}
if (bytesRead != readLen) {
break;
}
// Instead of calling realloc(), allocate a new buffer, copy the data,
// and then clear the old buffer. This way, we are not accidentally
// leaving key material in memory.
unsigned char *newBuf;
check((newBuf = malloc(bufSize + inc)) != NULL);
memcpy(newBuf, buf, len);
memset(buf, 0, bufSize);
free(buf);
buf = newBuf;
bufSize += inc;
}
check(len < bufSize);
buf[len] = '\000';
return buf;
}
static const unsigned char *sslPEMtoASN1(const unsigned char *pem,
const char *record,
long *size,
const unsigned char **eor) {
if (eor) {
*eor = NULL;
}
*size = 0;
char *marker;
check((marker = stringPrintf(NULL, "-----BEGIN %s-----",record))!=NULL);
unsigned char *ptr = (unsigned char *)strstr((char *)pem, marker);
if (!ptr) {
free(marker);
return NULL;
} else {
ptr += strlen(marker);
}
*marker = '\000';
check((marker = stringPrintf(marker, "-----END %s-----",record))!=NULL);
unsigned char *end = (unsigned char *)strstr((char *)ptr, marker);
if (eor) {
*eor = end + strlen(marker);
}
free(marker);
if (!end) {
return NULL;
}
unsigned char *ret;
size_t maxSize = (((end - ptr)*6)+7)/8;
check((ret = malloc(maxSize)) != NULL);
unsigned char *out = ret;
unsigned bits = 0;
int count = 0;
while (ptr < end) {
unsigned char ch = *ptr++;
if (ch >= 'A' && ch <= 'Z') {
ch -= 'A';
} else if (ch >= 'a' && ch <= 'z') {
ch -= 'a' - 26;
} else if (ch >= '0' && ch <= '9') {
ch += 52 - '0';
} else if (ch == '+') {
ch += 62 - '+';
} else if (ch == '/') {
ch += 63 - '/';
} else if (ch == '=') {
while (ptr < end) {
if ((ch = *ptr++) != '=' && ch > ' ') {
goto err;
}
}
break;
} else if (ch <= ' ') {
continue;
} else {
err:
free(ret);
return NULL;
}
check(ch <= 63);
check(count >= 0);
check(count <= 6);
bits = (bits << 6) | ch;
count += 6;
if (count >= 8) {
*out++ = (bits >> (count -= 8)) & 0xFF;
}
}
check(out - ret <= maxSize);
*size = out - ret;
return ret;
}
static int sslSetCertificateFromFd(SSL_CTX *context, int fd) {
int rc = 0;
check(serverSupportsSSL());
check(fd >= 0);
const unsigned char *data = sslSecureReadASCIIFileToMem(fd);
check(!NOINTR(close(fd)));
long dataSize = (long)strlen((const char *)data);
long certSize, rsaSize, dsaSize, ecSize;
const unsigned char *record;
const unsigned char *cert = sslPEMtoASN1(data, "CERTIFICATE", &certSize,
&record);
const unsigned char *rsa = sslPEMtoASN1(data, "RSA PRIVATE KEY",&rsaSize,
NULL);
const unsigned char *dsa = sslPEMtoASN1(data, "DSA PRIVATE KEY",&dsaSize,
NULL);
const unsigned char *ec = sslPEMtoASN1(data, "EC PRIVATE KEY", &ecSize,
NULL);
if (certSize && (rsaSize || dsaSize
#ifdef EVP_PKEY_EC
|| ecSize
#endif
) &&
SSL_CTX_use_certificate_ASN1(context, certSize, cert) &&
(!rsaSize ||
SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA, context, rsa, rsaSize)) &&
(!dsaSize ||
SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_DSA, context, dsa, dsaSize))
#ifdef EVP_PKEY_EC
&&
(!ecSize ||
SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_EC, context, ec, ecSize))
#endif
) {
memset((char *)cert, 0, certSize);
free((char *)cert);
while (record) {
cert = sslPEMtoASN1(record, "CERTIFICATE", &certSize,
&record);
if (cert) {
X509 *x509;
const unsigned char *c = cert;
check(x509 = d2i_X509(NULL, &c, certSize));
memset((char *)cert, 0, certSize);
free((char *)cert);
if (!SSL_CTX_add_extra_chain_cert(context, x509)) {
X509_free(x509);
break;
}
}
}
if (!record && SSL_CTX_check_private_key(context)) {
rc = 1;
}
dcheck(!ERR_peek_error());
ERR_clear_error();
} else {
memset((char *)cert, 0, certSize);
free((char *)cert);
}
memset((char *)data, 0, dataSize);
free((char *)data);
memset((char *)rsa, 0, rsaSize);
free((char *)rsa);
memset((char *)dsa, 0, dsaSize);
free((char *)dsa);
memset((char *)ec, 0, ecSize);
free((char *)ec);
return rc;
}
static int sslSetCertificateFromFile(SSL_CTX *context,
const char *filename) {
int fd = open(filename, O_RDONLY);
if (fd < 0) {
return -1;
}
int rc = sslSetCertificateFromFd(context, fd);
return rc;
}
#endif
#ifdef HAVE_TLSEXT
static int sslSNICallback(SSL *sslHndl, int *al, struct SSLSupport *ssl) {
check(!ERR_peek_error());
const char *name = SSL_get_servername(sslHndl,
TLSEXT_NAMETYPE_host_name);
if (name == NULL || !*name) {
return SSL_TLSEXT_ERR_OK;
}
struct HttpConnection *http =
(struct HttpConnection *)SSL_get_app_data(sslHndl);
debug("Received SNI callback for virtual host \"%s\" from \"%s:%d\"",
name, httpGetPeerName(http), httpGetPort(http));
char *serverName;
check(serverName = malloc(strlen(name)+2));
serverName[0] = '-';
for (int i = 0;;) {
char ch = name[i];
if (ch >= 'A' && ch <= 'Z') {
ch |= 0x20;
} else if (ch != '\000' && ch != '.' && ch != '-' &&
(ch < '0' ||(ch > '9' && ch < 'A') || (ch > 'Z' &&
ch < 'a')|| ch > 'z')) {
i++;
continue;
}
serverName[++i] = ch;
if (!ch) {
break;
}
}
if (!*serverName) {
free(serverName);
return SSL_TLSEXT_ERR_OK;
}
SSL_CTX *context = (SSL_CTX *)getFromTrie(&ssl->sniContexts,
serverName+1,
NULL);
if (context == NULL) {
check(context = SSL_CTX_new(SSLv23_server_method()));
check(ssl->sniCertificatePattern);
char *certificate = stringPrintfUnchecked(NULL,
ssl->sniCertificatePattern,
serverName);
if (sslSetCertificateFromFile(context, certificate) < 0) {
if (ssl->generateMissing) {
sslGenerateCertificate(certificate, serverName + 1);
// No need to check the certificate. If we fail to set it, we will use
// the default certificate, instead.
sslSetCertificateFromFile(context, certificate);
} else {
warn("Could not find matching certificate \"%s\" for \"%s\"",
certificate, serverName + 1);
SSL_CTX_free(context);
context = ssl->sslContext;
}
}
ERR_clear_error();
free(certificate);
addToTrie(&ssl->sniContexts, serverName+1, (char *)context);
}
free(serverName);
if (context != ssl->sslContext) {
check(SSL_set_SSL_CTX(sslHndl, context) > 0);
}
check(!ERR_peek_error());
return SSL_TLSEXT_ERR_OK;
}
#endif
#if defined(HAVE_OPENSSL) && 1
// This is a not-thread-safe replacement for gethostbyname_r()
#define gethostbyname_r x_gethostbyname_r
static int gethostbyname_r(const char *name, struct hostent *ret,
char *buf, size_t buflen,
struct hostent **result, int *h_errnop) {
if (result) {
*result = NULL;
}
if (h_errnop) {
*h_errnop = ERANGE;
}
if (!ret) {
return -1;
}
struct hostent *he = gethostbyname(name);
*ret = *he;
if (result) {
*result = ret;
}
if (h_errnop) {
*h_errnop = h_errno;
}
return 0;
}
#endif
void sslSetCertificate(struct SSLSupport *ssl, const char *filename,
int autoGenerateMissing) {
#if defined(HAVE_OPENSSL)
check(serverSupportsSSL());
char *defaultCertificate;
check(defaultCertificate = strdup(filename));
char *ptr = strchr(defaultCertificate, '%');
if (ptr != NULL) {
check(!strchr(ptr+1, '%'));
check(ptr[1] == 's');
memmove(ptr, ptr + 2, strlen(ptr)-1);
}
// Try to set the default certificate. If necessary, (re-)generate it.
check(ssl->sslContext = SSL_CTX_new(SSLv23_server_method()));
if (autoGenerateMissing) {
if (sslSetCertificateFromFile(ssl->sslContext, defaultCertificate) < 0) {
char hostname[256], buf[4096];
check(!gethostname(hostname, sizeof(hostname)));
struct hostent he_buf, *he;
int h_err;
if (gethostbyname_r(hostname, &he_buf, buf, sizeof(buf),
&he, &h_err)) {
sslGenerateCertificate(defaultCertificate, hostname);
} else {
sslGenerateCertificate(defaultCertificate, he->h_name);
}
} else {
goto valid_certificate;
}
}
if (sslSetCertificateFromFile(ssl->sslContext, defaultCertificate) < 0) {
fatal("Cannot read valid certificate from \"%s\". "
"Check file permissions and file format.", defaultCertificate);
}
valid_certificate:
free(defaultCertificate);
// Enable SNI support so that we can set a different certificate, if the
// client asked for it.
#ifdef HAVE_TLSEXT
if (ptr != NULL) {
check(ssl->sniCertificatePattern = strdup(filename));
check(SSL_CTX_set_tlsext_servername_callback(ssl->sslContext,
sslSNICallback));
check(SSL_CTX_set_tlsext_servername_arg(ssl->sslContext, ssl));
}
#endif
dcheck(!ERR_peek_error());
ERR_clear_error();
ssl->generateMissing = autoGenerateMissing;
#endif
}
// Convert the file descriptor to a human-readable format. Attempts to
// retrieve the original file name where possible.
#ifdef HAVE_OPENSSL
static char *sslFdToFilename(int fd) {
char *proc, *buf;
int len = 128;
check(proc = stringPrintf(NULL, "/proc/self/fd/%d", fd));
check(buf = malloc(len));
for (;;) {
ssize_t i;
if ((i = readlink(proc, buf + 1, len-3)) < 0) {
free(proc);
free(buf);
check(buf = stringPrintf(NULL, "fd %d", fd));
return buf;
} else if (i >= len-3) {
len += 512;
check(buf = realloc(buf, len));
} else {
free(proc);
check(i >= 0 && i < len);
buf[i+1] = '\000';
struct stat sb;
if (!stat(buf + 1, &sb) && S_ISREG(sb.st_mode)) {
*buf = '"';
buf[i + 1] = '"';
buf[i + 2] = '\000';
return buf;
} else {
free(buf);
check(buf = stringPrintf(NULL, "fd %d", fd));
return buf;
}
}
}
}
#endif
void sslSetCertificateFd(struct SSLSupport *ssl, int fd) {
#ifdef HAVE_OPENSSL
check(ssl->sslContext = SSL_CTX_new(SSLv23_server_method()));
char *filename = sslFdToFilename(fd);
if (!sslSetCertificateFromFd(ssl->sslContext, fd)) {
fatal("Cannot read valid certificate from %s. Check file format.",
filename);
}
free(filename);
ssl->generateMissing = 0;
#endif
}
int sslEnable(struct SSLSupport *ssl, int enabled) {
int old = ssl->enabled;
ssl->enabled = enabled;
return old;
}
void sslBlockSigPipe(void) {
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGPIPE);
#if defined(HAVE_PTHREAD_H) && defined(__linux__) && defined(__i386__)
if (&pthread_sigmask) {
dcheck(!pthread_sigmask(SIG_BLOCK, &set, NULL));
} else
#endif
{
dcheck(!sigprocmask(SIG_BLOCK, &set, NULL));
}
}
#ifndef HAVE_SIGWAIT
// This is a non-thread-safe replacement for sigwait()
static int dummysignalno;
static void dummysignal(int signo) {
dummysignalno = signo;
}
#define sigwait x_sigwait
static int sigwait(const sigset_t *set, int *sig) {
sigset_t mask, old_mask;
sigfillset(&mask);
#if defined(HAVE_PTHREAD_H) && defined(__linux__) && defined(__i386__)
if (&pthread_sigmask) {
dcheck(!pthread_sigmask(SIG_BLOCK, &mask, &old_mask));
} else
#endif
{
dcheck(!sigprocmask(SIG_BLOCK, &mask, &old_mask));
}
#ifndef NSIG
#define NSIG 32
#endif
struct sigaction sa[NSIG];
memset(sa, 0, sizeof(sa));
sa->sa_handler = dummysignal;
for (int i = 1; i <= NSIG; i++) {
if (sigismember(set, i)) {
sigdelset(&mask, i);
sigaction(i, sa, sa + i);
}
}
dummysignalno = -1;
sigsuspend(&mask);
#if defined(HAVE_PTHREAD_H) && defined(__linux__) && defined(__i386__)
if (&pthread_sigmask) {
dcheck(!pthread_sigmask(SIG_SETMASK, &old_mask, NULL));
} else
#endif
{
dcheck(!sigprocmask(SIG_BLOCK, &old_mask, NULL));
}
return dummysignalno;
}
#endif
int sslUnblockSigPipe(void) {
int signum = 0;
sigset_t set;
check(!sigpending(&set));
if (sigismember(&set, SIGPIPE)) {
sigwait(&set, &signum);
}
sigemptyset(&set);
sigaddset(&set, SIGPIPE);
#if defined(HAVE_PTHREAD_H) && defined(__linux__) && defined(__i386__)
if (&pthread_sigmask) {
dcheck(!pthread_sigmask(SIG_UNBLOCK, &set, NULL));
} else
#endif
{
dcheck(!sigprocmask(SIG_UNBLOCK, &set, NULL));
}
return signum;
}
int sslPromoteToSSL(struct SSLSupport *ssl, SSL **sslHndl, int fd,
const char *buf, int len) {
#if defined(HAVE_OPENSSL)
sslBlockSigPipe();
int rc = 0;
check(!*sslHndl);
dcheck(!ERR_peek_error());
dcheck(*sslHndl = SSL_new(ssl->sslContext));
if (*sslHndl == NULL) {
ERR_clear_error();
errno = EINVAL;
rc = -1;
} else {
SSL_set_mode(*sslHndl, SSL_MODE_ENABLE_PARTIAL_WRITE);
BIO *writeBIO = BIO_new_socket(fd, 0);
BIO *readBIO = writeBIO;
if (len > 0) {
readBIO = BIO_new(BIO_f_buffer());
BIO_push(readBIO, writeBIO);
check(BIO_set_buffer_read_data(readBIO, (char *)buf, len));
}
SSL_set_bio(*sslHndl, readBIO, writeBIO);
SSL_set_accept_state(*sslHndl);
dcheck(!ERR_peek_error());
}
sslUnblockSigPipe();
return rc;
#else
errno = EINVAL;
return -1;
#endif
}
void sslFreeHndl(SSL **sslHndl) {
#if defined(HAVE_OPENSSL)
if (*sslHndl) {
// OpenSSL does not always correctly perform reference counting for stacked
// BIOs. This is particularly a problem if an SSL connection has two
// different BIOs for the read and the write end, with one being a stacked
// derivative of the other. Unfortunately, this is exactly the scenario
// that we set up.
// As a work-around, we un-stack the BIOs prior to freeing the SSL
// connection.
ERR_clear_error();
BIO *writeBIO, *readBIO;
check(writeBIO = SSL_get_wbio(*sslHndl));
check(readBIO = SSL_get_rbio(*sslHndl));
if (writeBIO != readBIO) {
if (readBIO->next_bio == writeBIO) {
// OK, that's exactly the bug we are looking for. We know how to
// fix it.
check(BIO_pop(readBIO) == writeBIO);
check(readBIO->references == 1);
check(writeBIO->references == 1);
check(!readBIO->next_bio);
check(!writeBIO->prev_bio);
} else if (readBIO->next_bio == writeBIO->next_bio &&
writeBIO->next_bio->prev_bio == writeBIO) {
// Things get even more confused, if the SSL handshake is aborted
// prematurely.
// OpenSSL appears to internally stack a BIO onto the read end that
// does not get removed afterwards. We end up with the original
// socket BIO having two different BIOs prepended to it (one for
// reading and one for writing). In this case, not only is the
// reference count wrong, but the chain of next_bio/prev_bio pairs
// is corrupted, too.
BIO *sockBIO;
check(sockBIO = BIO_pop(readBIO));
check(sockBIO == BIO_pop(writeBIO));
check(readBIO->references == 1);
check(writeBIO->references == 1);
check(sockBIO->references == 1);
check(!readBIO->next_bio);
check(!writeBIO->next_bio);
check(!sockBIO->prev_bio);
BIO_free_all(sockBIO);
} else {
// We do not know, how to fix this situation. Something must have
// changed in the OpenSSL internals. Either, this is a new bug, or
// somebody fixed the code in a way that we did not anticipate.
fatal("Unexpected corruption of OpenSSL data structures");
}
}
SSL_free(*sslHndl);
dcheck(!ERR_peek_error());
}
#endif
*sslHndl = NULL;
}