Commit 90cc0568 authored by Stefy Lanza (nextime / spora )'s avatar Stefy Lanza (nextime / spora )

Merge branch 'v2web' into v2

parents 2b005518 9667e60f
...@@ -5,6 +5,80 @@ All notable changes to this project will be documented in this file. ...@@ -5,6 +5,80 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.7.0] - 2025-09-21
### Major Changes
- **Complete Architecture Transition**: Migrated from Python/PyInstaller to native C implementation
- Replaced Python wssshd with high-performance C daemon (`wssshd2/`)
- Eliminated Python runtime dependencies for server component
- Maintained full backward compatibility for client tools
- Improved performance and reduced resource usage
### Added
- **SQLite Database Integration**: Persistent user management with secure storage
- Automatic database creation and schema management
- User authentication with password hashing
- Admin user role management
- Security warnings for default credentials
- Database-backed session management
- **Embedded Web Interface**: Self-contained web server with no external dependencies
- HTML/CSS/JavaScript assets embedded in binary
- User management interface with add/edit/delete functionality
- Real-time terminal interface with fullscreen support
- Responsive Bootstrap-based design
- AJAX-powered dynamic content updates
- **Enhanced Security Features**:
- Automatic security warnings for default admin credentials
- Proper session management with configurable timeouts
- Input validation and bounds checking
- Secure password storage with hashing
- Admin role-based access control
- **Professional ASCII Art**: Startup banner with project branding
- **Comprehensive Debug Options**: Database, web, and general debug logging
- **Smart Directory Selection**: Automatic root vs user directory detection
### Changed
- **Build System**: Complete overhaul for C implementation
- Updated build.sh to compile C binaries
- Modified Debian packaging for C dependencies
- Added SQLite3 and SSL library dependencies
- Removed Python/PyInstaller build process
- **Package Architecture**: Updated wsssh-server package for C binary
- Changed from PyInstaller bundle to native C executable
- Updated dependencies to include libsqlite3-0 and libssl3
- Modified package description and metadata
- Updated man pages for C version options
- **Documentation**: Comprehensive updates for C implementation
- Updated README.md with new architecture details
- Modified installation instructions for C build process
- Updated project structure documentation
- Removed Python-specific references
### Removed
- **Python Server Implementation**: Complete removal of Python wssshd
- Deleted wssshd.py entry point
- Removed wsssd/ Python server directory
- Eliminated templates/ directory (now embedded)
- Cleaned up Python-specific build artifacts
### Technical Details
- **Performance Improvements**: Native C implementation with direct system calls
- **Memory Efficiency**: Reduced memory footprint compared to Python runtime
- **Startup Time**: Faster daemon initialization and connection handling
- **Security**: Enhanced with proper bounds checking and input validation
- **Maintainability**: Cleaner C codebase with modular architecture
### Migration Notes
- **Backward Compatibility**: All existing client functionality preserved
- **Configuration**: Existing config files remain compatible
- **API Compatibility**: WebSocket protocol unchanged for client connections
- **Database Migration**: Automatic creation of new SQLite database on first run
## [1.6.7] - 2025-09-21 ## [1.6.7] - 2025-09-21
### Fixed ### Fixed
......
...@@ -15,15 +15,16 @@ ...@@ -15,15 +15,16 @@
## Overview ## Overview
WSSSH is a universal tunneling system that provides secure access to remote machines through various transport protocols. The system consists of multiple components implemented in both Python and C, offering high performance and flexibility for different deployment scenarios. WSSSH is a universal tunneling system that provides secure access to remote machines through various transport protocols. The system consists of multiple components implemented in C, offering high performance, minimal dependencies, and enterprise-grade reliability.
### Key Components ### Key Components
#### Server Component #### Server Component
- **wssshd**: Universal tunneling daemon (Python) with web interface - **wssshd2**: High-performance C daemon with embedded SQLite database and web interface
- **WebSocket/Transport Support**: Multiple transport protocol implementations - **WebSocket/Transport Support**: Multiple transport protocol implementations
- **Client Management**: Registration and authentication system - **Client Management**: Registration and authentication system with persistent storage
- **Tunnel Routing**: Intelligent request routing to registered clients - **Tunnel Routing**: Intelligent request routing to registered clients
- **Embedded Web UI**: Self-contained web interface with user management
#### C Implementation (Primary) #### C Implementation (Primary)
- **wssshc**: Lightweight C client for registration (OpenSSL-based) - **wssshc**: Lightweight C client for registration (OpenSSL-based)
...@@ -566,20 +567,23 @@ Enable debug output for detailed troubleshooting: ...@@ -566,20 +567,23 @@ Enable debug output for detailed troubleshooting:
``` ```
wsssh/ wsssh/
├── wssshd.py # Universal tunneling daemon ├── build.sh # Build script for all components
├── build.sh # Build script
├── clean.sh # Clean script ├── clean.sh # Clean script
├── requirements.txt # Python dependencies ├── wssshd2/ # C server implementation
├── cert.pem # SSL certificate │ ├── main.c # Server main entry point
├── key.pem # SSL private key │ ├── web.c # Embedded web interface with SQLite
├── templates/ # Web interface templates │ ├── config.c # Configuration handling
│ ├── base.html # Base template │ ├── terminal.c # Terminal session management
│ ├── index.html # Dashboard │ ├── websocket.c # WebSocket protocol handling
│ ├── login.html # Authentication │ ├── ssl.c # SSL/TLS encryption
│ ├── terminal.html # HTML5 terminal │ ├── tunnel.c # Tunnel management
│ └── users.html # User management │ ├── assets.c # Embedded web assets
├── wssshtools/ # C implementation │ ├── html_pages/ # Embedded HTML templates
│ ├── wssshc.c # Client registration │ ├── configure.sh # Build configuration
│ ├── Makefile # Build system
│ └── assets.o # Compiled embedded assets
├── wssshtools/ # C client tools
│ ├── wssshc.c # Client registration tool
│ ├── wsssh.c # SSH wrapper │ ├── wsssh.c # SSH wrapper
│ ├── wsscp.c # SCP wrapper │ ├── wsscp.c # SCP wrapper
│ ├── wsssht.c # Tunnel setup tool │ ├── wsssht.c # Tunnel setup tool
...@@ -594,46 +598,50 @@ wsssh/ ...@@ -594,46 +598,50 @@ wsssh/
│ │ └── wssshlib.h/c # Core library │ │ └── wssshlib.h/c # Core library
│ ├── configure.sh # Build configuration │ ├── configure.sh # Build configuration
│ ├── Makefile # Build system │ ├── Makefile # Build system
│ ├── man/ # Manual pages
│ └── debian/ # Debian packaging │ └── debian/ # Debian packaging
├── wsssh-server/ # Server package ├── wsssh-server/ # Server Debian package
└── CHANGELOG.md # Version history ├── CHANGELOG.md # Version history
├── DOCUMENTATION.md # Technical documentation
├── TODO.md # Development tasks
├── README.md # Project overview
└── BRIDGE_MODE_TESTING.md # Testing documentation
``` ```
### Dependencies ### Dependencies
#### Server Dependencies (wssshd) #### Server Dependencies (wssshd2)
- **Python 3.7+** - **GCC**: GNU C Compiler (4.8+)
- **websockets**: WebSocket client/server library - **Make**: GNU Make build system
- **Flask**: Web framework for admin interface - **SQLite3**: Database library (libsqlite3-dev)
- **Flask-Login**: User authentication - **OpenSSL**: SSL/TLS library (libssl-dev)
- **Flask-SQLAlchemy**: Database support - **pkg-config**: Build configuration tool
- **ssl**: SSL/TLS support (built-in)
#### C Implementation #### Client Tools Dependencies (wssshtools)
- **GCC**: GNU C Compiler - **GCC**: GNU C Compiler (4.8+)
- **Make**: GNU Make build system - **Make**: GNU Make build system
- **OpenSSL**: SSL/TLS library (libssl-dev) - **OpenSSL**: SSL/TLS library (libssl-dev)
- **pkg-config**: Build configuration tool - **pkg-config**: Build configuration tool
### Building from Source ### Building from Source
#### Server Build #### Server Build (C Implementation)
```bash ```bash
# Install Python dependencies # Build server binary with embedded web interface
pip3 install -r requirements.txt
# Build server binary
./build.sh --server-only ./build.sh --server-only
# Or build everything
./build.sh
``` ```
#### C Implementation #### Client Tools Build
```bash ```bash
# Configure and build C tools # Configure and build C tools
cd wssshtools cd wssshtools
./configure.sh ./configure.sh
make make
# Install system-wide # Install system-wide (optional)
sudo make install sudo make install
``` ```
...@@ -661,6 +669,39 @@ python3 -m pytest tests/integration/ ...@@ -661,6 +669,39 @@ python3 -m pytest tests/integration/
## Recent Updates ## Recent Updates
### Version 1.7.0 - Major C Implementation Transition
- **Complete Architecture Migration**: Transitioned from Python/PyInstaller to native C implementation
- Replaced Python wssshd with high-performance C daemon (`wssshd2/`)
- Eliminated Python runtime dependencies for server component
- Maintained full backward compatibility for client tools
- Improved performance and reduced resource usage
- **SQLite Database Integration**: Persistent user management with secure storage
- Automatic database creation and schema management
- User authentication with password hashing
- Admin user role management
- Security warnings for default credentials
- Database-backed session management
- **Embedded Web Interface**: Self-contained web server with no external dependencies
- HTML/CSS/JavaScript assets embedded in binary
- User management interface with add/edit/delete functionality
- Real-time terminal interface with fullscreen support
- Responsive Bootstrap-based design
- AJAX-powered dynamic content updates
- **Enhanced Security Features**:
- Automatic security warnings for default admin credentials
- Proper session management with configurable timeouts
- Input validation and bounds checking
- Secure password storage with hashing
- Admin role-based access control
- **Professional ASCII Art**: Startup banner with project branding
- **Comprehensive Debug Options**: Database, web, and general debug logging
- **Smart Directory Selection**: Automatic root vs user directory detection
- **Updated Debian Packaging**: Modified for C binary with proper dependencies
### Version 1.6.5 ### Version 1.6.5
- **Flexible Data Encoding Support**: New `--enc` option for wsssht with multiple encoding modes - **Flexible Data Encoding Support**: New `--enc` option for wsssht with multiple encoding modes
- `--enc hex`: Hexadecimal encoding of binary data (default, backward compatible) - `--enc hex`: Hexadecimal encoding of binary data (default, backward compatible)
......
...@@ -26,11 +26,14 @@ ...@@ -26,11 +26,14 @@
### Core Components ### Core Components
#### Server Component (`wssshd`) #### Server Component (`wssshd`)
- **C Implementation**: High-performance native C daemon with embedded web interface
- **SQLite Database**: Persistent user management with secure password storage
- **Universal Daemon**: Central server managing connections with multiple transport protocols - **Universal Daemon**: Central server managing connections with multiple transport protocols
- **Client Registration**: Handles client authentication with password-based registration - **Client Registration**: Handles client authentication with password-based registration
- **Tunnel Routing**: Routes tunnel requests to appropriate registered clients - **Tunnel Routing**: Routes tunnel requests to appropriate registered clients
- **Web Management Interface**: Professional admin panel with user management and HTML5 terminal - **Web Management Interface**: Professional admin panel with user management and HTML5 terminal
- **Session Management**: Maintains persistent connections and handles reconnection logic - **Session Management**: Maintains persistent connections and handles reconnection logic
- **Security Features**: Automatic warnings for default credentials and secure session handling
#### Client Tools (`wssshtools/`) #### Client Tools (`wssshtools/`)
...@@ -71,10 +74,7 @@ ...@@ -71,10 +74,7 @@
git clone https://git.nexlab.net/nexlab/wsssh.git git clone https://git.nexlab.net/nexlab/wsssh.git
cd wsssh cd wsssh
# Install Python dependencies for the server # Build all components (C implementation with embedded web interface)
pip3 install -r requirements.txt
# Build all components
./build.sh ./build.sh
``` ```
...@@ -353,37 +353,49 @@ openssl x509 -in cert.pem -text -noout ...@@ -353,37 +353,49 @@ openssl x509 -in cert.pem -text -noout
### Building from Source ### Building from Source
```bash ```bash
# Install dependencies # Build all components (C implementation)
pip3 install -r requirements.txt
# Build all components
./build.sh ./build.sh
# Run tests
python3 -m pytest tests/
# Clean build artifacts # Clean build artifacts
./clean.sh ./clean.sh
``` ```
### C Implementation Details
The server component (`wssshd2/`) is implemented in C for maximum performance and minimal dependencies:
- **SQLite Database**: Persistent user management
- **Embedded Web Interface**: No external web server required
- **Native SSL/TLS**: Direct OpenSSL integration
- **Cross-platform**: Linux, macOS, Windows support
### Project Structure ### Project Structure
``` ```
wsssh/ wsssh/
├── wssshd.py # Universal tunneling daemon ├── build.sh # Build script for all components
├── build.sh # Build script
├── clean.sh # Clean script ├── clean.sh # Clean script
├── requirements.txt # Python dependencies ├── wssshd2/ # C server implementation
├── wssshtools/ # C implementation directory │ ├── main.c # Server main entry point
│ ├── web.c # Embedded web interface with SQLite
│ ├── config.c # Configuration handling
│ ├── terminal.c # Terminal session management
│ ├── websocket.c # WebSocket protocol handling
│ ├── ssl.c # SSL/TLS encryption
│ ├── tunnel.c # Tunnel management
│ ├── assets.c # Embedded web assets
│ ├── html_pages/ # Embedded HTML templates
│ └── Makefile # Build configuration
├── wssshtools/ # C client tools
│ ├── wssshc.c # Client registration tool │ ├── wssshc.c # Client registration tool
│ ├── wsssh.c # SSH wrapper │ ├── wsssh.c # SSH wrapper
│ ├── wsscp.c # SCP wrapper │ ├── wsscp.c # SCP wrapper
│ ├── wsssht.c # Tunnel setup tool │ ├── wsssht.c # Tunnel setup tool
│ ├── libwsssht/ # Shared libraries │ ├── libwsssht/ # Shared libraries
│ ├── man/ # Manual pages
│ └── debian/ # Debian packaging │ └── debian/ # Debian packaging
├── wsssh-server/ # Server Debian package ├── wsssh-server/ # Server Debian package
├── templates/ # Web interface templates ├── CHANGELOG.md # Version history
├── static/ # Web assets ├── DOCUMENTATION.md # Technical documentation
└── CHANGELOG.md # Version history ├── TODO.md # Development tasks
└── README.md # This file
``` ```
## Support ## Support
......
# WSSSH: Warp-Powered Stefy's Spatial Secure Hyperdrive - Future Enhancements Roadmap # WSSSH: Warp-Powered Stefy's Spatial Secure Hyperdrive - Future Enhancements Roadmap
## Recently Completed (v1.7.0) - Major C Implementation Transition
- [x] **Complete Architecture Migration**: Transitioned from Python/PyInstaller to native C implementation
- Replaced Python wssshd with high-performance C daemon (`wssshd2/`)
- Eliminated Python runtime dependencies for server component
- Maintained full backward compatibility for client tools
- Improved performance and reduced resource usage
- [x] **SQLite Database Integration**: Persistent user management with secure storage
- Automatic database creation and schema management
- User authentication with password hashing
- Admin user role management
- Security warnings for default credentials
- Database-backed session management
- [x] **Embedded Web Interface**: Self-contained web server with no external dependencies
- HTML/CSS/JavaScript assets embedded in binary
- User management interface with add/edit/delete functionality
- Real-time terminal interface with fullscreen support
- Responsive Bootstrap-based design
- AJAX-powered dynamic content updates
- [x] **Enhanced Security Features**:
- Automatic security warnings for default admin credentials
- Proper session management with configurable timeouts
- Input validation and bounds checking
- Secure password storage with hashing
- Admin role-based access control
- [x] **Professional ASCII Art**: Startup banner with project branding
- [x] **Comprehensive Debug Options**: Database, web, and general debug logging
- [x] **Smart Directory Selection**: Automatic root vs user directory detection
- [x] **Updated Debian Packaging**: Modified for C binary with proper dependencies
- [x] **Documentation Updates**: Comprehensive updates for C implementation
## Recently Completed (v1.6.5) ## Recently Completed (v1.6.5)
- [x] **Flexible Data Encoding Support**: New `--enc` option for wsssht with multiple encoding modes - [x] **Flexible Data Encoding Support**: New `--enc` option for wsssht with multiple encoding modes
- `--enc hex`: Hexadecimal encoding of binary data (default, backward compatible) - `--enc hex`: Hexadecimal encoding of binary data (default, backward compatible)
......
...@@ -180,14 +180,6 @@ prepare_assets() { ...@@ -180,14 +180,6 @@ prepare_assets() {
fi fi
fi fi
# Generate HTML templates for wssshd2
log_info "Generating HTML templates..."
mkdir -p wssshd2/templates
# Copy templates from root templates directory if they exist
if [[ -d "templates" ]]; then
cp -r templates/* wssshd2/templates/ 2>/dev/null || true
fi
log_success "Assets prepared" log_success "Assets prepared"
} }
......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}WebSocket SSH Daemon{% endblock %}</title>
<link rel="icon" href="/image.jpg" 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="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>
// 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;
}
// 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="{{ url_for('index') }}">
<i class="fas fa-terminal"></i> WebSocket SSH Daemon
</a>
<div class="navbar-nav ms-auto">
{% if current_user.is_authenticated %}
<span class="navbar-text me-3">
Welcome, {{ current_user.username }}!
</span>
<button class="btn btn-outline-warning btn-sm me-2" data-bs-toggle="modal" data-bs-target="#donationModal">
<i class="fas fa-heart"></i> Donate
</button>
<a class="nav-link" href="{{ url_for('logout') }}">
<i class="fas fa-sign-out-alt"></i> Logout
</a>
{% endif %}
</div>
</div>
</nav>
<div class="container mt-4">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ 'danger' if category == 'error' else 'info' }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
<!-- Donation Modal -->
<div class="modal fade" id="donationModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-heart text-danger"></i> Support WebSocket SSH Development
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p class="text-muted">Your support helps us continue developing and maintaining this open-source project!</p>
<div class="row">
<div class="col-md-4 text-center mb-3">
<h6><i class="fab fa-paypal text-primary"></i> PayPal</h6>
<a href="https://www.paypal.com/paypalme/nexlab" target="_blank" class="btn btn-primary btn-sm">
<i class="fab fa-paypal"></i> Donate via PayPal
</a>
<small class="d-block text-muted mt-1">info@nexlab.net</small>
</div>
<div class="col-md-4 text-center mb-3">
<h6><i class="fab fa-bitcoin text-warning"></i> Bitcoin</h6>
<div class="mb-2">
<img src="https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=bitcoin:bc1q3zlkpu95amtcltsk85y0eacyzzk29v68tgc5hx" alt="BTC QR Code" class="img-fluid rounded">
</div>
<div class="input-group input-group-sm">
<input type="text" class="form-control form-control-sm font-monospace" value="bc1q3zlkpu95amtcltsk85y0eacyzzk29v68tgc5hx" readonly style="font-size: 0.75rem;">
<button class="btn btn-outline-secondary btn-sm" type="button" onclick="copyToClipboard('bc1q3zlkpu95amtcltsk85y0eacyzzk29v68tgc5hx')">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="col-md-4 text-center mb-3">
<h6><i class="fab fa-ethereum text-secondary"></i> Ethereum</h6>
<div class="mb-2">
<img src="https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=ethereum:0xdA6dAb526515b5cb556d20269207D43fcc760E51" alt="ETH QR Code" class="img-fluid rounded">
</div>
<div class="input-group input-group-sm">
<input type="text" class="form-control form-control-sm font-monospace" value="0xdA6dAb526515b5cb556d20269207D43fcc760E51" readonly style="font-size: 0.75rem;">
<button class="btn btn-outline-secondary btn-sm" type="button" onclick="copyToClipboard('0xdA6dAb526515b5cb556d20269207D43fcc760E51')">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
</div>
<hr>
<p class="text-center mb-0">
<small class="text-muted">
Thank you for your support! ❤️
</small>
</p>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"></script>
<script>
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(function() {
// Show a temporary success message
const btn = event.target.closest('button');
const originalHtml = btn.innerHTML;
btn.innerHTML = '<i class="fas fa-check"></i>';
btn.classList.remove('btn-outline-secondary');
btn.classList.add('btn-success');
setTimeout(() => {
btn.innerHTML = originalHtml;
btn.classList.remove('btn-success');
btn.classList.add('btn-outline-secondary');
}, 1000);
});
}
</script>
{% block scripts %}{% endblock %}
</body>
</html>
\ No newline at end of file
{% extends "base.html" %}
{% block title %}Dashboard - WebSocket SSH Daemon{% endblock %}
{% block content %}
<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">
{% if clients %}
<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 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">
{% if current_user.is_admin %}
<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()">
<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">{{ websocket_port or 'N/A' }}</span></p>
<p class="mb-1"><strong>Domain:</strong> <span id="domain">{{ domain or 'N/A' }}</span></p>
<p class="mb-0"><strong>Connected Clients:</strong> <span id="client-count">{{ clients|length }}</span></p>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let currentClients = {{ clients|tojson }};
function updateClients() {
fetch('/api/clients')
.then(response => response.json())
.then(data => {
// Update client count
document.getElementById('client-count').textContent = data.count;
// Check if client list changed
if (JSON.stringify(data.clients.sort()) !== JSON.stringify(currentClients.sort())) {
// Reload the page to show updated client list
location.reload();
}
})
.catch(error => {
console.log('Error fetching client data:', error);
});
}
// Update every 5 seconds
setInterval(updateClients, 5000);
// Initial update after 1 second
setTimeout(updateClients, 1000);
</script>
{% endblock %}
\ No newline at end of file
{% extends "base.html" %}
{% block title %}Login - WebSocket SSH Daemon{% endblock %}
{% block content %}
<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>
{% endblock %}
\ No newline at end of file
This diff is collapsed.
{% extends "base.html" %}
{% block title %}User Management - WebSocket SSH Daemon{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<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="{{ url_for('index') }}" 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>
{% for user in users %}
<tr>
<td>{{ user.username }}</td>
<td>
{% 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>
<!-- 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>
{% endblock %}
{% block scripts %}
<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>
{% endblock %}
\ No newline at end of file
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <signal.h>
#include <fcntl.h>
#include <getopt.h>
volatile int running = 1;
void sigint_handler(int sig) {
running = 0;
}
// Simple base64 encoding function
char *base64_encode(const unsigned char *data, size_t input_length) {
static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
size_t output_length = 4 * ((input_length + 2) / 3);
char *encoded_data = malloc(output_length + 1);
if (encoded_data == NULL) return NULL;
for (size_t i = 0, j = 0; i < input_length;) {
uint32_t octet_a = i < input_length ? data[i++] : 0;
uint32_t octet_b = i < input_length ? data[i++] : 0;
uint32_t octet_c = i < input_length ? data[i++] : 0;
uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
encoded_data[j++] = base64_chars[(triple >> 3 * 6) & 0x3F];
encoded_data[j++] = base64_chars[(triple >> 2 * 6) & 0x3F];
encoded_data[j++] = base64_chars[(triple >> 1 * 6) & 0x3F];
encoded_data[j++] = base64_chars[(triple >> 0 * 6) & 0x3F];
}
// Add padding
size_t padding = (3 - (input_length % 3)) % 3;
for (size_t i = 0; i < padding; i++) {
encoded_data[output_length - 1 - i] = '=';
}
encoded_data[output_length] = '\0';
return encoded_data;
}
int main(int argc, char *argv[]) {
char *host = "127.0.0.1"; // Default host
int port = 8080; // Default port
int timeout_seconds = 2; // Default timeout
int num_messages = 0; // Default: infinite loop
int string_length = 0; // Default: random length (10-40)
// Parse command line arguments
static struct option long_options[] = {
{"timeout", required_argument, 0, 't'},
{"n", required_argument, 0, 'n'},
{"string-length", required_argument, 0, 's'},
{0, 0, 0, 0}
};
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, "t:n:s:", long_options, &option_index)) != -1) {
switch (opt) {
case 't':
timeout_seconds = atoi(optarg);
if (timeout_seconds < 0) {
fprintf(stderr, "Invalid timeout value: %s\n", optarg);
return 1;
}
break;
case 'n':
num_messages = atoi(optarg);
if (num_messages < 0) {
fprintf(stderr, "Invalid number of messages: %s\n", optarg);
return 1;
}
break;
case 's':
string_length = atoi(optarg);
if (string_length < 1 || string_length > 1000) {
fprintf(stderr, "Invalid string length: %s (must be 1-1000)\n", optarg);
return 1;
}
break;
default:
fprintf(stderr, "Usage: %s [options] [host] [port]\n", argv[0]);
fprintf(stderr, "Options:\n");
fprintf(stderr, " --timeout, -t <seconds> Timeout between messages (default: 2)\n");
fprintf(stderr, " --n, -n <count> Number of messages to send (0 for infinite, default: 0)\n");
fprintf(stderr, " --string-length, -s <len> String length to generate (1-1000, default: random 10-40)\n");
fprintf(stderr, "Arguments:\n");
fprintf(stderr, " host Server host (default: 127.0.0.1)\n");
fprintf(stderr, " port Server port (default: 8080)\n");
return 1;
}
}
// Parse positional arguments for host and port
if (optind < argc) {
host = argv[optind++];
}
if (optind < argc) {
port = atoi(argv[optind++]);
}
if (port <= 0 || port > 65535) {
fprintf(stderr, "Invalid port: %d\n", port);
return 1;
}
printf("Connecting to %s:%d\n", host, port);
printf("Timeout: %d seconds\n", timeout_seconds);
printf("Number of messages: %s\n", num_messages == 0 ? "infinite" : "limited");
printf("String length: %s\n", string_length > 0 ? "fixed" : "random (10-40)");
if (string_length > 0) {
printf("Fixed string length: %d\n", string_length);
}
signal(SIGINT, sigint_handler);
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("socket");
return 1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_pton(AF_INET, host, &addr.sin_addr) <= 0) {
perror("inet_pton");
close(sock);
return 1;
}
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("connect");
close(sock);
return 1;
}
// Make socket non-blocking
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
int message_count = 0;
while (running) {
// Check if we've sent the required number of messages
if (num_messages > 0 && message_count >= num_messages) {
printf("Sent %d messages as requested. Exiting.\n", num_messages);
break;
}
// Generate string
srand(time(NULL) + message_count); // Add message_count to avoid same seed
int len;
if (string_length > 0) {
len = string_length;
} else {
len = rand() % 31 + 10; // 10 to 40
}
char str[len + 1];
for (int i = 0; i < len; i++) {
str[i] = 'A' + rand() % 26;
}
str[len] = '\0';
char *b64_sent = base64_encode((unsigned char *)str, len);
printf("(SENT) %s <-> %s\n", str, b64_sent ? b64_sent : "ERROR");
write(sock, str, len);
message_count++;
if (b64_sent) free(b64_sent);
// Wait for response with specified timeout
fd_set read_set;
FD_ZERO(&read_set);
FD_SET(sock, &read_set);
struct timeval timeout;
timeout.tv_sec = timeout_seconds;
timeout.tv_usec = 0;
int ret = select(sock + 1, &read_set, NULL, NULL, &timeout);
if (ret > 0 && FD_ISSET(sock, &read_set)) {
char buffer[1024];
int n = read(sock, buffer, sizeof(buffer) - 1);
if (n > 0) {
buffer[n] = '\0';
// Remove trailing newline if present
if (n > 0 && buffer[n-1] == '\n') {
buffer[n-1] = '\0';
n--;
}
char *b64_recv = base64_encode((unsigned char *)buffer, n);
printf("(RECV) %s <-> %s\n", buffer, b64_recv ? b64_recv : "ERROR");
if (b64_recv) free(b64_recv);
} else if (n == 0) {
// Connection closed
printf("Connection closed by server\n");
break;
} else {
perror("read");
break;
}
} else if (ret == 0) {
printf("Timeout waiting for response\n");
} else {
perror("select");
break;
}
// Add delay between messages based on timeout parameter
if (timeout_seconds > 0) {
sleep(timeout_seconds);
} else {
usleep(100000); // 100ms delay for 0 timeout
}
}
close(sock);
return 0;
}
\ No newline at end of file
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
volatile int running = 1;
void sigint_handler(int sig) {
running = 0;
}
// Simple base64 encoding function
char *base64_encode(const unsigned char *data, size_t input_length) {
static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
size_t output_length = 4 * ((input_length + 2) / 3);
char *encoded_data = malloc(output_length + 1);
if (encoded_data == NULL) return NULL;
for (size_t i = 0, j = 0; i < input_length;) {
uint32_t octet_a = i < input_length ? data[i++] : 0;
uint32_t octet_b = i < input_length ? data[i++] : 0;
uint32_t octet_c = i < input_length ? data[i++] : 0;
uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
encoded_data[j++] = base64_chars[(triple >> 3 * 6) & 0x3F];
encoded_data[j++] = base64_chars[(triple >> 2 * 6) & 0x3F];
encoded_data[j++] = base64_chars[(triple >> 1 * 6) & 0x3F];
encoded_data[j++] = base64_chars[(triple >> 0 * 6) & 0x3F];
}
// Add padding
size_t padding = (3 - (input_length % 3)) % 3;
for (size_t i = 0; i < padding; i++) {
encoded_data[output_length - 1 - i] = '=';
}
encoded_data[output_length] = '\0';
return encoded_data;
}
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <port>\n", argv[0]);
return 1;
}
int port = atoi(argv[1]);
if (port <= 0 || port > 65535) {
fprintf(stderr, "Invalid port number\n");
return 1;
}
signal(SIGINT, sigint_handler);
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket");
return 1;
}
// Set SO_REUSEADDR to allow immediate reuse of the port
int opt = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
perror("setsockopt");
close(server_fd);
return 1;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind");
close(server_fd);
return 1;
}
if (listen(server_fd, 10) < 0) {
perror("listen");
close(server_fd);
return 1;
}
printf("Listening on 0.0.0.0:%d\n", port);
fd_set master_set, read_set;
FD_ZERO(&master_set);
FD_SET(server_fd, &master_set);
int max_fd = server_fd;
while (running) {
read_set = master_set;
struct timeval timeout;
timeout.tv_sec = 1; // 1 second timeout to check running flag
timeout.tv_usec = 0;
int ret = select(max_fd + 1, &read_set, NULL, NULL, &timeout);
if (ret < 0) {
if (running) perror("select");
break;
} else if (ret == 0) {
// Timeout - check if we should still be running
continue;
}
for (int i = 0; i <= max_fd; i++) {
if (FD_ISSET(i, &read_set)) {
if (i == server_fd) {
// New connection
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);
if (client_fd < 0) {
perror("accept");
continue;
}
printf("CONNECTION FROM %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
FD_SET(client_fd, &master_set);
if (client_fd > max_fd) max_fd = client_fd;
} else {
// Data from client
char buffer[1024];
int n = read(i, buffer, sizeof(buffer) - 1);
if (n <= 0) {
// Connection closed or error
close(i);
FD_CLR(i, &master_set);
printf("CONNECTION CLOSED\n");
} else {
buffer[n] = '\0';
// Remove trailing newline if present
if (n > 0 && buffer[n-1] == '\n') {
buffer[n-1] = '\0';
n--;
}
char *b64 = base64_encode((unsigned char *)buffer, n);
printf("(RECEIVED) %s <-> %s\n", buffer, b64 ? b64 : "ERROR");
char response[1024 + 100];
char *response_b64 = base64_encode((unsigned char *)buffer, n);
snprintf(response, sizeof(response), "RECEIVED: %s <-> %s", buffer, response_b64 ? response_b64 : "ERROR");
write(i, response, strlen(response));
if (b64) free(b64);
if (response_b64) free(response_b64);
}
}
}
}
}
// Close all sockets
for (int i = 0; i <= max_fd; i++) {
if (FD_ISSET(i, &master_set)) {
close(i);
}
}
printf("Exiting...\n");
return 0;
}
\ No newline at end of file
"""
WSSSH Daemon (wssshd) - Modular implementation
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/>.
"""
from .server import main
from .config import load_config
from .websocket import clients, active_tunnels, active_terminals
from .terminal import create_terminal_session, send_terminal_data, get_terminal_output, disconnect_terminal, resize_terminal
__version__ = "1.0.0"
__all__ = [
'main',
'load_config',
'clients',
'active_tunnels',
'active_terminals',
'create_terminal_session',
'send_terminal_data',
'get_terminal_output',
'disconnect_terminal',
'resize_terminal'
]
\ No newline at end of file
"""
Entry point for running wssshd as a module
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/>.
"""
from .server import main
if __name__ == '__main__':
main()
\ No newline at end of file
"""
Configuration handling 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/>.
"""
import argparse
import configparser
import os
def load_config(args_config=None):
"""
Load configuration from file and command line arguments
Returns parsed arguments
"""
parser = argparse.ArgumentParser(description='WSSSH Daemon (wssshd)')
parser.add_argument('--config', help='Configuration file path (default: /etc/wssshd.conf)')
parser.add_argument('--host', help='WebSocket server host')
parser.add_argument('--port', type=int, default=9898, help='WebSocket server port (default: 9898)')
parser.add_argument('--domain', help='Base domain name')
parser.add_argument('--password', help='Registration password')
parser.add_argument('--web-host', help='Web interface host (optional)')
parser.add_argument('--web-port', type=int, help='Web interface port (optional)')
parser.add_argument('--web-https', action='store_true', help='Enable HTTPS for web interface')
parser.add_argument('--debug', action='store_true', help='Enable debug output')
# Parse just the config argument first to determine config file location
temp_parser = argparse.ArgumentParser(add_help=False)
temp_parser.add_argument('--config')
temp_args, remaining = temp_parser.parse_known_args()
config = configparser.ConfigParser()
config_path = temp_args.config or '/etc/wssshd.conf'
defaults = {}
if os.path.exists(config_path):
config.read(config_path)
if 'wssshd' in config:
section = config['wssshd']
for key in ['password', 'domain']:
if key in section:
defaults[key] = section[key]
if 'host' in section:
defaults['host'] = section['host']
if 'port' in section:
defaults['port'] = int(section['port'])
if 'web-host' in section:
defaults['web_host'] = section['web-host']
if 'web-port' in section:
defaults['web_port'] = int(section['web-port'])
if 'web-https' in section:
defaults['web_https'] = section.getboolean('web-https', False)
parser.set_defaults(**defaults)
args = parser.parse_args()
# Handle web-https from config if not specified on command line
if 'web_https' in defaults and not any(arg.startswith('--web-https') for arg in remaining):
args.web_https = defaults['web_https']
# Check required arguments
if not args.host:
parser.error('--host is required')
if not args.domain:
parser.error('--domain is required')
if not args.password:
parser.error('--password is required')
return args
\ No newline at end of file
This diff is collapsed.
"""
Terminal and PTY handling 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/>.
"""
import os
import pty
import select
import fcntl
import termios
import struct
import threading
import signal
import subprocess
import uuid
def openpty_with_fallback():
"""Open a PTY with fallback to different device paths for systems where /dev/pty doesn't exist"""
# First try the standard pty.openpty()
try:
master, slave = pty.openpty()
return master, slave
except OSError as e:
if hasattr(openpty_with_fallback, '_debug') and openpty_with_fallback._debug:
print(f"[DEBUG] Standard pty.openpty() failed: {e}, trying fallback methods")
# Fallback: try to open /dev/ptmx directly
ptmx_paths = ['/dev/ptmx', '/dev/pts/ptmx']
for ptmx_path in ptmx_paths:
try:
if os.path.exists(ptmx_path):
# Open master PTY
master = os.open(ptmx_path, os.O_RDWR | os.O_NOCTTY)
if master < 0:
continue
# Get slave PTY name
slave_name = os.ttyname(master)
if not slave_name:
os.close(master)
continue
# Open slave PTY
slave = os.open(slave_name, os.O_RDWR | os.O_NOCTTY)
if slave < 0:
os.close(master)
continue
if hasattr(openpty_with_fallback, '_debug') and openpty_with_fallback._debug:
print(f"[DEBUG] Successfully opened PTY using {ptmx_path}: master={master}, slave={slave}")
return master, slave
except (OSError, AttributeError) as e:
if hasattr(openpty_with_fallback, '_debug') and openpty_with_fallback._debug:
print(f"[DEBUG] Failed to open PTY using {ptmx_path}: {e}")
continue
# Last resort: try to create PTY devices manually
try:
# Try to find an available PTY number
for i in range(256): # Try PTY numbers 0-255
pty_name = f"/dev/pts/{i}"
try:
if os.path.exists(pty_name):
continue
# Try to create the PTY device
master = os.open('/dev/ptmx', os.O_RDWR | os.O_NOCTTY)
slave_name = os.ttyname(master)
if slave_name and os.path.exists(slave_name):
slave = os.open(slave_name, os.O_RDWR | os.O_NOCTTY)
if hasattr(openpty_with_fallback, '_debug') and openpty_with_fallback._debug:
print(f"[DEBUG] Created PTY manually: master={master}, slave={slave}")
return master, slave
os.close(master)
except (OSError, AttributeError):
continue
except Exception as e:
if hasattr(openpty_with_fallback, '_debug') and openpty_with_fallback._debug:
print(f"[DEBUG] Manual PTY creation failed: {e}")
# If all methods fail, raise the original exception
raise OSError("Failed to open PTY: no available PTY devices found")
def create_terminal_session(args, username, client_id):
"""Create a new terminal session for a client"""
request_id = str(uuid.uuid4())
# Force echo mode before launching wsssh
command = ['sh', '-c', f'stty echo && wsssh -p {args.port} {username}@{client_id}.{args.domain}']
# Debug output for the command being launched
if hasattr(args, 'debug') and args.debug:
print(f"[DEBUG] [Terminal] Launching command: {' '.join(command)}")
print(f"[DEBUG] [Terminal] Request ID: {request_id}")
print(f"[DEBUG] [Terminal] Username: {username}, Client ID: {client_id}, Domain: {args.domain}")
# Spawn wsssh process with pty using fallback method
master, slave = openpty_with_fallback()
slave_name = os.ttyname(slave)
def set_controlling_terminal():
os.setsid()
# Set the controlling terminal
try:
fcntl.ioctl(slave, termios.TIOCSCTTY, 0)
except (OSError, AttributeError):
pass # Some systems don't support TIOCSCTTY
# Set terminal size to match xterm.js dimensions (default 80x24)
winsize = struct.pack('HHHH', 24, 80, 0, 0)
try:
fcntl.ioctl(0, termios.TIOCSWINSZ, winsize)
except (OSError, AttributeError):
pass
# Set raw mode - let SSH client handle terminal behavior
import tty
try:
tty.setraw(0)
except (OSError, AttributeError):
pass
proc = subprocess.Popen(
command,
stdin=slave,
stdout=slave,
stderr=slave,
preexec_fn=set_controlling_terminal,
env=dict(os.environ, TERM='xterm', COLUMNS='80', LINES='24')
)
os.close(slave)
# Start a thread to read output
output_buffer = []
def read_output():
output_buffer.append(f'Process PID: {proc.pid}\r\n')
while proc.poll() is None:
r, w, e = select.select([master], [], [], 0.1)
if master in r:
try:
data = os.read(master, 1024)
if data:
decoded = data.decode('utf-8', errors='ignore')
output_buffer.append(decoded)
except:
break
# Read any remaining data
try:
data = os.read(master, 1024)
while data:
decoded = data.decode('utf-8', errors='ignore')
output_buffer.append(decoded)
data = os.read(master, 1024)
except:
pass
output_buffer.append('\r\nProcess finished.\r\n')
os.close(master)
thread = threading.Thread(target=read_output, daemon=True)
thread.start()
return {
'request_id': request_id,
'proc': proc,
'output_buffer': output_buffer,
'master': master,
'command': f'wsssh -p {args.port} {username}@{client_id}.{args.domain}'
}
def send_terminal_data(terminal_session, data):
"""Send data to a terminal session"""
proc = terminal_session['proc']
master = terminal_session['master']
if proc.poll() is None: # Process is still running
try:
os.write(master, data.encode())
return True
except:
return False
return False
def get_terminal_output(terminal_session):
"""Get output from a terminal session"""
proc = terminal_session['proc']
output_buffer = terminal_session['output_buffer']
if output_buffer:
data = ''.join(output_buffer)
output_buffer.clear()
return data
elif proc.poll() is not None:
# Process terminated
return '\r\nProcess terminated.\r\n'
return ''
def disconnect_terminal(terminal_session):
"""Disconnect a terminal session"""
proc = terminal_session['proc']
if proc.poll() is None:
proc.terminate()
try:
proc.wait(timeout=5)
except:
proc.kill()
return True
def resize_terminal(terminal_session, cols, rows):
"""Resize a terminal session"""
proc = terminal_session['proc']
master = terminal_session['master']
if proc.poll() is None:
# Update terminal size
winsize = struct.pack('HHHH', rows, cols, 0, 0)
try:
fcntl.ioctl(master, termios.TIOCSWINSZ, winsize)
# Also try to update the process's controlling terminal
fcntl.ioctl(0, termios.TIOCSWINSZ, winsize)
except (OSError, AttributeError):
pass
# Send SIGWINCH to notify the process of size change
try:
os.kill(proc.pid, signal.SIGWINCH)
except (OSError, ProcessLookupError):
pass
return True
\ No newline at end of file
"""
Tunnel object management 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/>.
"""
import time
import socket
import ipaddress
class TunnelStatus:
"""Enumeration of tunnel statuses"""
CREATING = "creating"
ACTIVE = "active"
CLOSING = "closing"
CLOSED = "closed"
ERROR = "error"
class Tunnel:
"""Comprehensive tunnel object that tracks all tunnel attributes"""
def __init__(self, request_id, client_id):
self.request_id = request_id
self.client_id = client_id
self.tunnel_id = request_id # Use request_id as tunnel_id for now
# Status and lifecycle
self.status = TunnelStatus.CREATING
self.created_at = time.time()
self.updated_at = time.time()
# Protocol and type
self.protocol = "ssh" # default
self.tunnel = "any" # default
self.tunnel_control = "any" # default
self.service = "ssh" # default
# Destination (wssshc) information
self.wssshc_public_ip = None
self.wssshc_public_port = None
self.wssshc_private_ip = None
self.wssshc_private_port = None
# Source (wsssh/wsscp) information
self.tool_public_ip = None
self.tool_private_ip = None
self.tool_public_port = None
self.tool_private_port = None
# WebSocket connections
self.client_ws = None # wssshc WebSocket
self.wsssh_ws = None # wsssh/wsscp WebSocket
# Additional metadata
self.error_message = None
self.metadata = {}
# Keep-alive statistics and timing
self.last_keepalive_sent = time.time()
self.last_keepalive_received = time.time()
self.total_bytes_sent = 0
self.total_bytes_received = 0
self.bytes_last_period = 0
self.last_stats_reset = time.time()
# Dual-endpoint keep-alive monitoring (220s timeout)
self.last_keepalive_from_client = time.time() # wssshc endpoint
self.last_keepalive_from_tool = time.time() # wsssht/wsscp endpoint
# Keep-alive forwarding failure counters
self.keepalive_forward_failures = 0 # Consecutive forwarding failures
self.keepalive_ack_forward_failures = 0 # Consecutive ACK forwarding failures
def update_status(self, new_status, error_message=None):
"""Update tunnel status and timestamp"""
self.status = new_status
self.updated_at = time.time()
if error_message:
self.error_message = error_message
def set_destination_info(self, public_ip=None, public_port=None, private_ip=None, private_port=None):
"""Set destination (wssshc) connection information"""
if public_ip:
self.wssshc_public_ip = public_ip
if public_port:
self.wssshc_public_port = public_port
if private_ip:
self.wssshc_private_ip = private_ip
if private_port:
self.wssshc_private_port = private_port
self.updated_at = time.time()
def set_source_info(self, public_ip=None, private_ip=None, public_port=None, private_port=None):
"""Set source (wsssh/wsscp) connection information"""
if public_ip:
self.tool_public_ip = public_ip
if private_ip:
self.tool_private_ip = private_ip
if public_port:
self.tool_public_port = public_port
if private_port:
self.tool_private_port = private_port
self.updated_at = time.time()
def set_websockets(self, client_ws, wsssh_ws):
"""Set WebSocket connections"""
self.client_ws = client_ws
self.wsssh_ws = wsssh_ws
self.updated_at = time.time()
def to_dict(self):
"""Convert tunnel object to dictionary for serialization"""
return {
'request_id': self.request_id,
'client_id': self.client_id,
'tunnel_id': self.tunnel_id,
'status': self.status,
'created_at': self.created_at,
'updated_at': self.updated_at,
'protocol': self.protocol,
'tunnel': self.tunnel,
'tunnel_control': self.tunnel_control,
'service': self.service,
'wssshc_public_ip': self.wssshc_public_ip,
'wssshc_public_port': self.wssshc_public_port,
'wssshc_private_ip': self.wssshc_private_ip,
'wssshc_private_port': self.wssshc_private_port,
'tool_public_ip': self.tool_public_ip,
'tool_private_ip': self.tool_private_ip,
'tool_public_port': self.tool_public_port,
'tool_private_port': self.tool_private_port,
'error_message': self.error_message,
'last_keepalive_from_client': self.last_keepalive_from_client,
'last_keepalive_from_tool': self.last_keepalive_from_tool
}
def __str__(self):
return f"Tunnel(id={self.tunnel_id}, client={self.client_id}, status={self.status})"
def __repr__(self):
return self.__str__()
def detect_client_public_ip(websocket):
"""Detect the public IP address of a client from WebSocket connection"""
try:
# Get the remote address from WebSocket
remote_addr = websocket.remote_address
if remote_addr and len(remote_addr) >= 2:
ip = remote_addr[0]
# Check if it's a valid public IP
ip_obj = ipaddress.ip_address(ip)
if not ip_obj.is_private and not ip_obj.is_loopback:
return ip
except Exception:
pass
return None
def detect_client_private_ip(websocket):
"""Detect the private IP address of a client from WebSocket connection"""
try:
# Get the remote address from WebSocket
remote_addr = websocket.remote_address
if remote_addr and len(remote_addr) >= 2:
ip = remote_addr[0]
# Check if it's a valid private IP
ip_obj = ipaddress.ip_address(ip)
if ip_obj.is_private:
return ip
except Exception:
pass
return None
def get_server_public_ip():
"""Get the server's public IP address"""
try:
# Create a socket to connect to an external service
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80)) # Connect to Google DNS
public_ip = s.getsockname()[0]
s.close()
return public_ip
except Exception:
return None
def get_server_private_ip():
"""Get the server's private IP address"""
try:
# Create a socket and connect to get local IP
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80)) # Connect to Google DNS
private_ip = s.getsockname()[0]
s.close()
return private_ip
except Exception:
return None
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
wsssh-server (1.6.5-1) unstable; urgency=medium
* Version 1.6.5: Transition to C binary with SQLite database
* Complete rewrite from Python/PyInstaller to native C implementation
* Added SQLite database for persistent user management
* Implemented embedded web interface with user authentication
* Added comprehensive user management (add/edit/delete users)
* Security warning system for default admin credentials
* ASCII art banner on startup
* Improved performance with native C binary (no Python dependencies)
* Updated build system to use GCC instead of PyInstaller
* Added libsqlite3 and libssl runtime dependencies
* Updated man page with C version command line options
* Maintained all existing init scripts, logrotate, and service configurations
* Preserved backward compatibility for service management
* Enhanced security with proper session management
* Added debug options for database operations
-- Stefy Lanza <stefy@nexlab.net> Wed, 18 Sep 2025 08:47:00 +0200
wsssh-server (1.6.1-1) unstable; urgency=medium wsssh-server (1.6.1-1) unstable; urgency=medium
* Version 1.6.1: Major code refactoring and documentation updates * Version 1.6.1: Major code refactoring and documentation updates
......
...@@ -2,7 +2,7 @@ Source: wsssh-server ...@@ -2,7 +2,7 @@ Source: wsssh-server
Section: net Section: net
Priority: optional Priority: optional
Maintainer: Stefy Lanza <stefy@nexlab.net> Maintainer: Stefy Lanza <stefy@nexlab.net>
Build-Depends: debhelper-compat (= 13), python3, python3-pip, python3-setuptools Build-Depends: debhelper-compat (= 13), gcc, make, libssl-dev, libsqlite3-dev, uuid-dev
Standards-Version: 4.6.2 Standards-Version: 4.6.2
Homepage: https://git.nexlab.net/nexlab/wsssh Homepage: https://git.nexlab.net/nexlab/wsssh
Vcs-Browser: https://git.nexlab.net/nexlab/wsssh Vcs-Browser: https://git.nexlab.net/nexlab/wsssh
...@@ -10,13 +10,13 @@ Vcs-Git: https://git.nexlab.net/nexlab/wsssh.git ...@@ -10,13 +10,13 @@ Vcs-Git: https://git.nexlab.net/nexlab/wsssh.git
Package: wsssh-server Package: wsssh-server
Architecture: any Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, debconf (>= 0.5) | debconf-2.0 Depends: ${shlibs:Depends}, ${misc:Depends}, adduser, debconf (>= 0.5) | debconf-2.0, libsqlite3-0, libssl3
Description: WSSSH: Warp-Powered Stefy's Spatial Secure Hyperdrive Server (wssshd) Description: WSSSH: Warp-Powered Stefy's Spatial Secure Hyperdrive Server (wssshd)
wsssh is a swiss army's knife server assisted tunnelling system for the win initially born as a websocket ssh tunnelling system and evolved to a universal tunnelling utility. This package contains the server component wsssh is a swiss army's knife server assisted tunnelling system for the win initially born as a websocket ssh tunnelling system and evolved to a universal tunnelling utility. This package contains the server component
that handles WebSocket connections and manages SSH tunnels. that handles WebSocket connections and manages SSH tunnels.
. .
This package includes a standalone PyInstaller binary that bundles all This package includes a standalone C binary that provides high performance
required dependencies, eliminating the need for external Python packages. and includes an embedded web interface with SQLite database for user management.
. .
The wssshd server provides: The wssshd server provides:
- WSSSH tunnel management - WSSSH tunnel management
......
...@@ -16,88 +16,12 @@ ...@@ -16,88 +16,12 @@
dh $@ dh $@
override_dh_auto_configure: override_dh_auto_configure:
# Create PyInstaller spec file for wssshd # Configure wssshd2 build
@echo '# -*- mode: python ; coding: utf-8 -*-' > wssshd.spec cd ../wssshd2 && ./configure.sh
@echo '' >> wssshd.spec
@echo 'block_cipher = None' >> wssshd.spec
@echo '' >> wssshd.spec
@echo 'a = Analysis(' >> wssshd.spec
@echo " ['../wssshd.py']," >> wssshd.spec
@echo ' pathex=[],' >> wssshd.spec
@echo ' binaries=[],' >> wssshd.spec
@echo ' datas=[],' >> wssshd.spec
@echo ' hiddenimports=[' >> wssshd.spec
@echo " 'websockets'," >> wssshd.spec
@echo " 'flask'," >> wssshd.spec
@echo " 'flask_login'," >> wssshd.spec
@echo " 'flask_sqlalchemy'," >> wssshd.spec
@echo " 'ssl'," >> wssshd.spec
@echo " 'asyncio'," >> wssshd.spec
@echo " 'configparser'," >> wssshd.spec
@echo " 'argparse'," >> wssshd.spec
@echo " 'signal'," >> wssshd.spec
@echo " 'os'," >> wssshd.spec
@echo " 'sys'," >> wssshd.spec
@echo " 'json'," >> wssshd.spec
@echo " 'subprocess'," >> wssshd.spec
@echo " 'pty'," >> wssshd.spec
@echo " 'select'," >> wssshd.spec
@echo " 'termios'," >> wssshd.spec
@echo " 'fcntl'," >> wssshd.spec
@echo " 'stat'," >> wssshd.spec
@echo " 'threading'," >> wssshd.spec
@echo " 'time'," >> wssshd.spec
@echo " 'uuid'," >> wssshd.spec
@echo " 'socket'," >> wssshd.spec
@echo " 'netdb'," >> wssshd.spec
@echo " 'errno'," >> wssshd.spec
@echo " 'pysqlite3'" >> wssshd.spec
@echo ' ],' >> wssshd.spec
@echo ' hookspath=[],' >> wssshd.spec
@echo ' hooksconfig={},' >> wssshd.spec
@echo ' runtime_hooks=[],' >> wssshd.spec
@echo ' excludes=[],' >> wssshd.spec
@echo ' win_no_prefer_redirects=False,' >> wssshd.spec
@echo ' win_private_assemblies=False,' >> wssshd.spec
@echo ' cipher=block_cipher,' >> wssshd.spec
@echo ' noarchive=False,' >> wssshd.spec
@echo ')' >> wssshd.spec
@echo '' >> wssshd.spec
@echo 'pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)' >> wssshd.spec
@echo '' >> wssshd.spec
@echo 'exe = EXE(' >> wssshd.spec
@echo ' pyz,' >> wssshd.spec
@echo ' a.scripts,' >> wssshd.spec
@echo ' a.binaries,' >> wssshd.spec
@echo ' a.zipfiles,' >> wssshd.spec
@echo ' a.datas,' >> wssshd.spec
@echo ' [],' >> wssshd.spec
@echo " name='wssshd'," >> wssshd.spec
@echo ' debug=False,' >> wssshd.spec
@echo ' bootloader_ignore_signals=False,' >> wssshd.spec
@echo ' strip=False,' >> wssshd.spec
@echo ' upx=True,' >> wssshd.spec
@echo ' upx_exclude=[],' >> wssshd.spec
@echo ' runtime_tmpdir=None,' >> wssshd.spec
@echo ' console=True,' >> wssshd.spec
@echo ' disable_windowed_traceback=False,' >> wssshd.spec
@echo ' argv_emulation=False,' >> wssshd.spec
@echo ' target_arch=None,' >> wssshd.spec
@echo ' codesign_identity=None,' >> wssshd.spec
@echo ' entitlements_file=None,' >> wssshd.spec
@echo ')' >> wssshd.spec
override_dh_auto_build: override_dh_auto_build:
# Check if wssshd binary already exists in dist directory # Build wssshd2 C binary
if [ -f ../dist/wssshd ]; then \ cd ../wssshd2 && make clean && make
echo "Using existing wssshd binary from ../dist/wssshd"; \
mkdir -p dist; \
cp ../dist/wssshd dist/wssshd; \
else \
echo "Building PyInstaller binary"; \
pip3 install pyinstaller; \
pyinstaller --clean --onefile wssshd.spec; \
fi
override_dh_auto_install: override_dh_auto_install:
# Create necessary directories # Create necessary directories
...@@ -114,8 +38,8 @@ override_dh_auto_install: ...@@ -114,8 +38,8 @@ override_dh_auto_install:
mkdir -p debian/wsssh-server/usr/sbin mkdir -p debian/wsssh-server/usr/sbin
mkdir -p debian/wsssh-server/etc/logrotate.d mkdir -p debian/wsssh-server/etc/logrotate.d
# Install PyInstaller binary # Install C binary
install -m 755 dist/wssshd debian/wsssh-server/usr/bin/ install -m 755 ../wssshd2/wssshd debian/wsssh-server/usr/bin/
# Install init script # Install init script
install -m 755 ../wssshd.init debian/wsssh-server/etc/init.d/wssshd install -m 755 ../wssshd.init debian/wsssh-server/etc/init.d/wssshd
...@@ -142,6 +66,5 @@ override_dh_auto_install: ...@@ -142,6 +66,5 @@ override_dh_auto_install:
cp -r ../logos/* debian/wsssh-server/usr/share/wsssh/logos/ cp -r ../logos/* debian/wsssh-server/usr/share/wsssh/logos/
override_dh_auto_clean: override_dh_auto_clean:
rm -rf build *.spec rm -rf build *.spec dist
# Don't clean dist directory to preserve existing wssshd binary
dh_auto_clean dh_auto_clean
\ No newline at end of file
...@@ -6,9 +6,14 @@ wssshd \- WebSocket SSH Server daemon for secure tunneling ...@@ -6,9 +6,14 @@ wssshd \- WebSocket SSH Server daemon for secure tunneling
[\fB\-\-config\fR \fIFILE\fR] [\fB\-\-config\fR \fIFILE\fR]
[\fB\-\-host\fR \fIHOST\fR] [\fB\-\-host\fR \fIHOST\fR]
[\fB\-\-port\fR \fIPORT\fR] [\fB\-\-port\fR \fIPORT\fR]
[\fB\-\-ssl\-cert\fR \fIFILE\fR] [\fB\-\-domain\fR \fIDOMAIN\fR]
[\fB\-\-ssl\-key\fR \fIFILE\fR] [\fB\-\-password\fR \fIPASSWORD\fR]
[\fB\-\-web\-host\fR \fIHOST\fR]
[\fB\-\-web\-port\fR \fIPORT\fR]
[\fB\-\-web\-https\fR]
[\fB\-\-debug\fR] [\fB\-\-debug\fR]
[\fB\-\-debug\-web\fR]
[\fB\-\-debug\-database\fR]
[\fB\-\-help\fR] [\fB\-\-help\fR]
.SH DESCRIPTION .SH DESCRIPTION
.B wssshd .B wssshd
...@@ -19,52 +24,82 @@ is a WebSocket SSH server daemon that provides secure tunneling capabilities for ...@@ -19,52 +24,82 @@ is a WebSocket SSH server daemon that provides secure tunneling capabilities for
Configuration file path (default: /etc/wssshd.conf) Configuration file path (default: /etc/wssshd.conf)
.TP .TP
.BR \-\-host " \fIHOST\fR" .BR \-\-host " \fIHOST\fR"
Server bind address (default: 0.0.0.0) WebSocket server bind address (default: 0.0.0.0)
.TP .TP
.BR \-\-port " \fIPORT\fR" .BR \-\-port " \fIPORT\fR"
Server port (default: 9898) WebSocket server port (default: 9898)
.TP .TP
.BR \-\-ssl\-cert " \fIFILE\fR" .BR \-\-domain " \fIDOMAIN\fR"
SSL certificate file path Domain name for the server
.TP .TP
.BR \-\-ssl\-key " \fIFILE\fR" .BR \-\-password " \fIPASSWORD\fR"
SSL private key file path Server password for client authentication
.TP
.BR \-\-web\-host " \fIHOST\fR"
Web interface bind address (default: 127.0.0.1)
.TP
.BR \-\-web\-port " \fIPORT\fR"
Web interface port (default: 8080)
.TP
.B \-\-web\-https
Enable HTTPS for web interface
.TP .TP
.B \-\-debug .B \-\-debug
Enable debug output for troubleshooting Enable general debug output
.TP
.B \-\-debug\-web
Enable web interface debug output
.TP
.B \-\-debug\-database
Enable database operation debug output
.TP .TP
.B \-\-help .B \-\-help
Display help message and exit Display help message and exit
.SH CONFIGURATION .SH CONFIGURATION
The server can be configured through command line options or configuration files. The configuration file supports the following sections and options: The server can be configured through command line options or configuration files. The configuration file supports the following options:
.TP .TP
.B [server] .B host
- \fBhost\fR: Server bind address WebSocket server bind address (default: 0.0.0.0)
- \fBport\fR: Server port .TP
- \fBssl_cert\fR: SSL certificate file .B port
- \fBssl_key\fR: SSL private key file WebSocket server port (default: 9898)
- \fBdebug\fR: Enable debug mode .TP
.TP .B domain
.B [database] Domain name for the server
- \fBpath\fR: SQLite database file path .TP
.TP .B password
.B [web] Server password for client authentication
- \fBusername\fR: Web interface username .TP
- \fBpassword\fR: Web interface password (hashed) .B web_host
- \fBsecret_key\fR: Flask secret key for sessions Web interface bind address (default: 127.0.0.1)
.TP
.B web_port
Web interface port (default: 8080)
.TP
.B web_https
Enable HTTPS for web interface (default: false)
.TP
.B debug
Enable general debug output (default: false)
.TP
.B debug_web
Enable web interface debug output (default: false)
.TP
.B debug_database
Enable database operation debug output (default: false)
.SH EXAMPLES .SH EXAMPLES
.TP .TP
Start server with default configuration: Start server with default configuration:
.B wssshd .B wssshd
.TP .TP
Start server with custom configuration: Start server with custom configuration:
.B wssshd --config /etc/wssshd/custom.conf .B wssshd --config /etc/wssshd.conf
.TP .TP
Start server with debug output: Start server with debug output:
.B wssshd --debug --port 8080 .B wssshd --debug --debug-web --debug-database
.TP .TP
Start server with SSL: Start server with custom web interface:
.B wssshd --ssl-cert /etc/ssl/certs/wssshd.crt --ssl-key /etc/ssl/private/wssshd.key .B wssshd --web-host 0.0.0.0 --web-port 8080 --web-https
.SH WEB INTERFACE .SH WEB INTERFACE
The server provides a web-based management interface accessible at https://server:port/ (when SSL is enabled) or http://server:port/ (without SSL). The web interface allows: The server provides a web-based management interface accessible at https://server:port/ (when SSL is enabled) or http://server:port/ (without SSL). The web interface allows:
- Client registration and management - Client registration and management
...@@ -89,8 +124,11 @@ Reload configuration (if supported) ...@@ -89,8 +124,11 @@ Reload configuration (if supported)
.I /etc/wssshd.conf .I /etc/wssshd.conf
Main configuration file Main configuration file
.TP .TP
.I /var/lib/wssshd/wssshd.db .I ~/.config/wssshd/users.db
SQLite database for client and tunnel data SQLite database for user management (per-user)
.TP
.I /etc/wssshd/users.db
SQLite database for user management (system-wide, when running as root)
.TP .TP
.I /var/log/wssshd/ .I /var/log/wssshd/
Log directory Log directory
......
#!/usr/bin/env python3
"""
WSSSH Daemon (wssshd)
Handles WebSocket connections from clients and wsssh/wsscp applications.
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/>.
"""
# Explicit imports to ensure PyInstaller includes them
import websockets
import websockets.server
import websockets.client
import websockets.exceptions
import websockets.protocol
import websockets.uri
from wsssd import main
if __name__ == '__main__':
main()
\ No newline at end of file
...@@ -46,8 +46,7 @@ const char *get_embedded_asset(const char *path, size_t *size) { ...@@ -46,8 +46,7 @@ const char *get_embedded_asset(const char *path, size_t *size) {
if (size) *size = strlen(terminal_page_html); if (size) *size = strlen(terminal_page_html);
return terminal_page_html; return terminal_page_html;
} else if (strcmp(path, "/users.html") == 0) { } else if (strcmp(path, "/users.html") == 0) {
if (size) *size = strlen(users_page_html); return NULL; // Handled dynamically
return users_page_html;
} 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;
......
...@@ -39,6 +39,7 @@ static void set_default_config(wssshd_config_t *config) { ...@@ -39,6 +39,7 @@ static void set_default_config(wssshd_config_t *config) {
config->web_https = false; config->web_https = false;
config->debug = false; config->debug = false;
config->debug_web = false; config->debug_web = false;
config->debug_database = false;
} }
static void load_config_file(wssshd_config_t *config, const char *config_file) { 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[]) { ...@@ -150,6 +151,7 @@ wssshd_config_t *load_config(int argc, char *argv[]) {
{"web-https", no_argument, 0, 's'}, {"web-https", no_argument, 0, 's'},
{"debug", no_argument, 0, 'D'}, {"debug", no_argument, 0, 'D'},
{"debug-web", no_argument, 0, 'E'}, {"debug-web", no_argument, 0, 'E'},
{"debug-database", no_argument, 0, 'F'},
{"help", no_argument, 0, '?'}, {"help", no_argument, 0, '?'},
{0, 0, 0, 0} {0, 0, 0, 0}
}; };
...@@ -157,7 +159,7 @@ wssshd_config_t *load_config(int argc, char *argv[]) { ...@@ -157,7 +159,7 @@ wssshd_config_t *load_config(int argc, char *argv[]) {
int opt; int opt;
int option_index = 0; 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) { switch (opt) {
case 'c': case 'c':
if (config->config_file) free(config->config_file); if (config->config_file) free(config->config_file);
...@@ -194,6 +196,9 @@ wssshd_config_t *load_config(int argc, char *argv[]) { ...@@ -194,6 +196,9 @@ wssshd_config_t *load_config(int argc, char *argv[]) {
case 'E': case 'E':
config->debug_web = true; config->debug_web = true;
break; break;
case 'F':
config->debug_database = true;
break;
case '?': case '?':
printf("Usage: %s [OPTIONS]\n", argv[0]); printf("Usage: %s [OPTIONS]\n", argv[0]);
printf("Options:\n"); printf("Options:\n");
...@@ -207,6 +212,7 @@ wssshd_config_t *load_config(int argc, char *argv[]) { ...@@ -207,6 +212,7 @@ wssshd_config_t *load_config(int argc, char *argv[]) {
printf(" --web-https Enable HTTPS for web interface\n"); printf(" --web-https Enable HTTPS for web interface\n");
printf(" --debug Enable debug output\n"); printf(" --debug Enable debug output\n");
printf(" --debug-web Enable comprehensive web interface 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"); printf(" --help Show this help\n");
free_config(config); free_config(config);
exit(0); exit(0);
...@@ -280,4 +286,5 @@ void print_config(const wssshd_config_t *config) { ...@@ -280,4 +286,5 @@ void print_config(const wssshd_config_t *config) {
printf(" Web HTTPS: %s\n", config->web_https ? "yes" : "no"); printf(" Web HTTPS: %s\n", config->web_https ? "yes" : "no");
printf(" Debug: %s\n", config->debug ? "yes" : "no"); printf(" Debug: %s\n", config->debug ? "yes" : "no");
printf(" Debug Web: %s\n", config->debug_web ? "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 { ...@@ -34,6 +34,7 @@ typedef struct {
bool web_https; bool web_https;
bool debug; bool debug;
bool debug_web; bool debug_web;
bool debug_database;
} wssshd_config_t; } wssshd_config_t;
// Function declarations // Function declarations
......
...@@ -42,12 +42,12 @@ check_command make ...@@ -42,12 +42,12 @@ check_command make
check_command pkg-config check_command pkg-config
# Check for required libraries # Check for required libraries
if ! pkg-config --exists libssl libcrypto uuid; then if ! pkg-config --exists libssl libcrypto uuid sqlite3; then
echo "Error: Required libraries (libssl, libcrypto, uuid) not found" echo "Error: Required libraries (libssl, libcrypto, uuid, sqlite3) not found"
echo "Please install development packages:" echo "Please install development packages:"
echo " Ubuntu/Debian: sudo apt-get install libssl-dev uuid-dev" echo " Ubuntu/Debian: sudo apt-get install libssl-dev uuid-dev libsqlite3-dev"
echo " CentOS/RHEL: sudo yum install openssl-devel libuuid-devel" echo " CentOS/RHEL: sudo yum install openssl-devel libuuid-devel sqlite-devel"
echo " macOS: brew install openssl ossp-uuid" echo " macOS: brew install openssl ossp-uuid sqlite"
exit 1 exit 1
fi fi
...@@ -58,7 +58,7 @@ cat > Makefile << 'EOF' ...@@ -58,7 +58,7 @@ cat > Makefile << 'EOF'
CC = gcc CC = gcc
CFLAGS = -Wall -Wextra -O2 -I. -pthread CFLAGS = -Wall -Wextra -O2 -I. -pthread
LDFLAGS = -lssl -lcrypto -lm -luuid LDFLAGS = -lssl -lcrypto -lm -luuid -lsqlite3
PREFIX = /usr/local PREFIX = /usr/local
BINDIR = $(PREFIX)/bin BINDIR = $(PREFIX)/bin
MANDIR = $(PREFIX)/share/man MANDIR = $(PREFIX)/share/man
......
#!/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 @@ ...@@ -20,30 +20,6 @@
#ifndef USERS_PAGE_H #ifndef USERS_PAGE_H
#define USERS_PAGE_H #define USERS_PAGE_H
// Users page HTML template // Users page HTML template - now generated dynamically
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>";
#endif /* USERS_PAGE_H */ #endif /* USERS_PAGE_H */
\ No newline at end of file
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