From be0007b68adc5bc67cba9873c3c0de23e2ef1e6b Mon Sep 17 00:00:00 2001 From: TolaMironcenko Date: Fri, 13 Feb 2026 21:22:21 +0700 Subject: [PATCH] update with end to end encryption --- CMakeLists.txt | 1 + Makefile | 3 +- meson.build | 10 +- src/client.c | 578 ++++++++++++++++++++++++++++++++++++++++++++++--- src/main.c | 290 ++++++++++++++++--------- 5 files changed, 748 insertions(+), 134 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 70fb887..e5525ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,3 +15,4 @@ add_executable( schc ${SOURCES_CLIENT} ) +target_link_libraries(schc ssl crypto) diff --git a/Makefile b/Makefile index 390f8f8..e609324 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ CC = gcc CFLAGS = -Wall -g -std=gnu2x LDFLAGS = LIBS = +CLIBS = -lcrypto -lssl INCLUDES = -I./src/lib -I./src SRCS = src/main.c CSRCS = src/client.c @@ -17,7 +18,7 @@ $(TARGET): $(OBJS) $(CROSS_COMPILE)$(CC) $(LDFLAGS) $^ -o $@ $(LIBS) $(CTARGET): $(COBJS) - $(CROSS_COMPILE)$(CC) $(LDFLAGS) $^ -o $@ $(LIBS) + $(CROSS_COMPILE)$(CC) $(LDFLAGS) $^ -o $@ $(CLIBS) %.o: %.c $(CROSS_COMPILE)$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@ diff --git a/meson.build b/meson.build index 2e47809..fbbc85c 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,12 @@ project('sch', 'c', default_options: ['cpp_std=c20']) include_dirs = include_directories('src') +extra_c_args = ['-ffunction-sections', '-fdata-sections'] +deps = [dependency('openssl')] executable('sch', 'src/main.c', include_directories: include_dirs, install: true) -executable('schc', 'src/client.c', include_directories: include_dirs, install: true) +executable( + 'schc', + 'src/client.c', + include_directories: include_dirs, + dependencies: deps, + install: true, +) diff --git a/src/client.c b/src/client.c index d2aef56..1a9e841 100644 --- a/src/client.c +++ b/src/client.c @@ -1,18 +1,537 @@ #include +#include +#include +#include +#include #include +#include #include +#include #include #include +#include #include -#include "log.h" #define PORT 7777 -#define BUFFER_SIZE 1024 +#define BUFFER_SIZE 4096 +#define USERNAME_MAX 50 +#define INPUT_HEIGHT 3 +#define DEFAULT_MSG_BUFFER 100 +#define AES_KEY_SIZE 32 +#define AES_IV_SIZE 12 +#define AES_TAG_SIZE 16 -int main() { - int sock; +int sock; +char username[USERNAME_MAX]; +int term_rows, term_cols; +struct termios orig_termios; + +int msg_buffer_size = DEFAULT_MSG_BUFFER; +char **message_history = NULL; +int history_count = 0; +int history_start = 0; +int scroll_offset = 0; + +unsigned char aes_key[AES_KEY_SIZE]; + +volatile int need_redraw = 0; +volatile int running = 1; + +void get_terminal_size() { + struct winsize ws; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); + term_rows = ws.ws_row; + term_cols = ws.ws_col; +} + +void enable_raw_mode() { + tcgetattr(STDIN_FILENO, &orig_termios); + struct termios raw = orig_termios; + raw.c_lflag &= ~(ECHO | ICANON); + raw.c_cc[VMIN] = 0; + raw.c_cc[VTIME] = 1; + tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); +} + +void disable_raw_mode() { + tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios); +} + +void clear_screen() { + printf("\033[2J\033[H"); + fflush(stdout); +} + +void move_cursor(int row, int col) { + printf("\033[%d;%dH", row, col); + fflush(stdout); +} + +void clear_line() { + printf("\033[2K"); + fflush(stdout); +} + +void hide_cursor() { + printf("\033[?25l"); + fflush(stdout); +} + +void show_cursor() { + printf("\033[?25h"); + fflush(stdout); +} + +void derive_key_from_password(const char *password, unsigned char *key) { + unsigned char salt[16] = {0}; + if (!PKCS5_PBKDF2_HMAC(password, strlen(password), salt, sizeof(salt), 100000, + EVP_sha256(), AES_KEY_SIZE, key)) { + fprintf(stderr, "Failed to derive key\n"); + exit(1); + } +} + +int encrypt_message(const unsigned char *plaintext, int plaintext_len, + const unsigned char *key, const unsigned char *iv, + unsigned char *ciphertext, unsigned char *tag) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + int len; + int ciphertext_len; + + if (!ctx) return -1; + + if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL) != 1) + return -1; + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, AES_IV_SIZE, NULL) != 1) + return -1; + if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv) != 1) return -1; + + if (EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len) != 1) + return -1; + ciphertext_len = len; + + if (EVP_EncryptFinal_ex(ctx, ciphertext + len, &len) != 1) return -1; + ciphertext_len += len; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, AES_TAG_SIZE, tag) != 1) + return -1; + + EVP_CIPHER_CTX_free(ctx); + return ciphertext_len; +} + +int decrypt_message(const unsigned char *ciphertext, int ciphertext_len, + const unsigned char *key, const unsigned char *iv, + const unsigned char *tag, unsigned char *plaintext) { + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + int len; + int plaintext_len; + int ret; + + if (!ctx) return -1; + + if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL)) { + EVP_CIPHER_CTX_free(ctx); + return -1; + } + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, AES_IV_SIZE, NULL)) { + EVP_CIPHER_CTX_free(ctx); + return -1; + } + if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) { + EVP_CIPHER_CTX_free(ctx); + return -1; + } + + if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len)) { + EVP_CIPHER_CTX_free(ctx); + return -1; + } + plaintext_len = len; + + if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, AES_TAG_SIZE, + (void *) tag)) { + EVP_CIPHER_CTX_free(ctx); + return -1; + } + + ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len); + EVP_CIPHER_CTX_free(ctx); + + if (ret > 0) { + plaintext_len += len; + return plaintext_len; + } else { + return -1; + } +} + +void init_message_buffer() { + message_history = calloc(msg_buffer_size, sizeof(char *)); +} + +void free_message_buffer() { + if (!message_history) return; + for (int i = 0; i < msg_buffer_size; i++) { + free(message_history[i]); + } + free(message_history); +} + +void add_to_history(const char *msg) { + int idx = (history_start + history_count) % msg_buffer_size; + + free(message_history[idx]); + message_history[idx] = strdup(msg); + + if (history_count < msg_buffer_size) { + history_count++; + } else { + history_start = (history_start + 1) % msg_buffer_size; + } +} + +char *get_message_by_index(int idx) { + if (idx < 0 || idx >= history_count) return NULL; + int real_idx = (history_start + idx) % msg_buffer_size; + return message_history[real_idx]; +} + +int get_last_visible_index() { + return history_count - 1 - scroll_offset; +} + +void draw_input_frame() { + int input_row = term_rows - INPUT_HEIGHT + 1; + + move_cursor(input_row, 1); + printf("+"); + for (int i = 0; i < term_cols - 2; i++) printf("-"); + printf("+"); + + move_cursor(input_row + 1, 1); + printf("| %s: ", username); + int username_len = strlen(username) + 4; + for (int i = username_len; i < term_cols - 1; i++) printf(" "); + printf("|"); + + move_cursor(input_row + 2, 1); + printf("+"); + for (int i = 0; i < term_cols - 2; i++) printf("-"); + printf("+"); + + if (scroll_offset > 0) { + move_cursor(input_row, term_cols - 10); + printf("[^%d]", scroll_offset); + } + + fflush(stdout); +} + +void redraw_chat() { + hide_cursor(); + + for (int i = 1; i <= term_rows - INPUT_HEIGHT; i++) { + move_cursor(i, 1); + clear_line(); + } + + int max_visible = term_rows - INPUT_HEIGHT; + int last_idx = get_last_visible_index(); + + int row = max_visible; + for (int i = 0; i < max_visible && (last_idx - i) >= 0; i++) { + char *msg = get_message_by_index(last_idx - i); + if (!msg) break; + + move_cursor(row, 1); + + int msg_len = strlen(msg); + if (msg_len > 0 && msg[msg_len - 1] == '\n') msg_len--; + + int max_line = term_cols - 1; + if (msg_len > max_line) msg_len = max_line; + + printf("%.*s", msg_len, msg); + + row--; + if (row < 1) break; + } + + draw_input_frame(); + show_cursor(); +} + +void update_input_text(const char *text) { + int input_row = term_rows - INPUT_HEIGHT + 2; + move_cursor(input_row, strlen(username) + 5); + printf("%s", text); + int text_len = strlen(text); + int max_len = term_cols - strlen(username) - 6; + for (int i = text_len; i < max_len; i++) printf(" "); + move_cursor(input_row, strlen(username) + 5 + text_len); + fflush(stdout); +} + +void add_chat_message(const char *msg) { + int was_at_bottom = (scroll_offset == 0); + add_to_history(msg); + if (was_at_bottom) { + scroll_offset = 0; + } + redraw_chat(); +} + +void scroll_up() { + int max_visible = term_rows - INPUT_HEIGHT; + int max_offset = + history_count > max_visible ? history_count - max_visible : 0; + if (scroll_offset < max_offset) { + scroll_offset++; + redraw_chat(); + } +} + +void scroll_down() { + if (scroll_offset > 0) { + scroll_offset--; + redraw_chat(); + } +} + +// Формат пакета: [total_len:4][iv:12][tag:16][ciphertext:N] +void send_encrypted_message(int socket, const char *plaintext) { + unsigned char iv[AES_IV_SIZE]; + RAND_bytes(iv, AES_IV_SIZE); + + unsigned char ciphertext[BUFFER_SIZE]; + unsigned char tag[AES_TAG_SIZE]; + + int ciphertext_len = + encrypt_message((unsigned char *) plaintext, strlen(plaintext), aes_key, + iv, ciphertext, tag); + if (ciphertext_len < 0) { + fprintf(stderr, "Encryption failed\n"); + return; + } + + int total_len = AES_IV_SIZE + AES_TAG_SIZE + ciphertext_len; + int net_total_len = htonl(total_len); + + send(socket, &net_total_len, sizeof(int), 0); + send(socket, iv, AES_IV_SIZE, 0); + send(socket, tag, AES_TAG_SIZE, 0); + send(socket, ciphertext, ciphertext_len, 0); +} + +// Получить и расшифровать сообщение +int receive_encrypted_message(int socket, char *plaintext, int max_len) { + int net_total_len; + int valread = read(socket, &net_total_len, sizeof(int)); + if (valread <= 0) return -1; + + int total_len = ntohl(net_total_len); + if (total_len <= AES_IV_SIZE + AES_TAG_SIZE || total_len > BUFFER_SIZE) { + fprintf(stderr, "Invalid total length: %d\n", total_len); + return -1; + } + + unsigned char iv[AES_IV_SIZE]; + unsigned char tag[AES_TAG_SIZE]; + unsigned char ciphertext[BUFFER_SIZE]; + + valread = read(socket, iv, AES_IV_SIZE); + if (valread != AES_IV_SIZE) { + fprintf(stderr, "Failed to read IV: %d\n", valread); + return -1; + } + + valread = read(socket, tag, AES_TAG_SIZE); + if (valread != AES_TAG_SIZE) { + fprintf(stderr, "Failed to read tag: %d\n", valread); + return -1; + } + + int ciphertext_len = total_len - AES_IV_SIZE - AES_TAG_SIZE; + int total_read = 0; + while (total_read < ciphertext_len) { + valread = + read(socket, ciphertext + total_read, ciphertext_len - total_read); + if (valread <= 0) { + fprintf(stderr, "Failed to read ciphertext\n"); + return -1; + } + total_read += valread; + } + + int plaintext_len = decrypt_message(ciphertext, ciphertext_len, aes_key, iv, + tag, (unsigned char *) plaintext); + + if (plaintext_len > 0) { + plaintext[plaintext_len] = '\0'; + } else { + fprintf(stderr, "Decryption failed (ciphertext_len=%d)\n", ciphertext_len); + } + + return plaintext_len; +} + +void load_history_from_server() { + int net_count; + if (read(sock, &net_count, sizeof(int)) <= 0) return; + int count = ntohl(net_count); + + printf("[INFO] Loading %d encrypted messages from history\n", count); + + for (int i = 0; i < count && i < msg_buffer_size; i++) { + char plaintext[BUFFER_SIZE]; + int len = receive_encrypted_message(sock, plaintext, BUFFER_SIZE); + if (len > 0) { + add_to_history(plaintext); + } else { + fprintf(stderr, "[DEBUG] Failed to decrypt history message %d\n", i); + } + } +} + +void handle_input() { + char input_buffer[BUFFER_SIZE] = {0}; + int pos = 0; + char c; + + redraw_chat(); + update_input_text(""); + + while (running) { + if (read(STDIN_FILENO, &c, 1) != 1) { + if (need_redraw) { + need_redraw = 0; + get_terminal_size(); + redraw_chat(); + update_input_text(input_buffer); + } + continue; + } + + if (c == '\n') { + if (pos == 0) continue; + + input_buffer[pos] = '\0'; + + if (strcmp(input_buffer, "/exit") == 0) { + printf("[INFO] Client is exiting\n"); + running = 0; + return; + } + + // Форматируем и отправляем на сервер, НЕ добавляем локально! + char full_msg[BUFFER_SIZE]; + int written = snprintf(full_msg, sizeof(full_msg), "[%s]: %s", username, + input_buffer); + + // Если truncation произошёл, ensure null-termination + if (written >= (int) sizeof(full_msg)) { + full_msg[sizeof(full_msg) - 1] = '\0'; + } + + send_encrypted_message(sock, full_msg); + // Сообщение появится когда придёт от сервера в receive_messages() + + pos = 0; + input_buffer[0] = '\0'; + update_input_text(""); + + } else if (c == 127 || c == '\b') { + if (pos > 0) { + pos--; + input_buffer[pos] = '\0'; + update_input_text(input_buffer); + } + + } else if (c == 27) { + char seq[2]; + if (read(STDIN_FILENO, &seq[0], 1) != 1) continue; + if (read(STDIN_FILENO, &seq[1], 1) != 1) continue; + + if (seq[0] == '[') { + if (seq[1] == 'A') { + scroll_up(); + } else if (seq[1] == 'B') { + scroll_down(); + } else if (seq[1] == '3') { + read(STDIN_FILENO, &c, 1); + } + } + + } else if (c == 21) { + pos = 0; + input_buffer[0] = '\0'; + update_input_text(""); + + } else if (pos < BUFFER_SIZE - 1 && c >= 32 && c < 127) { + input_buffer[pos++] = c; + input_buffer[pos] = '\0'; + update_input_text(input_buffer); + } + } +} + +void receive_messages() { + while (running) { + char plaintext[BUFFER_SIZE]; + int len = receive_encrypted_message(sock, plaintext, BUFFER_SIZE); + if (len > 0) { + // ВСЕ сообщения приходят через сервер — и свои, и чужие + add_chat_message(plaintext); + } else if (len < 0) { + add_chat_message("*** Ошибка расшифровки сообщения ***"); + } else { + add_chat_message("*** Соединение закрыто сервером ***"); + break; + } + } +} + +void sigwinch_handler(int sig) { + need_redraw = 1; +} + +void parse_args(int argc, char *argv[]) { + static struct option long_options[] = { + {"messagebuffer", required_argument, 0, 'm'}, {0, 0, 0, 0}}; + + int opt; + while ((opt = getopt_long(argc, argv, "m:", long_options, NULL)) != -1) { + switch (opt) { + case 'm': + msg_buffer_size = atoi(optarg); + if (msg_buffer_size < 10) msg_buffer_size = 10; + if (msg_buffer_size > 10000) msg_buffer_size = 10000; + break; + default: + fprintf(stderr, "Usage: %s [-m N|--messagebuffer=N]\n", argv[0]); + exit(1); + } + } +} + +int main(int argc, char *argv[]) { struct sockaddr_in server_addr; - char buffer[BUFFER_SIZE]; + + parse_args(argc, argv); + + printf("Enter encryption password: "); + fflush(stdout); + char password[256]; + fgets(password, sizeof(password), stdin); + password[strcspn(password, "\n")] = '\0'; + + derive_key_from_password(password, aes_key); + + printf("Enter your username: "); + fflush(stdout); + fgets(username, USERNAME_MAX, stdin); + username[strcspn(username, "\n")] = '\0'; sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { @@ -30,33 +549,36 @@ int main() { return 1; } + send(sock, username, USERNAME_MAX, 0); + printf("[INFO] username = %s, buffer_size = %d\n", username, msg_buffer_size); + + init_message_buffer(); + load_history_from_server(); + + get_terminal_size(); + enable_raw_mode(); + clear_screen(); + hide_cursor(); + + signal(SIGWINCH, sigwinch_handler); + pid_t child_pid = fork(); if (child_pid == 0) { - while (1) { - int valread = read(sock, buffer, BUFFER_SIZE); - if (valread > 0) { - buffer[valread] = '\0'; - printf("%s", buffer); - } - } + receive_messages(); + exit(0); } else { - char username[50]; - printf("Enter your username: "); - fgets(username, 50, stdin); - username[strlen(username) - 1] = '\0'; - send(sock, username, 50, 0); - log_info("username = %s\n", username); - while (1) { - fgets(buffer, BUFFER_SIZE, stdin); - if (strcmp(buffer, "/exit\n") == 0) { - log_info("Client is exiting\n"); - kill(child_pid, SIGKILL); - close(sock); - break; - } - send(sock, buffer, strlen(buffer), 0); - } + handle_input(); + kill(child_pid, SIGKILL); + wait(NULL); } + close(sock); + show_cursor(); + disable_raw_mode(); + clear_screen(); + printf("Goodbye, %s!\n", username); + + free_message_buffer(); + return 0; } diff --git a/src/main.c b/src/main.c index 20d26b5..756d918 100644 --- a/src/main.c +++ b/src/main.c @@ -1,129 +1,211 @@ #include -#include -#include -#include +#include #include #include #include -#include #include #include -#include "log.h" #define PORT 7777 -#define MAX_CLIENTS 10 -#define BUFFER_SIZE 1024 +#define BUFFER_SIZE 8192 +#define MAX_CLIENTS 100 +#define HISTORY_SIZE 1000 +#define USERNAME_MAX 50 +#define AES_IV_SIZE 12 +#define AES_TAG_SIZE 16 -struct client_t { - int sock; - char username[50]; -}; +typedef struct { + int socket; + char username[USERNAME_MAX]; +} client_t; + +client_t *clients[MAX_CLIENTS]; +pthread_mutex_t clients_mutex = PTHREAD_MUTEX_INITIALIZER; + +typedef struct { + int data_len; + unsigned char *data; +} encrypted_message_t; + +encrypted_message_t message_history[HISTORY_SIZE]; +int history_count = 0; +int history_start = 0; +pthread_mutex_t history_mutex = PTHREAD_MUTEX_INITIALIZER; + +void add_to_history(const unsigned char *data, int len) { + pthread_mutex_lock(&history_mutex); + int idx = (history_start + history_count) % HISTORY_SIZE; + + free(message_history[idx].data); + message_history[idx].data = malloc(len); + memcpy(message_history[idx].data, data, len); + message_history[idx].data_len = len; + + if (history_count < HISTORY_SIZE) { + history_count++; + } else { + history_start = (history_start + 1) % HISTORY_SIZE; + } + pthread_mutex_unlock(&history_mutex); +} + +void send_history(int socket) { + pthread_mutex_lock(&history_mutex); + + int net_count = htonl(history_count); + write(socket, &net_count, sizeof(int)); + + for (int i = 0; i < history_count; i++) { + int idx = (history_start + i) % HISTORY_SIZE; + int net_len = htonl(message_history[idx].data_len); + write(socket, &net_len, sizeof(int)); + write(socket, message_history[idx].data, message_history[idx].data_len); + } + + pthread_mutex_unlock(&history_mutex); +} + +// Рассылка ВСЕМ клиентам включая отправителя (-1 = всем) +void broadcast_to_all(const unsigned char *data, int len) { + pthread_mutex_lock(&clients_mutex); + for (int i = 0; i < MAX_CLIENTS; i++) { + if (clients[i]) { + int net_len = htonl(len); + send(clients[i]->socket, &net_len, sizeof(int), 0); + send(clients[i]->socket, data, len, 0); + } + } + pthread_mutex_unlock(&clients_mutex); +} + +void *handle_client(void *arg) { + client_t *cli = (client_t *)arg; + + int valread = read(cli->socket, cli->username, USERNAME_MAX); + if (valread <= 0) goto cleanup; + cli->username[valread] = '\0'; + cli->username[strcspn(cli->username, "\n")] = '\0'; + + printf("User '%s' connected\n", cli->username); + + send_history(cli->socket); + + while (1) { + int net_total_len; + valread = read(cli->socket, &net_total_len, sizeof(int)); + if (valread <= 0) break; + + int total_len = ntohl(net_total_len); + if (total_len <= 0 || total_len > BUFFER_SIZE) { + printf("Invalid length from %s: %d\n", cli->username, total_len); + break; + } + + unsigned char *data = malloc(total_len); + int total_read = 0; + while (total_read < total_len) { + valread = read(cli->socket, data + total_read, total_len - total_read); + if (valread <= 0) { + free(data); + goto cleanup; + } + total_read += valread; + } + + // Сохраняем + add_to_history(data, total_len); + + // Рассылаем ВСЕМ включая отправителя + broadcast_to_all(data, total_len); + + free(data); + } + +cleanup: + printf("User '%s' disconnected\n", cli->username); + close(cli->socket); + + pthread_mutex_lock(&clients_mutex); + for (int i = 0; i < MAX_CLIENTS; i++) { + if (clients[i] == cli) { + clients[i] = NULL; + break; + } + } + pthread_mutex_unlock(&clients_mutex); + + free(cli); + pthread_exit(NULL); +} int main() { - int server_fd; - struct client_t clients[MAX_CLIENTS]; - struct sockaddr_in address; - int addrlen = sizeof(address); - fd_set readfds; - char buffer[BUFFER_SIZE]; + int server_fd, new_socket; + struct sockaddr_in address; + int opt = 1; + int addrlen = sizeof(address); - for (int i = 0; i < MAX_CLIENTS; i++) { - clients[i].sock = 0; - bzero(clients[i].username, sizeof(clients[i].username)); - } - - server_fd = socket(AF_INET, SOCK_STREAM, 0); - if (server_fd < 0) { - log_error("socket"); - exit(EXIT_FAILURE); - } - - int opt = 1; - if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { - log_error("setsockopt"); - exit(EXIT_FAILURE); - } - - address.sin_family = AF_INET; - address.sin_addr.s_addr = INADDR_ANY; - address.sin_port = htons(PORT); - - if (bind(server_fd, (struct sockaddr *) &address, sizeof(address)) < 0) { - log_error("bind"); - exit(EXIT_FAILURE); - } - - if (listen(server_fd, 5) < 0) { - log_error("listen"); - exit(EXIT_FAILURE); - } - - printf("Chat server started on port %d\n", PORT); - - while (1) { - FD_ZERO(&readfds); - FD_SET(server_fd, &readfds); - int max_sd = server_fd; - - for (int i = 0; i < MAX_CLIENTS; i++) { - int sd = clients[i].sock; - if (sd > 0) FD_SET(sd, &readfds); - if (sd > max_sd) max_sd = sd; + for (int i = 0; i < HISTORY_SIZE; i++) { + message_history[i].data = NULL; + message_history[i].data_len = 0; } - if (select(max_sd + 1, &readfds, NULL, NULL, NULL) < 0) { - log_error("select"); - exit(EXIT_FAILURE); + if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { + perror("socket failed"); + exit(EXIT_FAILURE); } - // Новое подключение - if (FD_ISSET(server_fd, &readfds)) { - int new_socket = accept(server_fd, (struct sockaddr *) &address, - (socklen_t *) &addrlen); + if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { + perror("setsockopt"); + exit(EXIT_FAILURE); + } - log_info("New connection: socket %d\n", new_socket); - read(new_socket, buffer, BUFFER_SIZE); + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; + address.sin_port = htons(PORT); - for (int i = 0; i < MAX_CLIENTS; i++) { - if (clients[i].sock == 0) { - clients[i].sock = new_socket; - strcpy(clients[i].username, buffer); - break; + if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { + perror("bind failed"); + exit(EXIT_FAILURE); + } + + if (listen(server_fd, 3) < 0) { + perror("listen"); + exit(EXIT_FAILURE); + } + + printf("E2E Encrypted Server listening on port %d\n", PORT); + + while (1) { + if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) { + perror("accept"); + continue; } - } - } - // Сообщения от клиентов - for (int i = 0; i < MAX_CLIENTS; i++) { - int sd = clients[i].sock; - char sd_username[50]; - bzero(sd_username, sizeof(sd_username)); - strcpy(sd_username, clients[i].username); + client_t *cli = malloc(sizeof(client_t)); + cli->socket = new_socket; - if (FD_ISSET(sd, &readfds)) { - int valread = read(sd, buffer, BUFFER_SIZE); - - if (valread == 0) { - log_info("Client disconnected: socket %d\n", sd); - close(sd); - clients[i].sock = 0; - bzero(clients[i].username, sizeof(clients[i].username)); - } else { - buffer[valread] = '\0'; - log_info("[%s]: %s", sd_username, buffer); - char *message = malloc(strlen(buffer) + strlen(sd_username) + 5); - sprintf(message, "[%s]: %s", sd_username, buffer); - // Рассылка всем другим - for (int j = 0; j < MAX_CLIENTS; j++) { - if (clients[j].sock != 0 && clients[j].sock != sd) { - send(clients[j].sock, message, strlen(message), 0); - free(message); + pthread_mutex_lock(&clients_mutex); + int added = 0; + for (int i = 0; i < MAX_CLIENTS; i++) { + if (!clients[i]) { + clients[i] = cli; + added = 1; + break; } - } } - } - } - } + pthread_mutex_unlock(&clients_mutex); - return 0; + if (!added) { + printf("Max clients reached\n"); + close(new_socket); + free(cli); + continue; + } + + pthread_t tid; + pthread_create(&tid, NULL, handle_client, cli); + pthread_detach(tid); + } + + return 0; }