mirror of
https://gitee.com/wangbin579/cetus.git
synced 2024-12-03 12:27:42 +08:00
493 lines
14 KiB
C
493 lines
14 KiB
C
#include "network-ssl.h"
|
|
#include "glib-ext.h"
|
|
|
|
#ifdef HAVE_OPENSSL
|
|
|
|
#include <openssl/ssl.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/conf.h>
|
|
#include <errno.h>
|
|
|
|
enum network_ssl_error_t {
|
|
SSL_OK = 1,
|
|
SSL_RBIO_BUFFER_FULL = -10,
|
|
};
|
|
struct network_ssl_connection_s {
|
|
SSL* ssl;
|
|
enum network_ssl_error_t error;
|
|
};
|
|
|
|
static SSL_CTX *g_ssl_context = NULL;
|
|
|
|
static void network_ssl_info_callback(const SSL *s, int where, int ret)
|
|
{
|
|
const char *str;
|
|
int w;
|
|
|
|
w=where& ~SSL_ST_MASK;
|
|
|
|
if (w & SSL_ST_CONNECT) str="SSL_connect";
|
|
else if (w & SSL_ST_ACCEPT) str="SSL_accept";
|
|
else str="undefined";
|
|
|
|
if (where & SSL_CB_LOOP)
|
|
{
|
|
g_warning("%s:%s",str,SSL_state_string_long(s));
|
|
}
|
|
else if (where & SSL_CB_ALERT)
|
|
{
|
|
str=(where & SSL_CB_READ)?"read":"write";
|
|
g_warning("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)
|
|
g_warning("%s:failed in %s",
|
|
str,SSL_state_string_long(s));
|
|
else if (ret < 0)
|
|
{
|
|
g_warning("%s:error in %s",
|
|
str,SSL_state_string_long(s));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void network_ssl_clear_error(network_ssl_connection_t* ssl)
|
|
{
|
|
while (ERR_peek_error()) {
|
|
g_critical("ignoring stale global SSL error");
|
|
}
|
|
ERR_clear_error();
|
|
ssl->error = 0;
|
|
}
|
|
|
|
static gboolean network_ssl_create_context(char* conf_dir)
|
|
{
|
|
gboolean ret = TRUE;
|
|
g_ssl_context = SSL_CTX_new(TLSv1_method());
|
|
if (g_ssl_context == NULL) {
|
|
g_critical(G_STRLOC " SSL_CTX_new failed");
|
|
return FALSE;
|
|
}
|
|
|
|
/* server side options */
|
|
|
|
#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
|
|
SSL_CTX_set_options(g_ssl_context, SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG);
|
|
#endif
|
|
|
|
#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING
|
|
/* this option allow a potential SSL 2.0 rollback (CAN-2005-2969) */
|
|
SSL_CTX_set_options(g_ssl_context, SSL_OP_MSIE_SSLV2_RSA_PADDING);
|
|
#endif
|
|
|
|
#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
|
|
SSL_CTX_set_options(g_ssl_context, SSL_OP_SSLEAY_080_CLIENT_DH_BUG);
|
|
#endif
|
|
|
|
#ifdef SSL_OP_TLS_D5_BUG
|
|
SSL_CTX_set_options(g_ssl_context, SSL_OP_TLS_D5_BUG);
|
|
#endif
|
|
|
|
#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG
|
|
SSL_CTX_set_options(g_ssl_context, SSL_OP_TLS_BLOCK_PADDING_BUG);
|
|
#endif
|
|
|
|
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
|
|
SSL_CTX_set_options(g_ssl_context, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
|
|
#endif
|
|
#ifdef SSL_CTRL_CLEAR_OPTIONS
|
|
/* only in 0.9.8m+ */
|
|
SSL_CTX_clear_options(g_ssl_context,
|
|
SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1);
|
|
#endif
|
|
|
|
#ifdef SSL_OP_NO_COMPRESSION
|
|
SSL_CTX_set_options(g_ssl_context, SSL_OP_NO_COMPRESSION);
|
|
#endif
|
|
|
|
#ifdef SSL_MODE_RELEASE_BUFFERS
|
|
SSL_CTX_set_mode(g_ssl_context, SSL_MODE_RELEASE_BUFFERS);
|
|
#endif
|
|
|
|
#ifdef SSL_MODE_NO_AUTO_CHAIN
|
|
SSL_CTX_set_mode(g_ssl_context, SSL_MODE_NO_AUTO_CHAIN);
|
|
#endif
|
|
|
|
SSL_CTX_set_read_ahead(g_ssl_context, 1); /* SSL_read clear data on the wire */
|
|
SSL_CTX_set_options(g_ssl_context, SSL_OP_SINGLE_DH_USE);
|
|
#if 0
|
|
SSL_CTX_set_info_callback(g_ssl_context, network_ssl_info_callback);
|
|
#endif
|
|
char* cert = g_build_filename(conf_dir, "server-cert.pem", NULL);
|
|
char* key = g_build_filename(conf_dir, "server-key.pem", NULL);
|
|
if (1 != SSL_CTX_use_certificate_file(g_ssl_context, cert , SSL_FILETYPE_PEM)) {
|
|
g_warning("server-cert.pem open error");
|
|
ret = FALSE;
|
|
}
|
|
if (1 != SSL_CTX_use_PrivateKey_file(g_ssl_context, key, SSL_FILETYPE_PEM)) {
|
|
g_warning("server-key.pem open error");
|
|
ret = FALSE;
|
|
}
|
|
g_free(cert);
|
|
g_free(key);
|
|
if (ret == FALSE) {
|
|
SSL_CTX_free(g_ssl_context);
|
|
g_ssl_context = NULL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
gboolean network_ssl_init(char* conf_dir)
|
|
{
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10100003L
|
|
|
|
if (OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL) == 0) {
|
|
g_critical(G_STRLOC " OPENSSL_init_ssl() failed");
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* OPENSSL_init_ssl() may leave errors in the error queue
|
|
* while returning success
|
|
*/
|
|
|
|
ERR_clear_error();
|
|
|
|
#else
|
|
|
|
OPENSSL_config(NULL);
|
|
|
|
SSL_library_init();
|
|
SSL_load_error_strings();
|
|
|
|
OpenSSL_add_all_algorithms();
|
|
|
|
#endif
|
|
return network_ssl_create_context(conf_dir);
|
|
}
|
|
|
|
gboolean
|
|
network_ssl_create_connection(network_socket* sock, int flags)
|
|
{
|
|
network_ssl_connection_t* conn = g_new0(network_ssl_connection_t, 1);
|
|
SSL* connection = SSL_new(g_ssl_context);
|
|
if (connection == NULL) {
|
|
g_critical(G_STRLOC " SSL_new failed");
|
|
return FALSE;
|
|
}
|
|
BIO* rbio = BIO_new(BIO_s_mem());
|
|
if (!rbio) {
|
|
g_critical(G_STRLOC " BIO_new() failed");
|
|
SSL_free(connection);
|
|
return FALSE;
|
|
}
|
|
BIO* wbio = BIO_new_fd(sock->fd, 0);
|
|
if (!wbio) {
|
|
g_critical(G_STRLOC " BIO_new_fd() failed");
|
|
SSL_free(connection);
|
|
return FALSE;
|
|
}
|
|
SSL_set_bio(connection, rbio, wbio);
|
|
|
|
if (flags & NETWORK_SSL_CLIENT) {
|
|
SSL_set_connect_state(connection);
|
|
} else {
|
|
SSL_set_accept_state(connection);
|
|
}
|
|
conn->ssl = connection;
|
|
sock->ssl = conn;
|
|
return TRUE;
|
|
}
|
|
|
|
void network_ssl_free_connection(network_socket* sock)
|
|
{
|
|
if (sock->ssl) {
|
|
SSL_free(sock->ssl->ssl);
|
|
g_free(sock->ssl);
|
|
}
|
|
}
|
|
|
|
static ssize_t SSL_writev(SSL *ssl, const struct iovec *vector, int count)
|
|
{
|
|
int i;
|
|
/* Find the total number of bytes to be written. */
|
|
size_t bytes = 0;
|
|
for (i = 0; i < count; ++i)
|
|
bytes += vector[i].iov_len;
|
|
|
|
/* Allocate a temporary buffer to hold the data. */
|
|
char *buffer = (char *) g_malloc(bytes);
|
|
|
|
/* Copy the data into BUFFER. */
|
|
size_t to_copy = bytes;
|
|
char *bp = buffer;
|
|
for (i = 0; i < count; ++i) {
|
|
size_t copy = MIN(vector[i].iov_len, to_copy);
|
|
|
|
memcpy((void *) bp, (void *) vector[i].iov_base, copy);
|
|
bp += copy;
|
|
|
|
to_copy -= copy;
|
|
if (to_copy == 0)
|
|
break;
|
|
}
|
|
|
|
ssize_t written = SSL_write(ssl, buffer, bytes);
|
|
g_free(buffer);
|
|
return written;
|
|
}
|
|
|
|
network_socket_retval_t
|
|
network_ssl_write(network_socket *sock, int send_chunks)
|
|
{
|
|
if (send_chunks == 0)
|
|
return NETWORK_SOCKET_SUCCESS;
|
|
|
|
network_queue* send_queue = sock->do_compress ?
|
|
sock->send_queue_compressed : sock->send_queue;
|
|
|
|
gint chunk_count = send_chunks > 0 ? send_chunks : (gint)send_queue->chunks->length;
|
|
|
|
if (chunk_count == 0)
|
|
return NETWORK_SOCKET_SUCCESS;
|
|
|
|
gint max_chunk_count = UIO_MAXIOV;
|
|
|
|
chunk_count = chunk_count > max_chunk_count ? max_chunk_count : chunk_count;
|
|
|
|
g_assert_cmpint(chunk_count, >, 0); /* make sure it is never negative */
|
|
|
|
struct iovec *iov = g_new0(struct iovec, chunk_count);
|
|
|
|
GList *chunk;
|
|
gint chunk_id;
|
|
|
|
for (chunk = send_queue->chunks->head, chunk_id = 0;
|
|
chunk && chunk_id < chunk_count; chunk_id++, chunk = chunk->next) {
|
|
GString *s = chunk->data;
|
|
|
|
if (chunk_id == 0) {
|
|
g_assert(send_queue->offset < s->len);
|
|
|
|
iov[chunk_id].iov_base = s->str + send_queue->offset;
|
|
iov[chunk_id].iov_len = s->len - send_queue->offset;
|
|
} else {
|
|
iov[chunk_id].iov_base = s->str;
|
|
iov[chunk_id].iov_len = s->len;
|
|
}
|
|
|
|
if (s->len == 0) {
|
|
g_warning("%s: s->len is zero", G_STRLOC);
|
|
}
|
|
}
|
|
|
|
gssize len = SSL_writev(sock->ssl->ssl, iov, chunk_count);
|
|
|
|
g_free(iov);
|
|
|
|
if (len < 0) {
|
|
int sslerr = SSL_get_error(sock->ssl->ssl, len);
|
|
if (sslerr == SSL_ERROR_WANT_WRITE) {
|
|
g_debug(G_STRLOC " SSL_write() WANT_WRITE");
|
|
return NETWORK_SOCKET_WAIT_FOR_EVENT;
|
|
}
|
|
if (sslerr == SSL_ERROR_WANT_READ) {
|
|
g_warning(G_STRLOC " peer started SSL renegotiation");
|
|
return NETWORK_SOCKET_WAIT_FOR_EVENT; /* TODO: read event */
|
|
}
|
|
g_critical(G_STRLOC " SSL_write() failed");
|
|
return NETWORK_SOCKET_ERROR;
|
|
} else if (len == 0) {
|
|
int sslerr = SSL_get_error(sock->ssl->ssl, len);
|
|
g_critical(G_STRLOC " SSL_write() failed: %d", sslerr);
|
|
return NETWORK_SOCKET_ERROR;
|
|
}
|
|
|
|
send_queue->offset += len;
|
|
send_queue->len -= len;
|
|
|
|
/* check all the chunks which we have sent out */
|
|
for (chunk = send_queue->chunks->head; chunk;) {
|
|
GString *s = chunk->data;
|
|
|
|
if (s->len == 0) {
|
|
g_warning("%s: s->len is zero", G_STRLOC);
|
|
}
|
|
|
|
if (send_queue->offset >= s->len) {
|
|
send_queue->offset -= s->len;
|
|
#if NETWORK_DEBUG_TRACE_IO
|
|
g_debug("%s:output for sock:%p", G_STRLOC, sock);
|
|
#endif
|
|
if (!sock->do_query_cache) {
|
|
g_string_free(s, TRUE);
|
|
} else {
|
|
size_t len = sock->cache_queue->len + s->len;
|
|
if (len > MAX_QUERY_CACHE_SIZE) {
|
|
if (!sock->query_cache_too_long) {
|
|
g_message("%s:too long for cache queue:%p, len:%d", G_STRLOC, sock, (int)len);
|
|
sock->query_cache_too_long = 1;
|
|
}
|
|
g_string_free(s, TRUE);
|
|
} else {
|
|
g_debug("%s:append packet to cache queue:%p, len:%d, total:%d",
|
|
G_STRLOC, sock, (int)s->len, (int)len);
|
|
network_queue_append(sock->cache_queue, s);
|
|
}
|
|
}
|
|
|
|
g_queue_delete_link(send_queue->chunks, chunk);
|
|
|
|
chunk = send_queue->chunks->head;
|
|
} else {
|
|
g_debug("%s:wait for event", G_STRLOC);
|
|
return NETWORK_SOCKET_WAIT_FOR_EVENT;
|
|
}
|
|
}
|
|
|
|
return NETWORK_SOCKET_SUCCESS;
|
|
}
|
|
|
|
static int network_ssl_write_to_rbio(network_socket* sock)
|
|
{
|
|
network_ssl_clear_error(sock->ssl);
|
|
|
|
BIO* rbio = SSL_get_rbio(sock->ssl->ssl);
|
|
network_queue* queue = sock->recv_queue_raw;
|
|
int bytes_written = queue->len;
|
|
|
|
int i = 0;
|
|
GList* chunk;
|
|
for (chunk = queue->chunks->head, i = 0; chunk; i++, chunk = chunk->next) {
|
|
GString *s = chunk->data;
|
|
char* buf;
|
|
int buf_len;
|
|
if (i == 0) {
|
|
g_assert(queue->offset < s->len);
|
|
buf = s->str + queue->offset;
|
|
buf_len = s->len - queue->offset;
|
|
} else {
|
|
buf = s->str;
|
|
buf_len = s->len;
|
|
}
|
|
int len = BIO_write(rbio, buf, buf_len);
|
|
if (len == buf_len) {
|
|
queue->offset += len;
|
|
queue->len -= len;
|
|
} else if (len >= 0) {
|
|
queue->offset += len;
|
|
queue->len -= len;
|
|
sock->ssl->error = SSL_RBIO_BUFFER_FULL;
|
|
break;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
bytes_written = bytes_written - queue->len;
|
|
|
|
/* delete used chunks, adjust offset */
|
|
for (chunk = queue->chunks->head; chunk; ) {
|
|
GString *s = chunk->data;
|
|
|
|
if (queue->offset >= s->len) {
|
|
queue->offset -= s->len;
|
|
g_string_free(s, TRUE);
|
|
g_queue_delete_link(queue->chunks, chunk);
|
|
chunk = queue->chunks->head;
|
|
} else {
|
|
g_message("write_to_rbio have residual");
|
|
return bytes_written; /* have some residual */
|
|
}
|
|
}
|
|
|
|
return bytes_written;
|
|
}
|
|
|
|
/**
|
|
[sock->recv_queue_raw] === SSL decrypt ===> [sock->recv_queue_decrypted_raw]
|
|
*/
|
|
gboolean network_ssl_decrypt_packet(network_socket* sock)
|
|
{
|
|
/* TODO: the raw packet is very likely a complete SSL record
|
|
better solution: write one-packet to rbio and SSL_read one-packet */
|
|
network_ssl_clear_error(sock->ssl);
|
|
|
|
char buf[16*1024];/*TODO: size? */
|
|
while (TRUE) {
|
|
int written = network_ssl_write_to_rbio(sock);
|
|
if (written == 0) {
|
|
return TRUE;
|
|
} else if (written < 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
while (TRUE) {
|
|
int len = SSL_read(sock->ssl->ssl, buf, sizeof(buf));
|
|
if (len < 0) {
|
|
int sslerr = SSL_get_error(sock->ssl->ssl, len);
|
|
if (sslerr == SSL_ERROR_WANT_WRITE) {
|
|
g_warning(G_STRLOC " peer started SSL renegotiation");
|
|
return TRUE; /*TODO: how to renegotiate? */
|
|
}
|
|
if (sslerr == SSL_ERROR_WANT_READ) {
|
|
g_debug(G_STRLOC " SSL_read() WANT_READ");
|
|
return TRUE; /* TODO: read event */
|
|
}
|
|
g_critical(G_STRLOC " SSL_read() failed");
|
|
if (sslerr == SSL_ERROR_SYSCALL) {
|
|
g_critical(G_STRLOC " %s", strerror(errno));
|
|
}
|
|
return FALSE;
|
|
} else if (len == 0) {
|
|
int sslerr = SSL_get_error(sock->ssl->ssl, len);
|
|
g_critical(G_STRLOC " SSL_read() failed: %d", sslerr);
|
|
return FALSE;
|
|
}
|
|
network_queue_append(sock->recv_queue_decrypted_raw, g_string_new_len(buf, len));
|
|
}
|
|
}
|
|
}
|
|
|
|
network_socket_retval_t network_ssl_handshake(network_socket* sock)
|
|
{
|
|
network_ssl_clear_error(sock->ssl);
|
|
|
|
while (TRUE) {
|
|
int len = network_ssl_write_to_rbio(sock);
|
|
if (len < 0) {
|
|
return NETWORK_SOCKET_ERROR;
|
|
} else if (len == 0) {
|
|
return NETWORK_SOCKET_WAIT_FOR_EVENT;
|
|
}
|
|
|
|
int ret = SSL_do_handshake(sock->ssl->ssl);
|
|
|
|
if (ret < 0) {
|
|
int sslerr = SSL_get_error(sock->ssl->ssl, ret);
|
|
g_debug(G_STRLOC " SSL_get_error: %d", sslerr);
|
|
|
|
if (sslerr == SSL_ERROR_WANT_WRITE) {
|
|
g_debug(G_STRLOC " handshake continuation WANT_WRITE");
|
|
return NETWORK_SOCKET_WAIT_FOR_EVENT;
|
|
} else if (sslerr == SSL_ERROR_WANT_READ) {
|
|
g_debug(G_STRLOC " handshake continuation WANT_READ");
|
|
return NETWORK_SOCKET_WAIT_FOR_EVENT;
|
|
}
|
|
return NETWORK_SOCKET_ERROR;
|
|
} else if (ret == 0) {
|
|
return NETWORK_SOCKET_ERROR;
|
|
} else {
|
|
g_debug(G_STRLOC " handshake success");
|
|
return NETWORK_SOCKET_SUCCESS;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#endif /* HAVE_OPENSSL */
|