Implement SQLite database for wssshd2 web interface user management

- Add SQLite support with proper database initialization
- Create users table with id, username, password_hash, is_admin columns
- Implement user management functions (add, update, delete, find)
- Add security warning for default admin credentials
- Add ASCII art banner on startup
- Fix login/logout redirects to home page
- Add --debug-database option for database operations logging
- Support root user directory selection (/etc/wssshd vs ~/.config/wssshd)
- Generate dynamic user management interface
- Maintain compatibility with existing web interface features
parent b4b18e3e
......@@ -3,7 +3,7 @@
CC = gcc
CFLAGS = -Wall -Wextra -O2 -I. -pthread
LDFLAGS = -lssl -lcrypto -lm -luuid
LDFLAGS = -lssl -lcrypto -lm -luuid -lsqlite3
PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
MANDIR = $(PREFIX)/share/man
......
......@@ -39,6 +39,7 @@ static void set_default_config(wssshd_config_t *config) {
config->web_https = false;
config->debug = false;
config->debug_web = false;
config->debug_database = false;
}
static void load_config_file(wssshd_config_t *config, const char *config_file) {
......@@ -150,6 +151,7 @@ wssshd_config_t *load_config(int argc, char *argv[]) {
{"web-https", no_argument, 0, 's'},
{"debug", no_argument, 0, 'D'},
{"debug-web", no_argument, 0, 'E'},
{"debug-database", no_argument, 0, 'F'},
{"help", no_argument, 0, '?'},
{0, 0, 0, 0}
};
......@@ -157,7 +159,7 @@ wssshd_config_t *load_config(int argc, char *argv[]) {
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, "c:h:p:d:P:w:W:sDE?", long_options, &option_index)) != -1) {
while ((opt = getopt_long(argc, argv, "c:h:p:d:P:w:W:sDEF?", long_options, &option_index)) != -1) {
switch (opt) {
case 'c':
if (config->config_file) free(config->config_file);
......@@ -194,6 +196,9 @@ wssshd_config_t *load_config(int argc, char *argv[]) {
case 'E':
config->debug_web = true;
break;
case 'F':
config->debug_database = true;
break;
case '?':
printf("Usage: %s [OPTIONS]\n", argv[0]);
printf("Options:\n");
......@@ -207,6 +212,7 @@ wssshd_config_t *load_config(int argc, char *argv[]) {
printf(" --web-https Enable HTTPS for web interface\n");
printf(" --debug Enable debug output\n");
printf(" --debug-web Enable comprehensive web interface debug output\n");
printf(" --debug-database Enable database debug output\n");
printf(" --help Show this help\n");
free_config(config);
exit(0);
......@@ -280,4 +286,5 @@ void print_config(const wssshd_config_t *config) {
printf(" Web HTTPS: %s\n", config->web_https ? "yes" : "no");
printf(" Debug: %s\n", config->debug ? "yes" : "no");
printf(" Debug Web: %s\n", config->debug_web ? "yes" : "no");
printf(" Debug Database: %s\n", config->debug_database ? "yes" : "no");
}
\ No newline at end of file
......@@ -34,6 +34,7 @@ typedef struct {
bool web_https;
bool debug;
bool debug_web;
bool debug_database;
} wssshd_config_t;
// Function declarations
......
#!/bin/bash
# Script to embed web assets into C code
#
# Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
echo "Embedding web assets..."
# Clean up old embedded files
rm -f image_data.h
# Embed logo from logos directory (created by build.sh)
if [ -f ../logos/logo-128.png ]; then
echo "Embedding logo-128.png..."
xxd -i ../logos/logo-128.png > image_data.h
# Rename the variables to match our expected names
# xxd generates names like ___logos_logo_128_png, so we need to replace the entire variable names
sed -i 's/unsigned char ___logos_logo_128_png\[\]/unsigned char image_jpg[]/g' image_data.h
sed -i 's/unsigned int ___logos_logo_128_png_len/unsigned int image_jpg_len/g' image_data.h
elif [ -f ../image.jpg ]; then
echo "Embedding image.jpg..."
xxd -i ../image.jpg | sed 's/___image_jpg/image_jpg/g' > image_data.h
else
echo "Warning: No image found, creating empty placeholder"
cat > image_data.h << 'EOF'
unsigned char image_jpg[] = {};
unsigned int image_jpg_len = 0;
EOF
fi
echo "Assets embedded successfully"
\ No newline at end of file
......@@ -20,30 +20,6 @@
#ifndef USERS_PAGE_H
#define USERS_PAGE_H
// Users page HTML template
static const char *users_page_html =
"<!DOCTYPE html>"
"<html lang=\"en\">"
"<head>"
" <meta charset=\"UTF-8\">"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
" <title>Users - WebSocket SSH Daemon</title>"
" <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\">"
"</head>"
"<body>"
" <div class=\"container mt-4\">"
" <div class=\"card\">"
" <div class=\"card-header\">"
" <h3 class=\"card-title mb-0\">"
" <i class=\"fas fa-users\"></i> User Management"
" </h3>"
" </div>"
" <div class=\"card-body\">"
" <p>User management interface would be implemented here.</p>"
" </div>"
" </div>"
" </div>"
"</body>"
"</html>";
// Users page HTML template - now generated dynamically
#endif /* USERS_PAGE_H */
\ No newline at end of file
......@@ -29,6 +29,8 @@
#include <errno.h>
#include <time.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sqlite3.h>
#include "web.h"
#include "terminal.h"
#include "assets.h"
......@@ -44,18 +46,17 @@ static wssshd_state_t *global_state = NULL;
static const wssshd_config_t *global_config = NULL;
static int server_socket = -1;
static volatile int server_running = 0;
static sqlite3 *db = NULL;
// Simple user management
#define MAX_USERS 100
typedef struct {
int id;
char username[50];
char password_hash[100];
int is_admin;
} web_user_t;
static web_user_t users[MAX_USERS];
static int user_count = 0;
// Session management
#define MAX_SESSIONS 100
typedef struct {
......@@ -85,13 +86,230 @@ static void simple_hash(const char *input, char *output, size_t output_size) {
snprintf(output, output_size, "%lx", hash);
}
// Initialize default users
static void init_users(void) {
if (user_count == 0) {
strcpy(users[0].username, "admin");
simple_hash("admin123", users[0].password_hash, sizeof(users[0].password_hash));
users[0].is_admin = 1;
user_count = 1;
// Database initialization
static int init_database(const wssshd_config_t *config) {
char *config_dir = NULL;
char db_path[1024];
char *err_msg = NULL;
int rc;
if (config->debug_database) {
printf("[DB-DEBUG] Initializing database...\n");
}
// Determine config directory based on user privileges
char user_config_dir[1024];
char system_config_dir[1024] = "/etc/wssshd";
// Check if running as root
if (getuid() == 0) {
// Running as root - prefer system directory
strcpy(db_path, system_config_dir);
if (config->debug_database) {
printf("[DB-DEBUG] Running as root, using system config directory: %s\n", db_path);
}
} else {
// Running as regular user - use user directory
config_dir = getenv("HOME");
if (!config_dir) {
fprintf(stderr, "Failed to get HOME environment variable\n");
return -1;
}
snprintf(user_config_dir, sizeof(user_config_dir), "%s/.config/wssshd", config_dir);
strcpy(db_path, user_config_dir);
if (config->debug_database) {
printf("[DB-DEBUG] Running as user, using config directory: %s\n", db_path);
}
}
// Check if preferred directory exists
if (access(db_path, F_OK) != 0) {
// Preferred directory doesn't exist
if (getuid() == 0) {
// Running as root, check if user directory exists
config_dir = getenv("HOME");
if (config_dir) {
snprintf(user_config_dir, sizeof(user_config_dir), "%s/.config/wssshd", config_dir);
if (access(user_config_dir, F_OK) == 0) {
// User directory exists, use it
strcpy(db_path, user_config_dir);
if (config->debug_database) {
printf("[DB-DEBUG] User config directory exists, using: %s\n", db_path);
}
}
}
} else {
// Running as user, check if system directory exists
if (access(system_config_dir, F_OK) == 0) {
// System directory exists, use it
strcpy(db_path, system_config_dir);
if (config->debug_database) {
printf("[DB-DEBUG] System config directory exists, using: %s\n", db_path);
}
}
}
}
if (config->debug_database) {
printf("[DB-DEBUG] Final config directory: %s\n", db_path);
}
if (mkdir(db_path, 0755) == -1 && errno != EEXIST) {
perror("Failed to create config directory");
return -1;
}
char db_file_path[1024];
snprintf(db_file_path, sizeof(db_file_path), "%s/users.db", db_path);
strcpy(db_path, db_file_path);
if (config->debug_database) {
printf("[DB-DEBUG] Database path: %s\n", db_path);
printf("[DB-DEBUG] Opening database...\n");
}
rc = sqlite3_open(db_path, &db);
if (rc != SQLITE_OK) {
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
return -1;
}
if (config->debug_database) {
printf("[DB-DEBUG] Database opened successfully\n");
}
// Create users table
const char *sql = "CREATE TABLE IF NOT EXISTS users ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"username TEXT UNIQUE NOT NULL,"
"password_hash TEXT NOT NULL,"
"is_admin INTEGER DEFAULT 0);";
if (config->debug_database) {
printf("[DB-DEBUG] Creating users table...\n");
printf("[DB-DEBUG] SQL: %s\n", sql);
}
rc = sqlite3_exec(db, sql, NULL, NULL, &err_msg);
if (rc != SQLITE_OK) {
fprintf(stderr, "SQL error: %s\n", err_msg);
sqlite3_free(err_msg);
sqlite3_close(db);
return -1;
}
if (config->debug_database) {
printf("[DB-DEBUG] Users table created successfully\n");
}
// Check if admin user exists, create if not
sql = "SELECT COUNT(*) FROM users WHERE is_admin = 1;";
if (config->debug_database) {
printf("[DB-DEBUG] Checking if any admin user exists...\n");
printf("[DB-DEBUG] SQL: %s\n", sql);
}
sqlite3_stmt *stmt;
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return -1;
}
int count = 0;
if (sqlite3_step(stmt) == SQLITE_ROW) {
count = sqlite3_column_int(stmt, 0);
}
sqlite3_finalize(stmt);
if (config->debug_database) {
printf("[DB-DEBUG] Admin user count: %d\n", count);
}
if (count == 0) {
// Create default admin user
char hashed[100];
simple_hash("admin123", hashed, sizeof(hashed));
if (config->debug_database) {
printf("[DB-DEBUG] Creating default admin user...\n");
printf("[DB-DEBUG] Password hash: %s\n", hashed);
}
sql = "INSERT INTO users (username, password_hash, is_admin) VALUES ('admin', ?, 1);";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to prepare insert statement: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return -1;
}
sqlite3_bind_text(stmt, 1, hashed, -1, SQLITE_STATIC);
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
fprintf(stderr, "Failed to insert admin user: %s\n", sqlite3_errmsg(db));
sqlite3_finalize(stmt);
sqlite3_close(db);
return -1;
}
sqlite3_finalize(stmt);
if (config->debug_database) {
printf("[DB-DEBUG] Default admin user created successfully\n");
}
printf("Created default admin user (username: admin, password: admin123)\n");
} else {
if (config->debug_database) {
printf("[DB-DEBUG] Admin user already exists, skipping creation\n");
}
// Check if default admin with default password exists (security warning)
char default_hashed[100];
simple_hash("admin123", default_hashed, sizeof(default_hashed));
sql = "SELECT COUNT(*) FROM users WHERE username = 'admin' AND password_hash = ?;";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc == SQLITE_OK) {
sqlite3_bind_text(stmt, 1, default_hashed, -1, SQLITE_STATIC);
if (sqlite3_step(stmt) == SQLITE_ROW) {
int default_admin_count = sqlite3_column_int(stmt, 0);
if (default_admin_count > 0) {
printf("\n");
printf("###############################################################################\n");
printf("# SECURITY WARNING #\n");
printf("###############################################################################\n");
printf("# #\n");
printf("# WARNING: Default admin credentials detected! #\n");
printf("# #\n");
printf("# The system has detected that the default admin user 'admin' with the #\n");
printf("# default password 'admin123' is still active. #\n");
printf("# #\n");
printf("# This is a SECURITY RISK! Please change the default password immediately. #\n");
printf("# #\n");
printf("# To change the password: #\n");
printf("# 1. Start the web interface #\n");
printf("# 2. Log in with username 'admin' and password 'admin123' #\n");
printf("# 3. Go to User Management and change the password #\n");
printf("# #\n");
printf("###############################################################################\n");
printf("\n");
}
}
sqlite3_finalize(stmt);
}
}
return 0;
}
// Initialize database and default users
static void init_users(const wssshd_config_t *config) {
if (init_database(config) != 0) {
fprintf(stderr, "Failed to initialize database\n");
exit(1);
}
}
......@@ -103,14 +321,210 @@ static void generate_session_id(char *session_id, size_t size) {
// Find user by username
static web_user_t *find_user(const char *username) {
for (int i = 0; i < user_count; i++) {
if (strcmp(users[i].username, username) == 0) {
return &users[i];
static web_user_t user;
sqlite3_stmt *stmt;
const char *sql = "SELECT id, username, password_hash, is_admin FROM users WHERE username = ?;";
if (global_config && global_config->debug_database) {
printf("[DB-DEBUG] Finding user: %s\n", username);
printf("[DB-DEBUG] SQL: %s\n", sql);
}
int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to prepare find_user statement: %s\n", sqlite3_errmsg(db));
return NULL;
}
sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC);
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
user.id = sqlite3_column_int(stmt, 0);
const char *uname = (const char *)sqlite3_column_text(stmt, 1);
const char *phash = (const char *)sqlite3_column_text(stmt, 2);
int is_admin = sqlite3_column_int(stmt, 3);
strncpy(user.username, uname, sizeof(user.username) - 1);
strncpy(user.password_hash, phash, sizeof(user.password_hash) - 1);
user.is_admin = is_admin;
if (global_config && global_config->debug_database) {
printf("[DB-DEBUG] User found: id=%d, username=%s, is_admin=%d\n", user.id, user.username, user.is_admin);
}
sqlite3_finalize(stmt);
return &user;
}
if (global_config && global_config->debug_database) {
printf("[DB-DEBUG] User not found: %s\n", username);
}
sqlite3_finalize(stmt);
return NULL;
}
// Add a new user
static int db_add_user(const char *username, const char *password_hash, int is_admin) {
sqlite3_stmt *stmt;
const char *sql = "INSERT INTO users (username, password_hash, is_admin) VALUES (?, ?, ?);";
if (global_config && global_config->debug_database) {
printf("[DB-DEBUG] Adding user: username=%s, is_admin=%d\n", username, is_admin);
printf("[DB-DEBUG] SQL: %s\n", sql);
}
int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to prepare add_user statement: %s\n", sqlite3_errmsg(db));
return -1;
}
sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, password_hash, -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 3, is_admin);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
fprintf(stderr, "Failed to add user: %s\n", sqlite3_errmsg(db));
return -1;
}
if (global_config && global_config->debug_database) {
printf("[DB-DEBUG] User added successfully\n");
}
return 0;
}
// Update user
static int db_update_user(int user_id, const char *username, int is_admin, const char *password_hash) {
sqlite3_stmt *stmt;
const char *sql;
if (password_hash) {
sql = "UPDATE users SET username = ?, is_admin = ?, password_hash = ? WHERE id = ?;";
} else {
sql = "UPDATE users SET username = ?, is_admin = ? WHERE id = ?;";
}
if (global_config && global_config->debug_database) {
printf("[DB-DEBUG] Updating user: id=%d, username=%s, is_admin=%d, password_change=%s\n",
user_id, username, is_admin, password_hash ? "yes" : "no");
printf("[DB-DEBUG] SQL: %s\n", sql);
}
int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to prepare update_user statement: %s\n", sqlite3_errmsg(db));
return -1;
}
sqlite3_bind_text(stmt, 1, username, -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 2, is_admin);
if (password_hash) {
sqlite3_bind_text(stmt, 3, password_hash, -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 4, user_id);
} else {
sqlite3_bind_int(stmt, 3, user_id);
}
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
fprintf(stderr, "Failed to update user: %s\n", sqlite3_errmsg(db));
return -1;
}
if (global_config && global_config->debug_database) {
printf("[DB-DEBUG] User updated successfully\n");
}
return 0;
}
// Delete user
static int db_delete_user(int user_id) {
sqlite3_stmt *stmt;
const char *sql = "DELETE FROM users WHERE id = ?;";
if (global_config && global_config->debug_database) {
printf("[DB-DEBUG] Deleting user: id=%d\n", user_id);
printf("[DB-DEBUG] SQL: %s\n", sql);
}
int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to prepare delete_user statement: %s\n", sqlite3_errmsg(db));
return -1;
}
sqlite3_bind_int(stmt, 1, user_id);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
fprintf(stderr, "Failed to delete user: %s\n", sqlite3_errmsg(db));
return -1;
}
if (global_config && global_config->debug_database) {
printf("[DB-DEBUG] User deleted successfully\n");
}
return 0;
}
// Get all users (for users page)
static int db_get_all_users(web_user_t *users_array, int max_users) {
sqlite3_stmt *stmt;
const char *sql = "SELECT id, username, password_hash, is_admin FROM users ORDER BY username;";
int count = 0;
if (global_config && global_config->debug_database) {
printf("[DB-DEBUG] Getting all users (max: %d)\n", max_users);
printf("[DB-DEBUG] SQL: %s\n", sql);
}
int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
fprintf(stderr, "Failed to prepare get_all_users statement: %s\n", sqlite3_errmsg(db));
return -1;
}
while (sqlite3_step(stmt) == SQLITE_ROW && count < max_users) {
users_array[count].id = sqlite3_column_int(stmt, 0);
const char *uname = (const char *)sqlite3_column_text(stmt, 1);
const char *phash = (const char *)sqlite3_column_text(stmt, 2);
int is_admin = sqlite3_column_int(stmt, 3);
users_array[count].id = sqlite3_column_int(stmt, 0);
strncpy(users_array[count].username, uname, sizeof(users_array[count].username) - 1);
strncpy(users_array[count].password_hash, phash, sizeof(users_array[count].password_hash) - 1);
users_array[count].is_admin = is_admin;
if (global_config && global_config->debug_database) {
printf("[DB-DEBUG] Found user: id=%d, username=%s, is_admin=%d\n",
users_array[count].id, users_array[count].username, users_array[count].is_admin);
}
count++;
}
sqlite3_finalize(stmt);
if (global_config && global_config->debug_database) {
printf("[DB-DEBUG] Total users retrieved: %d\n", count);
}
return count;
}
// Create new session
static const char *create_session(const char *username) {
pthread_mutex_lock(&session_mutex);
......@@ -434,6 +848,196 @@ static char *generate_index_html(const char *username, int is_admin) {
return html;
}
// Generate dynamic HTML for users page
static int generate_users_html(char *html, size_t max_len) {
web_user_t users[MAX_USERS];
int user_count = db_get_all_users(users, MAX_USERS);
if (user_count < 0) {
return snprintf(html, max_len, "Error loading users");
}
char user_rows[8192] = "";
for (int i = 0; i < user_count; i++) {
char role_badge[64];
if (users[i].is_admin) {
strcpy(role_badge, "<span class=\"badge bg-danger\">Admin</span>");
} else {
strcpy(role_badge, "<span class=\"badge bg-secondary\">User</span>");
}
char row[1024];
snprintf(row, sizeof(row),
"<tr>"
"<td>%s</td>"
"<td>%s</td>"
"<td>"
"<button class=\"btn btn-sm btn-outline-primary\" onclick=\"editUser(%d, '%s', %s)\">"
"<i class=\"fas fa-edit\"></i> Edit</button>"
"<button class=\"btn btn-sm btn-outline-danger ms-1\" onclick=\"deleteUser(%d, '%s')\">"
"<i class=\"fas fa-trash\"></i> Delete</button>"
"</td>"
"</tr>",
users[i].username, role_badge, users[i].id, users[i].username,
users[i].is_admin ? "true" : "false", users[i].id, users[i].username);
strcat(user_rows, row);
}
return snprintf(html, max_len,
"<!DOCTYPE html>"
"<html lang=\"en\">"
"<head>"
" <meta charset=\"UTF-8\">"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
" <title>Users - WebSocket SSH Daemon</title>"
" <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\">"
"</head>"
"<body>"
" <div class=\"container mt-4\">"
" <div class=\"card\">"
" <div class=\"card-header d-flex justify-content-between align-items-center\">"
" <h3 class=\"card-title mb-0\">"
" <i class=\"fas fa-users\"></i> User Management"
" </h3>"
" <div>"
" <a href=\"/\" class=\"btn btn-outline-secondary btn-sm me-2\">"
" <i class=\"fas fa-home\"></i> Back to Home"
" </a>"
" <button class=\"btn btn-primary btn-sm\" data-bs-toggle=\"modal\" data-bs-target=\"#addUserModal\">"
" <i class=\"fas fa-plus\"></i> Add User"
" </button>"
" </div>"
" </div>"
" <div class=\"card-body\">"
" <div class=\"table-responsive\">"
" <table class=\"table table-striped\">"
" <thead>"
" <tr>"
" <th>Username</th>"
" <th>Role</th>"
" <th>Actions</th>"
" </tr>"
" </thead>"
" <tbody>%s</tbody>"
" </table>"
" </div>"
" </div>"
" </div>"
" </div>"
" <!-- Add User Modal -->"
" <div class=\"modal fade\" id=\"addUserModal\" tabindex=\"-1\">"
" <div class=\"modal-dialog\">"
" <div class=\"modal-content\">"
" <div class=\"modal-header\">"
" <h5 class=\"modal-title\">Add New User</h5>"
" <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\"></button>"
" </div>"
" <form id=\"addUserForm\">"
" <div class=\"modal-body\">"
" <div class=\"mb-3\">"
" <label for=\"addUsername\" class=\"form-label\">Username</label>"
" <input type=\"text\" class=\"form-control\" id=\"addUsername\" name=\"username\" required>"
" </div>"
" <div class=\"mb-3\">"
" <label for=\"addPassword\" class=\"form-label\">Password</label>"
" <input type=\"password\" class=\"form-control\" id=\"addPassword\" name=\"password\" required>"
" </div>"
" <div class=\"mb-3 form-check\">"
" <input type=\"checkbox\" class=\"form-check-input\" id=\"addIsAdmin\" name=\"is_admin\">"
" <label class=\"form-check-label\" for=\"addIsAdmin\">Administrator</label>"
" </div>"
" </div>"
" <div class=\"modal-footer\">"
" <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button>"
" <button type=\"submit\" class=\"btn btn-primary\">Add User</button>"
" </div>"
" </form>"
" </div>"
" </div>"
" </div>"
" <!-- Edit User Modal -->"
" <div class=\"modal fade\" id=\"editUserModal\" tabindex=\"-1\">"
" <div class=\"modal-dialog\">"
" <div class=\"modal-content\">"
" <div class=\"modal-header\">"
" <h5 class=\"modal-title\">Edit User</h5>"
" <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\"></button>"
" </div>"
" <form id=\"editUserForm\">"
" <input type=\"hidden\" id=\"editUserId\" name=\"user_id\">"
" <div class=\"modal-body\">"
" <div class=\"mb-3\">"
" <label for=\"editUsername\" class=\"form-label\">Username</label>"
" <input type=\"text\" class=\"form-control\" id=\"editUsername\" name=\"username\" required>"
" </div>"
" <div class=\"mb-3\">"
" <label for=\"editPassword\" class=\"form-label\">New Password (leave empty to keep current)</label>"
" <input type=\"password\" class=\"form-control\" id=\"editPassword\" name=\"password\">"
" </div>"
" <div class=\"mb-3 form-check\">"
" <input type=\"checkbox\" class=\"form-check-input\" id=\"editIsAdmin\" name=\"is_admin\">"
" <label class=\"form-check-label\" for=\"editIsAdmin\">Administrator</label>"
" </div>"
" </div>"
" <div class=\"modal-footer\">"
" <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button>"
" <button type=\"submit\" class=\"btn btn-primary\">Update User</button>"
" </div>"
" </form>"
" </div>"
" </div>"
" </div>"
" <script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js\"></script>"
" <script>"
" function editUser(userId, username, isAdmin) {"
" document.getElementById('editUserId').value = userId;"
" document.getElementById('editUsername').value = username;"
" document.getElementById('editPassword').value = '';"
" document.getElementById('editIsAdmin').checked = isAdmin;"
" new bootstrap.Modal(document.getElementById('editUserModal')).show();"
" }"
" function deleteUser(userId, username) {"
" if (confirm(`Are you sure you want to delete user \"${username}\"?`)) {"
" fetch(`/delete_user/${userId}`, {"
" method: 'POST',"
" headers: {'Content-Type': 'application/x-www-form-urlencoded'}"
" })"
" .then(response => response.json())"
" .then(data => {"
" if (data.success) location.reload();"
" else alert('Error: ' + data.error);"
" });"
" }"
" }"
" document.getElementById('addUserForm').addEventListener('submit', function(e) {"
" e.preventDefault();"
" const formData = new FormData(this);"
" fetch('/add_user', {method: 'POST', body: formData})"
" .then(response => response.json())"
" .then(data => {"
" if (data.success) {"
" bootstrap.Modal.getInstance(document.getElementById('addUserModal')).hide();"
" location.reload();"
" } else alert('Error: ' + data.error);"
" });"
" });"
" document.getElementById('editUserForm').addEventListener('submit', function(e) {"
" e.preventDefault();"
" const formData = new FormData(this);"
" const userId = document.getElementById('editUserId').value;"
" fetch(`/edit_user/${userId}`, {method: 'POST', body: formData})"
" .then(response => response.json())"
" .then(data => {"
" if (data.success) {"
" bootstrap.Modal.getInstance(document.getElementById('editUserModal')).hide();"
" location.reload();"
" } else alert('Error: ' + data.error);"
" });"
" });"
" </script>"
"</body>"
"</html>", user_rows);
}
// Handle HTTP requests
static void handle_request(int client_fd, const http_request_t *req) {
if (global_config && global_config->debug_web) {
......@@ -483,14 +1087,17 @@ static void handle_request(int client_fd, const http_request_t *req) {
} else if (strcmp(req->path, "/login") == 0) {
send_response(client_fd, 200, "OK", "text/html", login_page_html, strlen(login_page_html), NULL, NULL);
} else if (strcmp(req->path, "/logout") == 0) {
send_response(client_fd, 302, "Found", "text/html", NULL, 0, "session_id=; Max-Age=0; Path=/", NULL);
send_response(client_fd, 302, "Found", "text/html", NULL, 0, "session_id=; Max-Age=0; Path=/", "Location: /");
return;
} else if (strcmp(req->path, "/users") == 0) {
if (!username || !is_admin) {
send_response(client_fd, 403, "Forbidden", "text/html", "Access denied", 13, NULL, NULL);
return;
}
send_response(client_fd, 200, "OK", "text/html", users_page_html, strlen(users_page_html), NULL, NULL);
// Generate dynamic users page
char html[16384];
int len = generate_users_html(html, sizeof(html));
send_response(client_fd, 200, "OK", "text/html", html, len, NULL, NULL);
} else if (strncmp(req->path, "/terminal/", 9) == 0) {
if (!username) {
send_response(client_fd, 302, "Found", "text/html", NULL, 0, NULL, NULL);
......@@ -819,7 +1426,7 @@ static void handle_request(int client_fd, const http_request_t *req) {
}
char cookie[256];
snprintf(cookie, sizeof(cookie), "session_id=%s; Path=/; HttpOnly", new_session);
send_response(client_fd, 302, "Found", "text/html", NULL, 0, cookie, NULL);
send_response(client_fd, 302, "Found", "text/html", NULL, 0, cookie, "Location: /");
return;
} else {
if (global_config && global_config->debug_web) {
......@@ -833,6 +1440,112 @@ static void handle_request(int client_fd, const http_request_t *req) {
}
const char *asset = get_embedded_asset("/login", NULL);
send_response(client_fd, 200, "OK", "text/html", asset, strlen(asset), NULL, NULL);
} else if (strcmp(req->path, "/add_user") == 0) {
if (!username || !is_admin) {
send_response(client_fd, 403, "Forbidden", "application/json", "{\"error\":\"Access denied\"}", 23, NULL, NULL);
return;
}
char form_username[50] = "";
char form_password[50] = "";
int form_is_admin = 0;
// Parse form data
char temp_body[4096];
strcpy(temp_body, req->body);
char *pair = strtok(temp_body, "&");
while (pair) {
char *eq = strchr(pair, '=');
if (eq) {
*eq = '\0';
char *key = pair;
char *value = eq + 1;
url_decode(value);
if (strcmp(key, "username") == 0) {
strncpy(form_username, value, sizeof(form_username) - 1);
} else if (strcmp(key, "password") == 0) {
strncpy(form_password, value, sizeof(form_password) - 1);
} else if (strcmp(key, "is_admin") == 0) {
form_is_admin = 1;
}
}
pair = strtok(NULL, "&");
}
// Check if username already exists
if (find_user(form_username)) {
send_response(client_fd, 400, "Bad Request", "application/json", "{\"error\":\"Username already exists\"}", 33, NULL, NULL);
return;
}
char hashed[100];
simple_hash(form_password, hashed, sizeof(hashed));
if (db_add_user(form_username, hashed, form_is_admin) == 0) {
send_response(client_fd, 200, "OK", "application/json", "{\"success\":true}", 16, NULL, NULL);
} else {
send_response(client_fd, 500, "Internal Server Error", "application/json", "{\"error\":\"Failed to add user\"}", 29, NULL, NULL);
}
} else if (strncmp(req->path, "/edit_user/", 11) == 0) {
if (!username || !is_admin) {
send_response(client_fd, 403, "Forbidden", "application/json", "{\"error\":\"Access denied\"}", 23, NULL, NULL);
return;
}
int user_id = atoi(req->path + 11);
char form_username[50] = "";
char form_password[50] = "";
int form_is_admin = 0;
// Parse form data
char temp_body[4096];
strcpy(temp_body, req->body);
char *pair = strtok(temp_body, "&");
while (pair) {
char *eq = strchr(pair, '=');
if (eq) {
*eq = '\0';
char *key = pair;
char *value = eq + 1;
url_decode(value);
if (strcmp(key, "username") == 0) {
strncpy(form_username, value, sizeof(form_username) - 1);
} else if (strcmp(key, "password") == 0 && strlen(value) > 0) {
strncpy(form_password, value, sizeof(form_password) - 1);
} else if (strcmp(key, "is_admin") == 0) {
form_is_admin = 1;
}
}
pair = strtok(NULL, "&");
}
char *password_hash = NULL;
if (strlen(form_password) > 0) {
password_hash = malloc(100);
simple_hash(form_password, password_hash, 100);
}
if (db_update_user(user_id, form_username, form_is_admin, password_hash) == 0) {
send_response(client_fd, 200, "OK", "application/json", "{\"success\":true}", 16, NULL, NULL);
} else {
send_response(client_fd, 500, "Internal Server Error", "application/json", "{\"error\":\"Failed to update user\"}", 31, NULL, NULL);
}
if (password_hash) free(password_hash);
} else if (strncmp(req->path, "/delete_user/", 13) == 0) {
if (!username || !is_admin) {
send_response(client_fd, 403, "Forbidden", "application/json", "{\"error\":\"Access denied\"}", 23, NULL, NULL);
return;
}
int user_id = atoi(req->path + 13);
if (db_delete_user(user_id) == 0) {
send_response(client_fd, 200, "OK", "application/json", "{\"success\":true}", 16, NULL, NULL);
} else {
send_response(client_fd, 500, "Internal Server Error", "application/json", "{\"error\":\"Failed to delete user\"}", 31, NULL, NULL);
}
} else {
send_response(client_fd, 405, "Method Not Allowed", "text/html", "Method not allowed", 18, NULL, NULL);
}
......@@ -904,7 +1617,27 @@ int web_start_server(const wssshd_config_t *config, wssshd_state_t *state) {
global_config = config;
global_state = state;
init_users();
// Print ASCII art banner
printf("\033[38;5;117m⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⠖⠢⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
printf("\033[38;5;117m⠀⠀⠀⠀⠀⠀⠀⢠⠖⠋⠀⠀⠀⣀⣹⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
printf("\033[38;5;117m⠀⠀⠀⠀⠀⠀⠀⢸⡄⠀⢠⣶⡞⢁⣸⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
printf("\033[38;5;218m⠀⠀⠀⠀⠀⠀⠀⠈⢁⣠⠞⢻⡁⢻⣷⣾⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
printf("\033[38;5;218m⠀⠀⠀⠀⠀⣠⠤⢄⣘⣿⢬⣛⡷⢋⣈⣉⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
printf("\033[38;5;218m⠀⠀⠀⠀⢰⡇⠀⠀⠙⠿⠀⢀⠿⠋⠀⠀⠀⢱⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
printf("⠀⠀⠀⠀⠘⡃⠀⢰⠀⠀⠀⡁⠀⡀⠀⠘⣆⠈⢇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\n");
printf("\033[38;5;231m⠀⠀⠀⠀⠀⡇⠀⣼⡦⠠⠤⣈⡆⢃⡤⠒⠙⡆⠀⠳⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
printf("\033[38;5;231m⠀⠀⠀⠀⢀⠇⠀⡇⢷⣄⣀⡠⠟⠛⠢⠤⠞⡟⠦⡀⠙⠦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
printf("\033[38;5;231m⠀⠀⠀⠀⡞⢀⠀⠀⠀⠳⡄⠀⠀⠠⡀⠀⠀⠘⠉⠙⠦⣀⠀⠉⠢⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
printf("\033[38;5;231m⠀⠀⠀⠀⡇⠀⢠⠀⠀⠀⠈⠓⢄⠀⠁⠀⠀⠀⠒⠂⠀⣾⠋⠉⠒⠢⢍⣙⡒⢄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
printf("\033[38;5;218m⠀⠀⠀⠀⡇⠀⡜⠀⠀⠀⠀⠀⠀⠱⡀⢀⡀⠁⠀⢀⡼⡇⠀⠀⠀⠀⠀⣏⠙⠯⣆⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
printf("\033[38;5;218m⠀⠀⠀⠀⡇⢠⠇⠀⠀⠀⠀⠀⠀⠀⢱⣀⣽⠶⠾⠛⠒⠛⠒⠒⠒⠤⠤⣸⡍⠀⠀⠉⠲⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
printf("\033[38;5;218m⠀⣀⣀⣀⠇⣼⠀⠀⠀⠀⠀⠀⠀⠀⠀⢫⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⣙⠢⣀⠀⠀⠈⠳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
printf("\033[38;5;117m⠉⠙⠛⠥⠰⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠂⠤⠤⠤⠤⠴⠒⠚⠉⠙⠢⣀⠀⠈⠑⠢⢄⡀⠈⠳⡀⠀⠀⠀⠀⠀⠀⠀\033[0m\n");
printf("\033[38;5;117m⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠑⠒⠒⠒⠒⠚⠑⠢⢌⡓⠤⠤⠤⠤⣀⠀\033[0m\n");
printf("\033[38;5;117m⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠓⠒⠠⠤⣼⠇\033[0m\n");
printf("\n");
init_users(config);
// Start HTTP server thread
pthread_t thread;
......@@ -923,5 +1656,9 @@ void web_stop_server(void) {
close(server_socket);
server_socket = -1;
}
if (db) {
sqlite3_close(db);
db = NULL;
}
printf("Web interface stopping\n");
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment