- Add test framework (test.h) with assertion macros and test runner - Add comprehensive unit tests (test_network.c) covering: - Packet parsing and error handling - User registration and authentication - Room operations (create, join, leave, delete, list) - Direct messaging functionality - Message broadcasting - Password hashing - Update Makefile with 'make test' target - Rewrite PROTOCOL file with complete specification: - All 14 packet types with data layouts - All error codes with descriptions - Typical usage flows
168 lines
3.8 KiB
C
168 lines
3.8 KiB
C
#define _GNU_SOURCE
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <uv.h>
|
|
#include <openssl/ssl.h>
|
|
#include <openssl/err.h>
|
|
#include "config.h"
|
|
#include "client.h"
|
|
#include "network.h"
|
|
|
|
uv_loop_t *loop;
|
|
SSL_CTX *ctx;
|
|
|
|
void cleanup_client(uv_handle_t *handle)
|
|
{
|
|
struct client *client = (struct client*) handle;
|
|
network_client_remove(client);
|
|
if (client->ssl) {
|
|
SSL_free(client->ssl);
|
|
}
|
|
free(client);
|
|
}
|
|
|
|
void on_write_end(uv_write_t *req, int status)
|
|
{
|
|
if (status < 0) {
|
|
fprintf(stderr, "Write error %s\n", uv_strerror(status));
|
|
}
|
|
free(req->data);
|
|
free(req);
|
|
}
|
|
|
|
void flush_ssl_to_socket(struct client *client)
|
|
{
|
|
char buf[4096];
|
|
int pending;
|
|
|
|
while ((pending = BIO_pending(client->wbio)) > 0) {
|
|
int bytes_read = BIO_read(client->wbio, buf, sizeof(buf));
|
|
if (bytes_read > 0) {
|
|
uv_write_t *req = malloc(sizeof(uv_write_t));
|
|
char *send_data = malloc(bytes_read);
|
|
memcpy(send_data, buf, bytes_read);
|
|
|
|
req->data = send_data;
|
|
|
|
uv_buf_t uvbuf = uv_buf_init(send_data, bytes_read);
|
|
uv_write(req, (uv_stream_t*)&client->handle, &uvbuf, 1, on_write_end);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ssl_write_msg(struct client *client, const char *data, size_t len)
|
|
{
|
|
int written = SSL_write(client->ssl, data, len);
|
|
if (written > 0) {
|
|
flush_ssl_to_socket(client);
|
|
}
|
|
}
|
|
|
|
void on_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
|
|
{
|
|
struct client *client = (struct client*) stream;
|
|
|
|
if (nread > 0) {
|
|
BIO_write(client->rbio, buf->base, nread);
|
|
|
|
char plain_buf[4096];
|
|
int p;
|
|
|
|
while ((p = SSL_read(client->ssl, plain_buf, sizeof(plain_buf))) > 0) {
|
|
network_handle_data(client, plain_buf, p);
|
|
}
|
|
|
|
flush_ssl_to_socket(client);
|
|
}
|
|
else if (nread < 0) {
|
|
if (nread != UV_EOF) fprintf(stderr, "Read error %s\n", uv_err_name(nread));
|
|
uv_close((uv_handle_t*) client, cleanup_client);
|
|
}
|
|
|
|
free(buf->base);
|
|
}
|
|
|
|
void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
|
|
{
|
|
buf->base = malloc(suggested_size);
|
|
buf->len = suggested_size;
|
|
}
|
|
|
|
void on_new_connection(uv_stream_t *server, int status)
|
|
{
|
|
if (status < 0) {
|
|
fprintf(stderr, "New connection error %s\n", uv_strerror(status));
|
|
return;
|
|
}
|
|
|
|
struct client *client = malloc(sizeof(struct client));
|
|
uv_tcp_init(loop, &client->handle);
|
|
|
|
if (uv_accept(server, (uv_stream_t*) &client->handle) == 0) {
|
|
client->ssl = SSL_new(ctx);
|
|
client->rbio = BIO_new(BIO_s_mem());
|
|
client->wbio = BIO_new(BIO_s_mem());
|
|
SSL_set_bio(client->ssl, client->rbio, client->wbio);
|
|
SSL_set_accept_state(client->ssl);
|
|
|
|
client->user_id = 0;
|
|
client->current_room_id = 0;
|
|
client->is_authenticated = false;
|
|
client->username[0] = '\0';
|
|
|
|
network_client_add(client);
|
|
uv_read_start((uv_stream_t*) &client->handle, alloc_buffer, on_read);
|
|
} else {
|
|
uv_close((uv_handle_t*) &client->handle, cleanup_client);
|
|
}
|
|
}
|
|
|
|
void init_openssl()
|
|
{
|
|
SSL_load_error_strings();
|
|
OpenSSL_add_ssl_algorithms();
|
|
ctx = SSL_CTX_new(TLS_server_method());
|
|
if (!ctx) {
|
|
perror("Unable to create SSL context");
|
|
ERR_print_errors_fp(stderr);
|
|
exit(1);
|
|
}
|
|
|
|
SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION);
|
|
|
|
if (SSL_CTX_use_certificate_file(ctx, CONFIG_CERT_FILE, SSL_FILETYPE_PEM) <= 0 ||
|
|
SSL_CTX_use_PrivateKey_file(ctx, CONFIG_KEY_FILE, SSL_FILETYPE_PEM) <= 0) {
|
|
ERR_print_errors_fp(stderr);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
int main()
|
|
{
|
|
init_openssl();
|
|
|
|
if (network_init() != 0) {
|
|
fprintf(stderr, "Failed to initialize network/database\n");
|
|
return 1;
|
|
}
|
|
|
|
loop = uv_default_loop();
|
|
|
|
uv_tcp_t server;
|
|
uv_tcp_init(loop, &server);
|
|
|
|
struct sockaddr_in addr;
|
|
uv_ip4_addr("0.0.0.0", 7000, &addr);
|
|
|
|
uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);
|
|
int r = uv_listen((uv_stream_t*) &server, 128, on_new_connection);
|
|
|
|
if (r) {
|
|
fprintf(stderr, "Listen error %s\n", uv_strerror(r));
|
|
return 1;
|
|
}
|
|
|
|
printf("server listening on port 7000...\n");
|
|
return uv_run(loop, UV_RUN_DEFAULT);
|
|
}
|