Fix terminal output display on first connect and other improvements

- Add polling delay to ensure terminal output appears immediately
- Update dashboard to refresh client list dynamically without page reload
- Fix dashboard layout with proper container
- Update debug logging to exclude frequent polling requests
- Embed updated HTML templates
parent 84fd785b
...@@ -132,6 +132,7 @@ cleanup() { ...@@ -132,6 +132,7 @@ cleanup() {
make clean >/dev/null 2>&1 || true make clean >/dev/null 2>&1 || true
fi fi
rm -f Makefile configure.sh.stamp rm -f Makefile configure.sh.stamp
rm -rf html_pages/
cd .. cd ..
fi fi
# Remove server debian artifacts # Remove server debian artifacts
...@@ -168,15 +169,23 @@ prepare_assets() { ...@@ -168,15 +169,23 @@ prepare_assets() {
# Create logos directory if it doesn't exist # Create logos directory if it doesn't exist
mkdir -p logos mkdir -p logos
# Generate logo-128.png from image.jpg if imagemagick is available # Generate logos from image.jpg if imagemagick is available
if command -v convert >/dev/null 2>&1 && [[ -f "image.jpg" ]]; then if command -v convert >/dev/null 2>&1 && [[ -f "image.jpg" ]]; then
log_info "Generating logo-128.png from image.jpg..." log_info "Generating logos from image.jpg..."
convert image.jpg -resize 128x128 logos/logo-128.png for size in 16 32 48 64 128; do
convert image.jpg -resize ${size}x${size} logos/logo-${size}.png
done
# Generate favicon.ico
convert logos/logo-16.png logos/logo-32.png logos/favicon.ico
elif [[ ! -f "logos/logo-128.png" ]]; then elif [[ ! -f "logos/logo-128.png" ]]; then
log_warning "ImageMagick not found or image.jpg missing. Using placeholder logo." log_warning "ImageMagick not found or image.jpg missing. Using placeholder logo."
# Create a simple placeholder if no logo exists # Create a simple placeholder if no logo exists
if command -v convert >/dev/null 2>&1; then if command -v convert >/dev/null 2>&1; then
convert -size 128x128 xc:"#4A90E2" -fill white -pointsize 72 -gravity center -annotate +0+0 "WSSSH" logos/logo-128.png convert -size 128x128 xc:"#4A90E2" -fill white -pointsize 72 -gravity center -annotate +0+0 "WSSSH" logos/logo-128.png
# Also create favicon from placeholder
convert logos/logo-128.png -resize 16x16 logos/logo-16.png
convert logos/logo-128.png -resize 32x32 logos/logo-32.png
convert logos/logo-16.png logos/logo-32.png logos/favicon.ico
fi fi
fi fi
......
...@@ -20,9 +20,11 @@ ...@@ -20,9 +20,11 @@
#include <string.h> #include <string.h>
#include "assets.h" #include "assets.h"
#include "image_data.h" #include "image_data.h"
#include "favicon_data.h"
#include "html_pages/login_page.h" #include "html_pages/login_page.h"
#include "html_pages/terminal_page.h" #include "html_pages/terminal_page.h"
#include "html_pages/users_page.h" #include "html_pages/xterm_page.h"
#include "html_pages/xterm_addon_page.h"
// HTML pages are now defined in separate header files in html_pages/ directory // HTML pages are now defined in separate header files in html_pages/ directory
// This file now only contains fallback definitions for compatibility // This file now only contains fallback definitions for compatibility
...@@ -50,6 +52,15 @@ const char *get_embedded_asset(const char *path, size_t *size) { ...@@ -50,6 +52,15 @@ const char *get_embedded_asset(const char *path, size_t *size) {
} else if (strcmp(path, "/image.jpg") == 0) { } else if (strcmp(path, "/image.jpg") == 0) {
if (size) *size = image_jpg_len; if (size) *size = image_jpg_len;
return (const char *)image_jpg; return (const char *)image_jpg;
} else if (strcmp(path, "/favicon.ico") == 0) {
if (size) *size = favicon_ico_len;
return (const char *)favicon_ico;
} else if (strcmp(path, "/xterm.js") == 0) {
if (size) *size = strlen(xterm_js);
return xterm_js;
} else if (strcmp(path, "/xterm-addon-fit.js") == 0) {
if (size) *size = strlen(xterm_addon_fit_js);
return xterm_addon_fit_js;
} }
return NULL; return NULL;
......
...@@ -28,9 +28,15 @@ extern const char *login_html; ...@@ -28,9 +28,15 @@ extern const char *login_html;
extern const char *terminal_html; extern const char *terminal_html;
extern const char *users_html; extern const char *users_html;
// Embedded JavaScript libraries
extern const char *xterm_js;
extern const char *xterm_addon_fit_js;
// Embedded images // Embedded images
extern unsigned char image_jpg[]; extern unsigned char image_jpg[];
extern unsigned int image_jpg_len; extern unsigned int image_jpg_len;
extern unsigned char favicon_ico[];
extern unsigned int favicon_ico_len;
// Function to get asset by path // Function to get asset by path
const char *get_embedded_asset(const char *path, size_t *size); const char *get_embedded_asset(const char *path, size_t *size);
......
...@@ -98,8 +98,12 @@ assets.o: image_data.h ...@@ -98,8 +98,12 @@ assets.o: image_data.h
image_data.h: embed_assets.sh image_data.h: embed_assets.sh
./embed_assets.sh ./embed_assets.sh
# HTML page generation
html_pages/index_page.h html_pages/login_page.h html_pages/terminal_page.h: embed_assets.sh
./embed_assets.sh
clean: clean:
rm -f $(OBJS) $(TARGET) image_data.h rm -f $(OBJS) $(TARGET) image_data.h favicon_data.h
install: $(TARGET) install: $(TARGET)
install -d $(DESTDIR)$(BINDIR) install -d $(DESTDIR)$(BINDIR)
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
echo "Embedding web assets..." echo "Embedding web assets..."
# Clean up old embedded files # Clean up old embedded files
rm -f image_data.h rm -f image_data.h favicon_data.h
# Embed logo from logos directory (created by build.sh) # Embed logo from logos directory (created by build.sh)
if [ -f ../logos/logo-128.png ]; then if [ -f ../logos/logo-128.png ]; then
...@@ -41,4 +41,224 @@ unsigned int image_jpg_len = 0; ...@@ -41,4 +41,224 @@ unsigned int image_jpg_len = 0;
EOF EOF
fi fi
# Embed favicon.ico
if [ -f ../logos/favicon.ico ]; then
echo "Embedding favicon.ico..."
xxd -i ../logos/favicon.ico > favicon_data.h
# Rename the variables
sed -i 's/unsigned char ___logos_favicon_ico\[\]/unsigned char favicon_ico[]/g' favicon_data.h
sed -i 's/unsigned int ___logos_favicon_ico_len/unsigned int favicon_ico_len/g' favicon_data.h
else
echo "Warning: favicon.ico not found, creating empty placeholder"
cat > favicon_data.h << 'EOF'
unsigned char favicon_ico[] = {};
unsigned int favicon_ico_len = 0;
EOF
fi
# Embed xterm.js
if [ -f templates/xterm.js ]; then
echo "Embedding xterm.js..."
mkdir -p html_pages
HEADER_FILE="html_pages/xterm_page.h"
cat > "$HEADER_FILE" << EOF
/**
* xterm.js library for wssshd
*
* 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/>.
*/
#ifndef XTERM_PAGE_H
#define XTERM_PAGE_H
// xterm.js library
const char *xterm_js =
EOF
# Process the JS file, escape quotes and backslashes
sed 's/\\/\\\\/g; s/"/\\"/g; s/^/"/; s/$/\\n"/' templates/xterm.js >> "$HEADER_FILE"
# Close the string and header
cat >> "$HEADER_FILE" << EOF
;
#endif /* XTERM_PAGE_H */
EOF
else
echo "Warning: xterm.js not found, creating empty placeholder"
mkdir -p html_pages
cat > html_pages/xterm_page.h << 'EOF'
#ifndef XTERM_PAGE_H
#define XTERM_PAGE_H
static const char *xterm_js = "";
#endif /* XTERM_PAGE_H */
EOF
fi
# Embed xterm-addon-fit.js
if [ -f templates/xterm-addon-fit.js ]; then
echo "Embedding xterm-addon-fit.js..."
mkdir -p html_pages
HEADER_FILE="html_pages/xterm_addon_page.h"
cat > "$HEADER_FILE" << EOF
/**
* xterm-addon-fit.js library for wssshd
*
* 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/>.
*/
#ifndef XTERM_ADDON_PAGE_H
#define XTERM_ADDON_PAGE_H
// xterm-addon-fit.js library
const char *xterm_addon_fit_js =
EOF
# Process the JS file, escape quotes and backslashes
sed 's/\\/\\\\/g; s/"/\\"/g; s/^/"/; s/$/\\n"/' templates/xterm-addon-fit.js >> "$HEADER_FILE"
# Close the string and header
cat >> "$HEADER_FILE" << EOF
;
#endif /* XTERM_ADDON_PAGE_H */
EOF
else
echo "Warning: xterm-addon-fit.js not found, creating empty placeholder"
mkdir -p html_pages
cat > html_pages/xterm_addon_page.h << 'EOF'
#ifndef XTERM_ADDON_PAGE_H
#define XTERM_ADDON_PAGE_H
static const char *xterm_addon_fit_js = "";
#endif /* XTERM_ADDON_PAGE_H */
EOF
fi
# Check if we need to process an HTML template
if [ $# -eq 1 ] && [[ "$1" == *.html ]]; then
echo "Processing HTML template: $1"
BASENAME=$(basename "$1" .html)
HEADER_FILE="html_pages/${BASENAME}_page.h"
mkdir -p html_pages
# Generate C header with HTML content
cat > "$HEADER_FILE" << EOF
/**
* ${BASENAME} page HTML template for wssshd
*
* 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/>.
*/
#ifndef ${BASENAME^^}_PAGE_H
#define ${BASENAME^^}_PAGE_H
// ${BASENAME} page HTML template
static const char *${BASENAME}_page_html =
EOF
# Process the HTML file, escape quotes and replace {{ variables }} with %s
sed 's/\\/\\\\/g; s/"/\\"/g; s/^/"/; s/$/\\n"/' "$1" | \
sed 's/{{[^}]*}}/%s/g' >> "$HEADER_FILE"
# Close the string and header
cat >> "$HEADER_FILE" << EOF
;
#endif /* ${BASENAME^^}_PAGE_H */
EOF
echo "HTML template processed: $HEADER_FILE"
fi
# Process all HTML templates in templates/ directory
for template in templates/*.html; do
if [ -f "$template" ]; then
BASENAME=$(basename "$template" .html)
echo "Processing HTML template: $template"
HEADER_FILE="html_pages/${BASENAME}_page.h"
mkdir -p html_pages
# Generate C header with HTML content
cat > "$HEADER_FILE" << EOF
/**
* ${BASENAME} page HTML template for wssshd
*
* 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/>.
*/
#ifndef ${BASENAME^^}_PAGE_H
#define ${BASENAME^^}_PAGE_H
// ${BASENAME} page HTML template
static const char *${BASENAME}_page_html =
EOF
# Process the HTML file, escape quotes and backslashes
sed 's/\\/\\\\/g; s/"/\\"/g; s/^/"/; s/$/\\n"/' "$template" >> "$HEADER_FILE"
# Close the string and header
cat >> "$HEADER_FILE" << EOF
;
#endif /* ${BASENAME^^}_PAGE_H */
EOF
echo "HTML template processed: $HEADER_FILE"
fi
done
echo "Assets embedded successfully" echo "Assets embedded successfully"
\ No newline at end of file
/**
* Index/Dashboard page HTML template for wssshd
*
* 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/>.
*/
#ifndef INDEX_PAGE_H
#define INDEX_PAGE_H
// Index page HTML template with placeholders:
// %s - username
// %s - client_list HTML
// %s - admin_actions HTML
// %d - websocket port
// %s - domain
// %zu - client count
static const char *index_page_html =
"<!DOCTYPE html>"
"<html lang=\"en\">"
"<head>"
"<meta charset=\"UTF-8\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<title>Dashboard - WebSocket SSH Daemon</title>"
"<link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\">"
"<link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" rel=\"stylesheet\">"
"</head>"
"<body>"
"<nav class=\"navbar navbar-expand-lg navbar-dark bg-primary\">"
"<div class=\"container\">"
"<a class=\"navbar-brand\" href=\"/\">"
"<i class=\"fas fa-terminal\"></i> WebSocket SSH Daemon</a>"
"<div class=\"navbar-nav ms-auto\">"
"<span class=\"navbar-text me-3\">Welcome, %s</span>"
"<a class=\"nav-link\" href=\"/logout\">Logout</a>"
"</div></div></nav>"
"<div class=\"container mt-4\">"
"<div class=\"row\">"
"<div class=\"col-md-8\">"
"<div class=\"card\">"
"<div class=\"card-header\">"
"<h3 class=\"card-title mb-0\">"
"<i class=\"fas fa-server\"></i> Connected Clients</h3></div>"
"<div class=\"card-body\"><div class=\"row\">%s</div></div></div></div>"
"<div class=\"col-md-4\">"
"<div class=\"card\">"
"<div class=\"card-header\">"
"<h3 class=\"card-title mb-0\">"
"<i class=\"fas fa-cogs\"></i> Quick Actions</h3></div>"
"<div class=\"card-body\">%s"
"<button class=\"btn btn-outline-secondary btn-sm w-100\" onclick=\"location.reload()\">"
"<i class=\"fas fa-sync\"></i> Refresh Status</button></div></div>"
"<div class=\"card mt-3\">"
"<div class=\"card-header\">"
"<h3 class=\"card-title mb-0\">"
"<i class=\"fas fa-info-circle\"></i> System Info</h3></div>"
"<div class=\"card-body\">"
"<p class=\"mb-1\"><strong>WebSocket Port:</strong> <span id=\"websocket-port\">%d</span></p>"
"<p class=\"mb-1\"><strong>Domain:</strong> <span id=\"domain\">%s</span></p>"
"<p class=\"mb-0\"><strong>Connected Clients:</strong> <span id=\"client-count\">%zu</span></p>"
"</div></div></div></div></div>"
"<script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js\"></script>"
"</body></html>";
#endif /* INDEX_PAGE_H */
\ No newline at end of file
/**
* Login page HTML template for wssshd
*
* 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/>.
*/
#ifndef LOGIN_PAGE_H
#define LOGIN_PAGE_H
// Login page HTML template
static const char *login_page_html =
"<!DOCTYPE html>"
"<html lang=\"en\">"
"<head>"
" <meta charset=\"UTF-8\">"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
" <title>Login - 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-5\">"
" <div class=\"row justify-content-center\">"
" <div class=\"col-md-6\">"
" <div class=\"card\">"
" <div class=\"card-header\">"
" <h3 class=\"card-title mb-0\"><i class=\"fas fa-sign-in-alt\"></i> Login</h3>"
" </div>"
" <div class=\"card-body\">"
" <form method=\"post\">"
" <div class=\"mb-3\">"
" <label for=\"username\" class=\"form-label\">Username</label>"
" <input type=\"text\" class=\"form-control\" id=\"username\" name=\"username\" required>"
" </div>"
" <div class=\"mb-3\">"
" <label for=\"password\" class=\"form-label\">Password</label>"
" <input type=\"password\" class=\"form-control\" id=\"password\" name=\"password\" required>"
" </div>"
" <button type=\"submit\" class=\"btn btn-primary\">"
" <i class=\"fas fa-sign-in-alt\"></i> Login"
" </button>"
" </form>"
" <div class=\"mt-3\">"
" <small class=\"text-muted\">"
" Default credentials: admin / admin123"
" </small>"
" </div>"
" </div>"
" </div>"
" </div>"
" </div>"
" </div>"
" <script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js\"></script>"
"</body>"
"</html>";
#endif /* LOGIN_PAGE_H */
\ No newline at end of file
/**
* Terminal page HTML template for wssshd
*
* 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/>.
*/
#ifndef TERMINAL_PAGE_H
#define TERMINAL_PAGE_H
// Terminal page HTML template with placeholders:
// %s - client_id (appears multiple times)
static const char *terminal_page_html =
"<!DOCTYPE html>"
"<html lang=\"en\">"
"<head>"
"<meta charset=\"UTF-8\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"
"<title>Terminal - %s</title>"
"<link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\">"
"<link rel=\"stylesheet\" href=\"https://unpkg.com/xterm@5.3.0/css/xterm.css\">"
"</head>"
"<body>"
"<div class=\"container mt-4\">"
"<div class=\"row\">"
"<div class=\"col-12\">"
"<div class=\"card\">"
"<div class=\"card-header d-flex justify-content-between align-items-center\">"
"<div class=\"d-flex align-items-center\">"
"<a href=\"/\" class=\"btn btn-outline-secondary btn-sm me-3\">"
"<i class=\"fas fa-arrow-left\"></i> Back to Dashboard</a>"
"<h3 class=\"card-title mb-0\">"
"<i class=\"fas fa-terminal\"></i> SSH Terminal - %s</h3></div>"
"<div>"
"<input type=\"text\" id=\"sshUsername\" class=\"form-control form-control-sm d-inline-block w-auto me-2\" placeholder=\"Username\" value=\"root\">"
"<button id=\"connectBtn\" class=\"btn btn-success btn-sm\">"
"<i class=\"fas fa-play\"></i> Connect</button>"
"<button id=\"disconnectBtn\" class=\"btn btn-danger btn-sm\" disabled>"
"<i class=\"fas fa-stop\"></i> Disconnect</button>"
"</div></div>"
"<div class=\"card-body p-2\">"
"<div id=\"terminal\" class=\"terminal-container w-100\"></div>"
"</div></div></div></div></div>"
"<script src=\"https://unpkg.com/xterm@5.3.0/lib/xterm.js\"></script>"
"<script src=\"https://unpkg.com/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js\"></script>"
"<script>"
"let term = null; let fitAddon = null; let connected = false; let requestId = null; let pollInterval = null;"
"document.getElementById('connectBtn').addEventListener('click', connect);"
"document.getElementById('disconnectBtn').addEventListener('click', disconnect);"
"function connect() {"
"const username = document.getElementById('sshUsername').value;"
"if (!username) { alert('Please enter a username'); return; }"
"if (!term) {"
"term = new Terminal({cursorBlink: true, fontSize: 14, theme: {background: '#1e1e1e', foreground: '#f8f8f2'}});"
"term.open(document.getElementById('terminal'));"
"fitAddon = new FitAddon.FitAddon(); term.loadAddon(fitAddon); fitAddon.fit();"
"window.addEventListener('resize', () => { if (fitAddon) fitAddon.fit(); });"
"term.onData(data => {"
"if (!connected || !requestId) return;"
"fetch('/terminal/%s/data', {"
"method: 'POST',"
"headers: {'Content-Type': 'application/x-www-form-urlencoded'},"
"body: 'request_id=' + encodeURIComponent(requestId) + '&data=' + encodeURIComponent(data)"
"});"
"});"
"}"
"term.write('Connecting...\\r\\n');"
"connected = true;"
"document.getElementById('connectBtn').disabled = true;"
"document.getElementById('disconnectBtn').disabled = false;"
"document.getElementById('sshUsername').disabled = true;"
"let cols = 80, rows = 24;"
"if (fitAddon) { const d = fitAddon.proposeDimensions(); cols = d.cols || 80; rows = d.rows || 24; }"
"fetch('/terminal/%s/connect', {"
"method: 'POST',"
"headers: {'Content-Type': 'application/x-www-form-urlencoded'},"
"body: 'username=' + encodeURIComponent(username) + '&cols=' + cols + '&rows=' + rows"
"}).then(r => r.json()).then(data => {"
"if (data.request_id) {"
"requestId = data.request_id;"
"term.write('Connected!\\r\\n');"
"pollInterval = setInterval(pollData, 100);"
"} else {"
"term.write('Error: ' + (data.error || 'Unknown') + '\\r\\n');"
"disconnect();"
"}"
"}).catch(e => { term.write('Connection failed\\r\\n'); disconnect(); });"
"}"
"function disconnect() {"
"connected = false;"
"document.getElementById('connectBtn').disabled = false;"
"document.getElementById('disconnectBtn').disabled = true;"
"document.getElementById('sshUsername').disabled = false;"
"if (pollInterval) { clearInterval(pollInterval); pollInterval = null; }"
"if (requestId) {"
"fetch('/terminal/%s/disconnect', {"
"method: 'POST',"
"headers: {'Content-Type': 'application/x-www-form-urlencoded'},"
"body: 'request_id=' + encodeURIComponent(requestId)"
"});"
"requestId = null;"
"}"
"if (term) term.write('\\r\\nDisconnected.\\r\\n');"
"}"
"function pollData() {"
"if (!requestId) return;"
"fetch('/terminal/%s/data?request_id=' + encodeURIComponent(requestId))"
".then(r => r.text()).then(data => {"
"if (data) term.write(data.replace(/\\n/g, '\\r\\n'));"
"});"
"}"
"</script>"
"</body></html>";
#endif /* TERMINAL_PAGE_H */
\ No newline at end of file
/**
* Users page HTML template for wssshd
*
* 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/>.
*/
#ifndef USERS_PAGE_H
#define USERS_PAGE_H
// Users page HTML template - now generated dynamically
#endif /* USERS_PAGE_H */
\ No newline at end of file
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}WebSocket SSH Daemon{% endblock %}</title> <title>{% block title %}WebSocket SSH Daemon{% endblock %}</title>
<link rel="icon" href="/image.jpg" type="image/x-icon"> <link rel="icon" href="/favicon.ico" type="image/x-icon">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/xterm@5.3.0/css/xterm.css"> <link rel="stylesheet" href="https://unpkg.com/xterm@5.3.0/css/xterm.css">
......
{% extends "base.html" %} <!DOCTYPE html>
<html lang="en">
{% block title %}Dashboard - WebSocket SSH Daemon{% endblock %} <head>
<meta charset="UTF-8">
{% block content %} <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard - WebSocket SSH Daemon</title>
<link rel="icon" href="/favicon.ico" type="image/x-icon">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">
<i class="fas fa-terminal"></i> WebSocket SSH Daemon</a>
<div class="navbar-nav ms-auto">
<span class="navbar-text me-3">%s</span>
<a class="nav-link" href="/logout">Logout</a>
</div></div></nav>
%s
<div class="container mt-4">
<div class="row"> <div class="row">
<div class="col-md-8"> <div class="col-md-8">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3 class="card-title mb-0"> <h3 class="card-title mb-0">
<i class="fas fa-server"></i> Connected Clients <i class="fas fa-server"></i> Registered Clients
</h3> </h3>
</div> </div>
<div class="card-body"> <div class="card-body">
{% if clients %} <div id="client-list">%s</div>
<div class="row">
{% for client in clients %}
<div class="col-md-4 mb-3">
<div class="card client-card h-100">
<div class="card-body text-center">
<i class="fas fa-desktop fa-3x text-success mb-3"></i>
<h5 class="card-title">{{ client }}</h5>
<p class="card-text text-muted">Connected</p>
<a href="{{ url_for('terminal', client_id=client) }}" class="btn btn-primary">
<i class="fas fa-terminal"></i> Connect
</a>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="text-center py-5">
<i class="fas fa-server fa-4x text-muted mb-3"></i>
<h4 class="text-muted">No clients connected</h4>
<p class="text-muted">Clients will appear here when they connect to the daemon.</p>
</div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
...@@ -48,11 +41,7 @@ ...@@ -48,11 +41,7 @@
</h3> </h3>
</div> </div>
<div class="card-body"> <div class="card-body">
{% if current_user.is_admin %} %s
<a href="{{ url_for('users') }}" class="btn btn-outline-primary btn-sm mb-2 w-100">
<i class="fas fa-users"></i> Manage Users
</a>
{% endif %}
<button class="btn btn-outline-secondary btn-sm w-100" onclick="location.reload()"> <button class="btn btn-outline-secondary btn-sm w-100" onclick="location.reload()">
<i class="fas fa-sync"></i> Refresh Status <i class="fas fa-sync"></i> Refresh Status
</button> </button>
...@@ -66,18 +55,28 @@ ...@@ -66,18 +55,28 @@
</h3> </h3>
</div> </div>
<div class="card-body"> <div class="card-body">
<p class="mb-1"><strong>WebSocket Port:</strong> <span id="websocket-port">{{ websocket_port or 'N/A' }}</span></p> <p class="mb-1"><strong>WebSocket Port:</strong> <span id="websocket-port">%d</span></p>
<p class="mb-1"><strong>Domain:</strong> <span id="domain">{{ domain or 'N/A' }}</span></p> <p class="mb-1"><strong>Domain:</strong> <span id="domain">%s</span></p>
<p class="mb-0"><strong>Connected Clients:</strong> <span id="client-count">{{ clients|length }}</span></p> <p class="mb-0"><strong>Connected Clients:</strong> <span id="client-count">%d</span></p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} </div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
{% block scripts %}
<script> <script>
let currentClients = {{ clients|tojson }}; function showNotification(message, type = 'info') {
const notificationArea = document.getElementById('notification-area');
const notification = document.createElement('div');
notification.className = `alert alert-${type} alert-dismissible fade show`;
notification.innerHTML = `${message}<button type="button" class="btn-close" data-bs-dismiss="alert"></button>`;
notificationArea.appendChild(notification);
setTimeout(() => {
if (notification.parentNode) {
notification.remove();
}
}, 5000);
}
function updateClients() { function updateClients() {
fetch('/api/clients') fetch('/api/clients')
...@@ -86,10 +85,39 @@ function updateClients() { ...@@ -86,10 +85,39 @@ function updateClients() {
// Update client count // Update client count
document.getElementById('client-count').textContent = data.count; document.getElementById('client-count').textContent = data.count;
// Check if client list changed // Generate HTML for client list
if (JSON.stringify(data.clients.sort()) !== JSON.stringify(currentClients.sort())) { let clientListHtml = '';
// Reload the page to show updated client list if (data.count === 0) {
location.reload(); clientListHtml = '<div class="text-center py-5"><i class="fas fa-server fa-4x text-muted mb-3"></i><h4 class="text-muted">No clients registered</h4><p class="text-muted">Clients will appear here when they register.</p></div>';
} else {
for (const [clientId, clientData] of Object.entries(data.clients)) {
const statusIcon = clientData.status === 'connected' ? 'fa-desktop text-success' : 'fa-server text-warning';
const statusText = clientData.status === 'connected' ? 'Connected' : 'Registered';
const services = clientData.services;
let actionsHtml = '';
if (services.includes('ssh')) {
actionsHtml += `<a href="/terminal/${clientId}" class="btn btn-primary btn-sm me-1"><i class="fas fa-terminal"></i> SSH</a>`;
}
// Add other services if needed
clientListHtml += `
<div class="col-md-4 mb-3">
<div class="card client-card h-100">
<div class="card-body text-center">
<i class="fas ${statusIcon} fa-3x mb-3"></i>
<h5 class="card-title">${clientId}</h5>
<p class="card-text text-muted">${statusText}</p>
<p class="card-text small">Services: ${services}</p>
<div class="d-flex justify-content-center">${actionsHtml}</div>
</div>
</div>
</div>`;
}
}
// Update client list only if changed
const clientListDiv = document.getElementById('client-list');
if (clientListDiv.innerHTML !== clientListHtml) {
clientListDiv.innerHTML = clientListHtml;
} }
}) })
.catch(error => { .catch(error => {
...@@ -103,4 +131,5 @@ setInterval(updateClients, 5000); ...@@ -103,4 +131,5 @@ setInterval(updateClients, 5000);
// Initial update after 1 second // Initial update after 1 second
setTimeout(updateClients, 1000); setTimeout(updateClients, 1000);
</script> </script>
{% endblock %} </body>
\ No newline at end of file </html>
\ No newline at end of file
{% extends "base.html" %} <!DOCTYPE html>
<html lang="en">
{% block title %}Login - WebSocket SSH Daemon{% endblock %} <head>
<meta charset="UTF-8">
{% block content %} <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - WebSocket SSH Daemon</title>
<link rel="icon" href="/favicon.ico" type="image/x-icon">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">
<i class="fas fa-terminal"></i> WebSocket SSH Daemon</a>
</div></nav>
<div class="container mt-4">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-6"> <div class="col-md-6">
<div class="card"> <div class="card">
...@@ -32,4 +44,7 @@ ...@@ -32,4 +44,7 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock %} </div>
\ No newline at end of file <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
\ No newline at end of file
{% extends "base.html" %} <!DOCTYPE html>
<html lang="en">
{% block title %}Terminal - {{ client_id }}{% endblock %} <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Terminal - %s</title>
<link rel="icon" href="/favicon.ico" type="image/x-icon">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://unpkg.com/xterm@5.3.0/css/xterm.css">
<script src="/xterm.js"></script>
<script src="/xterm-addon-fit.js"></script>
<script>
// Ensure libraries are loaded
function checkLibraries() {
if (typeof Terminal === 'undefined') {
console.error('Terminal not loaded from CDN');
return false;
}
if (typeof FitAddon === 'undefined') {
console.error('FitAddon not loaded from CDN');
return false;
}
console.log('All xterm libraries loaded successfully');
return true;
}
{% block content %} // Check immediately and after a delay
if (!checkLibraries()) {
setTimeout(checkLibraries, 1000);
}
</script>
<style>
.navbar-brand {
font-weight: bold;
}
.client-card {
transition: transform 0.2s;
}
.client-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.terminal-container {
background-color: #1e1e1e;
color: #f8f8f2;
font-family: 'Courier New', monospace;
border-radius: 8px;
height: calc(100vh - 200px);
min-height: 400px;
overflow: hidden;
position: relative;
}
.terminal-input {
background: transparent;
border: none;
color: #f8f8f2;
font-family: 'Courier New', monospace;
width: 100%;
outline: none;
}
.terminal-input:focus {
box-shadow: none;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">
<i class="fas fa-terminal"></i> WebSocket SSH Daemon</a>
<div class="navbar-nav ms-auto">
<span class="navbar-text me-3">SSH Terminal - %s</span>
<a class="nav-link" href="/logout">Logout</a>
</div></div></nav>
<div id="notification-area" class="position-fixed top-0 end-0 p-3" style="z-index: 1050;"></div>
<div class="container mt-4">
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="card"> <div class="card">
<div class="card-header d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<a href="{{ url_for('index') }}" class="btn btn-outline-secondary btn-sm me-3"> <a href="/" class="btn btn-outline-secondary btn-sm me-3">
<i class="fas fa-arrow-left"></i> Back to Dashboard <i class="fas fa-arrow-left"></i> Back to Dashboard
</a> </a>
<h3 class="card-title mb-0"> <h3 class="card-title mb-0">
<i class="fas fa-terminal"></i> SSH Terminal - {{ client_id }} <i class="fas fa-terminal"></i> SSH Terminal - %s
</h3> </h3>
</div> </div>
<div> <div>
...@@ -34,9 +106,9 @@ ...@@ -34,9 +106,9 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock %} </div>
{% block scripts %} <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script> <script>
console.log('Terminal script starting...'); console.log('Terminal script starting...');
console.log('xterm available:', typeof Terminal); console.log('xterm available:', typeof Terminal);
...@@ -109,7 +181,7 @@ function handleFullscreenChange() { ...@@ -109,7 +181,7 @@ function handleFullscreenChange() {
const newCols = newDimensions.cols || 80; const newCols = newDimensions.cols || 80;
const newRows = newDimensions.rows || 24; const newRows = newDimensions.rows || 24;
fetch('/terminal/{{ client_id }}/resize', { fetch('/terminal/%s/xterm/resize', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
...@@ -129,7 +201,12 @@ document.addEventListener('webkitfullscreenchange', handleFullscreenChange); // ...@@ -129,7 +201,12 @@ document.addEventListener('webkitfullscreenchange', handleFullscreenChange); //
document.addEventListener('msfullscreenchange', handleFullscreenChange); // IE11 document.addEventListener('msfullscreenchange', handleFullscreenChange); // IE11
function connect() { function connect() {
console.log('Connect button clicked');
console.log('Connect function called'); console.log('Connect function called');
if (typeof Terminal === 'undefined') {
alert('xterm.js library failed to load. Please check your internet connection or refresh the page.');
return;
}
const username = document.getElementById('sshUsername').value; const username = document.getElementById('sshUsername').value;
console.log('Username value:', username); console.log('Username value:', username);
if (!username) { if (!username) {
...@@ -206,7 +283,7 @@ function connect() { ...@@ -206,7 +283,7 @@ function connect() {
const newCols = newDimensions.cols || 80; const newCols = newDimensions.cols || 80;
const newRows = newDimensions.rows || 24; const newRows = newDimensions.rows || 24;
fetch('/terminal/{{ client_id }}/resize', { fetch('/terminal/%s/xterm/resize', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
...@@ -230,7 +307,7 @@ function connect() { ...@@ -230,7 +307,7 @@ function connect() {
} }
}; };
term.write('Connecting to ' + username + '@{{ client_id }}...\r\n'); term.write('Connecting to ' + username + '@%s...\r\n');
connected = true; connected = true;
document.getElementById('connectBtn').disabled = true; document.getElementById('connectBtn').disabled = true;
...@@ -247,7 +324,7 @@ function connect() { ...@@ -247,7 +324,7 @@ function connect() {
} }
// Send connect request with terminal dimensions // Send connect request with terminal dimensions
const connectUrl = '/terminal/{{ client_id }}/connect'; const connectUrl = '/terminal/%s/xterm/connect';
console.log('Sending connect request to:', connectUrl); console.log('Sending connect request to:', connectUrl);
console.log('Username:', username, 'Cols:', cols, 'Rows:', rows); console.log('Username:', username, 'Cols:', cols, 'Rows:', rows);
...@@ -274,11 +351,13 @@ function connect() { ...@@ -274,11 +351,13 @@ function connect() {
if (data.request_id) { if (data.request_id) {
requestId = data.request_id; requestId = data.request_id;
if (data.command) { if (data.command) {
term.write('Launching: ' + data.command + '\r\n'); console.log('Launching command:', data.command);
} }
term.write('Connected successfully!\r\n$ '); term.write('Connected successfully!\r\n');
// Start polling for data with shorter interval for better responsiveness
pollInterval = setInterval(pollData, 100); setTimeout(() => {
pollInterval = setInterval(pollData, 100);
}, 100);
} else { } else {
term.write('Error: ' + (data.error || 'Unknown error') + '\r\n'); term.write('Error: ' + (data.error || 'Unknown error') + '\r\n');
disconnect(); disconnect();
...@@ -291,13 +370,14 @@ function connect() { ...@@ -291,13 +370,14 @@ function connect() {
}); });
// Handle input - send all keystrokes to server, let SSH handle echo // Handle input - send all keystrokes to server, let SSH handle echo
term.onData(data => { term.onKey(e => {
if (!connected || !requestId) return; if (!connected || !requestId) return;
console.log('Sending input data:', data.length, 'characters'); let data = e.key;
console.log('Sending input data:', data);
// Send all input to server, let SSH handle echo and display // Send to server
fetch('/terminal/{{ client_id }}/data', { fetch('/terminal/%s/xterm/data', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
...@@ -310,6 +390,9 @@ function connect() { ...@@ -310,6 +390,9 @@ function connect() {
}).catch(error => { }).catch(error => {
console.error('Input send error:', error); console.error('Input send error:', error);
}); });
// Prevent local display of input
e.domEvent.preventDefault();
}); });
} }
...@@ -325,7 +408,7 @@ function disconnect() { ...@@ -325,7 +408,7 @@ function disconnect() {
} }
if (requestId) { if (requestId) {
fetch('/terminal/{{ client_id }}/disconnect', { fetch('/terminal/%s/xterm/disconnect', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
...@@ -342,22 +425,44 @@ function disconnect() { ...@@ -342,22 +425,44 @@ function disconnect() {
function pollData() { function pollData() {
if (!requestId) return; if (!requestId) return;
fetch('/terminal/{{ client_id }}/data?request_id=' + encodeURIComponent(requestId)) fetch('/terminal/%s/xterm/data?request_id=' + encodeURIComponent(requestId))
.then(response => { .then(response => {
if (response.status !== 200) { if (response.status !== 200) {
console.log('Poll response status:', response.status); console.log('Poll response status:', response.status);
} }
return response.text(); const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('json')) {
return response.json();
} else {
return response.arrayBuffer();
}
}) })
.then(data => { .then(data => {
if (data) { console.log('Poll data received:', data);
console.log('Received data:', data.length, 'characters'); if (data.ended !== undefined) {
// Let the server handle all echo and display logic console.log('Session ended, reloading page');
term.write(data.replace(/\n/g, '\r\n')); clearInterval(pollInterval);
pollInterval = null;
location.reload();
} else if (data) {
console.log('Received data:', data.byteLength || data.length, 'bytes/characters');
// Write data to terminal
if (data.byteLength !== undefined) {
// Binary data
term.write(new Uint8Array(data));
} else {
// Text data
term.write(data);
}
} }
}) })
.catch(error => { .catch(error => {
console.error('Polling error:', error); console.error('Polling error:', error);
// Stop polling on error
if (pollInterval) {
clearInterval(pollInterval);
pollInterval = null;
}
}); });
} }
...@@ -368,4 +473,5 @@ document.addEventListener('keydown', function(e) { ...@@ -368,4 +473,5 @@ document.addEventListener('keydown', function(e) {
} }
}); });
</script> </script>
{% endblock %} </body>
\ No newline at end of file </html>
This diff is collapsed.
{% extends "base.html" %} <!DOCTYPE html>
<html lang="en">
{% block title %}User Management - WebSocket SSH Daemon{% endblock %} <head>
<meta charset="UTF-8">
{% block content %} <meta name="viewport" content="width=device-width, initial-scale=1.0">
<div class="row"> <title>Users - WebSocket SSH Daemon</title>
<div class="col-12"> <link rel="icon" href="/favicon.ico" type="image/x-icon">
<div class="card"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<div class="card-header d-flex justify-content-between align-items-center"> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<h3 class="card-title mb-0"> </head>
<i class="fas fa-users"></i> User Management <body>
</h3> <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div> <div class="container">
<a href="{{ url_for('index') }}" class="btn btn-outline-secondary btn-sm me-2"> <a class="navbar-brand" href="/">
<i class="fas fa-home"></i> Back to Home <i class="fas fa-terminal"></i> WebSocket SSH Daemon</a>
</a> <div class="navbar-nav ms-auto">
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addUserModal"> <span class="navbar-text me-3">%s</span>
<i class="fas fa-plus"></i> Add User <a class="nav-link" href="/logout">Logout</a>
</button> </div></div></nav>
</div> <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>
<div class="table-responsive"> <div class="card-body">
<table class="table table-striped"> <div class="table-responsive">
<thead> <table class="table table-striped">
<tr> <thead>
<th>Username</th> <tr>
<th>Role</th> <th>Username</th>
<th>Actions</th> <th>Role</th>
</tr> <th>Actions</th>
</thead> </tr>
<tbody> </thead>
{% for user in users %} <tbody>
<tr> %s
<td>{{ user.username }}</td> </tbody>
<td> </table>
{% if user.is_admin %}
<span class="badge bg-danger">Admin</span>
{% else %}
<span class="badge bg-secondary">User</span>
{% endif %}
</td>
<td>
<button class="btn btn-sm btn-outline-primary" onclick="editUser({{ user.id }}, '{{ user.username }}', {{ user.is_admin|lower }})">
<i class="fas fa-edit"></i> Edit
</button>
{% if user.username != current_user.username %}
<button class="btn btn-sm btn-outline-danger" onclick="deleteUser({{ user.id }}, '{{ user.username }}')">
<i class="fas fa-trash"></i> Delete
</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div> </div>
</div> </div>
</div> </div>
...@@ -125,9 +116,7 @@ ...@@ -125,9 +116,7 @@
</div> </div>
</div> </div>
{% endblock %} <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
{% block scripts %}
<script> <script>
function editUser(userId, username, isAdmin) { function editUser(userId, username, isAdmin) {
document.getElementById('editUserId').value = userId; document.getElementById('editUserId').value = userId;
...@@ -141,17 +130,12 @@ function deleteUser(userId, username) { ...@@ -141,17 +130,12 @@ function deleteUser(userId, username) {
if (confirm(`Are you sure you want to delete user "${username}"?`)) { if (confirm(`Are you sure you want to delete user "${username}"?`)) {
fetch(`/delete_user/${userId}`, { fetch(`/delete_user/${userId}`, {
method: 'POST', method: 'POST',
headers: { headers: {'Content-Type': 'application/x-www-form-urlencoded'}
'Content-Type': 'application/x-www-form-urlencoded',
}
}) })
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) location.reload();
location.reload(); else alert('Error: ' + data.error);
} else {
alert('Error: ' + data.error);
}
}); });
} }
} }
...@@ -159,19 +143,13 @@ function deleteUser(userId, username) { ...@@ -159,19 +143,13 @@ function deleteUser(userId, username) {
document.getElementById('addUserForm').addEventListener('submit', function(e) { document.getElementById('addUserForm').addEventListener('submit', function(e) {
e.preventDefault(); e.preventDefault();
const formData = new FormData(this); const formData = new FormData(this);
fetch('/add_user', {method: 'POST', body: formData})
fetch('/add_user', {
method: 'POST',
body: formData
})
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
bootstrap.Modal.getInstance(document.getElementById('addUserModal')).hide(); bootstrap.Modal.getInstance(document.getElementById('addUserModal')).hide();
location.reload(); location.reload();
} else { } else alert('Error: ' + data.error);
alert('Error: ' + data.error);
}
}); });
}); });
...@@ -179,20 +157,15 @@ document.getElementById('editUserForm').addEventListener('submit', function(e) { ...@@ -179,20 +157,15 @@ document.getElementById('editUserForm').addEventListener('submit', function(e) {
e.preventDefault(); e.preventDefault();
const formData = new FormData(this); const formData = new FormData(this);
const userId = document.getElementById('editUserId').value; const userId = document.getElementById('editUserId').value;
fetch(`/edit_user/${userId}`, {method: 'POST', body: formData})
fetch(`/edit_user/${userId}`, {
method: 'POST',
body: formData
})
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
bootstrap.Modal.getInstance(document.getElementById('editUserModal')).hide(); bootstrap.Modal.getInstance(document.getElementById('editUserModal')).hide();
location.reload(); location.reload();
} else { } else alert('Error: ' + data.error);
alert('Error: ' + data.error);
}
}); });
}); });
</script> </script>
{% endblock %} </body>
\ No newline at end of file </html>
\ No newline at end of file
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(self,(()=>(()=>{"use strict";var e={};return(()=>{var t=e;Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core,t=e._renderService.dimensions;if(0===t.css.cell.width||0===t.css.cell.height)return;const r=0===this._terminal.options.scrollback?0:e.viewport.scrollBarWidth,i=window.getComputedStyle(this._terminal.element.parentElement),o=parseInt(i.getPropertyValue("height")),s=Math.max(0,parseInt(i.getPropertyValue("width"))),n=window.getComputedStyle(this._terminal.element),l=o-(parseInt(n.getPropertyValue("padding-top"))+parseInt(n.getPropertyValue("padding-bottom"))),a=s-(parseInt(n.getPropertyValue("padding-right"))+parseInt(n.getPropertyValue("padding-left")))-r;return{cols:Math.max(2,Math.floor(a/t.css.cell.width)),rows:Math.max(1,Math.floor(l/t.css.cell.height))}}}})(),e})()));
//# sourceMappingURL=xterm-addon-fit.js.map
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -32,67 +32,11 @@ ...@@ -32,67 +32,11 @@
#include <pthread.h> #include <pthread.h>
#include "terminal.h" #include "terminal.h"
// Output buffer for reading from PTY
typedef struct {
char *buffer;
size_t size;
size_t used;
pthread_mutex_t mutex;
} output_buffer_t;
static output_buffer_t *output_buffer_create(void) {
output_buffer_t *buf = calloc(1, sizeof(output_buffer_t));
if (!buf) return NULL;
buf->size = 4096;
buf->buffer = malloc(buf->size);
if (!buf->buffer) {
free(buf);
return NULL;
}
pthread_mutex_init(&buf->mutex, NULL);
return buf;
}
static void output_buffer_free(output_buffer_t *buf) {
if (!buf) return;
pthread_mutex_destroy(&buf->mutex);
free(buf->buffer);
free(buf);
}
static void output_buffer_append(output_buffer_t *buf, const char *data, size_t len) {
if (!buf || !data) return;
pthread_mutex_lock(&buf->mutex);
if (buf->used + len > buf->size) {
size_t new_size = buf->size * 2;
while (new_size < buf->used + len) {
new_size *= 2;
}
char *new_buf = realloc(buf->buffer, new_size);
if (!new_buf) {
pthread_mutex_unlock(&buf->mutex);
return;
}
buf->buffer = new_buf;
buf->size = new_size;
}
memcpy(buf->buffer + buf->used, data, len);
buf->used += len;
pthread_mutex_unlock(&buf->mutex);
}
// Thread function to read PTY output // Thread function to read PTY output
static void *read_pty_output(void *arg) { static void *read_pty_output(void *arg) {
terminal_session_t *session = (terminal_session_t *)arg; terminal_session_t *session = (terminal_session_t *)arg;
output_buffer_t *output_buf = output_buffer_create();
if (!output_buf) return NULL;
char buffer[1024]; char buffer[1024];
fd_set readfds; fd_set readfds;
...@@ -113,13 +57,29 @@ static void *read_pty_output(void *arg) { ...@@ -113,13 +57,29 @@ static void *read_pty_output(void *arg) {
ssize_t bytes_read = read(session->master_fd, buffer, sizeof(buffer)); ssize_t bytes_read = read(session->master_fd, buffer, sizeof(buffer));
if (bytes_read <= 0) break; if (bytes_read <= 0) break;
output_buffer_append(output_buf, buffer, bytes_read); // Append to session output buffer
pthread_mutex_lock(&session->output_mutex);
if (session->output_used + bytes_read > session->output_size) {
size_t new_size = session->output_size * 2;
while (new_size < session->output_used + bytes_read) {
new_size *= 2;
}
char *new_buf = realloc(session->output_buffer, new_size);
if (new_buf) {
session->output_buffer = new_buf;
session->output_size = new_size;
} else {
// Out of memory, skip this data
pthread_mutex_unlock(&session->output_mutex);
continue;
}
}
memcpy(session->output_buffer + session->output_used, buffer, bytes_read);
session->output_used += bytes_read;
pthread_mutex_unlock(&session->output_mutex);
} }
} }
// Store the output buffer in the session (simplified - in real implementation
// we'd need a way to access this from the session)
output_buffer_free(output_buf);
return NULL; return NULL;
} }
...@@ -149,6 +109,18 @@ terminal_session_t *terminal_create_session(const wssshd_config_t *config, const ...@@ -149,6 +109,18 @@ terminal_session_t *terminal_create_session(const wssshd_config_t *config, const
return NULL; return NULL;
} }
// Initialize output buffer
session->output_buffer = malloc(4096);
if (!session->output_buffer) {
close(master_fd);
close(slave_fd);
free(session);
return NULL;
}
session->output_size = 4096;
session->output_used = 0;
pthread_mutex_init(&session->output_mutex, NULL);
session->master_fd = master_fd; session->master_fd = master_fd;
// Set terminal size (default 80x24) // Set terminal size (default 80x24)
...@@ -157,8 +129,17 @@ terminal_session_t *terminal_create_session(const wssshd_config_t *config, const ...@@ -157,8 +129,17 @@ terminal_session_t *terminal_create_session(const wssshd_config_t *config, const
// Build command // Build command
snprintf(session->command, sizeof(session->command), snprintf(session->command, sizeof(session->command),
"sh -c 'stty echo && wsssh -p %d %s@%s.%s'", "sh -c 'stty echo && wsssh -p %d %s@%s.%s'",
config->port, username, client_id, config->domain); config->port, username, client_id, config->domain);
// Create pipe to communicate grandchild PID
int pipefd[2];
if (pipe(pipefd) < 0) {
close(master_fd);
close(slave_fd);
free(session);
return NULL;
}
// Fork and execute // Fork and execute
pid_t pid = fork(); pid_t pid = fork();
...@@ -170,17 +151,20 @@ terminal_session_t *terminal_create_session(const wssshd_config_t *config, const ...@@ -170,17 +151,20 @@ terminal_session_t *terminal_create_session(const wssshd_config_t *config, const
} }
if (pid == 0) { // Child process if (pid == 0) { // Child process
// Set up controlling terminal // Create new session
setsid(); setsid();
// Set up controlling terminal
if (ioctl(slave_fd, TIOCSCTTY, 0) < 0) { if (ioctl(slave_fd, TIOCSCTTY, 0) < 0) {
// Some systems don't support TIOCSCTTY // Some systems don't support TIOCSCTTY, try alternative
// Redirect stdio to PTY anyway
} }
// Set raw mode // Set raw mode on the slave PTY
struct termios term; struct termios term;
tcgetattr(0, &term); tcgetattr(slave_fd, &term);
cfmakeraw(&term); cfmakeraw(&term);
tcsetattr(0, TCSANOW, &term); tcsetattr(slave_fd, TCSANOW, &term);
// Redirect stdin/stdout/stderr to slave PTY // Redirect stdin/stdout/stderr to slave PTY
dup2(slave_fd, 0); dup2(slave_fd, 0);
...@@ -201,8 +185,8 @@ terminal_session_t *terminal_create_session(const wssshd_config_t *config, const ...@@ -201,8 +185,8 @@ terminal_session_t *terminal_create_session(const wssshd_config_t *config, const
} }
// Parent process // Parent process
session->proc_pid = pid;
close(slave_fd); close(slave_fd);
session->proc_pid = pid;
// Start output reading thread // Start output reading thread
pthread_t thread; pthread_t thread;
...@@ -219,6 +203,11 @@ void terminal_free_session(terminal_session_t *session) { ...@@ -219,6 +203,11 @@ void terminal_free_session(terminal_session_t *session) {
close(session->master_fd); close(session->master_fd);
} }
if (session->output_buffer) {
free(session->output_buffer);
}
pthread_mutex_destroy(&session->output_mutex);
free(session); free(session);
} }
...@@ -234,12 +223,35 @@ bool terminal_send_data(terminal_session_t *session, const char *data) { ...@@ -234,12 +223,35 @@ bool terminal_send_data(terminal_session_t *session, const char *data) {
return written > 0; return written > 0;
} }
char *terminal_get_output(terminal_session_t *session) { char *terminal_get_output(terminal_session_t *session, size_t *len) {
if (!session) return NULL; if (!session) {
*len = 0;
return NULL;
}
// In a real implementation, we'd have access to the output buffer pthread_mutex_lock(&session->output_mutex);
// For now, return NULL (output reading would be handled differently) if (session->output_used == 0) {
return NULL; pthread_mutex_unlock(&session->output_mutex);
*len = 0;
return NULL;
}
// Return the buffer and length
*len = session->output_used;
char *output = session->output_buffer;
session->output_buffer = malloc(4096);
if (session->output_buffer) {
session->output_size = 4096;
} else {
// Out of memory
*len = 0;
pthread_mutex_unlock(&session->output_mutex);
return NULL;
}
session->output_used = 0;
pthread_mutex_unlock(&session->output_mutex);
return output;
} }
bool terminal_disconnect(terminal_session_t *session) { bool terminal_disconnect(terminal_session_t *session) {
......
...@@ -32,13 +32,17 @@ typedef struct { ...@@ -32,13 +32,17 @@ typedef struct {
pid_t proc_pid; pid_t proc_pid;
int master_fd; int master_fd;
char command[1024]; char command[1024];
char *output_buffer;
size_t output_size;
size_t output_used;
pthread_mutex_t output_mutex;
} terminal_session_t; } terminal_session_t;
// Function declarations // Function declarations
terminal_session_t *terminal_create_session(const wssshd_config_t *config, const char *username, const char *client_id); terminal_session_t *terminal_create_session(const wssshd_config_t *config, const char *username, const char *client_id);
void terminal_free_session(terminal_session_t *session); void terminal_free_session(terminal_session_t *session);
bool terminal_send_data(terminal_session_t *session, const char *data); bool terminal_send_data(terminal_session_t *session, const char *data);
char *terminal_get_output(terminal_session_t *session); char *terminal_get_output(terminal_session_t *session, size_t *len);
bool terminal_disconnect(terminal_session_t *session); bool terminal_disconnect(terminal_session_t *session);
bool terminal_resize(terminal_session_t *session, int cols, int rows); bool terminal_resize(terminal_session_t *session, int cols, int rows);
bool terminal_is_running(terminal_session_t *session); bool terminal_is_running(terminal_session_t *session);
......
This diff is collapsed.
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <signal.h> #include <signal.h>
#include <setjmp.h> #include <setjmp.h>
#include <ctype.h>
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include <openssl/err.h> #include <openssl/err.h>
#include <stdarg.h> #include <stdarg.h>
...@@ -134,7 +135,7 @@ client_t *websocket_find_client(wssshd_state_t *state, const char *client_id) { ...@@ -134,7 +135,7 @@ client_t *websocket_find_client(wssshd_state_t *state, const char *client_id) {
return NULL; return NULL;
} }
client_t *websocket_add_client(wssshd_state_t *state, const char *client_id, void *websocket) { client_t *websocket_add_client(wssshd_state_t *state, const char *client_id, void *websocket, const char *services) {
pthread_mutex_lock(&state->client_mutex); pthread_mutex_lock(&state->client_mutex);
// Check if client already exists // Check if client already exists
for (size_t i = 0; i < state->clients_count; i++) { for (size_t i = 0; i < state->clients_count; i++) {
...@@ -142,6 +143,11 @@ client_t *websocket_add_client(wssshd_state_t *state, const char *client_id, voi ...@@ -142,6 +143,11 @@ client_t *websocket_add_client(wssshd_state_t *state, const char *client_id, voi
state->clients[i].active = true; state->clients[i].active = true;
state->clients[i].last_seen = time(NULL); state->clients[i].last_seen = time(NULL);
state->clients[i].websocket = websocket; state->clients[i].websocket = websocket;
if (services) {
strncpy(state->clients[i].services, services, sizeof(state->clients[i].services) - 1);
} else if (strlen(state->clients[i].services) == 0) {
strcpy(state->clients[i].services, "ssh");
}
pthread_mutex_unlock(&state->client_mutex); pthread_mutex_unlock(&state->client_mutex);
return &state->clients[i]; return &state->clients[i];
} }
...@@ -166,6 +172,12 @@ client_t *websocket_add_client(wssshd_state_t *state, const char *client_id, voi ...@@ -166,6 +172,12 @@ client_t *websocket_add_client(wssshd_state_t *state, const char *client_id, voi
client->active = true; client->active = true;
strcpy(client->tunnel, "any"); strcpy(client->tunnel, "any");
strcpy(client->tunnel_control, "any"); strcpy(client->tunnel_control, "any");
strcpy(client->services, "ssh"); // Default service
if (services) {
strncpy(client->services, services, sizeof(client->services) - 1);
} else {
strcpy(client->services, "ssh"); // Default to ssh if no services specified
}
pthread_mutex_unlock(&state->client_mutex); pthread_mutex_unlock(&state->client_mutex);
return client; return client;
...@@ -512,6 +524,36 @@ int websocket_handle_message(wssshd_state_t *state, ws_connection_t *conn __attr ...@@ -512,6 +524,36 @@ int websocket_handle_message(wssshd_state_t *state, ws_connection_t *conn __attr
if (state->debug) printf("[DEBUG - %s -> wssshd] Password start not found\n", direction); if (state->debug) printf("[DEBUG - %s -> wssshd] Password start not found\n", direction);
} }
// Extract services (optional, defaults to "ssh")
char *services = NULL;
char *services_start = strstr(msg_copy, "\"services\":\"");
if (services_start) {
services_start += strlen("\"services\":\"");
char *services_end = strchr(services_start, '"');
if (services_end && services_end > services_start && services_end < msg_copy + MSG_BUFFER_SIZE) {
size_t services_len = services_end - services_start;
if (services_len > 0 && services_len < 256) { // Reasonable limit for services
char *services_copy = malloc(services_len + 1);
if (services_copy) {
memcpy(services_copy, services_start, services_len);
services_copy[services_len] = '\0';
services = services_copy;
// Convert services to lowercase for consistency
for (char *p = services; *p; p++) {
*p = tolower(*p);
}
if (state->debug) printf("[DEBUG - %s -> wssshd] Extracted services: '%s'\n", direction, services);
}
} else {
if (state->debug) printf("[DEBUG - %s -> wssshd] Services length invalid: %zu\n", direction, services_len);
}
} else {
if (state->debug) printf("[DEBUG - %s -> wssshd] Services end quote not found or invalid\n", direction);
}
} else {
if (state->debug) printf("[DEBUG - %s -> wssshd] Services not specified, using default\n", direction);
}
if (state->debug) { if (state->debug) {
printf("[DEBUG - %s -> wssshd] Password check: received='%s', expected='%s'\n", printf("[DEBUG - %s -> wssshd] Password check: received='%s', expected='%s'\n",
direction, direction,
...@@ -520,7 +562,7 @@ int websocket_handle_message(wssshd_state_t *state, ws_connection_t *conn __attr ...@@ -520,7 +562,7 @@ int websocket_handle_message(wssshd_state_t *state, ws_connection_t *conn __attr
} }
if (client_id && password && strcmp(password, state->server_password) == 0) { if (client_id && password && strcmp(password, state->server_password) == 0) {
client_t *client = websocket_add_client(state, client_id, (void *)conn); client_t *client = websocket_add_client(state, client_id, (void *)conn, services);
if (client) { if (client) {
// Send registration success // Send registration success
char response[512]; char response[512];
...@@ -537,17 +579,18 @@ int websocket_handle_message(wssshd_state_t *state, ws_connection_t *conn __attr ...@@ -537,17 +579,18 @@ int websocket_handle_message(wssshd_state_t *state, ws_connection_t *conn __attr
ws_send_frame(conn, WS_OPCODE_TEXT, response, strlen(response)); ws_send_frame(conn, WS_OPCODE_TEXT, response, strlen(response));
if (state->debug) { if (state->debug) {
printf("[DEBUG - %s -> wssshd] Client %s registration failed: client_id=%s, password=%s, server_password=%s\n", printf("[DEBUG - %s -> wssshd] Client %s registration failed: client_id=%s, password=%s, server_password=%s\n",
direction, direction,
client_id ? client_id : "unknown", client_id ? client_id : "unknown",
client_id ? client_id : "(null)", client_id ? client_id : "(null)",
password ? password : "(null)", password ? password : "(null)",
state->server_password ? state->server_password : "(null)"); state->server_password ? state->server_password : "(null)");
} }
} }
// Free allocated strings // Free allocated strings
if (client_id) free(client_id); if (client_id) free(client_id);
if (password) free(password); if (password) free(password);
if (services) free(services);
} else if (strstr(msg_copy, "\"type\":\"tunnel_request\"") || strstr(msg_copy, "\"type\": \"tunnel_request\"")) { } else if (strstr(msg_copy, "\"type\":\"tunnel_request\"") || strstr(msg_copy, "\"type\": \"tunnel_request\"")) {
if (state->debug) { if (state->debug) {
printf("[DEBUG - %s -> wssshd] Processing tunnel request\n", direction); printf("[DEBUG - %s -> wssshd] Processing tunnel request\n", direction);
...@@ -1505,15 +1548,42 @@ void *status_thread(void *arg) { ...@@ -1505,15 +1548,42 @@ void *status_thread(void *arg) {
int hours = (uptime % 86400) / 3600; int hours = (uptime % 86400) / 3600;
int minutes = (uptime % 3600) / 60; int minutes = (uptime % 3600) / 60;
int seconds = uptime % 60; int seconds = uptime % 60;
// Count active clients and build client list with services
pthread_mutex_lock(&state->client_mutex);
int client_count = 0;
char client_list[2048] = "";
for (size_t i = 0; i < state->clients_count; i++) {
if (state->clients[i].active) {
client_count++;
if (strlen(client_list) > 0) strncat(client_list, ", ", sizeof(client_list) - strlen(client_list) - 1);
size_t remaining = sizeof(client_list) - strlen(client_list) - 1;
if (remaining > 0) {
strncat(client_list, state->clients[i].client_id, remaining);
remaining = sizeof(client_list) - strlen(client_list) - 1;
if (remaining > 2) { // space for (services)
strncat(client_list, "(", remaining);
remaining = sizeof(client_list) - strlen(client_list) - 1;
strncat(client_list, state->clients[i].services, remaining);
remaining = sizeof(client_list) - strlen(client_list) - 1;
if (remaining > 1) {
strncat(client_list, ")", remaining);
}
}
}
}
}
pthread_mutex_unlock(&state->client_mutex);
pthread_mutex_lock(&state->tunnel_mutex); pthread_mutex_lock(&state->tunnel_mutex);
int tunnel_count = state->tunnels_count; int tunnel_count = state->tunnels_count;
if (days > 0) { if (days > 0) {
log_message("STATUS", "Uptime: %d-%02d:%02d:%02d | Active tunnels: %d\n", log_message("STATUS", "Uptime: %d-%02d:%02d:%02d | Registered wssshc: %d (%s) | Active tunnels: %d\n",
days, hours, minutes, seconds, tunnel_count); days, hours, minutes, seconds, client_count, client_count > 0 ? client_list : "", tunnel_count);
} else { } else {
log_message("STATUS", "Uptime: %02d:%02d:%02d | Active tunnels: %d\n", log_message("STATUS", "Uptime: %02d:%02d:%02d | Registered wssshc: %d (%s) | Active tunnels: %d\n",
hours, minutes, seconds, tunnel_count); hours, minutes, seconds, client_count, client_count > 0 ? client_list : "", tunnel_count);
} }
// List active tunnels with details // List active tunnels with details
......
...@@ -37,6 +37,7 @@ typedef struct { ...@@ -37,6 +37,7 @@ typedef struct {
char tunnel[16]; char tunnel[16];
char tunnel_control[16]; char tunnel_control[16];
char wssshd_private_ip[64]; char wssshd_private_ip[64];
char services[256]; // Comma-separated list of services (e.g., "ssh,rdp,vnc")
} client_t; } client_t;
// Terminal session (defined in terminal.h) // Terminal session (defined in terminal.h)
...@@ -70,7 +71,7 @@ void websocket_free_state(wssshd_state_t *state); ...@@ -70,7 +71,7 @@ void websocket_free_state(wssshd_state_t *state);
// Client management // Client management
client_t *websocket_find_client(wssshd_state_t *state, const char *client_id); client_t *websocket_find_client(wssshd_state_t *state, const char *client_id);
client_t *websocket_add_client(wssshd_state_t *state, const char *client_id, void *websocket); client_t *websocket_add_client(wssshd_state_t *state, const char *client_id, void *websocket, const char *services);
void websocket_remove_client(wssshd_state_t *state, const char *client_id); void websocket_remove_client(wssshd_state_t *state, const char *client_id);
void websocket_update_client_activity(wssshd_state_t *state, const char *client_id); void websocket_update_client_activity(wssshd_state_t *state, const char *client_id);
......
unsigned char xterm_addon_fit_js[] = {
0x21, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x28, 0x65, 0x2c,
0x74, 0x29, 0x7b, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x3d,
0x3d, 0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x65, 0x78, 0x70, 0x6f,
0x72, 0x74, 0x73, 0x26, 0x26, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74,
0x22, 0x3d, 0x3d, 0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x6d, 0x6f,
0x64, 0x75, 0x6c, 0x65, 0x3f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e,
0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x3d, 0x74, 0x28, 0x29, 0x3a,
0x22, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3d, 0x3d,
0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e,
0x65, 0x26, 0x26, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x2e, 0x61, 0x6d,
0x64, 0x3f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x28, 0x5b, 0x5d, 0x2c,
0x74, 0x29, 0x3a, 0x22, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x22, 0x3d,
0x3d, 0x74, 0x79, 0x70, 0x65, 0x6f, 0x66, 0x20, 0x65, 0x78, 0x70, 0x6f,
0x72, 0x74, 0x73, 0x3f, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e,
0x46, 0x69, 0x74, 0x41, 0x64, 0x64, 0x6f, 0x6e, 0x3d, 0x74, 0x28, 0x29,
0x3a, 0x65, 0x2e, 0x46, 0x69, 0x74, 0x41, 0x64, 0x64, 0x6f, 0x6e, 0x3d,
0x74, 0x28, 0x29, 0x7d, 0x28, 0x73, 0x65, 0x6c, 0x66, 0x2c, 0x28, 0x28,
0x29, 0x3d, 0x3e, 0x28, 0x28, 0x29, 0x3d, 0x3e, 0x7b, 0x22, 0x75, 0x73,
0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x22, 0x3b, 0x76, 0x61,
0x72, 0x20, 0x65, 0x3d, 0x7b, 0x7d, 0x3b, 0x72, 0x65, 0x74, 0x75, 0x72,
0x6e, 0x28, 0x28, 0x29, 0x3d, 0x3e, 0x7b, 0x76, 0x61, 0x72, 0x20, 0x74,
0x3d, 0x65, 0x3b, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x64, 0x65,
0x66, 0x69, 0x6e, 0x65, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79,
0x28, 0x74, 0x2c, 0x22, 0x5f, 0x5f, 0x65, 0x73, 0x4d, 0x6f, 0x64, 0x75,
0x6c, 0x65, 0x22, 0x2c, 0x7b, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x21,
0x30, 0x7d, 0x29, 0x2c, 0x74, 0x2e, 0x46, 0x69, 0x74, 0x41, 0x64, 0x64,
0x6f, 0x6e, 0x3d, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x30, 0x2c, 0x74, 0x2e,
0x46, 0x69, 0x74, 0x41, 0x64, 0x64, 0x6f, 0x6e, 0x3d, 0x63, 0x6c, 0x61,
0x73, 0x73, 0x7b, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x28,
0x65, 0x29, 0x7b, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x5f, 0x74, 0x65, 0x72,
0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x3d, 0x65, 0x7d, 0x64, 0x69, 0x73, 0x70,
0x6f, 0x73, 0x65, 0x28, 0x29, 0x7b, 0x7d, 0x66, 0x69, 0x74, 0x28, 0x29,
0x7b, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20, 0x65, 0x3d, 0x74, 0x68, 0x69,
0x73, 0x2e, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x73, 0x65, 0x44, 0x69, 0x6d,
0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x28, 0x29, 0x3b, 0x69, 0x66,
0x28, 0x21, 0x65, 0x7c, 0x7c, 0x21, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x5f,
0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x7c, 0x7c, 0x69, 0x73,
0x4e, 0x61, 0x4e, 0x28, 0x65, 0x2e, 0x63, 0x6f, 0x6c, 0x73, 0x29, 0x7c,
0x7c, 0x69, 0x73, 0x4e, 0x61, 0x4e, 0x28, 0x65, 0x2e, 0x72, 0x6f, 0x77,
0x73, 0x29, 0x29, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x3b, 0x63, 0x6f,
0x6e, 0x73, 0x74, 0x20, 0x74, 0x3d, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x5f,
0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2e, 0x5f, 0x63, 0x6f,
0x72, 0x65, 0x3b, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x5f, 0x74, 0x65, 0x72,
0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2e, 0x72, 0x6f, 0x77, 0x73, 0x3d, 0x3d,
0x3d, 0x65, 0x2e, 0x72, 0x6f, 0x77, 0x73, 0x26, 0x26, 0x74, 0x68, 0x69,
0x73, 0x2e, 0x5f, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2e,
0x63, 0x6f, 0x6c, 0x73, 0x3d, 0x3d, 0x3d, 0x65, 0x2e, 0x63, 0x6f, 0x6c,
0x73, 0x7c, 0x7c, 0x28, 0x74, 0x2e, 0x5f, 0x72, 0x65, 0x6e, 0x64, 0x65,
0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x63, 0x6c, 0x65,
0x61, 0x72, 0x28, 0x29, 0x2c, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x5f, 0x74,
0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2e, 0x72, 0x65, 0x73, 0x69,
0x7a, 0x65, 0x28, 0x65, 0x2e, 0x63, 0x6f, 0x6c, 0x73, 0x2c, 0x65, 0x2e,
0x72, 0x6f, 0x77, 0x73, 0x29, 0x29, 0x7d, 0x70, 0x72, 0x6f, 0x70, 0x6f,
0x73, 0x65, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73,
0x28, 0x29, 0x7b, 0x69, 0x66, 0x28, 0x21, 0x74, 0x68, 0x69, 0x73, 0x2e,
0x5f, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x29, 0x72, 0x65,
0x74, 0x75, 0x72, 0x6e, 0x3b, 0x69, 0x66, 0x28, 0x21, 0x74, 0x68, 0x69,
0x73, 0x2e, 0x5f, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2e,
0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x7c, 0x7c, 0x21, 0x74, 0x68,
0x69, 0x73, 0x2e, 0x5f, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c,
0x2e, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x61, 0x72,
0x65, 0x6e, 0x74, 0x45, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x29, 0x72,
0x65, 0x74, 0x75, 0x72, 0x6e, 0x3b, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x20,
0x65, 0x3d, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x5f, 0x74, 0x65, 0x72, 0x6d,
0x69, 0x6e, 0x61, 0x6c, 0x2e, 0x5f, 0x63, 0x6f, 0x72, 0x65, 0x2c, 0x74,
0x3d, 0x65, 0x2e, 0x5f, 0x72, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x53, 0x65,
0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x64, 0x69, 0x6d, 0x65, 0x6e, 0x73,
0x69, 0x6f, 0x6e, 0x73, 0x3b, 0x69, 0x66, 0x28, 0x30, 0x3d, 0x3d, 0x3d,
0x74, 0x2e, 0x63, 0x73, 0x73, 0x2e, 0x63, 0x65, 0x6c, 0x6c, 0x2e, 0x77,
0x69, 0x64, 0x74, 0x68, 0x7c, 0x7c, 0x30, 0x3d, 0x3d, 0x3d, 0x74, 0x2e,
0x63, 0x73, 0x73, 0x2e, 0x63, 0x65, 0x6c, 0x6c, 0x2e, 0x68, 0x65, 0x69,
0x67, 0x68, 0x74, 0x29, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x3b, 0x63,
0x6f, 0x6e, 0x73, 0x74, 0x20, 0x72, 0x3d, 0x30, 0x3d, 0x3d, 0x3d, 0x74,
0x68, 0x69, 0x73, 0x2e, 0x5f, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61,
0x6c, 0x2e, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x73, 0x63,
0x72, 0x6f, 0x6c, 0x6c, 0x62, 0x61, 0x63, 0x6b, 0x3f, 0x30, 0x3a, 0x65,
0x2e, 0x76, 0x69, 0x65, 0x77, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x73, 0x63,
0x72, 0x6f, 0x6c, 0x6c, 0x42, 0x61, 0x72, 0x57, 0x69, 0x64, 0x74, 0x68,
0x2c, 0x69, 0x3d, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x67, 0x65,
0x74, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x64, 0x53, 0x74, 0x79,
0x6c, 0x65, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x5f, 0x74, 0x65, 0x72,
0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2e, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e,
0x74, 0x2e, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x45, 0x6c, 0x65, 0x6d,
0x65, 0x6e, 0x74, 0x29, 0x2c, 0x6f, 0x3d, 0x70, 0x61, 0x72, 0x73, 0x65,
0x49, 0x6e, 0x74, 0x28, 0x69, 0x2e, 0x67, 0x65, 0x74, 0x50, 0x72, 0x6f,
0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x28, 0x22,
0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0x29, 0x29, 0x2c, 0x73, 0x3d,
0x4d, 0x61, 0x74, 0x68, 0x2e, 0x6d, 0x61, 0x78, 0x28, 0x30, 0x2c, 0x70,
0x61, 0x72, 0x73, 0x65, 0x49, 0x6e, 0x74, 0x28, 0x69, 0x2e, 0x67, 0x65,
0x74, 0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c,
0x75, 0x65, 0x28, 0x22, 0x77, 0x69, 0x64, 0x74, 0x68, 0x22, 0x29, 0x29,
0x29, 0x2c, 0x6e, 0x3d, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x67,
0x65, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x64, 0x53, 0x74,
0x79, 0x6c, 0x65, 0x28, 0x74, 0x68, 0x69, 0x73, 0x2e, 0x5f, 0x74, 0x65,
0x72, 0x6d, 0x69, 0x6e, 0x61, 0x6c, 0x2e, 0x65, 0x6c, 0x65, 0x6d, 0x65,
0x6e, 0x74, 0x29, 0x2c, 0x6c, 0x3d, 0x6f, 0x2d, 0x28, 0x70, 0x61, 0x72,
0x73, 0x65, 0x49, 0x6e, 0x74, 0x28, 0x6e, 0x2e, 0x67, 0x65, 0x74, 0x50,
0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65,
0x28, 0x22, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d, 0x74, 0x6f,
0x70, 0x22, 0x29, 0x29, 0x2b, 0x70, 0x61, 0x72, 0x73, 0x65, 0x49, 0x6e,
0x74, 0x28, 0x6e, 0x2e, 0x67, 0x65, 0x74, 0x50, 0x72, 0x6f, 0x70, 0x65,
0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x28, 0x22, 0x70, 0x61,
0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d, 0x62, 0x6f, 0x74, 0x74, 0x6f, 0x6d,
0x22, 0x29, 0x29, 0x29, 0x2c, 0x61, 0x3d, 0x73, 0x2d, 0x28, 0x70, 0x61,
0x72, 0x73, 0x65, 0x49, 0x6e, 0x74, 0x28, 0x6e, 0x2e, 0x67, 0x65, 0x74,
0x50, 0x72, 0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75,
0x65, 0x28, 0x22, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d, 0x72,
0x69, 0x67, 0x68, 0x74, 0x22, 0x29, 0x29, 0x2b, 0x70, 0x61, 0x72, 0x73,
0x65, 0x49, 0x6e, 0x74, 0x28, 0x6e, 0x2e, 0x67, 0x65, 0x74, 0x50, 0x72,
0x6f, 0x70, 0x65, 0x72, 0x74, 0x79, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x28,
0x22, 0x70, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x2d, 0x6c, 0x65, 0x66,
0x74, 0x22, 0x29, 0x29, 0x29, 0x2d, 0x72, 0x3b, 0x72, 0x65, 0x74, 0x75,
0x72, 0x6e, 0x7b, 0x63, 0x6f, 0x6c, 0x73, 0x3a, 0x4d, 0x61, 0x74, 0x68,
0x2e, 0x6d, 0x61, 0x78, 0x28, 0x32, 0x2c, 0x4d, 0x61, 0x74, 0x68, 0x2e,
0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x28, 0x61, 0x2f, 0x74, 0x2e, 0x63, 0x73,
0x73, 0x2e, 0x63, 0x65, 0x6c, 0x6c, 0x2e, 0x77, 0x69, 0x64, 0x74, 0x68,
0x29, 0x29, 0x2c, 0x72, 0x6f, 0x77, 0x73, 0x3a, 0x4d, 0x61, 0x74, 0x68,
0x2e, 0x6d, 0x61, 0x78, 0x28, 0x31, 0x2c, 0x4d, 0x61, 0x74, 0x68, 0x2e,
0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x28, 0x6c, 0x2f, 0x74, 0x2e, 0x63, 0x73,
0x73, 0x2e, 0x63, 0x65, 0x6c, 0x6c, 0x2e, 0x68, 0x65, 0x69, 0x67, 0x68,
0x74, 0x29, 0x29, 0x7d, 0x7d, 0x7d, 0x7d, 0x29, 0x28, 0x29, 0x2c, 0x65,
0x7d, 0x29, 0x28, 0x29, 0x29, 0x29, 0x3b, 0x0a, 0x2f, 0x2f, 0x23, 0x20,
0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e,
0x67, 0x55, 0x52, 0x4c, 0x3d, 0x78, 0x74, 0x65, 0x72, 0x6d, 0x2d, 0x61,
0x64, 0x64, 0x6f, 0x6e, 0x2d, 0x66, 0x69, 0x74, 0x2e, 0x6a, 0x73, 0x2e,
0x6d, 0x61, 0x70
};
unsigned int xterm_addon_fit_js_len = 1503;
This diff is collapsed.
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