734 lines
19 KiB
C
734 lines
19 KiB
C
/************************************************************************
|
|
* IRC - Internet Relay Chat, ircd/ssl.c
|
|
* Copyright (C) 2002 Alex Badea <vampire@go.ro>
|
|
* Copyright (C) 2013 Matthew Beeching (Jobe)
|
|
*
|
|
* See file AUTHORS in IRC package for additional names of
|
|
* the programmers.
|
|
*
|
|
* 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 1, 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, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
/** @file
|
|
* @brief Implimentation of common SSL functions.
|
|
* @version $Id:$
|
|
*/
|
|
#include "config.h"
|
|
#include "client.h"
|
|
#include "ircd_alloc.h"
|
|
#include "ircd_features.h"
|
|
#include "ircd_log.h"
|
|
#include "ircd_snprintf.h"
|
|
#include "ircd_string.h"
|
|
#include "listener.h"
|
|
#include "s_bsd.h"
|
|
#include "s_conf.h"
|
|
#include "s_debug.h"
|
|
#include "send.h"
|
|
#include "ssl.h"
|
|
|
|
#ifdef USE_SSL
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <sys/uio.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <openssl/bio.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/rand.h>
|
|
#include <openssl/ssl.h>
|
|
|
|
#ifndef IOV_MAX
|
|
#define IOV_MAX 1024
|
|
#endif /* IOV_MAX */
|
|
|
|
SSL_CTX *ssl_server_ctx;
|
|
SSL_CTX *ssl_client_ctx;
|
|
|
|
SSL_CTX *ssl_init_server_ctx(void);
|
|
SSL_CTX *ssl_init_client_ctx(void);
|
|
int ssl_verify_callback(int preverify_ok, X509_STORE_CTX *cert);
|
|
void ssl_set_nonblocking(SSL *s);
|
|
int ssl_smart_shutdown(SSL *ssl);
|
|
void sslfail(char *txt);
|
|
void binary_to_hex(unsigned char *bin, char *hex, int length);
|
|
|
|
int ssl_init(void)
|
|
{
|
|
SSL_library_init();
|
|
SSL_load_error_strings();
|
|
ERR_load_crypto_strings();
|
|
|
|
Debug((DEBUG_NOTICE, "SSL: read %d bytes of randomness", RAND_load_file("/dev/urandom", 4096)));
|
|
|
|
ssl_server_ctx = ssl_init_server_ctx();
|
|
if (!ssl_server_ctx)
|
|
return -1;
|
|
ssl_client_ctx = ssl_init_client_ctx();
|
|
if (!ssl_client_ctx)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ssl_reinit(int sig)
|
|
{
|
|
SSL_CTX *temp_ctx;
|
|
|
|
if (1 == sig)
|
|
sendto_opmask_butone(0, SNO_OLDSNO, "Got signal SIGUSR1, reloading SSL certificates");
|
|
|
|
/* Attempt to reinitialize server context, return on error */
|
|
temp_ctx = ssl_init_server_ctx();
|
|
if (!temp_ctx)
|
|
return -1;
|
|
|
|
/* Now reinitialize server context for real. */
|
|
SSL_CTX_free(temp_ctx);
|
|
SSL_CTX_free(ssl_server_ctx);
|
|
ssl_server_ctx = ssl_init_server_ctx();
|
|
|
|
/* Attempt to reinitialize client context, return on error */
|
|
temp_ctx = ssl_init_client_ctx();
|
|
if (!temp_ctx)
|
|
return -1;
|
|
|
|
/* Now reinitialize client context for real. */
|
|
SSL_CTX_free(temp_ctx);
|
|
SSL_CTX_free(ssl_client_ctx);
|
|
ssl_client_ctx = ssl_init_client_ctx();
|
|
|
|
return 0;
|
|
}
|
|
|
|
SSL_CTX *ssl_init_server_ctx(void)
|
|
{
|
|
SSL_CTX *server_ctx = NULL;
|
|
int vrfyopts = SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE;
|
|
|
|
server_ctx = SSL_CTX_new(SSLv23_server_method());
|
|
if (!server_ctx)
|
|
{
|
|
sslfail("Error creating new server context");
|
|
return NULL;
|
|
}
|
|
|
|
if (feature_bool(FEAT_SSL_REQUIRECLIENTCERT))
|
|
vrfyopts |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
|
|
|
|
if (feature_bool(FEAT_SSL_NOSSLV2))
|
|
SSL_CTX_set_options(server_ctx, SSL_OP_NO_SSLv2);
|
|
if (feature_bool(FEAT_SSL_NOSSLV3))
|
|
SSL_CTX_set_options(server_ctx, SSL_OP_NO_SSLv3);
|
|
if (feature_bool(FEAT_SSL_NOTLSV1))
|
|
SSL_CTX_set_options(server_ctx, SSL_OP_NO_TLSv1);
|
|
SSL_CTX_set_verify(server_ctx, vrfyopts, ssl_verify_callback);
|
|
SSL_CTX_set_session_cache_mode(server_ctx, SSL_SESS_CACHE_OFF);
|
|
|
|
if (SSL_CTX_use_certificate_chain_file(server_ctx, feature_str(FEAT_SSL_CERTFILE)) <= 0)
|
|
{
|
|
sslfail("Error loading SSL certificate for server context");
|
|
SSL_CTX_free(server_ctx);
|
|
return NULL;
|
|
}
|
|
if (SSL_CTX_use_PrivateKey_file(server_ctx, feature_str(FEAT_SSL_KEYFILE), SSL_FILETYPE_PEM) <= 0)
|
|
{
|
|
sslfail("Error loading SSL key for server context");
|
|
SSL_CTX_free(server_ctx);
|
|
return NULL;
|
|
}
|
|
|
|
if (!SSL_CTX_check_private_key(server_ctx))
|
|
{
|
|
sslfail("Error checking SSL private key for server context");
|
|
SSL_CTX_free(server_ctx);
|
|
return NULL;
|
|
}
|
|
|
|
if (!EmptyString(feature_str(FEAT_SSL_CIPHERS)))
|
|
{
|
|
if (SSL_CTX_set_cipher_list(server_ctx, feature_str(FEAT_SSL_CIPHERS)) == 0)
|
|
{
|
|
sslfail("Error setting SSL cipher list for clients");
|
|
SSL_CTX_free(server_ctx);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (!EmptyString(feature_str(FEAT_SSL_CACERTFILE)))
|
|
{
|
|
if (!SSL_CTX_load_verify_locations(server_ctx, feature_str(FEAT_SSL_CACERTFILE), NULL))
|
|
{
|
|
sslfail("Error loading trusted CA certificates file for server context");
|
|
SSL_CTX_free(server_ctx);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
#if defined(SSL_CTX_set_ecdh_auto)
|
|
SSL_CTX_set_ecdh_auto(server_ctx, 1);
|
|
#elif OPENSSL_VERSION_NUMBER < 0x10100000L
|
|
SSL_CTX_set_tmp_ecdh(server_ctx, EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
|
|
#else
|
|
#endif
|
|
SSL_CTX_set_options(server_ctx, SSL_OP_SINGLE_ECDH_USE|SSL_OP_SINGLE_DH_USE);
|
|
|
|
return server_ctx;
|
|
}
|
|
|
|
SSL_CTX *ssl_init_client_ctx(void)
|
|
{
|
|
SSL_CTX *client_ctx = NULL;
|
|
|
|
client_ctx = SSL_CTX_new(SSLv23_client_method());
|
|
if (!client_ctx)
|
|
{
|
|
sslfail("Error creating new client context");
|
|
return NULL;
|
|
}
|
|
|
|
if (feature_bool(FEAT_SSL_NOSSLV2))
|
|
SSL_CTX_set_options(client_ctx, SSL_OP_NO_SSLv2);
|
|
if (feature_bool(FEAT_SSL_NOSSLV3))
|
|
SSL_CTX_set_options(client_ctx, SSL_OP_NO_SSLv3);
|
|
if (feature_bool(FEAT_SSL_NOTLSV1))
|
|
SSL_CTX_set_options(client_ctx, SSL_OP_NO_TLSv1);
|
|
SSL_CTX_set_session_cache_mode(client_ctx, SSL_SESS_CACHE_OFF);
|
|
|
|
if (SSL_CTX_use_certificate_chain_file(client_ctx, feature_str(FEAT_SSL_CERTFILE)) <= 0)
|
|
{
|
|
sslfail("Error loading SSL certificate for client context");
|
|
SSL_CTX_free(client_ctx);
|
|
return NULL;
|
|
}
|
|
if (SSL_CTX_use_PrivateKey_file(client_ctx, feature_str(FEAT_SSL_KEYFILE), SSL_FILETYPE_PEM) <= 0)
|
|
{
|
|
sslfail("Error loading SSL key for client context");
|
|
SSL_CTX_free(client_ctx);
|
|
return NULL;
|
|
}
|
|
|
|
if (!SSL_CTX_check_private_key(client_ctx))
|
|
{
|
|
sslfail("Error checking SSL private key for client context");
|
|
SSL_CTX_free(client_ctx);
|
|
return NULL;
|
|
}
|
|
|
|
return client_ctx;
|
|
}
|
|
|
|
int ssl_verify_callback(int preverify_ok, X509_STORE_CTX *cert)
|
|
{
|
|
int err = 0;
|
|
|
|
err = X509_STORE_CTX_get_error(cert);
|
|
|
|
if (feature_bool(FEAT_SSL_NOSELFSIGNED) &&
|
|
(err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT))
|
|
return 0;
|
|
|
|
if (feature_bool(FEAT_SSL_VERIFYCERT))
|
|
{
|
|
if (!feature_bool(FEAT_SSL_NOSELFSIGNED) &&
|
|
(err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT))
|
|
return 1;
|
|
return preverify_ok;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
void ssl_abort(struct Client *cptr)
|
|
{
|
|
Debug((DEBUG_DEBUG, "SSL: aborted"));
|
|
if (cli_socket(cptr).ssl)
|
|
SSL_free(cli_socket(cptr).ssl);
|
|
cli_socket(cptr).ssl = NULL;
|
|
}
|
|
|
|
int ssl_accept(struct Client *cptr)
|
|
{
|
|
int r = 0;
|
|
|
|
if (!IsSSLNeedAccept(cptr))
|
|
return -1;
|
|
|
|
if ((r = SSL_accept(cli_socket(cptr).ssl)) <= 0) {
|
|
unsigned long err = SSL_get_error(cli_socket(cptr).ssl, r);
|
|
|
|
if (err) {
|
|
switch (err) {
|
|
case SSL_ERROR_SYSCALL:
|
|
if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_WRITE:
|
|
return 1;
|
|
default:
|
|
cli_sslerror(cptr) = ssl_error_str(err, errno);
|
|
|
|
Debug((DEBUG_ERROR, "SSL_accept: %s", cli_sslerror(cptr)));
|
|
|
|
SSL_set_shutdown(cli_socket(cptr).ssl, SSL_RECEIVED_SHUTDOWN);
|
|
ssl_smart_shutdown(cli_socket(cptr).ssl);
|
|
SSL_free(cli_socket(cptr).ssl);
|
|
cli_socket(cptr).ssl = NULL;
|
|
|
|
cli_error(cptr) = errno;
|
|
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ClearSSLNeedAccept(cptr);
|
|
|
|
if (SSL_is_init_finished(cli_socket(cptr).ssl))
|
|
{
|
|
char *sslfp = ssl_get_fingerprint(cli_socket(cptr).ssl);
|
|
if (sslfp)
|
|
ircd_strncpy(cli_sslclifp(cptr), sslfp, BUFSIZE+1);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
int ssl_starttls(struct Client *cptr)
|
|
{
|
|
if (!cli_socket(cptr).ssl) {
|
|
cli_socket(cptr).ssl = SSL_new(ssl_server_ctx);
|
|
SSL_set_fd(cli_socket(cptr).ssl, cli_socket(cptr).s_fd);
|
|
ssl_set_nonblocking(cli_socket(cptr).ssl);
|
|
SetSSLNeedAccept(cptr);
|
|
}
|
|
|
|
return ssl_accept(cptr);
|
|
}
|
|
|
|
void ssl_add_connection(struct Listener *listener, int fd)
|
|
{
|
|
SSL* ssl;
|
|
|
|
assert(0 != listener);
|
|
|
|
if (!(ssl = SSL_new(ssl_server_ctx))) {
|
|
Debug((DEBUG_DEBUG, "SSL_new failed"));
|
|
close(fd);
|
|
return;
|
|
}
|
|
SSL_set_fd(ssl, fd);
|
|
ssl_set_nonblocking(ssl);
|
|
|
|
add_connection(listener, fd, ssl);
|
|
}
|
|
|
|
void ssl_doerror(struct Client *cptr)
|
|
{
|
|
unsigned long err = 0;
|
|
char ebuf[120];
|
|
|
|
memset(&ebuf, 0, 120);
|
|
err = ERR_get_error();
|
|
ERR_error_string(err, (char *)&ebuf);
|
|
|
|
sendto_opmask_butone(0, SNO_TCPCOMMON, "SSL Error for client %s: %s", cli_name(cptr), ebuf);
|
|
}
|
|
|
|
void ssl_doerror_anon(void)
|
|
{
|
|
unsigned long err = 0;
|
|
char ebuf[120];
|
|
|
|
memset(&ebuf, 0, 120);
|
|
err = ERR_get_error();
|
|
ERR_error_string(err, (char *)&ebuf);
|
|
|
|
sendto_opmask_butone(0, SNO_TCPCOMMON, "SSL Error for unknown client: %s", ebuf);
|
|
}
|
|
|
|
/*
|
|
* ssl_recv - non blocking read of a connection
|
|
* returns:
|
|
* 1 if data was read or socket is blocked (recoverable error)
|
|
* count_out > 0 if data was read
|
|
*
|
|
* 0 if socket closed from other end
|
|
* -1 if an unrecoverable error occurred
|
|
*/
|
|
IOResult ssl_recv(struct Socket *socketh, struct Client *cptr, char* buf,
|
|
unsigned int length, unsigned int* count_out)
|
|
{
|
|
int res;
|
|
unsigned long err = 0;
|
|
|
|
assert(0 != socketh);
|
|
assert(0 != buf);
|
|
assert(0 != count_out);
|
|
|
|
*count_out = 0;
|
|
errno = 0;
|
|
|
|
ERR_clear_error();
|
|
res = SSL_read(socketh->ssl, buf, length);
|
|
switch (SSL_get_error(socketh->ssl, res)) {
|
|
case SSL_ERROR_NONE:
|
|
*count_out = (unsigned) res;
|
|
return IO_SUCCESS;
|
|
case SSL_ERROR_WANT_WRITE:
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_X509_LOOKUP:
|
|
Debug((DEBUG_DEBUG, "SSL_read returned WANT_ - retrying"));
|
|
return IO_BLOCKED;
|
|
case SSL_ERROR_SYSCALL:
|
|
if (res < 0 && errno == EINTR)
|
|
return IO_BLOCKED; /* ??? */
|
|
break;
|
|
case SSL_ERROR_ZERO_RETURN: /* close_notify received */
|
|
SSL_shutdown(socketh->ssl); /* Send close_notify back */
|
|
break;
|
|
}
|
|
|
|
err = SSL_get_error(cli_socket(cptr).ssl, res);
|
|
cli_sslerror(cptr) = ssl_error_str(err, errno);
|
|
cli_error(cptr) = errno;
|
|
|
|
if (err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL)
|
|
ssl_doerror(cptr);
|
|
|
|
return IO_FAILURE;
|
|
}
|
|
|
|
/*
|
|
* ssl_sendv - non blocking writev to a connection
|
|
* returns:
|
|
* 1 if data was written
|
|
* count_out contains amount written
|
|
*
|
|
* 0 if write call blocked, recoverable error
|
|
* -1 if an unrecoverable error occurred
|
|
*/
|
|
IOResult ssl_sendv(struct Socket *socketh, struct Client *cptr, struct MsgQ* buf,
|
|
unsigned int* count_in, unsigned int* count_out)
|
|
{
|
|
int res;
|
|
int count;
|
|
int k;
|
|
struct iovec iov[IOV_MAX];
|
|
IOResult retval = IO_BLOCKED;
|
|
int ssl_err = 0;
|
|
|
|
errno = 0;
|
|
|
|
assert(0 != socketh);
|
|
assert(0 != buf);
|
|
assert(0 != count_in);
|
|
assert(0 != count_out);
|
|
|
|
*count_in = 0;
|
|
*count_out = 0;
|
|
|
|
count = msgq_mapiov(buf, iov, IOV_MAX, count_in);
|
|
for (k = 0; k < count; k++) {
|
|
res = SSL_write(socketh->ssl, iov[k].iov_base, iov[k].iov_len);
|
|
ssl_err = SSL_get_error(socketh->ssl, res);
|
|
Debug((DEBUG_DEBUG, "SSL_write returned %d, error code %d.", res, ssl_err));
|
|
switch (ssl_err) {
|
|
case SSL_ERROR_NONE:
|
|
*count_out += (unsigned) res;
|
|
retval = IO_SUCCESS;
|
|
break;
|
|
case SSL_ERROR_WANT_WRITE:
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_X509_LOOKUP:
|
|
Debug((DEBUG_DEBUG, "SSL_write returned want WRITE, READ, or X509; returning retval %d", retval));
|
|
return retval;
|
|
case SSL_ERROR_SSL:
|
|
{
|
|
int errorValue;
|
|
Debug((DEBUG_ERROR, "SSL_write returned SSL_ERROR_SSL, errno %d, retval %d, res %d, ssl error code %d", errno, retval, res, ssl_err));
|
|
while((errorValue = ERR_get_error())) {
|
|
Debug((DEBUG_ERROR, " Error Queue: %d -- %s", errorValue, ERR_error_string(errorValue, NULL)));
|
|
}
|
|
cli_sslerror(cptr) = ssl_error_str(ssl_err, errno);
|
|
cli_error(cptr) = errno;
|
|
ssl_doerror(cptr);
|
|
return IO_FAILURE;
|
|
}
|
|
case SSL_ERROR_SYSCALL:
|
|
if(res < 0 && (errno == EWOULDBLOCK ||
|
|
errno == EINTR ||
|
|
errno == EBUSY ||
|
|
errno == EAGAIN)) {
|
|
Debug((DEBUG_DEBUG, "SSL_write returned ERROR_SYSCALL, errno %d - returning retval %d", errno, retval));
|
|
return retval;
|
|
}
|
|
else {
|
|
Debug((DEBUG_DEBUG, "SSL_write returned ERROR_SYSCALL - errno %d - returning IO_FAILURE", errno));
|
|
cli_sslerror(cptr) = ssl_error_str(ssl_err, errno);
|
|
cli_error(cptr) = errno;
|
|
ssl_doerror(cptr);
|
|
return IO_FAILURE;
|
|
}
|
|
/*
|
|
if(errno == EAGAIN) * its what unreal ircd does..*
|
|
{
|
|
Debug((DEBUG_DEBUG, "SSL_write returned ERROR_SSL - errno %d returning retval %d", errno, retval));
|
|
return retval;
|
|
}
|
|
*/
|
|
case SSL_ERROR_ZERO_RETURN:
|
|
SSL_shutdown(socketh->ssl);
|
|
return IO_FAILURE;
|
|
default:
|
|
Debug((DEBUG_DEBUG, "SSL_write return fell through - errno %d returning retval %d", errno, retval));
|
|
return retval; /* unknown error, assume block or success*/
|
|
}
|
|
}
|
|
Debug((DEBUG_DEBUG, "SSL_write return fell through(2) - errno %d returning retval %d", errno, retval));
|
|
return retval;
|
|
}
|
|
|
|
int ssl_send(struct Client *cptr, const char *buf, unsigned int len)
|
|
{
|
|
char fmt[16];
|
|
|
|
if (!cli_socket(cptr).ssl)
|
|
return write(cli_fd(cptr), buf, len);
|
|
|
|
/*
|
|
* XXX HACK
|
|
*
|
|
* Incomplete SSL writes must be retried with the same write buffer;
|
|
* at this point SSL_write usually fails, so the data must be queued.
|
|
* We're abusing the normal send queue for this.
|
|
* Also strip \r\n from message, as sendrawto_one adds it later
|
|
* this hack sucks. it conflicted with prority queues - caused random
|
|
* ssl disconnections for YEARS. In summery, this hack == bad. I may
|
|
* have solved that, but this still makes me nervous.
|
|
*/
|
|
ircd_snprintf(0, fmt, sizeof(fmt), "%%.%us", len - 2);
|
|
sendrawto_one(cptr, fmt, buf);
|
|
send_queued(cptr);
|
|
return len;
|
|
}
|
|
|
|
int ssl_murder(void *ssl, int fd, const char *buf)
|
|
{
|
|
if (!ssl) {
|
|
if (buf)
|
|
(void)!write(fd, buf, strlen(buf));
|
|
} else {
|
|
if (buf)
|
|
SSL_write((SSL *) ssl, buf, strlen(buf));
|
|
SSL_free((SSL *) ssl);
|
|
}
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
|
|
void ssl_free(struct Socket *socketh)
|
|
{
|
|
if (!socketh->ssl)
|
|
return;
|
|
SSL_free(socketh->ssl);
|
|
}
|
|
|
|
char *ssl_get_cipher(SSL *ssl)
|
|
{
|
|
static char buf[400];
|
|
int bits;
|
|
const SSL_CIPHER *c;
|
|
|
|
c = SSL_get_current_cipher(ssl);
|
|
SSL_CIPHER_get_bits(c, &bits);
|
|
|
|
/* Use ircd_snprintf for safe bounded string formatting */
|
|
ircd_snprintf(0, buf, sizeof(buf), "%s-%s-%dbits",
|
|
SSL_get_version(ssl), SSL_get_cipher(ssl), bits);
|
|
return (buf);
|
|
}
|
|
|
|
int ssl_connect(struct Socket* sock, struct ConfItem *aconf)
|
|
{
|
|
int r = 0;
|
|
|
|
if (!sock->ssl) {
|
|
sock->ssl = SSL_new(ssl_client_ctx);
|
|
SSL_set_fd(sock->ssl, sock->s_fd);
|
|
SSL_set_connect_state(sock->ssl);
|
|
|
|
if (!EmptyString(aconf->sslciphers))
|
|
{
|
|
if (SSL_set_cipher_list(sock->ssl, aconf->sslciphers) == 0)
|
|
{
|
|
return -2;
|
|
}
|
|
}
|
|
|
|
ssl_set_nonblocking(sock->ssl);
|
|
}
|
|
|
|
r = SSL_connect(sock->ssl);
|
|
if (r<=0) {
|
|
if ((SSL_get_error(sock->ssl, r) == SSL_ERROR_WANT_WRITE) || (SSL_get_error(sock->ssl, r) == SSL_ERROR_WANT_READ))
|
|
return 0; /* Needs to call SSL_connect() again */
|
|
else if (SSL_get_error(sock->ssl, r) == SSL_ERROR_SSL) {
|
|
unsigned long e = ERR_get_error();
|
|
sendto_opmask_butone(0, SNO_TCPCOMMON, "SSL Error for connection attempt: %s", ERR_error_string(e, NULL));
|
|
return -1; /* Fatal error */
|
|
}
|
|
else {
|
|
sendto_opmask_butone(0, SNO_TCPCOMMON, "Unknown SSL error for connection attempt (%d)", SSL_get_error(sock->ssl, r));
|
|
return -1; /* Fatal error */
|
|
}
|
|
}
|
|
return 1; /* Connection complete */
|
|
}
|
|
|
|
char* ssl_get_fingerprint(SSL *ssl)
|
|
{
|
|
X509* cert;
|
|
unsigned int n = 0;
|
|
unsigned char md[EVP_MAX_MD_SIZE];
|
|
const EVP_MD *digest = EVP_sha256();
|
|
static char hex[BUFSIZE + 1];
|
|
|
|
cert = SSL_get_peer_certificate(ssl);
|
|
|
|
if (!(cert))
|
|
return NULL;
|
|
|
|
if (!X509_digest(cert, digest, md, &n))
|
|
{
|
|
X509_free(cert);
|
|
return NULL;
|
|
}
|
|
|
|
binary_to_hex(md, hex, n);
|
|
X509_free(cert);
|
|
|
|
return (hex);
|
|
}
|
|
|
|
void ssl_set_nonblocking(SSL *s)
|
|
{
|
|
BIO_set_nbio(SSL_get_rbio(s),1);
|
|
BIO_set_nbio(SSL_get_wbio(s),1);
|
|
}
|
|
|
|
int ssl_is_init_finished(SSL *s)
|
|
{
|
|
return SSL_is_init_finished(s);
|
|
}
|
|
|
|
int ssl_smart_shutdown(SSL *ssl)
|
|
{
|
|
char i;
|
|
int rc;
|
|
rc = 0;
|
|
for(i = 0; i < 4; i++) {
|
|
if((rc = SSL_shutdown(ssl)))
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* Retrieve a static string for the given SSL error.
|
|
*
|
|
* \param err The error to look up.
|
|
* \param my_errno The value of errno to use in case we want to call strerror().
|
|
*/
|
|
char *ssl_error_str(int err, int my_errno)
|
|
{
|
|
static char ssl_errbuf[256];
|
|
char *ssl_errstr = NULL;
|
|
|
|
switch(err) {
|
|
case SSL_ERROR_NONE:
|
|
ssl_errstr = "SSL: No error";
|
|
break;
|
|
case SSL_ERROR_SSL:
|
|
ssl_errstr = "Internal OpenSSL error or protocol error";
|
|
ssl_doerror_anon();
|
|
break;
|
|
case SSL_ERROR_WANT_READ:
|
|
ssl_errstr = "OpenSSL functions requested a read()";
|
|
break;
|
|
case SSL_ERROR_WANT_WRITE:
|
|
ssl_errstr = "OpenSSL functions requested a write()";
|
|
break;
|
|
case SSL_ERROR_WANT_X509_LOOKUP:
|
|
ssl_errstr = "OpenSSL requested a X509 lookup which didn't arrive";
|
|
break;
|
|
case SSL_ERROR_SYSCALL:
|
|
snprintf(ssl_errbuf, sizeof(ssl_errbuf), "%s", strerror(my_errno));
|
|
ssl_errstr = ssl_errbuf;
|
|
break;
|
|
case SSL_ERROR_ZERO_RETURN:
|
|
ssl_errstr = "Underlying socket operation returned zero";
|
|
break;
|
|
case SSL_ERROR_WANT_CONNECT:
|
|
ssl_errstr = "OpenSSL functions wanted a connect()";
|
|
break;
|
|
default:
|
|
ssl_errstr = "Unknown OpenSSL error (huh?)";
|
|
}
|
|
return ssl_errstr;
|
|
}
|
|
|
|
const char* ssl_get_verify_result(SSL *ssl)
|
|
{
|
|
int vrfyresult = SSL_get_verify_result(ssl);
|
|
|
|
return X509_verify_cert_error_string(vrfyresult);
|
|
}
|
|
|
|
void sslfail(char *txt)
|
|
{
|
|
unsigned long err = ERR_get_error();
|
|
char string[120];
|
|
|
|
if (!err) {
|
|
Debug((DEBUG_DEBUG, "%s: poof", txt));
|
|
} else {
|
|
ERR_error_string(err, string);
|
|
Debug((DEBUG_FATAL, "%s: %s", txt, string));
|
|
}
|
|
}
|
|
|
|
void binary_to_hex(unsigned char *bin, char *hex, int length)
|
|
{
|
|
static const char trans[] = "0123456789ABCDEF";
|
|
int i;
|
|
|
|
for(i = 0; i < length; i++)
|
|
{
|
|
hex[i << 1] = trans[bin[i] >> 4];
|
|
hex[(i << 1) + 1] = trans[bin[i] & 0xf];
|
|
}
|
|
|
|
hex[i << 1] = '\0';
|
|
}
|
|
|
|
#endif /* USE_SSL */
|
|
|