QUOTE: Never too old to chase dreams.

feat: Added multi-port/host binding - libhttp - A basic HTTP Framework

libhttp

A basic HTTP Framework
git clone git://192.168.2.2/libhttp
Log | Files | Refs | README

commit 8ecc334becef5d55c2e804e830902fe7eb02613e
parent 5696a76b5f636da3b52bafbb027454a99bef5041
Author: typable <contact@typable.dev>
Date:   Wed, 18 Sep 2024 11:05:01 +0200

feat: Added multi-port/host binding

Diffstat:
Mlibhttp.c | 201++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mlibhttp.h | 25++++++++++++++++++++-----
2 files changed, 142 insertions(+), 84 deletions(-)

diff --git a/libhttp.c b/libhttp.c @@ -13,13 +13,14 @@ #include "libhttp.h" -#define MAX_REQUEST_HEAD_SIZE 40960 +#define HTTP_MAX_REQUEST_HEAD_SIZE 1000000 -int http_sockfd; +http_bind_t *http_binds = NULL; +int http_binds_len = 0; pthread_t http_thread; bool http_quit = false; bool http_error = false; -SSL_CTX *http_ctx = NULL; +SSL_CTX *http_ssl_ctx = NULL; http_response_t (*on_request_fn)(http_request_t *) = NULL; @@ -168,14 +169,14 @@ char *http_status_str(http_status_t status) { } } -ssize_t http_read(conn_t *conn, void *buffer, size_t len) { +ssize_t http_read(http_conn_t *conn, void *buffer, size_t len) { if (conn->ssl != NULL) { return SSL_read(conn->ssl, buffer, len); } return read(conn->sockfd, buffer, len); } -ssize_t http_write(conn_t *conn, void *buffer, size_t len) { +ssize_t http_write(http_conn_t *conn, void *buffer, size_t len) { if (conn->ssl != NULL) { return SSL_write(conn->ssl, buffer, len); } @@ -298,11 +299,11 @@ void http_response_free(http_response_t *response) { } } -http_request_t http_read_request(conn_t *conn) { +http_request_t http_read_request(http_conn_t *conn) { int len = 0; int ln = 0; int start = 0; - char buffer[MAX_REQUEST_HEAD_SIZE]; + char buffer[HTTP_MAX_REQUEST_HEAD_SIZE]; http_request_t request = { .conn = conn, .method = NULL, @@ -321,7 +322,7 @@ http_request_t http_read_request(conn_t *conn) { break; } // break if too long - if (len == MAX_REQUEST_HEAD_SIZE) { + if (len == HTTP_MAX_REQUEST_HEAD_SIZE) { perror("request is too long"); break; } @@ -413,7 +414,7 @@ http_request_t http_read_request(conn_t *conn) { return request; } -void http_write_response(conn_t *conn, http_response_t response) { +void http_write_response(http_conn_t *conn, http_response_t response) { http_write(conn, response.version, strlen(response.version)); http_write(conn, " ", 1); char status[4]; @@ -442,27 +443,17 @@ void *http_handle_client(void *ptr) { if (!ptr) { pthread_exit(0); } - conn_t *conn = (conn_t *) ptr; - if (http_ctx != NULL) { - conn->ssl = SSL_new(http_ctx); + http_conn_t *conn = (http_conn_t *) ptr; + if (conn->bind->hosts_len > 0) { + SSL_CTX_set_tlsext_servername_arg(http_ssl_ctx, (void *) conn); + conn->ssl = SSL_new(http_ssl_ctx); SSL_set_fd(conn->ssl, conn->sockfd); if (SSL_accept(conn->ssl) <= 0) { - perror("cannot SSL handshake"); pthread_exit(0); } } http_request_t request = http_read_request(conn); if (request.method != NULL && request.url != NULL) { - // long addr = ((struct sockaddr_in *) &request.conn->addr)->sin_addr.s_addr; - // printf( - // "%ld.%ld.%ld.%ld -> %s %s\n", - // (addr ) & 0xff, - // (addr >> 8) & 0xff, - // (addr >> 16) & 0xff, - // (addr >> 24) & 0xff, - // request.method, - // request.url - // ); if (on_request_fn != NULL) { http_response_t response = on_request_fn(&request); http_write_response(conn, response); @@ -470,7 +461,7 @@ void *http_handle_client(void *ptr) { } } http_request_free(&request); - if (http_ctx != NULL) { + if (conn->ssl != NULL) { SSL_shutdown(conn->ssl); SSL_free(conn->ssl); } @@ -480,68 +471,67 @@ void *http_handle_client(void *ptr) { } void *http_handle_server(void *ptr) { - if (!ptr) { - pthread_exit(0); + (void)(ptr); + struct pollfd fds[http_binds_len]; + for (int i = 0; i < http_binds_len; i++) { + fds[i].fd = http_binds[i].sockfd; + fds[i].events = POLLIN | POLLPRI; } - int sockfd = *((int *) ptr); - struct pollfd fds[1]; - fds[0].fd = sockfd; - fds[0].events = POLLIN | POLLPRI; while (!http_quit) { struct timespec sleep_req, sleep_rem; sleep_req.tv_sec = 0; sleep_req.tv_nsec = 100000000; nanosleep(&sleep_req, &sleep_rem); - if (poll(fds, 1, 100)) { - conn_t *conn = malloc(sizeof(conn_t)); - conn->addr_len = sizeof(conn->addr); - conn->sockfd = accept(sockfd, &conn->addr, (socklen_t *) &conn->addr_len); - conn->ssl = NULL; - if (conn->sockfd <= 0) { - free(conn); - } - else { - pthread_t thread; - pthread_attr_t thread_attr; - pthread_attr_init(&thread_attr); - pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED); - pthread_create(&thread, &thread_attr, http_handle_client, (void *) conn); - pthread_attr_destroy(&thread_attr); + if (poll(fds, http_binds_len, 100)) { + for (int i = 0; i < http_binds_len; i++) { + if (fds[i].revents & POLLIN) { + http_conn_t *conn = malloc(sizeof(http_conn_t)); + conn->addr_len = sizeof(conn->addr); + conn->sockfd = accept(http_binds[i].sockfd, &conn->addr, (socklen_t *) &conn->addr_len); + conn->bind = &http_binds[i]; + conn->ssl = NULL; + if (conn->sockfd <= 0) { + free(conn); + } + else { + pthread_t thread; + pthread_attr_t thread_attr; + pthread_attr_init(&thread_attr); + pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED); + pthread_create(&thread, &thread_attr, http_handle_client, (void *) conn); + pthread_attr_destroy(&thread_attr); + } + } } } } pthread_exit(0); } -void http_tls_enable(char *certificate, char *private_key) { - const SSL_METHOD *method = TLS_server_method(); - http_ctx = SSL_CTX_new(method); - if (!http_ctx) { - perror("cannot create SSL context"); - errno = HTTP_ERR_NO_SSL_CONTEXT; - http_error = true; - return; - } - if (SSL_CTX_use_certificate_file(http_ctx, certificate, SSL_FILETYPE_PEM) <= 0) { - perror("cannot use certificate file"); - errno = HTTP_ERR_INVALID_CERTIFICATE; - http_error = true; - } - if (SSL_CTX_use_PrivateKey_file(http_ctx, private_key, SSL_FILETYPE_PEM) <= 0) { - perror("cannot use private key file"); - errno = HTTP_ERR_INVALID_PRIVATE_KEY; - http_error = true; +int http_tls_sni_callback(SSL *ssl, int *al, void *arg) { + (void)(al); + http_conn_t *conn = (http_conn_t *) arg; + const char *hostname = SSL_get_servername(ssl, 0); + if (hostname != NULL) { + for (int i = 0; i < http_binds_len; i++) { + for (int j = 0; j < http_binds[i].hosts_len; j++) { + http_host_t *http_host = &http_binds[i].hosts[j]; + if (strcmp(http_host->hostname, hostname) == 0) { + conn->curr_host = http_host; + SSL_set_SSL_CTX(ssl, http_host->ssl_ctx); + return SSL_TLSEXT_ERR_OK; + } + } + } } + return SSL_TLSEXT_ERR_NOACK; } -void http_listen(char *hostname, int port) { - signal(SIGPIPE, SIG_IGN); - if (http_error) { - return; - } - errno = 0; - http_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if (http_sockfd <= 0) { +void http_bind(char *hostname, int port, http_host_t *hosts, int hosts_len) { + http_binds = realloc(http_binds, sizeof(http_bind_t) * (http_binds_len + 1)); + http_bind_t *http_bind = &http_binds[http_binds_len]; + http_bind->sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (http_bind->sockfd <= 0) { perror("cannot create socket"); errno = HTTP_ERR_NO_SOCKET; http_error = true; @@ -559,34 +549,87 @@ void http_listen(char *hostname, int port) { memcpy(&addr.sin_addr, host->h_addr_list[0], host->h_length); addr.sin_port = htons(port); int enable = 1; - if (setsockopt(http_sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { + if (setsockopt(http_bind->sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) { perror("cannot reuse address"); errno = HTTP_ERR_NO_ADDR_REUSE; http_error = true; return; } - if (bind(http_sockfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) < 0) { + if (bind(http_bind->sockfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) < 0) { perror("cannot bind socket to port"); errno = HTTP_ERR_ADDR_IN_USE; http_error = true; return; } - if (listen(http_sockfd, 5) < 0) { + if (listen(http_bind->sockfd, 5) < 0) { perror("cannot listen on port"); errno = HTTP_ERR_NO_LISTEN; http_error = true; return; } - pthread_create(&http_thread, 0, http_handle_server, (void *) &http_sockfd); + for (int i = 0; i < hosts_len; i++) { + http_host_t *http_host = &hosts[i]; + if (http_host->certificate != NULL && http_host->private_key != NULL) { + const SSL_METHOD *method = TLS_server_method(); + http_host->ssl_ctx = SSL_CTX_new(method); + if (!http_host->ssl_ctx) { + perror("cannot create SSL context"); + errno = HTTP_ERR_NO_SSL_CONTEXT; + http_error = true; + return; + } + if (SSL_CTX_use_certificate_file(http_host->ssl_ctx, http_host->certificate, SSL_FILETYPE_PEM) <= 0) { + perror("cannot use certificate file"); + errno = HTTP_ERR_INVALID_CERTIFICATE; + http_error = true; + } + if (SSL_CTX_use_PrivateKey_file(http_host->ssl_ctx, http_host->private_key, SSL_FILETYPE_PEM) <= 0) { + perror("cannot use private key file"); + errno = HTTP_ERR_INVALID_PRIVATE_KEY; + http_error = true; + } + } + else { + http_host->ssl_ctx = NULL; + } + } + http_bind->hosts = hosts; + http_bind->hosts_len = hosts_len; + http_binds_len++; } -void http_close() { +void http_listen(void) { + signal(SIGPIPE, SIG_IGN); + if (http_error) { + return; + } + errno = 0; + const SSL_METHOD *method = TLS_server_method(); + http_ssl_ctx = SSL_CTX_new(method); + if (!http_ssl_ctx) { + perror("cannot create SSL context"); + errno = HTTP_ERR_NO_SSL_CONTEXT; + http_error = true; + return; + } + SSL_CTX_set_tlsext_servername_callback(http_ssl_ctx, http_tls_sni_callback); + pthread_create(&http_thread, 0, http_handle_server, NULL); +} + +void http_close(void) { http_quit = true; if (!http_error) { pthread_join(http_thread, NULL); } - close(http_sockfd); - if (http_ctx != NULL) { - SSL_CTX_free(http_ctx); + for (int i = 0; i < http_binds_len; i++) { + close(http_binds[i].sockfd); + for (int j = 0; j < http_binds[i].hosts_len; j++) { + if (http_binds[i].hosts[j].ssl_ctx != NULL) { + SSL_CTX_free(http_binds[i].hosts[j].ssl_ctx); + } + } + } + if (http_ssl_ctx != NULL) { + free(http_ssl_ctx); } } diff --git a/libhttp.h b/libhttp.h @@ -79,11 +79,26 @@ typedef enum { } http_status_t; typedef struct { + char *hostname; + char *certificate; + char *private_key; + SSL_CTX *ssl_ctx; +} http_host_t; + +typedef struct { + int sockfd; + http_host_t *hosts; + int hosts_len; +} http_bind_t; + +typedef struct { int sockfd; struct sockaddr addr; int addr_len; SSL *ssl; -} conn_t; + http_bind_t *bind; + http_host_t *curr_host; +} http_conn_t; typedef struct { char *key; @@ -91,7 +106,7 @@ typedef struct { } http_header_t; typedef struct { - conn_t *conn; + http_conn_t *conn; char *method; char *url; char *version; @@ -120,6 +135,6 @@ http_response_t http_response_create(http_status_t status); void http_response_body(http_response_t *response, char *body, long len); void http_response_file(http_response_t *response, char *filename); -void http_tls_enable(char *certificate, char *private_key); -void http_listen(char *hostname, int port); -void http_close(); +void http_bind(char *hostname, int port, http_host_t *hosts, int hosts_len); +void http_listen(void); +void http_close(void);