Merge branch 'gnutls_work'
This commit is contained in:
commit
46d365cafe
@ -17,13 +17,21 @@ set (PROJECT_SOURCE_DIR "${CMAKE_SOURCE_DIR}/src")
|
||||
option(RELEASE "Release build, debug build if disabled" ON)
|
||||
option(LINK_SUPPORT "Allow hub linking" OFF)
|
||||
option(SSL_SUPPORT "Enable SSL support" ON)
|
||||
option(USE_OPENSSL "Use OpenSSL's SSL support" OFF)
|
||||
option(SQLITE_SUPPORT "Enable SQLite support" ON)
|
||||
option(ADC_STRESS "Enable the stress tester client" OFF)
|
||||
|
||||
find_package(Git)
|
||||
|
||||
if (SSL_SUPPORT)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
if (USE_OPENSSL)
|
||||
find_package(OpenSSL)
|
||||
else()
|
||||
find_package(GnuTLS)
|
||||
endif()
|
||||
if (NOT GNUTLS_FOUND AND NOT OPENSSL_FOUND)
|
||||
message(FATAL_ERROR "Neither OpenSSL nor GnuTLS are not found!")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
@ -41,6 +49,7 @@ set (network_SOURCES
|
||||
${PROJECT_SOURCE_DIR}/network/timeout.c
|
||||
${PROJECT_SOURCE_DIR}/network/timer.c
|
||||
${PROJECT_SOURCE_DIR}/network/network.c
|
||||
${PROJECT_SOURCE_DIR}/network/openssl.c
|
||||
${PROJECT_SOURCE_DIR}/network/ipcalc.c
|
||||
)
|
||||
|
||||
@ -156,16 +165,26 @@ else()
|
||||
endif()
|
||||
|
||||
if(OPENSSL_FOUND)
|
||||
add_definitions(-DSSL_SUPPORT=1)
|
||||
set(SSL_LIBS ${OPENSSL_LIBRARIES})
|
||||
add_definitions(-DSSL_SUPPORT=1 -DSSL_USE_OPENSSL=1)
|
||||
include_directories(${OPENSSL_INCLUDE_DIR})
|
||||
target_link_libraries(uhub ${OPENSSL_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if (GNUTLS_FOUND)
|
||||
set(SSL_LIBS ${GNUTLS_LIBRARIES})
|
||||
add_definitions(-DSSL_SUPPORT=1 -DSSL_USE_GNUTLS=1 ${GNUTLS_DEFINITIONS})
|
||||
include_directories(${GNUTLS_INCLUDE_DIR})
|
||||
endif()
|
||||
|
||||
if(SSL_SUPPORT)
|
||||
target_link_libraries(uhub ${SSL_LIBS})
|
||||
if(UNIX)
|
||||
target_link_libraries(uhub-admin ${OPENSSL_LIBRARIES})
|
||||
target_link_libraries(uhub-admin ${SSL_LIBS})
|
||||
endif()
|
||||
target_link_libraries(mod_welcome ${OPENSSL_LIBRARIES})
|
||||
target_link_libraries(mod_logging ${OPENSSL_LIBRARIES})
|
||||
target_link_libraries(mod_welcome ${SSL_LIBS})
|
||||
target_link_libraries(mod_logging ${SSL_LIBS})
|
||||
if (ADC_STRESS)
|
||||
target_link_libraries(adcrush ${OPENSSL_LIBRARIES})
|
||||
target_link_libraries(adcrush ${SSL_LIBS})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
@ -731,41 +731,25 @@ static int load_ssl_certificates(struct hub_info* hub, struct hub_config* config
|
||||
{
|
||||
if (config->tls_enable)
|
||||
{
|
||||
hub->ssl_method = (SSL_METHOD*) SSLv23_method(); /* TLSv1_method() */
|
||||
hub->ssl_ctx = SSL_CTX_new(hub->ssl_method);
|
||||
|
||||
/* Disable SSLv2 */
|
||||
SSL_CTX_set_options(hub->ssl_ctx, SSL_OP_NO_SSLv2);
|
||||
SSL_CTX_set_quiet_shutdown(hub->ssl_ctx, 1);
|
||||
|
||||
if (SSL_CTX_use_certificate_file(hub->ssl_ctx, config->tls_certificate, SSL_FILETYPE_PEM) < 0)
|
||||
hub->ctx = net_ssl_context_create();
|
||||
if (ssl_load_certificate(hub->ctx, config->tls_certificate) &&
|
||||
ssl_load_private_key(hub->ctx, config->tls_private_key) &&
|
||||
ssl_check_private_key(hub->ctx))
|
||||
{
|
||||
LOG_ERROR("SSL_CTX_use_certificate_file: %s", ERR_error_string(ERR_get_error(), NULL));
|
||||
LOG_INFO("Enabling TLS (%s), using certificate: %s, private key: %s", net_ssl_get_provider(), config->tls_certificate, config->tls_private_key);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (SSL_CTX_use_PrivateKey_file(hub->ssl_ctx, config->tls_private_key, SSL_FILETYPE_PEM) < 0)
|
||||
{
|
||||
LOG_ERROR("SSL_CTX_use_PrivateKey_file: %s", ERR_error_string(ERR_get_error(), NULL));
|
||||
}
|
||||
|
||||
if (SSL_CTX_check_private_key(hub->ssl_ctx) != 1)
|
||||
{
|
||||
LOG_FATAL("SSL_CTX_check_private_key: Private key does not match the certificate public key: %s", ERR_error_string(ERR_get_error(), NULL));
|
||||
return 0;
|
||||
}
|
||||
LOG_INFO("Enabling TLS, using certificate: %s, private key: %s", config->tls_certificate, config->tls_private_key);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void unload_ssl_certificates(struct hub_info* hub)
|
||||
{
|
||||
if (hub->ssl_ctx)
|
||||
{
|
||||
SSL_CTX_free(hub->ssl_ctx);
|
||||
}
|
||||
if (hub->ctx)
|
||||
net_ssl_context_destroy(hub->ctx);
|
||||
}
|
||||
#endif
|
||||
#endif /* SSL_SUPPORT */
|
||||
|
||||
struct hub_info* hub_start_service(struct hub_config* config)
|
||||
{
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* uhub - A tiny ADC p2p connection hub
|
||||
* Copyright (C) 2007-2011, Jan Vidar Krey
|
||||
* Copyright (C) 2007-2012, Jan Vidar Krey
|
||||
*
|
||||
* 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
|
||||
@ -116,8 +116,7 @@ struct hub_info
|
||||
struct uhub_plugins* plugins; /* Plug-ins loaded for this hub instance. */
|
||||
|
||||
#ifdef SSL_SUPPORT
|
||||
SSL_METHOD* ssl_method;
|
||||
SSL_CTX* ssl_ctx;
|
||||
struct ssl_context_handle* ctx;
|
||||
#endif /* SSL_SUPPORT */
|
||||
};
|
||||
|
||||
|
@ -85,7 +85,7 @@ static void probe_net_event(struct net_connection* con, int events, void *arg)
|
||||
{
|
||||
probe->connection = 0;
|
||||
}
|
||||
net_con_ssl_handshake(con, net_con_ssl_mode_server, probe->hub->ssl_ctx);
|
||||
net_con_ssl_handshake(con, net_con_ssl_mode_server, probe->hub->ctx);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* uhub - A tiny ADC p2p connection hub
|
||||
* Copyright (C) 2007-2010, Jan Vidar Krey
|
||||
* Copyright (C) 2007-2012, Jan Vidar Krey
|
||||
*
|
||||
* 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
|
||||
@ -181,11 +181,8 @@ void net_con_close(struct net_connection* con)
|
||||
|
||||
#ifdef SSL_SUPPORT
|
||||
if (con->ssl)
|
||||
{
|
||||
SSL_shutdown(con->ssl);
|
||||
SSL_clear(con->ssl);
|
||||
}
|
||||
#endif
|
||||
net_ssl_shutdown(con);
|
||||
#endif /* SSL_SUPPORT */
|
||||
|
||||
net_close(con->sd);
|
||||
con->sd = -1;
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* uhub - A tiny ADC p2p connection hub
|
||||
* Copyright (C) 2007-2010, Jan Vidar Krey
|
||||
* Copyright (C) 2007-2012, Jan Vidar Krey
|
||||
*
|
||||
* 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
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* uhub - A tiny ADC p2p connection hub
|
||||
* Copyright (C) 2007-2010, Jan Vidar Krey
|
||||
* Copyright (C) 2007-2012, Jan Vidar Krey
|
||||
*
|
||||
* 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
|
||||
@ -20,11 +20,10 @@
|
||||
#define NET_WANT_READ NET_EVENT_READ
|
||||
#define NET_WANT_WRITE NET_EVENT_WRITE
|
||||
#define NET_WANT_ACCEPT NET_EVENT_READ
|
||||
#define NET_WANT_SSL_READ 0x0010
|
||||
#define NET_WANT_SSL_WRITE 0x0020
|
||||
#define NET_WANT_SSL_ACCEPT 0x0040
|
||||
#define NET_WANT_SSL_CONNECT 0x0080
|
||||
#define NET_WANT_SSL_X509_LOOKUP 0x0100
|
||||
|
||||
#define NET_SSL_ANY NET_WANT_SSL_READ | NET_WANT_SSL_WRITE | NET_WANT_SSL_ACCEPT | NET_WANT_SSL_CONNECT | NET_WANT_SSL_X509_LOOKUP
|
||||
|
||||
struct ssl_handle; /* abstract type */
|
||||
|
||||
#define NET_CLEANUP 0x8000
|
||||
|
||||
@ -36,9 +35,7 @@
|
||||
struct timeout_evt* timeout; /** timeout event handler */
|
||||
|
||||
#define NET_CON_STRUCT_SSL \
|
||||
SSL* ssl; /** SSL handle */ \
|
||||
uint32_t ssl_state; /** SSL state */ \
|
||||
size_t write_len; /** Length of last SSL_write(), only used if flags is NET_WANT_SSL_READ. */ \
|
||||
struct ssl_handle* ssl; /** SSL handle */
|
||||
|
||||
#ifdef SSL_SUPPORT
|
||||
#define NET_CON_STRUCT_COMMON \
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* uhub - A tiny ADC p2p connection hub
|
||||
* Copyright (C) 2007-2010, Jan Vidar Krey
|
||||
* Copyright (C) 2007-2012, Jan Vidar Krey
|
||||
*
|
||||
* 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
|
||||
@ -20,132 +20,23 @@
|
||||
#include "uhub.h"
|
||||
#include "network/common.h"
|
||||
|
||||
#ifdef SSL_SUPPORT
|
||||
|
||||
enum uhub_tls_state
|
||||
{
|
||||
tls_st_none,
|
||||
tls_st_error,
|
||||
tls_st_accepting,
|
||||
tls_st_connecting,
|
||||
tls_st_connected,
|
||||
tls_st_disconnecting,
|
||||
};
|
||||
|
||||
static int handle_openssl_error(struct net_connection* con, int ret)
|
||||
{
|
||||
uhub_assert(con);
|
||||
|
||||
int error = SSL_get_error(con->ssl, ret);
|
||||
switch (error)
|
||||
{
|
||||
case SSL_ERROR_ZERO_RETURN:
|
||||
LOG_PROTO("SSL_get_error: ret=%d, error=%d: SSL_ERROR_ZERO_RETURN", ret, error);
|
||||
con->ssl_state = tls_st_error;
|
||||
return -1;
|
||||
|
||||
case SSL_ERROR_WANT_READ:
|
||||
LOG_PROTO("SSL_get_error: ret=%d, error=%d: SSL_ERROR_WANT_READ", ret, error);
|
||||
con->flags |= NET_WANT_SSL_READ;
|
||||
net_con_update(con, NET_EVENT_READ);
|
||||
return 0;
|
||||
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
LOG_PROTO("SSL_get_error: ret=%d, error=%d: SSL_ERROR_WANT_WRITE", ret, error);
|
||||
con->flags |= NET_WANT_SSL_WRITE;
|
||||
net_con_update(con, NET_EVENT_READ | NET_EVENT_WRITE);
|
||||
return 0;
|
||||
|
||||
case SSL_ERROR_SYSCALL:
|
||||
LOG_PROTO("SSL_get_error: ret=%d, error=%d: SSL_ERROR_SYSCALL", ret, error);
|
||||
/* if ret == 0, connection closed, if ret == -1, check with errno */
|
||||
if (ret == 0)
|
||||
return -1;
|
||||
else
|
||||
return -net_error();
|
||||
|
||||
case SSL_ERROR_SSL:
|
||||
LOG_PROTO("SSL_get_error: ret=%d, error=%d: SSL_ERROR_SSL", ret, error);
|
||||
/* internal openssl error */
|
||||
con->ssl_state = tls_st_error;
|
||||
return -1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t net_con_ssl_accept(struct net_connection* con)
|
||||
{
|
||||
uhub_assert(con);
|
||||
con->ssl_state = tls_st_accepting;
|
||||
ssize_t ret = SSL_accept(con->ssl);
|
||||
#ifdef NETWORK_DUMP_DEBUG
|
||||
LOG_PROTO("SSL_accept() ret=%d", ret);
|
||||
#endif
|
||||
if (ret > 0)
|
||||
{
|
||||
net_con_update(con, NET_EVENT_READ);
|
||||
con->ssl_state = tls_st_connected;
|
||||
}
|
||||
else
|
||||
{
|
||||
return handle_openssl_error(con, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t net_con_ssl_connect(struct net_connection* con)
|
||||
{
|
||||
uhub_assert(con);
|
||||
|
||||
con->ssl_state = tls_st_connecting;
|
||||
ssize_t ret = SSL_connect(con->ssl);
|
||||
#ifdef NETWORK_DUMP_DEBUG
|
||||
LOG_PROTO("SSL_connect() ret=%d", ret);
|
||||
#endif
|
||||
if (ret > 0)
|
||||
{
|
||||
con->ssl_state = tls_st_connected;
|
||||
net_con_update(con, NET_EVENT_READ);
|
||||
}
|
||||
else
|
||||
{
|
||||
return handle_openssl_error(con, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t net_con_ssl_handshake(struct net_connection* con, enum net_con_ssl_mode ssl_mode, SSL_CTX* ssl_ctx)
|
||||
{
|
||||
uhub_assert(con);
|
||||
SSL* ssl = 0;
|
||||
|
||||
if (ssl_mode == net_con_ssl_mode_server)
|
||||
{
|
||||
ssl = SSL_new(ssl_ctx);
|
||||
if (!ssl)
|
||||
{
|
||||
LOG_ERROR("Unable to create new SSL stream\n");
|
||||
return -1;
|
||||
}
|
||||
SSL_set_fd(ssl, con->sd);
|
||||
net_con_set_ssl(con, ssl);
|
||||
return net_con_ssl_accept(con);
|
||||
}
|
||||
else
|
||||
{
|
||||
ssl = SSL_new(SSL_CTX_new(TLSv1_method()));
|
||||
SSL_set_fd(ssl, con->sd);
|
||||
net_con_set_ssl(con, ssl);
|
||||
return net_con_ssl_connect(con);
|
||||
}
|
||||
}
|
||||
#endif /* SSL_SUPPORT */
|
||||
|
||||
#ifdef SSL_SUPPORT
|
||||
void net_stats_add_tx(size_t bytes);
|
||||
void net_stats_add_rx(size_t bytes);
|
||||
#endif
|
||||
|
||||
static int is_blocked_or_interrupted()
|
||||
{
|
||||
int err = net_error();
|
||||
return
|
||||
#ifdef WINSOCK
|
||||
err == WSAEWOULDBLOCK
|
||||
#else
|
||||
err == EWOULDBLOCK
|
||||
#endif
|
||||
|| err == EINTR;
|
||||
}
|
||||
|
||||
ssize_t net_con_send(struct net_connection* con, const void* buf, size_t len)
|
||||
{
|
||||
int ret;
|
||||
@ -156,13 +47,7 @@ ssize_t net_con_send(struct net_connection* con, const void* buf, size_t len)
|
||||
ret = net_send(con->sd, buf, len, UHUB_SEND_SIGNAL);
|
||||
if (ret == -1)
|
||||
{
|
||||
if (
|
||||
#ifdef WINSOCK
|
||||
net_error() == WSAEWOULDBLOCK
|
||||
#else
|
||||
net_error() == EWOULDBLOCK
|
||||
#endif
|
||||
|| net_error() == EINTR)
|
||||
if (is_blocked_or_interrupted())
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
@ -170,19 +55,9 @@ ssize_t net_con_send(struct net_connection* con, const void* buf, size_t len)
|
||||
}
|
||||
else
|
||||
{
|
||||
con->write_len = len;
|
||||
ret = SSL_write(con->ssl, buf, len);
|
||||
LOG_PROTO("SSL_write(con=%p, buf=%p, len=" PRINTF_SIZE_T ") => %d", con, buf, len, ret);
|
||||
if (ret <= 0)
|
||||
{
|
||||
return handle_openssl_error(con, ret);
|
||||
}
|
||||
else
|
||||
{
|
||||
net_stats_add_tx(ret);
|
||||
}
|
||||
ret = net_ssl_send(con, buf, len);
|
||||
}
|
||||
#endif
|
||||
#endif /* SSL_SUPPORT */
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -190,19 +65,13 @@ ssize_t net_con_recv(struct net_connection* con, void* buf, size_t len)
|
||||
{
|
||||
int ret;
|
||||
#ifdef SSL_SUPPORT
|
||||
if (!net_con_is_ssl(con))
|
||||
if (!con->ssl)
|
||||
{
|
||||
#endif
|
||||
ret = net_recv(con->sd, buf, len, 0);
|
||||
if (ret == -1)
|
||||
{
|
||||
if (
|
||||
#ifdef WINSOCK
|
||||
net_error() == WSAEWOULDBLOCK
|
||||
#else
|
||||
net_error() == EWOULDBLOCK
|
||||
#endif
|
||||
|| net_error() == EINTR)
|
||||
if (is_blocked_or_interrupted())
|
||||
return 0;
|
||||
return -net_error();
|
||||
}
|
||||
@ -214,22 +83,9 @@ ssize_t net_con_recv(struct net_connection* con, void* buf, size_t len)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (con->ssl_state == tls_st_error)
|
||||
return -1;
|
||||
|
||||
ret = SSL_read(con->ssl, buf, len);
|
||||
LOG_PROTO("SSL_read(con=%p, buf=%p, len=" PRINTF_SIZE_T ") => %d", con, buf, len, ret);
|
||||
if (ret > 0)
|
||||
{
|
||||
net_con_update(con, NET_EVENT_READ);
|
||||
net_stats_add_rx(ret);
|
||||
}
|
||||
else
|
||||
{
|
||||
return handle_openssl_error(con, ret);
|
||||
}
|
||||
ret = net_ssl_recv(con, buf, len);
|
||||
}
|
||||
#endif
|
||||
#endif /* SSL_SUPPORT */
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -238,13 +94,7 @@ ssize_t net_con_peek(struct net_connection* con, void* buf, size_t len)
|
||||
int ret = net_recv(con->sd, buf, len, MSG_PEEK);
|
||||
if (ret == -1)
|
||||
{
|
||||
if (
|
||||
#ifdef WINSOCK
|
||||
net_error() == WSAEWOULDBLOCK
|
||||
#else
|
||||
net_error() == EWOULDBLOCK
|
||||
#endif
|
||||
|| net_error() == EINTR)
|
||||
if (is_blocked_or_interrupted())
|
||||
return 0;
|
||||
return -net_error();
|
||||
}
|
||||
@ -254,19 +104,10 @@ ssize_t net_con_peek(struct net_connection* con, void* buf, size_t len)
|
||||
}
|
||||
|
||||
#ifdef SSL_SUPPORT
|
||||
|
||||
int net_con_is_ssl(struct net_connection* con)
|
||||
{
|
||||
return con->ssl != 0;
|
||||
}
|
||||
|
||||
SSL* net_con_get_ssl(struct net_connection* con)
|
||||
{
|
||||
return con->ssl;
|
||||
}
|
||||
|
||||
void net_con_set_ssl(struct net_connection* con, SSL* ssl)
|
||||
{
|
||||
con->ssl = ssl;
|
||||
return !!con->ssl;
|
||||
}
|
||||
#endif /* SSL_SUPPORT */
|
||||
|
||||
@ -283,7 +124,8 @@ void* net_con_get_ptr(struct net_connection* con)
|
||||
void net_con_destroy(struct net_connection* con)
|
||||
{
|
||||
#ifdef SSL_SUPPORT
|
||||
SSL_free(con->ssl);
|
||||
if (con->ssl)
|
||||
net_ssl_destroy(con);
|
||||
#endif
|
||||
hub_free(con);
|
||||
}
|
||||
@ -301,62 +143,10 @@ void net_con_callback(struct net_connection* con, int events)
|
||||
}
|
||||
|
||||
#ifdef SSL_SUPPORT
|
||||
if (!con->ssl)
|
||||
{
|
||||
if (con->ssl)
|
||||
net_ssl_callback(con, events);
|
||||
else
|
||||
#endif
|
||||
con->callback(con, events, con->ptr);
|
||||
#ifdef SSL_SUPPORT
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef NETWORK_DUMP_DEBUG
|
||||
LOG_PROTO("net_con_event: events=%d, con=%p, state=%d", events, con, con->ssl_state);
|
||||
#endif
|
||||
switch (con->ssl_state)
|
||||
{
|
||||
case tls_st_none:
|
||||
con->callback(con, events, con->ptr);
|
||||
break;
|
||||
|
||||
case tls_st_error:
|
||||
con->callback(con, NET_EVENT_READ, con->ptr);
|
||||
break;
|
||||
|
||||
case tls_st_accepting:
|
||||
if (net_con_ssl_accept(con) < 0)
|
||||
{
|
||||
con->callback(con, NET_EVENT_READ, con->ptr);
|
||||
}
|
||||
break;
|
||||
|
||||
case tls_st_connecting:
|
||||
if (net_con_ssl_connect(con) < 0)
|
||||
{
|
||||
con->callback(con, NET_EVENT_READ, con->ptr);
|
||||
}
|
||||
break;
|
||||
|
||||
case tls_st_connected:
|
||||
LOG_PROTO("tls_st_connected, events=%s%s, ssl_flags=%s%s", (events & NET_EVENT_READ ? "R" : ""), (events & NET_EVENT_WRITE ? "W" : ""), con->flags & NET_WANT_SSL_READ ? "R" : "", con->flags & NET_WANT_SSL_WRITE ? "W" : "");
|
||||
if (events & NET_EVENT_WRITE && con->flags & NET_WANT_SSL_READ)
|
||||
{
|
||||
con->callback(con, events & NET_EVENT_READ, con->ptr);
|
||||
return;
|
||||
}
|
||||
|
||||
if (events & NET_EVENT_READ && con->flags & NET_WANT_SSL_WRITE)
|
||||
{
|
||||
con->callback(con, events & NET_EVENT_READ, con->ptr);
|
||||
return;
|
||||
}
|
||||
|
||||
con->callback(con, events, con->ptr);
|
||||
break;
|
||||
|
||||
case tls_st_disconnecting:
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
* uhub - A tiny ADC p2p connection hub
|
||||
* Copyright (C) 2007-2010, Jan Vidar Krey
|
||||
* Copyright (C) 2007-2012, Jan Vidar Krey
|
||||
*
|
||||
* 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
|
||||
@ -23,6 +23,7 @@
|
||||
#include "uhub.h"
|
||||
#include "network/common.h"
|
||||
#include "network/backend.h"
|
||||
#include "network/tls.h"
|
||||
|
||||
#define NET_EVENT_TIMEOUT 0x0001
|
||||
#define NET_EVENT_READ 0x0002
|
||||
@ -83,29 +84,5 @@ extern ssize_t net_con_peek(struct net_connection* con, void* buf, size_t len);
|
||||
extern void net_con_set_timeout(struct net_connection* con, int seconds);
|
||||
extern void net_con_clear_timeout(struct net_connection* con);
|
||||
|
||||
#ifdef SSL_SUPPORT
|
||||
/**
|
||||
* Start SSL_accept()
|
||||
*/
|
||||
extern ssize_t net_con_ssl_accept(struct net_connection*);
|
||||
|
||||
/**
|
||||
* Start SSL_connect()
|
||||
*/
|
||||
extern ssize_t net_con_ssl_connect(struct net_connection*);
|
||||
|
||||
enum net_con_ssl_mode
|
||||
{
|
||||
net_con_ssl_mode_server,
|
||||
net_con_ssl_mode_client,
|
||||
};
|
||||
|
||||
extern ssize_t net_con_ssl_handshake(struct net_connection* con, enum net_con_ssl_mode, SSL_CTX* ssl_ctx);
|
||||
|
||||
extern int net_con_is_ssl(struct net_connection* con);
|
||||
extern SSL* net_con_get_ssl(struct net_connection* con);
|
||||
extern void net_con_set_ssl(struct net_connection* con, SSL*);
|
||||
#endif /* SSL_SUPPORT */
|
||||
|
||||
#endif /* HAVE_UHUB_NETWORK_CONNECTION_H */
|
||||
|
||||
|
@ -59,9 +59,11 @@ int net_initialize()
|
||||
net_stats_initialize();
|
||||
|
||||
#ifdef SSL_SUPPORT
|
||||
#ifdef SSL_USE_OPENSSL
|
||||
LOG_TRACE("Initializing OpenSSL...");
|
||||
SSL_library_init();
|
||||
SSL_load_error_strings();
|
||||
#endif /* SSL_USE_OPENSSL */
|
||||
#endif /* SSL_SUPPORT */
|
||||
|
||||
net_initialized = 1;
|
||||
@ -100,10 +102,12 @@ int net_destroy()
|
||||
net_backend_shutdown();
|
||||
|
||||
#ifdef SSL_SUPPORT
|
||||
#ifdef SSL_USE_OPENSSL
|
||||
ERR_free_strings();
|
||||
EVP_cleanup();
|
||||
CRYPTO_cleanup_all_ex_data();
|
||||
#endif
|
||||
#endif /* SSL_USE_OPENSSL */
|
||||
#endif /* SSL_SUPPORT */
|
||||
|
||||
#ifdef WINSOCK
|
||||
WSACleanup();
|
||||
|
305
src/network/openssl.c
Normal file
305
src/network/openssl.c
Normal file
@ -0,0 +1,305 @@
|
||||
/*
|
||||
* uhub - A tiny ADC p2p connection hub
|
||||
* Copyright (C) 2007-2012, Jan Vidar Krey
|
||||
*
|
||||
* 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 3 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "uhub.h"
|
||||
#include "network/common.h"
|
||||
#include "network/tls.h"
|
||||
|
||||
#ifdef SSL_SUPPORT
|
||||
#ifdef SSL_USE_OPENSSL
|
||||
|
||||
#define NETWORK_DUMP_DEBUG
|
||||
|
||||
struct net_ssl_openssl
|
||||
{
|
||||
SSL* ssl;
|
||||
enum ssl_state state;
|
||||
uint32_t flags;
|
||||
BIO* biow;
|
||||
BIO* bior;
|
||||
};
|
||||
|
||||
struct net_context_openssl
|
||||
{
|
||||
SSL_METHOD* ssl_method;
|
||||
SSL_CTX* ssl_ctx;
|
||||
};
|
||||
|
||||
static struct net_ssl_openssl* get_handle(struct net_connection* con)
|
||||
{
|
||||
uhub_assert(con);
|
||||
return (struct net_ssl_openssl*) con->ssl;
|
||||
}
|
||||
|
||||
const char* net_ssl_get_provider()
|
||||
{
|
||||
return OPENSSL_VERSION_TEXT;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new SSL context.
|
||||
*/
|
||||
struct ssl_context_handle* net_ssl_context_create()
|
||||
{
|
||||
|
||||
struct net_context_openssl* ctx = (struct net_context_openssl*) hub_malloc_zero(sizeof(struct net_context_openssl));
|
||||
ctx->ssl_method = (SSL_METHOD*) SSLv23_method(); /* TLSv1_method() */
|
||||
ctx->ssl_ctx = SSL_CTX_new(ctx->ssl_method);
|
||||
|
||||
/* Disable SSLv2 */
|
||||
SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2);
|
||||
SSL_CTX_set_quiet_shutdown(ctx->ssl_ctx, 1);
|
||||
|
||||
return (struct ssl_context_handle*) ctx;
|
||||
}
|
||||
|
||||
extern void net_ssl_context_destroy(struct ssl_context_handle* ctx_)
|
||||
{
|
||||
struct net_context_openssl* ctx = (struct net_context_openssl*) ctx_;
|
||||
SSL_CTX_free(ctx->ssl_ctx);
|
||||
hub_free(ctx);
|
||||
}
|
||||
|
||||
int ssl_load_certificate(struct ssl_context_handle* ctx_, const char* pem_file)
|
||||
{
|
||||
struct net_context_openssl* ctx = (struct net_context_openssl*) ctx_;
|
||||
if (SSL_CTX_use_certificate_file(ctx->ssl_ctx, pem_file, SSL_FILETYPE_PEM) < 0)
|
||||
{
|
||||
LOG_ERROR("SSL_CTX_use_certificate_file: %s", ERR_error_string(ERR_get_error(), NULL));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ssl_load_private_key(struct ssl_context_handle* ctx_, const char* pem_file)
|
||||
{
|
||||
struct net_context_openssl* ctx = (struct net_context_openssl*) ctx_;
|
||||
if (SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, pem_file, SSL_FILETYPE_PEM) < 0)
|
||||
{
|
||||
LOG_ERROR("SSL_CTX_use_PrivateKey_file: %s", ERR_error_string(ERR_get_error(), NULL));
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int ssl_check_private_key(struct ssl_context_handle* ctx_)
|
||||
{
|
||||
struct net_context_openssl* ctx = (struct net_context_openssl*) ctx_;
|
||||
if (SSL_CTX_check_private_key(ctx->ssl_ctx) != 1)
|
||||
{
|
||||
LOG_FATAL("SSL_CTX_check_private_key: Private key does not match the certificate public key: %s", ERR_error_string(ERR_get_error(), NULL));
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int handle_openssl_error(struct net_connection* con, int ret, enum ssl_state forced_rwstate)
|
||||
{
|
||||
struct net_ssl_openssl* handle = get_handle(con);
|
||||
int err = SSL_get_error(handle->ssl, ret);
|
||||
switch (err)
|
||||
{
|
||||
case SSL_ERROR_ZERO_RETURN:
|
||||
// Not really an error, but SSL was shut down.
|
||||
return -1;
|
||||
|
||||
case SSL_ERROR_WANT_READ:
|
||||
handle->state = forced_rwstate;
|
||||
net_con_update(con, NET_EVENT_READ);
|
||||
return 0;
|
||||
|
||||
case SSL_ERROR_WANT_WRITE:
|
||||
handle->state = forced_rwstate;
|
||||
net_con_update(con, NET_EVENT_WRITE);
|
||||
return 0;
|
||||
|
||||
case SSL_ERROR_SYSCALL:
|
||||
handle->state = tls_st_error;
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t net_con_ssl_accept(struct net_connection* con)
|
||||
{
|
||||
struct net_ssl_openssl* handle = get_handle(con);
|
||||
handle->state = tls_st_accepting;
|
||||
ssize_t ret;
|
||||
|
||||
ret = SSL_accept(handle->ssl);
|
||||
LOG_PROTO("SSL_accept() ret=%d", ret);
|
||||
if (ret > 0)
|
||||
{
|
||||
net_con_update(con, NET_EVENT_READ);
|
||||
handle->state = tls_st_connected;
|
||||
return ret;
|
||||
}
|
||||
return handle_openssl_error(con, ret, tls_st_accepting);
|
||||
}
|
||||
|
||||
ssize_t net_con_ssl_connect(struct net_connection* con)
|
||||
{
|
||||
struct net_ssl_openssl* handle = get_handle(con);
|
||||
ssize_t ret;
|
||||
handle->state = tls_st_connecting;
|
||||
|
||||
ret = SSL_connect(handle->ssl);
|
||||
#ifdef NETWORK_DUMP_DEBUG
|
||||
LOG_PROTO("SSL_connect() ret=%d", ret);
|
||||
#endif /* NETWORK_DUMP_DEBUG */
|
||||
|
||||
if (ret > 0)
|
||||
{
|
||||
handle->state = tls_st_connected;
|
||||
net_con_update(con, NET_EVENT_READ);
|
||||
return ret;
|
||||
}
|
||||
return handle_openssl_error(con, ret, tls_st_connecting);
|
||||
}
|
||||
|
||||
ssize_t net_con_ssl_handshake(struct net_connection* con, enum net_con_ssl_mode ssl_mode, struct ssl_context_handle* ssl_ctx)
|
||||
{
|
||||
uhub_assert(con);
|
||||
|
||||
struct net_context_openssl* ctx = (struct net_context_openssl*) ssl_ctx;
|
||||
struct net_ssl_openssl* handle = (struct net_ssl_openssl*) hub_malloc_zero(sizeof(struct net_ssl_openssl));
|
||||
|
||||
if (ssl_mode == net_con_ssl_mode_server)
|
||||
{
|
||||
handle->ssl = SSL_new(ctx->ssl_ctx);
|
||||
if (!handle->ssl)
|
||||
{
|
||||
LOG_ERROR("Unable to create new SSL stream\n");
|
||||
return -1;
|
||||
}
|
||||
SSL_set_fd(handle->ssl, con->sd);
|
||||
handle->bior = SSL_get_rbio(handle->ssl);
|
||||
handle->biow = SSL_get_wbio(handle->ssl);
|
||||
con->ssl = (struct ssl_handle*) handle;
|
||||
return net_con_ssl_accept(con);
|
||||
}
|
||||
else
|
||||
{
|
||||
handle->ssl = SSL_new(SSL_CTX_new(TLSv1_method()));
|
||||
SSL_set_fd(handle->ssl, con->sd);
|
||||
handle->bior = SSL_get_rbio(handle->ssl);
|
||||
handle->biow = SSL_get_wbio(handle->ssl);
|
||||
con->ssl = (struct ssl_handle*) handle;
|
||||
return net_con_ssl_connect(con);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ssize_t net_ssl_send(struct net_connection* con, const void* buf, size_t len)
|
||||
{
|
||||
struct net_ssl_openssl* handle = get_handle(con);
|
||||
|
||||
uhub_assert(handle->state == tls_st_connected || handle->state == tls_st_need_write);
|
||||
|
||||
ERR_clear_error();
|
||||
ssize_t ret = SSL_write(handle->ssl, buf, len);
|
||||
LOG_PROTO("SSL_write(con=%p, buf=%p, len=" PRINTF_SIZE_T ") => %d", con, buf, len, ret);
|
||||
if (ret > 0)
|
||||
{
|
||||
handle->state = tls_st_connected;
|
||||
return ret;
|
||||
}
|
||||
return handle_openssl_error(con, ret, tls_st_need_write);
|
||||
}
|
||||
|
||||
ssize_t net_ssl_recv(struct net_connection* con, void* buf, size_t len)
|
||||
{
|
||||
struct net_ssl_openssl* handle = get_handle(con);
|
||||
ssize_t ret;
|
||||
|
||||
if (handle->state == tls_st_error)
|
||||
return -2;
|
||||
|
||||
uhub_assert(handle->state == tls_st_connected || handle->state == tls_st_need_read);
|
||||
|
||||
ERR_clear_error();
|
||||
|
||||
ret = SSL_read(handle->ssl, buf, len);
|
||||
LOG_PROTO("SSL_read(con=%p, buf=%p, len=" PRINTF_SIZE_T ") => %d", con, buf, len, ret);
|
||||
if (ret > 0)
|
||||
{
|
||||
handle->state = tls_st_connected;
|
||||
return ret;
|
||||
}
|
||||
return handle_openssl_error(con, ret, tls_st_need_read);
|
||||
}
|
||||
|
||||
void net_ssl_shutdown(struct net_connection* con)
|
||||
{
|
||||
struct net_ssl_openssl* handle = get_handle(con);
|
||||
SSL_shutdown(handle->ssl);
|
||||
SSL_clear(handle->ssl);
|
||||
}
|
||||
|
||||
void net_ssl_destroy(struct net_connection* con)
|
||||
{
|
||||
struct net_ssl_openssl* handle = get_handle(con);
|
||||
SSL_free(handle->ssl);
|
||||
}
|
||||
|
||||
void net_ssl_callback(struct net_connection* con, int events)
|
||||
{
|
||||
struct net_ssl_openssl* handle = get_handle(con);
|
||||
|
||||
switch (handle->state)
|
||||
{
|
||||
case tls_st_none:
|
||||
con->callback(con, events, con->ptr);
|
||||
break;
|
||||
|
||||
case tls_st_error:
|
||||
con->callback(con, NET_EVENT_READ, con->ptr);
|
||||
break;
|
||||
|
||||
case tls_st_accepting:
|
||||
if (net_con_ssl_accept(con) != 0)
|
||||
con->callback(con, NET_EVENT_READ, con->ptr);
|
||||
break;
|
||||
|
||||
case tls_st_connecting:
|
||||
if (net_con_ssl_connect(con) != 0)
|
||||
con->callback(con, NET_EVENT_READ, con->ptr);
|
||||
break;
|
||||
|
||||
case tls_st_need_read:
|
||||
con->callback(con, NET_EVENT_READ, con->ptr);
|
||||
break;
|
||||
|
||||
case tls_st_need_write:
|
||||
con->callback(con, NET_EVENT_WRITE, con->ptr);
|
||||
break;
|
||||
|
||||
case tls_st_connected:
|
||||
con->callback(con, events, con->ptr);
|
||||
break;
|
||||
|
||||
case tls_st_disconnecting:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* SSL_USE_OPENSSL */
|
||||
#endif /* SSL_SUPPORT */
|
101
src/network/tls.h
Normal file
101
src/network/tls.h
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* uhub - A tiny ADC p2p connection hub
|
||||
* Copyright (C) 2007-2012, Jan Vidar Krey
|
||||
*
|
||||
* 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 3 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef HAVE_UHUB_NETWORK_TLS_H
|
||||
#define HAVE_UHUB_NETWORK_TLS_H
|
||||
|
||||
#include "uhub.h"
|
||||
|
||||
#ifdef SSL_SUPPORT
|
||||
|
||||
|
||||
enum ssl_state
|
||||
{
|
||||
tls_st_none,
|
||||
tls_st_error,
|
||||
tls_st_accepting,
|
||||
tls_st_connecting,
|
||||
tls_st_connected,
|
||||
tls_st_need_read, /* special case of connected */
|
||||
tls_st_need_write, /* special case of connected */
|
||||
tls_st_disconnecting,
|
||||
};
|
||||
|
||||
enum net_con_ssl_mode
|
||||
{
|
||||
net_con_ssl_mode_server,
|
||||
net_con_ssl_mode_client,
|
||||
};
|
||||
|
||||
struct ssl_context_handle;
|
||||
|
||||
/**
|
||||
* Returns a string describing the TLS/SSL provider information
|
||||
*/
|
||||
extern const char* net_ssl_get_provider();
|
||||
|
||||
/**
|
||||
* Create a new SSL context.
|
||||
*/
|
||||
extern struct ssl_context_handle* net_ssl_context_create();
|
||||
extern void net_ssl_context_destroy(struct ssl_context_handle* ctx);
|
||||
|
||||
/**
|
||||
* Return 0 on error, 1 otherwise.
|
||||
*/
|
||||
extern int ssl_load_certificate(struct ssl_context_handle* ctx, const char* pem_file);
|
||||
|
||||
/**
|
||||
* Return 0 on error, 1 otherwise.
|
||||
*/
|
||||
extern int ssl_load_private_key(struct ssl_context_handle* ctx, const char* pem_file);
|
||||
|
||||
/**
|
||||
* Return 0 if private key does not match certificate, 1 if everything is OK.
|
||||
*/
|
||||
extern int ssl_check_private_key(struct ssl_context_handle* ctx);
|
||||
|
||||
/**
|
||||
* Start SSL_accept()
|
||||
*/
|
||||
extern ssize_t net_con_ssl_accept(struct net_connection*);
|
||||
|
||||
/**
|
||||
* Start SSL_connect()
|
||||
*/
|
||||
extern ssize_t net_con_ssl_connect(struct net_connection*);
|
||||
|
||||
extern ssize_t net_ssl_send(struct net_connection* con, const void* buf, size_t len);
|
||||
extern ssize_t net_ssl_recv(struct net_connection* con, void* buf, size_t len);
|
||||
|
||||
extern void net_ssl_shutdown(struct net_connection* con);
|
||||
extern void net_ssl_destroy(struct net_connection* con);
|
||||
extern void net_ssl_callback(struct net_connection* con, int events);
|
||||
|
||||
|
||||
|
||||
extern ssize_t net_con_ssl_handshake(struct net_connection* con, enum net_con_ssl_mode, struct ssl_context_handle* ssl_ctx);
|
||||
extern SSL* net_con_get_ssl(struct net_connection* con);
|
||||
#ifdef SSL_USE_OPENSSL
|
||||
extern void net_con_set_ssl(struct net_connection* con, SSL*);
|
||||
#endif // SSL_USE_OPENSSL
|
||||
extern int net_con_is_ssl(struct net_connection* con);
|
||||
|
||||
#endif /* SSL_SUPPORT */
|
||||
#endif /* HAVE_UHUB_NETWORK_TLS_H */
|
@ -99,8 +99,13 @@
|
||||
#endif
|
||||
|
||||
#ifdef SSL_SUPPORT
|
||||
#ifdef SSL_USE_OPENSSL
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/err.h>
|
||||
#endif /* SSL_USE_OPENSSL */
|
||||
#ifdef SSL_USE_GNUTLS
|
||||
#include <gnutls/gnutls.h>
|
||||
#endif /* SSL_USE_GNUTLS */
|
||||
#endif
|
||||
|
||||
#include "version.h"
|
||||
|
@ -65,10 +65,6 @@ struct ADC_client
|
||||
char* desc;
|
||||
int flags;
|
||||
void* ptr;
|
||||
#ifdef SSL_SUPPORT
|
||||
const SSL_METHOD* ssl_method;
|
||||
SSL_CTX* ssl_ctx;
|
||||
#endif /* SSL_SUPPORT */
|
||||
};
|
||||
|
||||
|
||||
@ -522,15 +518,7 @@ struct ADC_client* ADC_client_create(const char* nickname, const char* descripti
|
||||
if (sd == -1) return NULL;
|
||||
|
||||
client->con = net_con_create();
|
||||
#if 0
|
||||
/* FIXME */
|
||||
client->timer = 0; /* FIXME: hub_malloc(sizeof(struct net_timer)); */
|
||||
#endif
|
||||
net_con_initialize(client->con, sd, event_callback, client, 0);
|
||||
#if 0
|
||||
/* FIXME */
|
||||
net_timer_initialize(client->timer, timer_callback, client);
|
||||
#endif
|
||||
ADC_client_set_state(client, ps_none);
|
||||
|
||||
client->nick = hub_strdup(nickname);
|
||||
@ -547,10 +535,6 @@ void ADC_client_destroy(struct ADC_client* client)
|
||||
{
|
||||
ADC_TRACE;
|
||||
ADC_client_disconnect(client);
|
||||
#if 0
|
||||
/* FIXME */
|
||||
net_timer_shutdown(client->timer);
|
||||
#endif
|
||||
ioq_send_destroy(client->send_queue);
|
||||
ioq_recv_destroy(client->recv_queue);
|
||||
hub_free(client->timer);
|
||||
@ -598,6 +582,8 @@ static void ADC_client_on_connected(struct ADC_client* client)
|
||||
net_con_update(client->con, NET_EVENT_READ | NET_EVENT_WRITE);
|
||||
client->callback(client, ADC_CLIENT_SSL_HANDSHAKE, 0);
|
||||
ADC_client_set_state(client, ps_conn_ssl);
|
||||
|
||||
net_con_ssl_handshake(client->con, net_con_ssl_mode_client, NULL);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
@ -613,9 +599,9 @@ static void ADC_client_on_connected(struct ADC_client* client)
|
||||
static void ADC_client_on_connected_ssl(struct ADC_client* client)
|
||||
{
|
||||
ADC_TRACE;
|
||||
net_con_update(client->con, NET_EVENT_READ);
|
||||
client->callback(client, ADC_CLIENT_SSL_OK, 0);
|
||||
client->callback(client, ADC_CLIENT_CONNECTED, 0);
|
||||
net_con_update(client->con, NET_EVENT_READ);
|
||||
ADC_client_send(client, adc_msg_create(ADC_HANDSHAKE));
|
||||
ADC_client_set_state(client, ps_protocol);
|
||||
}
|
||||
@ -727,4 +713,4 @@ const char* ADC_client_get_description(const struct ADC_client* client)
|
||||
void* ADC_client_get_ptr(const struct ADC_client* client)
|
||||
{
|
||||
return client->ptr;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user