update with end to end encryption
This commit is contained in:
@@ -15,3 +15,4 @@ add_executable(
|
|||||||
schc
|
schc
|
||||||
${SOURCES_CLIENT}
|
${SOURCES_CLIENT}
|
||||||
)
|
)
|
||||||
|
target_link_libraries(schc ssl crypto)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ CC = gcc
|
|||||||
CFLAGS = -Wall -g -std=gnu2x
|
CFLAGS = -Wall -g -std=gnu2x
|
||||||
LDFLAGS =
|
LDFLAGS =
|
||||||
LIBS =
|
LIBS =
|
||||||
|
CLIBS = -lcrypto -lssl
|
||||||
INCLUDES = -I./src/lib -I./src
|
INCLUDES = -I./src/lib -I./src
|
||||||
SRCS = src/main.c
|
SRCS = src/main.c
|
||||||
CSRCS = src/client.c
|
CSRCS = src/client.c
|
||||||
@@ -17,7 +18,7 @@ $(TARGET): $(OBJS)
|
|||||||
$(CROSS_COMPILE)$(CC) $(LDFLAGS) $^ -o $@ $(LIBS)
|
$(CROSS_COMPILE)$(CC) $(LDFLAGS) $^ -o $@ $(LIBS)
|
||||||
|
|
||||||
$(CTARGET): $(COBJS)
|
$(CTARGET): $(COBJS)
|
||||||
$(CROSS_COMPILE)$(CC) $(LDFLAGS) $^ -o $@ $(LIBS)
|
$(CROSS_COMPILE)$(CC) $(LDFLAGS) $^ -o $@ $(CLIBS)
|
||||||
|
|
||||||
%.o: %.c
|
%.o: %.c
|
||||||
$(CROSS_COMPILE)$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
|
$(CROSS_COMPILE)$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
|
||||||
|
|||||||
+9
-1
@@ -1,4 +1,12 @@
|
|||||||
project('sch', 'c', default_options: ['cpp_std=c20'])
|
project('sch', 'c', default_options: ['cpp_std=c20'])
|
||||||
include_dirs = include_directories('src')
|
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('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,
|
||||||
|
)
|
||||||
|
|||||||
+550
-28
@@ -1,18 +1,537 @@
|
|||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/rand.h>
|
||||||
|
#include <signal.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
#include <termios.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include "log.h"
|
|
||||||
|
|
||||||
#define PORT 7777
|
#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;
|
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);
|
sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
if (sock < 0) {
|
if (sock < 0) {
|
||||||
@@ -30,33 +549,36 @@ int main() {
|
|||||||
return 1;
|
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();
|
pid_t child_pid = fork();
|
||||||
if (child_pid == 0) {
|
if (child_pid == 0) {
|
||||||
while (1) {
|
receive_messages();
|
||||||
int valread = read(sock, buffer, BUFFER_SIZE);
|
exit(0);
|
||||||
if (valread > 0) {
|
|
||||||
buffer[valread] = '\0';
|
|
||||||
printf("%s", buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
char username[50];
|
handle_input();
|
||||||
printf("Enter your username: ");
|
kill(child_pid, SIGKILL);
|
||||||
fgets(username, 50, stdin);
|
wait(NULL);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
close(sock);
|
||||||
|
show_cursor();
|
||||||
|
disable_raw_mode();
|
||||||
|
clear_screen();
|
||||||
|
printf("Goodbye, %s!\n", username);
|
||||||
|
|
||||||
|
free_message_buffer();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+186
-104
@@ -1,129 +1,211 @@
|
|||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <asm-generic/socket.h>
|
#include <pthread.h>
|
||||||
#include <colors.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <sys/select.h>
|
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include "log.h"
|
|
||||||
|
|
||||||
#define PORT 7777
|
#define PORT 7777
|
||||||
#define MAX_CLIENTS 10
|
#define BUFFER_SIZE 8192
|
||||||
#define BUFFER_SIZE 1024
|
#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 {
|
typedef struct {
|
||||||
int sock;
|
int socket;
|
||||||
char username[50];
|
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 main() {
|
||||||
int server_fd;
|
int server_fd, new_socket;
|
||||||
struct client_t clients[MAX_CLIENTS];
|
struct sockaddr_in address;
|
||||||
struct sockaddr_in address;
|
int opt = 1;
|
||||||
int addrlen = sizeof(address);
|
int addrlen = sizeof(address);
|
||||||
fd_set readfds;
|
|
||||||
char buffer[BUFFER_SIZE];
|
|
||||||
|
|
||||||
for (int i = 0; i < MAX_CLIENTS; i++) {
|
for (int i = 0; i < HISTORY_SIZE; i++) {
|
||||||
clients[i].sock = 0;
|
message_history[i].data = NULL;
|
||||||
bzero(clients[i].username, sizeof(clients[i].username));
|
message_history[i].data_len = 0;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (select(max_sd + 1, &readfds, NULL, NULL, NULL) < 0) {
|
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
|
||||||
log_error("select");
|
perror("socket failed");
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Новое подключение
|
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
|
||||||
if (FD_ISSET(server_fd, &readfds)) {
|
perror("setsockopt");
|
||||||
int new_socket = accept(server_fd, (struct sockaddr *) &address,
|
exit(EXIT_FAILURE);
|
||||||
(socklen_t *) &addrlen);
|
}
|
||||||
|
|
||||||
log_info("New connection: socket %d\n", new_socket);
|
address.sin_family = AF_INET;
|
||||||
read(new_socket, buffer, BUFFER_SIZE);
|
address.sin_addr.s_addr = INADDR_ANY;
|
||||||
|
address.sin_port = htons(PORT);
|
||||||
|
|
||||||
for (int i = 0; i < MAX_CLIENTS; i++) {
|
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
|
||||||
if (clients[i].sock == 0) {
|
perror("bind failed");
|
||||||
clients[i].sock = new_socket;
|
exit(EXIT_FAILURE);
|
||||||
strcpy(clients[i].username, buffer);
|
}
|
||||||
break;
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Сообщения от клиентов
|
client_t *cli = malloc(sizeof(client_t));
|
||||||
for (int i = 0; i < MAX_CLIENTS; i++) {
|
cli->socket = new_socket;
|
||||||
int sd = clients[i].sock;
|
|
||||||
char sd_username[50];
|
|
||||||
bzero(sd_username, sizeof(sd_username));
|
|
||||||
strcpy(sd_username, clients[i].username);
|
|
||||||
|
|
||||||
if (FD_ISSET(sd, &readfds)) {
|
pthread_mutex_lock(&clients_mutex);
|
||||||
int valread = read(sd, buffer, BUFFER_SIZE);
|
int added = 0;
|
||||||
|
for (int i = 0; i < MAX_CLIENTS; i++) {
|
||||||
if (valread == 0) {
|
if (!clients[i]) {
|
||||||
log_info("Client disconnected: socket %d\n", sd);
|
clients[i] = cli;
|
||||||
close(sd);
|
added = 1;
|
||||||
clients[i].sock = 0;
|
break;
|
||||||
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_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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user