From 6fe140a49d87427c4f90f66b2141da6b0cf81731 Mon Sep 17 00:00:00 2001 From: Surillya Date: Sun, 7 Sep 2025 00:53:14 +0200 Subject: [PATCH] init --- client | Bin 0 -> 29984 bytes client.c | 383 ++++++++++++++++++++++++ server | Bin 0 -> 39184 bytes server.c | 839 ++++++++++++++++++++++++++++++++++++++++++++++++++++ server.conf | 12 + 5 files changed, 1234 insertions(+) create mode 100755 client create mode 100644 client.c create mode 100755 server create mode 100644 server.c create mode 100644 server.conf diff --git a/client b/client new file mode 100755 index 0000000000000000000000000000000000000000..2ac5bec4d301c2a48007334fd85ba56d9759d807 GIT binary patch literal 29984 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 K3uOXjsQBM1i7-Y0 literal 0 HcmV?d00001 diff --git a/client.c b/client.c new file mode 100644 index 0000000..18129f8 --- /dev/null +++ b/client.c @@ -0,0 +1,383 @@ +#include +#include +#include +#include /* for strncasecmp */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_MESSAGE 1024 +#define MAX_USERNAME 32 +#define MAX_PASSWORD 64 +#define SERVER_IP "127.0.0.1" +#define PORT 8888 + +/* Color palette for structured display */ +#define COLOR_RESET "\033[0m" +#define COLOR_PROMPT "\033[36m" /* cyan */ +#define COLOR_MSG "\033[37m" /* white for normal messages */ +#define COLOR_USER "\033[1;33m" /* bold yellow for usernames */ +#define COLOR_JOIN "\033[32m" /* green for joins */ +#define COLOR_LEAVE "\033[31m" /* red for leaves */ +#define COLOR_SYSTEM "\033[35m" /* magenta for system/info */ +#define COLOR_COMMAND "\033[33m" /* yellow for local commands */ +#define COLOR_ERROR "\033[1;31m" /* bright red for errors */ + +int client_socket; +char username[MAX_USERNAME]; +char current_room[MAX_USERNAME]; + +#define SUPPRESS_WINDOW 2 /* seconds to suppress identical consecutive messages */ + +pthread_mutex_t room_mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t socket_mutex = PTHREAD_MUTEX_INITIALIZER; + +/* prevent duplicate consecutive prints (thread-safe) */ +static pthread_mutex_t print_mutex = PTHREAD_MUTEX_INITIALIZER; +static char last_printed[MAX_MESSAGE] = {0}; +static time_t last_print_ts = 0; + +/* helper to rtrim newline characters */ +static void rtrim_newlines(char *s) { + if (!s) return; + size_t len = strlen(s); + while (len > 0 && (s[len-1] == '\n' || s[len-1] == '\r')) { + s[len-1] = '\0'; + len--; + } +} + +/* classify and print a single server message with color */ +static int dedupe_and_store(const char *msg) { + int should_print = 0; + time_t now = time(NULL); + + pthread_mutex_lock(&print_mutex); + if (last_printed[0] == '\0' || strcmp(msg, last_printed) != 0) { + /* new message -> store and allow print */ + strncpy(last_printed, msg, sizeof(last_printed) - 1); + last_printed[sizeof(last_printed) - 1] = '\0'; + last_print_ts = now; + should_print = 1; + } else { + /* identical to last message: only allow if outside suppression window */ + if (difftime(now, last_print_ts) > SUPPRESS_WINDOW) { + last_print_ts = now; + should_print = 1; + } else { + should_print = 0; + } + } + pthread_mutex_unlock(&print_mutex); + return should_print; +} + +static void print_colored_message(const char *msg_in) { + if (!msg_in) return; + char msg[MAX_MESSAGE]; + strncpy(msg, msg_in, sizeof(msg)-1); + msg[sizeof(msg)-1] = '\0'; + rtrim_newlines(msg); + + /* dedupe consecutive identical messages */ + if (!dedupe_and_store(msg)) return; + + /* system responses */ + if (strstr(msg, "You joined room:") || strstr(msg, "Available Rooms:") || + strstr(msg, "Authentication successful") || strstr(msg, "Welcome,") || + strstr(msg, "Registration successful") || strstr(msg, "Server is full") || + strncasecmp(msg, "Error", 5) == 0) { + printf(COLOR_SYSTEM "%s" COLOR_RESET "\n", msg); + return; + } + + /* join / leave notifications */ + if (strstr(msg, " joined the room") || strstr(msg, " joined the chat")) { + printf(COLOR_JOIN "%s" COLOR_RESET "\n", msg); + return; + } + if (strstr(msg, " left the room") || strstr(msg, " left the chat")) { + printf(COLOR_LEAVE "%s" COLOR_RESET "\n", msg); + return; + } + + /* Private message indicator */ + if (strstr(msg, "[PM") || strstr(msg, "PM from") || strstr(msg, "PM to")) { + printf(COLOR_USER "%s" COLOR_RESET "\n", msg); + return; + } + + /* General chat message: try to color username differently when possible */ + /* Expected format: [room] username: message */ + const char *p = strchr(msg, ']'); + const char *colon = strchr(msg, ':'); + if (msg[0] == '[' && p && colon && colon > p) { + /* print prefix ([room]) + username in USER color + rest in MSG color */ + int prefix_len = (int)(p - msg + 1); + char prefix[64] = {0}; + strncpy(prefix, msg, prefix_len); + const char *user_start = p + 2; /* skip "] " */ + int user_len = (int)(colon - user_start); + if (user_len <= 0 || user_len >= 64) { + /* fallback */ + printf(COLOR_MSG "%s" COLOR_RESET "\n", msg); + return; + } + char user[64] = {0}; + strncpy(user, user_start, user_len); + const char *rest = colon + 1; + printf(COLOR_MSG "%s " COLOR_USER "%s" COLOR_MSG ":%s" COLOR_RESET "\n", + prefix, user, rest); + return; + } + + /* default: regular message color */ + printf(COLOR_MSG "%s" COLOR_RESET "\n", msg); +} + +void print_help() { + printf(COLOR_SYSTEM + "=== Available Commands ===\n" + " /rooms - List all available chat rooms\n" + " /join - Join or create a chat room\n" + " /help - Show this help message\n" + " /quit - Exit the chat\n\n" + "Tips:\n" + "- Messages will be sent to your current room\n" + "- Your current room is shown in the prompt [room]>\n" + "- Use /join to switch between rooms\n" + "- Private messages don't work yet\n" + COLOR_RESET); +} + +void clear_input_line() { + printf("\033[2K\r"); // Clear the current line +} + +void show_prompt() { + pthread_mutex_lock(&room_mutex); + printf("\033[2K\r" COLOR_PROMPT "[%s]> " COLOR_RESET, current_room[0] ? current_room : "lobby"); + fflush(stdout); + pthread_mutex_unlock(&room_mutex); +} + +void *receive_messages(void *arg) { + (void)arg; /* silence unused parameter warning */ + char message[MAX_MESSAGE]; + int read_size; + + while ((read_size = recv(client_socket, message, MAX_MESSAGE - 1, 0)) > 0) { + message[read_size] = '\0'; + rtrim_newlines(message); /* remove any trailing newlines sent by server */ + clear_input_line(); /* Clear current input line */ + print_colored_message(message); + show_prompt(); /* Show the prompt again */ + } + + /* server disconnected or error */ + clear_input_line(); + printf(COLOR_ERROR "Disconnected from server." COLOR_RESET "\n"); + show_prompt(); + return NULL; +} + +void hash_password(const char *password, char *hashed, size_t hashed_size) { + (void)hashed_size; /* silence unused parameter warning */ + EVP_MD_CTX *mdctx; + const EVP_MD *md; + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int hash_len; + + // Initialize the hashing context + mdctx = EVP_MD_CTX_new(); + md = EVP_sha256(); + + if (mdctx == NULL) { + fprintf(stderr, "Error creating hash context\n"); + return; + } + + EVP_DigestInit_ex(mdctx, md, NULL); + EVP_DigestUpdate(mdctx, password, strlen(password)); + EVP_DigestFinal_ex(mdctx, hash, &hash_len); + EVP_MD_CTX_free(mdctx); + + // Convert to hex string + for(unsigned int i = 0; i < hash_len; i++) { + snprintf(hashed + (i * 2), 3, "%02x", hash[i]); + } + hashed[hash_len * 2] = '\0'; +} + +int validate_username(const char *username) { + if (!username || strlen(username) < 3 || strlen(username) >= MAX_USERNAME) { + return 0; + } + + for (int i = 0; username[i]; i++) { + if (!isalnum(username[i]) && username[i] != '_') { + return 0; + } + } + return 1; +} + +int validate_password(const char *password) { + if (!password || strlen(password) < 8 || strlen(password) >= MAX_PASSWORD) { + return 0; + } + + int has_upper = 0, has_lower = 0, has_digit = 0; + for (int i = 0; password[i]; i++) { + if (isupper(password[i])) has_upper = 1; + if (islower(password[i])) has_lower = 1; + if (isdigit(password[i])) has_digit = 1; + } + + return has_upper && has_lower && has_digit; +} + +// Add error handling macro +#define HANDLE_ERROR(condition, message) \ + do { \ + if (condition) { \ + fprintf(stderr, "\033[31mError: %s (%s)\033[0m\n", message, strerror(errno)); \ + return 1; \ + } \ + } while(0) + +int main() { + struct sockaddr_in server_addr; + char password[MAX_PASSWORD]; + + char choice[10]; + printf(COLOR_SYSTEM "1. Login\n2. Register\nChoice: " COLOR_RESET); + fgets(choice, sizeof(choice), stdin); + + printf(COLOR_SYSTEM "Username: " COLOR_RESET); + fgets(username, MAX_USERNAME, stdin); + username[strcspn(username, "\n")] = 0; + + printf(COLOR_SYSTEM "Password: " COLOR_RESET); + fgets(password, MAX_PASSWORD, stdin); + password[strcspn(password, "\n")] = 0; + + // Validate username and password + if (!validate_username(username)) { + printf(COLOR_ERROR "Invalid username. Use 3-31 characters, alphanumeric and underscore only." COLOR_RESET "\n"); + return 1; + } + + if (!validate_password(password)) { + printf(COLOR_ERROR "Invalid password. Must be 8+ characters with uppercase, lowercase and numbers." COLOR_RESET "\n"); + return 1; + } + + printf(COLOR_SYSTEM "Connecting to %s:%d...\n" COLOR_RESET, SERVER_IP, PORT); + + client_socket = socket(AF_INET, SOCK_STREAM, 0); + HANDLE_ERROR(client_socket == -1, "Failed to create socket"); + + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(PORT); + + HANDLE_ERROR(inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0, + "Invalid address"); + + HANDLE_ERROR(connect(client_socket, (struct sockaddr *)&server_addr, + sizeof(server_addr)) < 0, "Connection failed"); + + // Send credentials with command + char credentials[MAX_USERNAME + MAX_PASSWORD + 10]; + char hashed_password[65]; + hash_password(password, hashed_password, sizeof(hashed_password)); + + snprintf(credentials, sizeof(credentials), "%s %s %s", + choice[0] == '2' ? "REGISTER" : "LOGIN", + username, hashed_password); + + send(client_socket, credentials, strlen(credentials), 0); + + // Handle response + char response[MAX_MESSAGE]; + ssize_t response_len = recv(client_socket, response, sizeof(response) - 1, 0); + if (response_len <= 0) { + printf(COLOR_ERROR "Server disconnected" COLOR_RESET "\n"); + close(client_socket); + return 1; + } + response[response_len] = '\0'; + rtrim_newlines(response); + print_colored_message(response); + + // If registration, exit after showing response + if (choice[0] == '2') { + close(client_socket); + return 0; + } + + // Continue only if authentication was successful + if (strncmp(response, "Authentication successful", 23) != 0) { + close(client_socket); + return 1; + } + + printf(COLOR_SYSTEM "Successfully connected to server!" COLOR_RESET "\n\n"); + printf(COLOR_SYSTEM "Type /help to see available commands\n\n" COLOR_RESET); + print_help(); + printf("\n"); + + pthread_t receive_thread; + pthread_create(&receive_thread, NULL, receive_messages, NULL); + + char message[MAX_MESSAGE]; + while (1) { + show_prompt(); + if (!fgets(message, MAX_MESSAGE, stdin)) break; + message[strcspn(message, "\n")] = 0; + + if (strcmp(message, "/help") == 0) { + print_help(); + continue; + } + + if (strcmp(message, "/quit") == 0) break; + + if (strncmp(message, "/join ", 6) == 0) { + char *new_room = message + 6; + pthread_mutex_lock(&room_mutex); + strncpy(current_room, new_room, MAX_USERNAME - 1); + current_room[MAX_USERNAME - 1] = '\0'; + pthread_mutex_unlock(&room_mutex); + } + + // Send message + if (strlen(message) > 0) { + pthread_mutex_lock(&socket_mutex); + + // Only print local messages for commands starting with / + if (message[0] == '/') { + /* Local command feedback (colored) */ + if (strcmp(message, "/help") != 0) { /* /help already handled above */ + printf(COLOR_COMMAND "Executing command: %s" COLOR_RESET "\n", message); + } + } + // Send message to server + ssize_t bytes_sent = send(client_socket, message, strlen(message), 0); + if (bytes_sent <= 0) { + printf(COLOR_ERROR "Failed to send message" COLOR_RESET "\n"); + } + + pthread_mutex_unlock(&socket_mutex); + } + } + + close(client_socket); + return 0; +} \ No newline at end of file diff --git a/server b/server new file mode 100755 index 0000000000000000000000000000000000000000..1026bb9639ce11f77c3fae060483d3f66ebb6090 GIT binary patch literal 39184 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_CLIENTS 500 +#define MAX_USERNAME 32 +#define MAX_PASSWORD 64 +#define MAX_MESSAGE 1024 +#define MAX_ROOMS 100 +#define MAX_BANNED_WORDS 50 +#define CONFIG_PATH "server.conf" +#define MAX_MESSAGES_PER_MINUTE 60 + +typedef enum +{ + USER_REGULAR, + USER_MODERATOR, + USER_ADMIN +} UserRole; + +typedef struct +{ + int socket; + char username[MAX_USERNAME]; + char current_room[MAX_USERNAME]; + UserRole role; + time_t last_activity; + int message_count; + time_t last_message_reset; + time_t last_message_time; +} Client; + +typedef struct +{ + char name[MAX_USERNAME]; + char description[256]; + Client *members[MAX_CLIENTS]; + int member_count; + int is_private; + char password[MAX_PASSWORD]; +} Room; + +typedef struct +{ + int port; + char default_room[MAX_USERNAME]; + int max_clients; + int allow_room_creation; + char banned_words[MAX_BANNED_WORDS][32]; + int banned_word_count; + + /* new config options */ + char motd[256]; + char admins[32][MAX_USERNAME]; + int admin_count; + int max_message_length; + int max_rooms; +} ServerConfig; + +sqlite3 *database; +Client clients[MAX_CLIENTS]; +Room rooms[MAX_ROOMS]; +ServerConfig config; +pthread_mutex_t clients_mutex = PTHREAD_MUTEX_INITIALIZER; +int server_socket; +FILE *log_file; +Room global_rooms[MAX_ROOMS] = {0}; +int active_room_count = 0; + +SSL_CTX *ssl_ctx; + +/* Forward declarations to avoid implicit declaration / conflicting type errors */ +void safe_strcpy(char *dest, const char *src, size_t size); +void safe_strcat(char *dest, const char *src, size_t size); + +void server_log(const char *level, const char *message) +{ + time_t now = time(NULL); + char *date = ctime(&now); + date[strlen(date) - 1] = '\0'; + + printf("\033[34m[%s] %s: %s\033[0m\n", date, level, message); + + if (log_file) + { + fprintf(log_file, "[%s] %s: %s\n", date, level, message); + fflush(log_file); + } +} + +int is_word_banned(const char *message) +{ + for (int i = 0; i < config.banned_word_count; i++) + { + if (strcasestr(message, config.banned_words[i])) + { + return 1; + } + } + return 0; +} + +/* small helper: trim leading/trailing whitespace in-place */ +static void trim(char *s) { + if (!s) return; + // trim leading + while (*s && isspace((unsigned char)*s)) memmove(s, s+1, strlen(s)); + // trim trailing + size_t len = strlen(s); + while (len > 0 && isspace((unsigned char)s[len-1])) s[--len] = '\0'; +} + +void parse_config() +{ + /* set sensible defaults */ + strcpy(config.default_room, "lobby"); + config.port = 8888; + config.max_clients = MAX_CLIENTS; + config.allow_room_creation = 1; + config.banned_word_count = 0; + config.motd[0] = '\0'; + config.admin_count = 0; + config.max_message_length = MAX_MESSAGE; /* fallback to compile-time */ + config.max_rooms = MAX_ROOMS; + + FILE *file = fopen(CONFIG_PATH, "r"); + if (!file) + { + server_log("WARN", "No config file found. Using defaults."); + return; + } + + char line[512]; + while (fgets(line, sizeof(line), file)) + { + trim(line); + if (line[0] == '#' || line[0] == ';' || line[0] == '\0') continue; /* comment/empty */ + + char key[128], value[384]; + if (sscanf(line, "%127[^=]=%383[^\n]", key, value) == 2) + { + trim(key); + trim(value); + + if (strcasecmp(key, "port") == 0) { + config.port = atoi(value); + } else if (strcasecmp(key, "default_room") == 0) { + safe_strcpy(config.default_room, value, sizeof(config.default_room)); + } else if (strcasecmp(key, "max_clients") == 0) { + config.max_clients = atoi(value); + if (config.max_clients < 1) config.max_clients = 1; + if (config.max_clients > MAX_CLIENTS) config.max_clients = MAX_CLIENTS; + } else if (strcasecmp(key, "allow_room_creation") == 0) { + config.allow_room_creation = (strcasecmp(value, "true") == 0 || strcmp(value, "1") == 0); + } else if (strcasecmp(key, "banned_words") == 0) { + config.banned_word_count = 0; + char *tok = strtok(value, ","); + while (tok && config.banned_word_count < MAX_BANNED_WORDS) { + trim(tok); + strncpy(config.banned_words[config.banned_word_count++], tok, 31); + config.banned_words[config.banned_word_count-1][31] = '\0'; + tok = strtok(NULL, ","); + } + } else if (strcasecmp(key, "motd") == 0) { + safe_strcpy(config.motd, value, sizeof(config.motd)); + } else if (strcasecmp(key, "admins") == 0) { + config.admin_count = 0; + char *tok = strtok(value, ","); + while (tok && config.admin_count < (int)(sizeof(config.admins)/sizeof(config.admins[0]))) { + trim(tok); + strncpy(config.admins[config.admin_count++], tok, MAX_USERNAME-1); + config.admins[config.admin_count-1][MAX_USERNAME-1] = '\0'; + tok = strtok(NULL, ","); + } + } else if (strcasecmp(key, "max_message_length") == 0) { + config.max_message_length = atoi(value); + if (config.max_message_length < 16) config.max_message_length = 16; + if (config.max_message_length > MAX_MESSAGE) config.max_message_length = MAX_MESSAGE; + } else if (strcasecmp(key, "max_rooms") == 0) { + config.max_rooms = atoi(value); + if (config.max_rooms < 1) config.max_rooms = 1; + if (config.max_rooms > MAX_ROOMS) config.max_rooms = MAX_ROOMS; + } + } + } + fclose(file); + + char buf[256]; + snprintf(buf, sizeof(buf), "Config loaded: port=%d default_room=%s max_clients=%d max_message_length=%d", + config.port, config.default_room, config.max_clients, config.max_message_length); + server_log("INFO", buf); +} + +Room *find_or_create_room(const char *room_name, const char *password, Client *creator) +{ + for (int i = 0; i < active_room_count; i++) + { + if (strcmp(global_rooms[i].name, room_name) == 0) + { + if (global_rooms[i].is_private && strcmp(global_rooms[i].password, password) != 0) + { + return NULL; // Incorrect password + } + return &global_rooms[i]; + } + } + + if (active_room_count < MAX_ROOMS && + (config.allow_room_creation || (creator && creator->role >= USER_MODERATOR))) + { + pthread_mutex_lock(&clients_mutex); /* protect global_rooms modifications */ + Room *new_room = &global_rooms[active_room_count++]; + + /* initialize the new room but do NOT add members here. + Membership must be managed by the caller under clients_mutex + to avoid races/duplicate entries. */ + memset(new_room, 0, sizeof(Room)); + strncpy(new_room->name, room_name, MAX_USERNAME - 1); + new_room->name[MAX_USERNAME - 1] = '\0'; + strncpy(new_room->password, password, MAX_PASSWORD - 1); + new_room->password[MAX_PASSWORD - 1] = '\0'; + new_room->is_private = (password && password[0] != '\0'); + new_room->member_count = 0; + pthread_mutex_unlock(&clients_mutex); + + char log_msg[256]; + snprintf(log_msg, sizeof(log_msg), + "Room created: %s by user %s", + room_name, creator ? creator->username : "system"); + server_log("ROOM", log_msg); + + return new_room; + } + + return NULL; +} + +// Add these safe room membership helpers to avoid duplicates +void add_client_to_room(Room *room, Client *client) { + if (!room || !client) return; + pthread_mutex_lock(&clients_mutex); + // avoid duplicates + for (int i = 0; i < room->member_count; i++) { + if (room->members[i] == client) { + pthread_mutex_unlock(&clients_mutex); + return; + } + } + if (room->member_count < MAX_CLIENTS) { + room->members[room->member_count++] = client; + } + pthread_mutex_unlock(&clients_mutex); +} + +void remove_client_from_room(Room *room, Client *client) { + if (!room || !client) return; + pthread_mutex_lock(&clients_mutex); + for (int i = 0; i < room->member_count; i++) { + if (room->members[i] == client) { + memmove(&room->members[i], &room->members[i + 1], + (room->member_count - i - 1) * sizeof(Client *)); + room->member_count--; + break; + } + } + pthread_mutex_unlock(&clients_mutex); +} + +void broadcast_room_message(Room *room, const char *message, Client *sender) { + if (!room || !sender) return; + + // Don't broadcast commands + if (message[0] == '/') return; + + char formatted_message[MAX_MESSAGE]; + snprintf(formatted_message, sizeof(formatted_message), + "[%s] %s: %s\n", room->name, sender->username, message); + + pthread_mutex_lock(&clients_mutex); + + // First verify sender is still in room + int sender_in_room = 0; + for (int i = 0; i < room->member_count; i++) { + if (room->members[i] == sender && room->members[i]->socket > 0) { + sender_in_room = 1; + break; + } + } + + if (!sender_in_room) { + pthread_mutex_unlock(&clients_mutex); + return; + } + + // Now broadcast to room members (skip sender so messages are not echoed back) + for (int i = 0; i < room->member_count; i++) { + Client *client = room->members[i]; + if (!client || client->socket <= 0) continue; + + // skip duplicates of the same client pointer in the list + int duplicate = 0; + for (int k = 0; k < i; k++) { + if (room->members[k] == client) { duplicate = 1; break; } + } + if (duplicate) continue; + + if (client == sender) continue; /* do not echo to sender */ + + ssize_t sent = send(client->socket, formatted_message, + strlen(formatted_message), MSG_NOSIGNAL); + + if (sent <= 0) { + /* Handle disconnected client: remove from members list while holding mutex */ + if (errno == EPIPE || errno == ECONNRESET || sent == -1) { + close(client->socket); + client->socket = 0; + memmove(&room->members[i], &room->members[i + 1], + (room->member_count - i - 1) * sizeof(Client*)); + room->member_count--; + i--; /* adjust index after removal */ + } + } + } + + pthread_mutex_unlock(&clients_mutex); + server_log("MESSAGE", formatted_message); +} + +int authenticate_user(const char *username, const char *password) +{ + sqlite3_stmt *stmt; + const char *query = "SELECT password FROM users WHERE username = ?"; + + sqlite3_prepare_v2(database, query, -1, &stmt, 0); + sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC); + + int authenticated = 0; + if (sqlite3_step(stmt) == SQLITE_ROW) + { + const char *stored_hash = (const char *)sqlite3_column_text(stmt, 0); + const char *computed_hash = crypt(password, stored_hash); + authenticated = (computed_hash && strcmp(computed_hash, stored_hash) == 0); + } + + sqlite3_finalize(stmt); + return authenticated; +} + +int register_user(const char *username, const char *password) +{ + sqlite3_stmt *stmt; + + // Check if user already exists + const char *check_query = "SELECT username FROM users WHERE username = ?"; + sqlite3_prepare_v2(database, check_query, -1, &stmt, 0); + sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC); + + if (sqlite3_step(stmt) == SQLITE_ROW) + { + sqlite3_finalize(stmt); + return 0; // User already exists + } + sqlite3_finalize(stmt); + + // Generate salt and hash password + char salt[32]; + snprintf(salt, sizeof(salt), "$6$%08x%08x", rand(), rand()); + char *hashed_password = crypt(password, salt); + + // Insert new user + const char *insert_query = "INSERT INTO users (username, password, role) VALUES (?, ?, ?)"; + sqlite3_prepare_v2(database, insert_query, -1, &stmt, 0); + sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, hashed_password, -1, SQLITE_STATIC); + sqlite3_bind_int(stmt, 3, USER_REGULAR); + + int result = sqlite3_step(stmt) == SQLITE_DONE; + sqlite3_finalize(stmt); + return result; +} + +void *handle_client(void *arg) +{ + Client *client = (Client *)arg; + char buffer[MAX_MESSAGE]; + int read_size; + + char welcome[MAX_MESSAGE]; + snprintf(welcome, sizeof(welcome), + "Welcome, %s! You are in room %s", + client->username, client->current_room); + send(client->socket, welcome, strlen(welcome), 0); + + // Find default room and add client + Room *default_room = NULL; + for (int i = 0; i < active_room_count; i++) { + if (strcmp(global_rooms[i].name, "lobby") == 0) { + default_room = &global_rooms[i]; + break; + } + } + + if (default_room) { + add_client_to_room(default_room, client); + + strncpy(client->current_room, "lobby", MAX_USERNAME); + + char join_msg[MAX_MESSAGE]; + snprintf(join_msg, sizeof(join_msg), "%s joined the chat", client->username); + broadcast_room_message(default_room, join_msg, client); + + /* send MOTD to the new client if configured */ + if (config.motd[0]) { + char motd_msg[512]; + snprintf(motd_msg, sizeof(motd_msg), "MOTD: %s\n", config.motd); + send(client->socket, motd_msg, strlen(motd_msg), 0); + } + } + + while ((read_size = recv(client->socket, buffer, MAX_MESSAGE, 0)) > 0) + { + /* enforce configured max message length */ + if (config.max_message_length > 0 && read_size > config.max_message_length) { + const char *err = "Your message is too long and was discarded.\n"; + send(client->socket, err, strlen(err), 0); + continue; + } + + buffer[read_size] = '\0'; + + char log_msg[MAX_MESSAGE + 100]; + snprintf(log_msg, sizeof(log_msg), + "Received from %s: %s", client->username, buffer); + server_log("RECV", log_msg); + + // Server: Inside handle_client() + server_log("DEBUG", "Received message"); + printf("Message from %s: %s\n", client->username, buffer); + + if (strncmp(buffer, "/join ", 6) == 0) + { + char room_name[MAX_USERNAME] = {0}; + char password[MAX_PASSWORD] = {0}; + sscanf(buffer + 6, "%s %s", room_name, password); + + Room *room = find_or_create_room(room_name, password, client); + if (room) { + // Remove client from current room (if present) - notify before modifying + Room *old_room = NULL; + int old_index = -1; + for (int i = 0; i < active_room_count; i++) { + if (strcmp(global_rooms[i].name, client->current_room) == 0) { + old_room = &global_rooms[i]; + break; + } + } + + if (old_room) { + for (int j = 0; j < old_room->member_count; j++) { + if (old_room->members[j] == client) { + // Notify old room before removing (don't hold mutex during broadcast) + char leave_msg[MAX_MESSAGE]; + snprintf(leave_msg, sizeof(leave_msg), "%s left the room", client->username); + broadcast_room_message(old_room, leave_msg, client); + + // remove safely using helper + remove_client_from_room(old_room, client); + break; + } + } + } + + // Check if client is already in the new room + int already_in_room = 0; + for (int i = 0; i < room->member_count; i++) { + if (room->members[i] == client) { + already_in_room = 1; + break; + } + } + + if (!already_in_room) { + // Add to new room safely (avoids duplicates) + add_client_to_room(room, client); + + safe_strcpy(client->current_room, room_name, MAX_USERNAME); + + // Notify new room (do not hold mutex while broadcasting) + char join_msg[MAX_MESSAGE]; + snprintf(join_msg, sizeof(join_msg), "%s joined the room", client->username); + broadcast_room_message(room, join_msg, client); + } + + // Send confirmation to client + char confirm_msg[MAX_MESSAGE]; + snprintf(confirm_msg, sizeof(confirm_msg), "You joined room: %s\n", room_name); + send(client->socket, confirm_msg, strlen(confirm_msg), 0); + } else { + const char *error_msg = "Could not join room. It might be private or full.\n"; + send(client->socket, error_msg, strlen(error_msg), 0); + } + } + else if (strcmp(buffer, "/rooms") == 0) + { + char room_list[MAX_MESSAGE] = "Available Rooms:\n"; + for (int i = 0; i < active_room_count; i++) + { + char room_info[128]; + snprintf(room_info, sizeof(room_info), + "- %s (Members: %d)\n", + global_rooms[i].name, global_rooms[i].member_count); + strcat(room_list, room_info); + } + send(client->socket, room_list, strlen(room_list), 0); + } + else + { + Room *current_room = NULL; + for (int i = 0; i < active_room_count; i++) + { + if (strcmp(global_rooms[i].name, client->current_room) == 0) + { + current_room = &global_rooms[i]; + break; + } + } + + if (current_room) + { + if (is_word_banned(buffer)) + { + const char *warning = "Your message contains banned words and was not sent."; + send(client->socket, warning, strlen(warning), 0); + continue; + } + // Server: Before broadcasting + server_log("DEBUG", "Broadcasting message"); + printf("Broadcasting message: %s\n", buffer); + + // Rate limiting + if (time(NULL) - client->last_message_time >= 60) { + client->message_count = 0; + client->last_message_time = time(NULL); + } + + if (client->message_count >= MAX_MESSAGES_PER_MINUTE) { + const char *rate_limit_msg = "Rate limit exceeded. Please wait.\n"; + send(client->socket, rate_limit_msg, strlen(rate_limit_msg), 0); + continue; + } + client->message_count++; + + broadcast_room_message(current_room, buffer, client); + } + } + } + + server_log("DISCONNECT", client->username); + close(client->socket); + client->socket = 0; + return NULL; +} + +void send_private_message(Client *sender, const char *recipient_name, const char *message) { + pthread_mutex_lock(&clients_mutex); + for (int i = 0; i < MAX_CLIENTS; i++) { + if (clients[i].socket != 0 && strcmp(clients[i].username, recipient_name) == 0) { + char formatted_message[MAX_MESSAGE]; + snprintf(formatted_message, sizeof(formatted_message), + "[PM from %s]: %s", sender->username, message); + send(clients[i].socket, formatted_message, strlen(formatted_message), 0); + + // Send confirmation to sender + snprintf(formatted_message, sizeof(formatted_message), + "[PM to %s]: %s", recipient_name, message); + send(sender->socket, formatted_message, strlen(formatted_message), 0); + pthread_mutex_unlock(&clients_mutex); + return; + } + } + pthread_mutex_unlock(&clients_mutex); + + // User not found + char error_msg[MAX_MESSAGE]; + snprintf(error_msg, sizeof(error_msg), "User '%s' not found or offline.\n", recipient_name); + send(sender->socket, error_msg, strlen(error_msg), 0); +} + +int init_database() +{ + int rc = sqlite3_open("chat.db", &database); + if (rc) + { + server_log("ERROR", "Cannot open database"); + return 0; + } + + const char *tables[] = { + "CREATE TABLE IF NOT EXISTS users (" + "username TEXT PRIMARY KEY, " + "password TEXT, " + "role INTEGER);", + + "CREATE TABLE IF NOT EXISTS rooms (" + "name TEXT PRIMARY KEY," + "description TEXT," + "is_private INTEGER," + "password TEXT);", + + "CREATE TABLE IF NOT EXISTS messages (" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "room TEXT," + "sender TEXT," + "content TEXT," + "timestamp INTEGER," + "FOREIGN KEY(room) REFERENCES rooms(name)," + "FOREIGN KEY(sender) REFERENCES users(username));" + }; + + char *err_msg = 0; + for (int i = 0; i < sizeof(tables)/sizeof(tables[0]); i++) { + int rc = sqlite3_exec(database, tables[i], 0, 0, &err_msg); + if (rc != SQLITE_OK) { + char error_message[256]; + snprintf(error_message, sizeof(error_message), "Database error: %s", err_msg); + server_log("ERROR", error_message); + sqlite3_free(err_msg); + return 0; + } + } + + // Create default room if it doesn't exist + const char *default_room_query = + "INSERT OR IGNORE INTO rooms (name, description, is_private) " + "VALUES ('lobby', 'Default chat room', 0);"; + + sqlite3_exec(database, default_room_query, 0, 0, &err_msg); + return 1; +} + +volatile sig_atomic_t server_running = 1; + +void handle_shutdown(int signal) { + server_running = 0; + server_log("INFO", "Shutting down server..."); + + // Close all client connections + pthread_mutex_lock(&clients_mutex); + for (int i = 0; i < MAX_CLIENTS; i++) { + if (clients[i].socket != 0) { + close(clients[i].socket); + } + } + pthread_mutex_unlock(&clients_mutex); + + // Close server socket + close(server_socket); + + // Close database connection + sqlite3_close(database); + + // Close log file + if (log_file) { + fclose(log_file); + } + + exit(0); +} + +// Add these safe string functions +void safe_strcpy(char *dest, const char *src, size_t size) { + if (!dest || !src || size == 0) return; + strncpy(dest, src, size - 1); + dest[size - 1] = '\0'; +} + +void safe_strcat(char *dest, const char *src, size_t size) { + if (!dest || !src || size == 0) return; + size_t current_len = strlen(dest); + if (current_len >= size - 1) return; + strncpy(dest + current_len, src, size - current_len - 1); + dest[size - 1] = '\0'; +} + +int main() { + srand(time(NULL)); // Initialize random number generator + log_file = fopen("server.log", "a"); + parse_config(); + init_database(); + + struct sockaddr_in server_addr, client_addr; + socklen_t client_addr_len = sizeof(client_addr); + + server_socket = socket(AF_INET, SOCK_STREAM, 0); + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_port = htons(config.port); + + int yes = 1; + if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) { + server_log("ERROR", "Failed to set socket options"); + return 1; + } + + if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { + server_log("ERROR", "Bind failed"); + return 1; + } + + if (listen(server_socket, MAX_CLIENTS) < 0) { + server_log("ERROR", "Listen failed"); + return 1; + } + + server_log("INFO", "Server initialized. Waiting for connections..."); + + // Initialize default room + Room *default_room = &global_rooms[active_room_count++]; + strncpy(default_room->name, "lobby", MAX_USERNAME); + default_room->description[0] = '\0'; + default_room->is_private = 0; + default_room->password[0] = '\0'; + default_room->member_count = 0; + + server_log("INFO", "Default room 'lobby' initialized"); + + signal(SIGINT, handle_shutdown); + signal(SIGTERM, handle_shutdown); + + while (server_running) + { + int client_socket = accept(server_socket, + (struct sockaddr *)&client_addr, + &client_addr_len); + + char client_ip[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip)); + server_log("INFO", "Client connected"); + printf("Client connected from %s:%d\n", client_ip, ntohs(client_addr.sin_port)); + + // Read the combined username and password + char credentials[MAX_USERNAME + MAX_PASSWORD + 10]; // Extra space for command + ssize_t recv_len = recv(client_socket, credentials, sizeof(credentials) - 1, 0); + if (recv_len <= 0) { + close(client_socket); + continue; + } + credentials[recv_len] = '\0'; + + // Parse command, username and password + char command[10]; + char username[MAX_USERNAME]; + char password[MAX_PASSWORD]; + + if (sscanf(credentials, "%9s %31s %63s", command, username, password) != 3) { + const char *error_msg = "Invalid format. Use: LOGIN username password or REGISTER username password\n"; + send(client_socket, error_msg, strlen(error_msg), 0); + close(client_socket); + continue; + } + + if (strcmp(command, "REGISTER") == 0) { + if (register_user(username, password)) { + const char *success_msg = "Registration successful. Please login.\n"; + send(client_socket, success_msg, strlen(success_msg), 0); + } else { + const char *error_msg = "Registration failed. Username may already exist.\n"; + send(client_socket, error_msg, strlen(error_msg), 0); + } + close(client_socket); + continue; + } else if (strcmp(command, "LOGIN") == 0) { + if (authenticate_user(username, password)) { + server_log("INFO", "Authentication successful"); + const char *success_msg = "Authentication successful\n"; + send(client_socket, success_msg, strlen(success_msg), 0); + + pthread_mutex_lock(&clients_mutex); + int slot_found = 0; + for (int i = 0; i < MAX_CLIENTS; i++) { + if (clients[i].socket == 0) { + clients[i].socket = client_socket; + safe_strcpy(clients[i].username, username, MAX_USERNAME); + safe_strcpy(clients[i].current_room, config.default_room, MAX_USERNAME); + clients[i].role = USER_REGULAR; + clients[i].last_activity = time(NULL); + clients[i].message_count = 0; + clients[i].last_message_time = time(NULL); + + pthread_t thread; + if (pthread_create(&thread, NULL, handle_client, &clients[i]) != 0) { + server_log("ERROR", "Failed to create client thread"); + clients[i].socket = 0; + close(client_socket); + } else { + pthread_detach(thread); + slot_found = 1; + } + break; + } + } + pthread_mutex_unlock(&clients_mutex); + + if (!slot_found) { + server_log("ERROR", "Server full"); + const char *error_msg = "Server is full\n"; + send(client_socket, error_msg, strlen(error_msg), 0); + close(client_socket); + } + } else { + server_log("WARN", "Authentication failed"); + const char *error_msg = "Authentication failed: Invalid credentials\n"; + send(client_socket, error_msg, strlen(error_msg), 0); + close(client_socket); + } + } else { + const char *error_msg = "Invalid command. Use LOGIN or REGISTER\n"; + send(client_socket, error_msg, strlen(error_msg), 0); + close(client_socket); + } + } + + sqlite3_close(database); + return 0; +} \ No newline at end of file diff --git a/server.conf b/server.conf new file mode 100644 index 0000000..656ff74 --- /dev/null +++ b/server.conf @@ -0,0 +1,12 @@ +port=8888 +default_room=lobby +max_clients=200 +allow_room_creation=true +max_message_length=1024 +max_rooms=100 + +banned_words=badword,spamword,foo + +admins=Surillya + +motd=Welcome to Suri Chat! Be kind and follow the rules. \ No newline at end of file