Rewrite
This commit is contained in:
+784
@@ -0,0 +1,784 @@
|
||||
#include <poll.h>
|
||||
#include <assert.h>
|
||||
#include "socket.h"
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/conf.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <netdb.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
|
||||
|
||||
void socket_global_init(void)
|
||||
{
|
||||
SSL_library_init();
|
||||
SSL_load_error_strings();
|
||||
OpenSSL_add_all_algorithms();
|
||||
}
|
||||
|
||||
void socket_global_free(void)
|
||||
{
|
||||
EVP_cleanup();
|
||||
ERR_free_strings();
|
||||
}
|
||||
|
||||
int socket_group_init(SocketGroup *group)
|
||||
{
|
||||
SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_client_method());
|
||||
if (!ssl_ctx) {
|
||||
fprintf(stderr, "Unable to create SSL context\n");
|
||||
ERR_print_errors_fp(stderr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Set minimum TLS version (optional - for better security)
|
||||
SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION);
|
||||
|
||||
// Set certificate verification mode
|
||||
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
|
||||
|
||||
// Load default trusted certificate store
|
||||
if (SSL_CTX_set_default_verify_paths(ssl_ctx) != 1) {
|
||||
fprintf(stderr, "Failed to set default verify paths\n");
|
||||
ERR_print_errors_fp(stderr);
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
group->ssl_ctx = ssl_ctx;
|
||||
group->domains = NULL;
|
||||
group->num_domains = 0;
|
||||
group->max_domains = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int servername_callback(SSL *ssl, int *ad, void *arg)
|
||||
{
|
||||
SocketGroup *group = arg;
|
||||
|
||||
const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
|
||||
if (servername == NULL)
|
||||
return SSL_TLSEXT_ERR_NOACK;
|
||||
|
||||
for (int i = 0; i < group->num_domains; i++) {
|
||||
Domain *domain = &group->domains[i];
|
||||
if (!strcmp(domain->name, servername)) {
|
||||
SSL_set_SSL_CTX(ssl, domain->ssl_ctx);
|
||||
return SSL_TLSEXT_ERR_OK;
|
||||
}
|
||||
}
|
||||
|
||||
return SSL_TLSEXT_ERR_NOACK;
|
||||
}
|
||||
|
||||
int socket_group_init_server(SocketGroup *group, HTTP_String cert_file, HTTP_String key_file)
|
||||
{
|
||||
SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_server_method());
|
||||
if (!ssl_ctx) {
|
||||
fprintf(stderr, "Unable to create server SSL context\n");
|
||||
ERR_print_errors_fp(stderr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Set minimum TLS version (optional - for better security)
|
||||
SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION);
|
||||
|
||||
// Copy certificate file path to static buffer
|
||||
static char cert_buffer[1024];
|
||||
if (cert_file.len >= (int) sizeof(cert_buffer)) {
|
||||
fprintf(stderr, "Certificate file path too long\n");
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
return -1;
|
||||
}
|
||||
memcpy(cert_buffer, cert_file.ptr, cert_file.len);
|
||||
cert_buffer[cert_file.len] = '\0';
|
||||
|
||||
// Copy private key file path to static buffer
|
||||
static char key_buffer[1024];
|
||||
if (key_file.len >= (int) sizeof(key_buffer)) {
|
||||
fprintf(stderr, "Private key file path too long\n");
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
return -1;
|
||||
}
|
||||
memcpy(key_buffer, key_file.ptr, key_file.len);
|
||||
key_buffer[key_file.len] = '\0';
|
||||
|
||||
// Load certificate and private key
|
||||
if (SSL_CTX_use_certificate_file(ssl_ctx, cert_buffer, SSL_FILETYPE_PEM) != 1) {
|
||||
fprintf(stderr, "Failed to load certificate file: %s\n", cert_buffer);
|
||||
ERR_print_errors_fp(stderr);
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key_buffer, SSL_FILETYPE_PEM) != 1) {
|
||||
fprintf(stderr, "Failed to load private key file: %s\n", key_buffer);
|
||||
ERR_print_errors_fp(stderr);
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Verify that the private key matches the certificate
|
||||
if (SSL_CTX_check_private_key(ssl_ctx) != 1) {
|
||||
fprintf(stderr, "Private key does not match certificate\n");
|
||||
ERR_print_errors_fp(stderr);
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
SSL_CTX_set_tlsext_servername_callback(group->ssl_ctx, servername_callback);
|
||||
SSL_CTX_set_tlsext_servername_arg(group->ssl_ctx, group);
|
||||
|
||||
group->ssl_ctx = ssl_ctx;
|
||||
group->domains = NULL;
|
||||
group->num_domains = 0;
|
||||
group->max_domains = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void socket_group_free(SocketGroup *group)
|
||||
{
|
||||
SSL_CTX_free(group->ssl_ctx);
|
||||
}
|
||||
|
||||
int socket_group_add_domain(SocketGroup *group, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file)
|
||||
{
|
||||
if (group->num_domains == group->max_domains) {
|
||||
|
||||
int new_max_domains = 2 * group->max_domains;
|
||||
if (new_max_domains == 0)
|
||||
new_max_domains = 4;
|
||||
|
||||
Domain *new_domains = malloc(new_max_domains * sizeof(Domain));
|
||||
if (new_domains == NULL)
|
||||
return -1;
|
||||
|
||||
if (group->max_domains > 0) {
|
||||
for (int i = 0; i < group->num_domains; i++)
|
||||
new_domains[i] = group->domains[i];
|
||||
free(group->domains);
|
||||
}
|
||||
|
||||
group->domains = new_domains;
|
||||
group->max_domains = new_max_domains;
|
||||
}
|
||||
|
||||
SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_server_method());
|
||||
if (!ssl_ctx) {
|
||||
fprintf(stderr, "Unable to create server SSL context\n");
|
||||
ERR_print_errors_fp(stderr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Set minimum TLS version (optional - for better security)
|
||||
SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION);
|
||||
|
||||
// Copy certificate file path to static buffer
|
||||
static char cert_buffer[1024];
|
||||
if (cert_file.len >= (int) sizeof(cert_buffer)) {
|
||||
fprintf(stderr, "Certificate file path too long\n");
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
return -1;
|
||||
}
|
||||
memcpy(cert_buffer, cert_file.ptr, cert_file.len);
|
||||
cert_buffer[cert_file.len] = '\0';
|
||||
|
||||
// Copy private key file path to static buffer
|
||||
static char key_buffer[1024];
|
||||
if (key_file.len >= (int) sizeof(key_buffer)) {
|
||||
fprintf(stderr, "Private key file path too long\n");
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
return -1;
|
||||
}
|
||||
memcpy(key_buffer, key_file.ptr, key_file.len);
|
||||
key_buffer[key_file.len] = '\0';
|
||||
|
||||
// Load certificate and private key
|
||||
if (SSL_CTX_use_certificate_file(ssl_ctx, cert_buffer, SSL_FILETYPE_PEM) != 1) {
|
||||
fprintf(stderr, "Failed to load certificate file: %s\n", cert_buffer);
|
||||
ERR_print_errors_fp(stderr);
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key_buffer, SSL_FILETYPE_PEM) != 1) {
|
||||
fprintf(stderr, "Failed to load private key file: %s\n", key_buffer);
|
||||
ERR_print_errors_fp(stderr);
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Verify that the private key matches the certificate
|
||||
if (SSL_CTX_check_private_key(ssl_ctx) != 1) {
|
||||
fprintf(stderr, "Private key does not match certificate\n");
|
||||
ERR_print_errors_fp(stderr);
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
Domain *domain_info = &group->domains[group->num_domains];
|
||||
if (domain.len >= (int) sizeof(domain_info->name)) {
|
||||
SSL_CTX_free(ssl_ctx);
|
||||
return -1;
|
||||
}
|
||||
memcpy(domain_info->name, domain.ptr, domain.len);
|
||||
domain_info->name[domain.len] = '\0';
|
||||
domain_info->ssl_ctx = ssl_ctx;
|
||||
group->num_domains++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
SocketState socket_state(Socket *sock)
|
||||
{
|
||||
return sock->state;
|
||||
}
|
||||
|
||||
void socket_accept(Socket *sock, SocketGroup *group, int fd)
|
||||
{
|
||||
// Initialize socket for server-side TLS handshake
|
||||
sock->state = SOCKET_STATE_ACCEPTED; // TCP connection already established
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->fd = fd;
|
||||
sock->ssl = NULL;
|
||||
sock->ssl_ctx = group ? group->ssl_ctx : NULL;
|
||||
sock->addr_list = NULL;
|
||||
sock->addr_count = 0;
|
||||
sock->addr_cursor = 0;
|
||||
sock->hostname = NULL;
|
||||
sock->port = 0;
|
||||
|
||||
// Set non-blocking mode for the accepted socket
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags >= 0) {
|
||||
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||
}
|
||||
|
||||
// Start the TLS handshake process
|
||||
socket_update(sock);
|
||||
}
|
||||
|
||||
void socket_connect(Socket *sock, SocketGroup *group, HTTP_String host, uint16_t port) {
|
||||
sock->state = SOCKET_STATE_PENDING;
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->fd = -1;
|
||||
sock->ssl = NULL;
|
||||
sock->ssl_ctx = group ? group->ssl_ctx : NULL;
|
||||
sock->addr_list = NULL;
|
||||
sock->addr_count = 0;
|
||||
sock->addr_cursor = 0;
|
||||
sock->port = port;
|
||||
sock->hostname = (char*)malloc(host.len + 1);
|
||||
memcpy(sock->hostname, host.ptr, host.len);
|
||||
sock->hostname[host.len] = '\0';
|
||||
// DNS query
|
||||
struct addrinfo hints = {0}, *res = NULL, *rp = NULL;
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
char portstr[16];
|
||||
snprintf(portstr, sizeof(portstr), "%u", port);
|
||||
if (getaddrinfo(sock->hostname, portstr, &hints, &res) != 0) {
|
||||
sock->state = SOCKET_STATE_DIED;
|
||||
return;
|
||||
}
|
||||
// Count addresses
|
||||
int count = 0;
|
||||
for (rp = res; rp; rp = rp->ai_next) {
|
||||
if (rp->ai_family == AF_INET || rp->ai_family == AF_INET6) count++;
|
||||
}
|
||||
if (count == 0) {
|
||||
freeaddrinfo(res);
|
||||
sock->state = SOCKET_STATE_DIED;
|
||||
return;
|
||||
}
|
||||
sock->addr_list = (AddrInfo*)malloc(sizeof(AddrInfo) * count);
|
||||
sock->addr_count = count;
|
||||
sock->addr_cursor = 0;
|
||||
int i = 0;
|
||||
for (rp = res; rp; rp = rp->ai_next) {
|
||||
if (rp->ai_family == AF_INET) {
|
||||
sock->addr_list[i].is_ipv6 = 0;
|
||||
memcpy(&sock->addr_list[i].addr.ipv4, &((struct sockaddr_in*)rp->ai_addr)->sin_addr, sizeof(HTTP_IPv4));
|
||||
i++;
|
||||
} else if (rp->ai_family == AF_INET6) {
|
||||
sock->addr_list[i].is_ipv6 = 1;
|
||||
memcpy(&sock->addr_list[i].addr.ipv6, &((struct sockaddr_in6*)rp->ai_addr)->sin6_addr, sizeof(HTTP_IPv6));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
freeaddrinfo(res);
|
||||
// Set event/state and call update
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_PENDING;
|
||||
socket_update(sock);
|
||||
}
|
||||
|
||||
void socket_connect_ipv4(Socket *sock, SocketGroup *group, HTTP_IPv4 addr, uint16_t port) {
|
||||
sock->state = SOCKET_STATE_PENDING;
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->fd = -1;
|
||||
sock->ssl = NULL;
|
||||
sock->ssl_ctx = group ? group->ssl_ctx : NULL;
|
||||
sock->addr_list = NULL;
|
||||
sock->addr_count = 0;
|
||||
sock->addr_cursor = 0;
|
||||
sock->hostname = NULL;
|
||||
sock->port = port;
|
||||
sock->addr_list = (AddrInfo*)malloc(sizeof(AddrInfo));
|
||||
sock->addr_list[0].is_ipv6 = 0;
|
||||
memcpy(&sock->addr_list[0].addr.ipv4, &addr, sizeof(HTTP_IPv4));
|
||||
sock->addr_count = 1;
|
||||
sock->addr_cursor = 0;
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_PENDING;
|
||||
socket_update(sock);
|
||||
}
|
||||
|
||||
void socket_connect_ipv6(Socket *sock, SocketGroup *group, HTTP_IPv6 addr, uint16_t port) {
|
||||
sock->state = SOCKET_STATE_PENDING;
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->fd = -1;
|
||||
sock->ssl = NULL;
|
||||
sock->ssl_ctx = group ? group->ssl_ctx : NULL;
|
||||
sock->addr_list = NULL;
|
||||
sock->addr_count = 0;
|
||||
sock->addr_cursor = 0;
|
||||
sock->hostname = NULL;
|
||||
sock->port = port;
|
||||
sock->addr_list = (AddrInfo*)malloc(sizeof(AddrInfo));
|
||||
sock->addr_list[0].is_ipv6 = 1;
|
||||
memcpy(&sock->addr_list[0].addr.ipv6, &addr, sizeof(HTTP_IPv6));
|
||||
sock->addr_count = 1;
|
||||
sock->addr_cursor = 0;
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_PENDING;
|
||||
socket_update(sock);
|
||||
}
|
||||
|
||||
void socket_update(Socket *sock)
|
||||
{
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
|
||||
bool again;
|
||||
do {
|
||||
|
||||
again = false;
|
||||
|
||||
switch (sock->state) {
|
||||
case SOCKET_STATE_PENDING:
|
||||
{
|
||||
if (sock->ssl) {
|
||||
SSL_free(sock->ssl);
|
||||
sock->ssl = NULL;
|
||||
}
|
||||
|
||||
if (sock->fd != -1)
|
||||
close(sock->fd);
|
||||
|
||||
// If cursor reached the end, die
|
||||
if (sock->addr_cursor >= sock->addr_count) {
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_DIED;
|
||||
break;
|
||||
}
|
||||
|
||||
// Take current address
|
||||
AddrInfo *ai = &sock->addr_list[sock->addr_cursor];
|
||||
int family = ai->is_ipv6 ? AF_INET6 : AF_INET;
|
||||
int fd = socket(family, SOCK_STREAM, 0);
|
||||
if (fd < 0) {
|
||||
// Try next address
|
||||
sock->addr_cursor++;
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_PENDING;
|
||||
again = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set non-blocking
|
||||
int flags = fcntl(fd, F_GETFL, 0);
|
||||
if (flags >= 0) fcntl(fd, F_SETFL, flags | O_NONBLOCK); // TODO: Handle error by setting the socket to DIED
|
||||
|
||||
// Prepare sockaddr
|
||||
int ret;
|
||||
if (ai->is_ipv6) {
|
||||
struct sockaddr_in6 sa6 = {0};
|
||||
sa6.sin6_family = AF_INET6;
|
||||
memcpy(&sa6.sin6_addr, &ai->addr.ipv6, sizeof(HTTP_IPv6));
|
||||
sa6.sin6_port = htons(sock->port);
|
||||
ret = connect(fd, (struct sockaddr*)&sa6, sizeof(sa6));
|
||||
} else {
|
||||
struct sockaddr_in sa4 = {0};
|
||||
sa4.sin_family = AF_INET;
|
||||
memcpy(&sa4.sin_addr, &ai->addr.ipv4, sizeof(HTTP_IPv4));
|
||||
sa4.sin_port = htons(sock->port);
|
||||
ret = connect(fd, (struct sockaddr*)&sa4, sizeof(sa4));
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
// Connected immediately
|
||||
sock->fd = fd;
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_CONNECTED;
|
||||
again = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret < 0 && errno == EINPROGRESS) {
|
||||
// Connection pending
|
||||
sock->fd = fd;
|
||||
sock->event = SOCKET_WANT_WRITE;
|
||||
sock->state = SOCKET_STATE_CONNECTING;
|
||||
break;
|
||||
}
|
||||
|
||||
// Connect failed
|
||||
// If remote peer not working, try next address
|
||||
if (errno == ECONNREFUSED || errno == ETIMEDOUT || errno == ENETUNREACH || errno == EHOSTUNREACH) {
|
||||
sock->addr_cursor++;
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_PENDING;
|
||||
again = true;
|
||||
} else {
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_DIED;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SOCKET_STATE_CONNECTING:
|
||||
{
|
||||
// Check connect result
|
||||
int err = 0;
|
||||
socklen_t len = sizeof(err);
|
||||
if (getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, &err, &len) < 0 || err != 0) {
|
||||
close(sock->fd);
|
||||
// If remote peer not working, try next address
|
||||
if (err == ECONNREFUSED || err == ETIMEDOUT || err == ENETUNREACH || err == EHOSTUNREACH) {
|
||||
sock->addr_cursor++;
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_PENDING;
|
||||
again = true;
|
||||
} else {
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_DIED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Connect succeeded
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_CONNECTED;
|
||||
again = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case SOCKET_STATE_CONNECTED:
|
||||
{
|
||||
if (sock->ssl_ctx == NULL) {
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_ESTABLISHED_READY;
|
||||
} else {
|
||||
|
||||
// Start SSL handshake
|
||||
if (!sock->ssl) {
|
||||
sock->ssl = SSL_new(sock->ssl_ctx);
|
||||
SSL_set_fd(sock->ssl, sock->fd); // TODO: handle error?
|
||||
if (sock->hostname) SSL_set_tlsext_host_name(sock->ssl, sock->hostname);
|
||||
}
|
||||
|
||||
int ret = SSL_connect(sock->ssl);
|
||||
if (ret == 1) {
|
||||
// Handshake done
|
||||
free(sock->addr_list); sock->addr_list = NULL;
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_ESTABLISHED_READY;
|
||||
break;
|
||||
}
|
||||
|
||||
int err = SSL_get_error(sock->ssl, ret);
|
||||
if (err == SSL_ERROR_WANT_READ) {
|
||||
sock->event = SOCKET_WANT_READ;
|
||||
break;
|
||||
}
|
||||
|
||||
if (err == SSL_ERROR_WANT_WRITE) {
|
||||
sock->event = SOCKET_WANT_WRITE;
|
||||
break;
|
||||
}
|
||||
|
||||
sock->addr_cursor++;
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_PENDING;
|
||||
again = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SOCKET_STATE_ACCEPTED:
|
||||
{
|
||||
if (sock->ssl_ctx == NULL) {
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_ESTABLISHED_READY;
|
||||
} else {
|
||||
|
||||
// Start server-side SSL handshake
|
||||
if (!sock->ssl) {
|
||||
sock->ssl = SSL_new(sock->ssl_ctx);
|
||||
SSL_set_fd(sock->ssl, sock->fd); // TODO: handle error?
|
||||
}
|
||||
|
||||
int ret = SSL_accept(sock->ssl);
|
||||
if (ret == 1) {
|
||||
// Handshake done
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_ESTABLISHED_READY;
|
||||
break;
|
||||
}
|
||||
|
||||
int err = SSL_get_error(sock->ssl, ret);
|
||||
if (err == SSL_ERROR_WANT_READ) {
|
||||
sock->event = SOCKET_WANT_READ;
|
||||
break;
|
||||
}
|
||||
|
||||
if (err == SSL_ERROR_WANT_WRITE) {
|
||||
sock->event = SOCKET_WANT_WRITE;
|
||||
break;
|
||||
}
|
||||
|
||||
// Server socket error - close the connection
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_DIED;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SOCKET_STATE_ESTABLISHED_WAIT:
|
||||
{
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_ESTABLISHED_READY;
|
||||
}
|
||||
break;
|
||||
|
||||
case SOCKET_STATE_SHUTDOWN:
|
||||
{
|
||||
if (sock->ssl_ctx == NULL) {
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_DIED;
|
||||
} else {
|
||||
|
||||
int ret = SSL_shutdown(sock->ssl);
|
||||
if (ret == 1) {
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_DIED;
|
||||
break;
|
||||
}
|
||||
|
||||
int err = SSL_get_error(sock->ssl, ret);
|
||||
if (err == SSL_ERROR_WANT_READ) {
|
||||
sock->event = SOCKET_WANT_READ;
|
||||
break;
|
||||
}
|
||||
|
||||
if (err == SSL_ERROR_WANT_WRITE) {
|
||||
sock->event = SOCKET_WANT_WRITE;
|
||||
break;
|
||||
}
|
||||
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_DIED;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Do nothing
|
||||
break;
|
||||
}
|
||||
|
||||
} while (again);
|
||||
}
|
||||
|
||||
int socket_read(Socket *sock, char *dst, int max) {
|
||||
// If not ESTABLISHED, set state to DIED and return
|
||||
if (sock->state != SOCKET_STATE_ESTABLISHED_READY) {
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_DIED;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (sock->ssl_ctx == NULL) {
|
||||
int ret = read(sock->fd, dst, max);
|
||||
if (ret == 0) {
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_DIED;
|
||||
} else {
|
||||
if (ret < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
sock->event = SOCKET_WANT_READ;
|
||||
sock->state = SOCKET_STATE_ESTABLISHED_WAIT;
|
||||
} else {
|
||||
if (errno != EINTR) {
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_DIED;
|
||||
}
|
||||
}
|
||||
ret = 0;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
int ret = SSL_read(sock->ssl, dst, max);
|
||||
if (ret <= 0) {
|
||||
int err = SSL_get_error(sock->ssl, ret);
|
||||
if (err == SSL_ERROR_WANT_READ) {
|
||||
sock->event = SOCKET_WANT_READ;
|
||||
sock->state = SOCKET_STATE_ESTABLISHED_WAIT;
|
||||
} else if (err == SSL_ERROR_WANT_WRITE) {
|
||||
sock->event = SOCKET_WANT_WRITE;
|
||||
sock->state = SOCKET_STATE_ESTABLISHED_WAIT;
|
||||
} else {
|
||||
fprintf(stderr, "OpenSSL error in socket_read: ");
|
||||
ERR_print_errors_fp(stderr);
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_DIED;
|
||||
}
|
||||
ret = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
int socket_write(Socket *sock, char *src, int len) {
|
||||
// If not ESTABLISHED, set state to DIED and return
|
||||
if (sock->state != SOCKET_STATE_ESTABLISHED_READY) {
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_DIED;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (sock->ssl_ctx == NULL) {
|
||||
int ret = write(sock->fd, src, len);
|
||||
if (ret < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
sock->event = SOCKET_WANT_WRITE;
|
||||
sock->state = SOCKET_STATE_ESTABLISHED_WAIT;
|
||||
} else {
|
||||
if (errno != EINTR) {
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_DIED;
|
||||
}
|
||||
}
|
||||
ret = 0;
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
int ret = SSL_write(sock->ssl, src, len);
|
||||
if (ret <= 0) {
|
||||
int err = SSL_get_error(sock->ssl, ret);
|
||||
if (err == SSL_ERROR_WANT_READ) {
|
||||
sock->event = SOCKET_WANT_READ;
|
||||
sock->state = SOCKET_STATE_ESTABLISHED_WAIT;
|
||||
} else if (err == SSL_ERROR_WANT_WRITE) {
|
||||
sock->event = SOCKET_WANT_WRITE;
|
||||
sock->state = SOCKET_STATE_ESTABLISHED_WAIT;
|
||||
} else {
|
||||
fprintf(stderr, "OpenSSL error in socket_write: ");
|
||||
ERR_print_errors_fp(stderr);
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_DIED;
|
||||
}
|
||||
ret = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
void socket_close(Socket *sock) {
|
||||
// Set state to SHUTDOWN and call update
|
||||
sock->event = SOCKET_WANT_NONE;
|
||||
sock->state = SOCKET_STATE_SHUTDOWN;
|
||||
socket_update(sock);
|
||||
}
|
||||
|
||||
void socket_free(Socket *sock) {
|
||||
// Release all resources associated to the socket
|
||||
if (sock->ssl) {
|
||||
SSL_free(sock->ssl);
|
||||
sock->ssl = NULL;
|
||||
}
|
||||
if (sock->fd >= 0) {
|
||||
close(sock->fd);
|
||||
sock->fd = -1;
|
||||
}
|
||||
if (sock->hostname) {
|
||||
free(sock->hostname);
|
||||
sock->hostname = NULL;
|
||||
}
|
||||
if (sock->addr_list) {
|
||||
free(sock->addr_list);
|
||||
sock->addr_list = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#define COUNT(X) (sizeof(X) / sizeof((X)[0]))
|
||||
|
||||
int socket_wait(Socket **socks, int num_socks)
|
||||
{
|
||||
if (num_socks <= 0)
|
||||
return -1;
|
||||
|
||||
struct pollfd polled[100]; // TODO: make this value configurable
|
||||
if (num_socks > (int) COUNT(polled))
|
||||
return -1;
|
||||
|
||||
for (;;) {
|
||||
|
||||
for (int i = 0; i < num_socks; i++) {
|
||||
|
||||
int events = 0;
|
||||
switch (socks[i]->event) {
|
||||
case SOCKET_WANT_READ : events = POLLIN; break;
|
||||
case SOCKET_WANT_WRITE: events = POLLOUT; break;
|
||||
case SOCKET_WANT_NONE : return i;
|
||||
default: HTTP_ASSERT(0); break;
|
||||
}
|
||||
|
||||
polled[i].fd = socks[i]->fd;
|
||||
polled[i].events = events;
|
||||
polled[i].revents = 0;
|
||||
}
|
||||
|
||||
int ret = poll(polled, num_socks, -1);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
// Update socket states based on poll results
|
||||
for (int i = 0; i < num_socks; i++) {
|
||||
|
||||
if (polled[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
||||
socks[i]->event = SOCKET_WANT_NONE;
|
||||
socks[i]->state = SOCKET_STATE_DIED;
|
||||
return i;
|
||||
}
|
||||
|
||||
if (polled[i].revents & (POLLIN | POLLOUT)) {
|
||||
socks[i]->event = SOCKET_WANT_NONE;
|
||||
socket_update(socks[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
Reference in New Issue
Block a user