Major refactoring: Extract shared libraries and fix port option behavior

- Extracted ~1500+ lines of duplicate code into shared libraries:
  * wssshlib.h/.c - Shared utilities (config, flags, ports, IDs)
  * websocket.h/.c - WebSocket functions (handshake, framing, messaging)
  * wssh_ssl.h/.c - SSL/TLS setup functions
  * tunnel.h/.c - Tunnel management (setup, reconnection, data handling)

- Fixed critical port option behavior:
  * -p/-P options now correctly specify wssshd server port
  * Removed misleading documentation about options being passed through
  * Updated help text and man pages to reflect correct behavior

- Cleaned up help documentation:
  * Removed duplicate 'Options:' sections from wsssh and wsscp
  * Consolidated all options into single, clean sections

- Updated build system:
  * Modified configure.sh and Makefile for new library structure
  * Proper linking of wsssh and wsscp against shared libraries

- Updated documentation:
  * CHANGELOG.md - Added version 1.4.6 with comprehensive details
  * README.md - Updated architecture and project structure
  * DOCUMENTATION.md - Reflected new library structure
  * Man pages - Corrected port option examples
  * TODO.md - Updated completion status

- Maintained backward compatibility while improving maintainability
parent 34202ca9
......@@ -5,6 +5,41 @@ 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/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.4.6] - 2025-09-16
### Added
- **Code Refactoring and Library Architecture**: Major refactoring to eliminate code duplication
- Created shared libraries: `wssshlib.h/.c`, `websocket.h/.c`, `wssh_ssl.h/.c`, `tunnel.h/.c`
- Extracted ~1500+ lines of duplicate code between wsssh.c and wsscp.c
- Improved maintainability and code organization with modular architecture
- Enhanced build system with proper library dependencies
- **Port Option Behavior Correction**: Fixed critical port option behavior
- `-p`/`-P` options now correctly specify wssshd server port (not SSH/SCP server port)
- Removed misleading "passed through to ssh/scp" documentation
- Updated help text to clearly indicate port options are consumed by wsssh/wsscp
- Fixed argument parsing to properly handle wssshd server port specification
### Changed
- **C Implementation Architecture**: Refactored wsssh.c and wsscp.c to use shared libraries
- Both tools now link against common libraries for WebSocket, SSL, and tunnel management
- Reduced binary size and improved memory efficiency
- Enhanced code reusability and maintainability
- Consistent behavior between wsssh and wsscp implementations
- **Help Documentation**: Updated help output for both wsssh and wsscp
- Removed duplicate "Options:" sections
- Consolidated all options into single, clean "Options:" section
- Corrected port option descriptions to reflect actual behavior
- Improved user experience with clearer documentation
### Technical Details
- **Library Dependencies**: New build system properly links wsssh and wsscp against shared libraries
- **Code Deduplication**: Eliminated redundant implementations of WebSocket, SSL, and tunnel functions
- **Port Handling**: Fixed hostname parsing and port precedence logic
- **Build System**: Updated configure.sh and Makefile to handle new library structure
- **Backward Compatibility**: All existing functionality preserved, new features are additive
## [1.4.5] - 2025-09-16
### Removed
......
......@@ -714,6 +714,14 @@ wsssh/
│ ├── wssshc.c # C client (280 lines)
│ ├── wsssh.c # C SSH wrapper (378 lines)
│ ├── wsscp.c # C SCP wrapper (418 lines)
│ ├── wssshlib.h # Shared utilities library header
│ ├── wssshlib.c # Shared utilities library implementation
│ ├── websocket.h # WebSocket functions library header
│ ├── websocket.c # WebSocket functions library implementation
│ ├── wssh_ssl.h # SSL functions library header
│ ├── wssh_ssl.c # SSL functions library implementation
│ ├── tunnel.h # Tunnel management library header
│ ├── tunnel.c # Tunnel management library implementation
│ ├── configure.sh # Build configuration
│ ├── Makefile # GNU Make build system
│ └── debian/ # Debian packaging for wsssh-tools
......
......@@ -41,20 +41,22 @@ The system consists of components implemented in C for optimal performance:
### C Implementation (Primary)
1. **`wssshc`** - WebSocket SSH Client (registration)
- Registers client machines with the daemon
- Maintains persistent WebSocket connection
- Automatic reconnection with configurable intervals
- Registers client machines with the daemon
- Maintains persistent WebSocket connection
- Automatic reconnection with configurable intervals
2. **`wsssh`** - SSH wrapper with tunneling
- Simplified CLI (no need to specify "ssh" command)
- Parses SSH commands and hostnames intelligently
- Establishes WebSocket tunnels automatically
- Launches SSH to local tunnel port
- Simplified CLI (no need to specify "ssh" command)
- Parses SSH commands and hostnames intelligently
- Establishes WebSocket tunnels automatically
- Launches SSH to local tunnel port
- Uses shared libraries for WebSocket, SSL, and tunnel management
3. **`wsscp`** - SCP wrapper with tunneling
- Simplified CLI (no need to specify "scp" command)
- Similar to wsssh but optimized for SCP operations
- Handles file transfers through secure tunnels
- Simplified CLI (no need to specify "scp" command)
- Similar to wsssh but optimized for SCP operations
- Handles file transfers through secure tunnels
- Uses shared libraries for WebSocket, SSL, and tunnel management
### C Implementation (Alternative)
Located in the `wssshtools/` directory:
......@@ -232,9 +234,9 @@ Port is specified using `-p` (SSH) or `-P` (SCP) options, or from config file.
## Port Detection Priority
1. **Command line option**: `-p <port>` (SSH) or `-P <port>` (SCP)
2. **Config file**: `port` setting in `~/.config/wsssh/wsssh.conf`
3. **Default**: `22` (standard SSH port)
1. **Command line option**: `-p <port>` (SSH) or `-P <port>` (SCP) - specifies wssshd server port
2. **Config file**: `port` setting in `~/.config/wsssh/wsssh.conf` - wssshd server port
3. **Default**: `9898` (wssshd default port)
## Detailed Usage
......@@ -508,6 +510,14 @@ wsssh/
│ ├── wssshc.c # C client for registration
│ ├── wsssh.c # C SSH wrapper
│ ├── wsscp.c # C SCP wrapper
│ ├── wssshlib.h # Shared utilities library header
│ ├── wssshlib.c # Shared utilities library implementation
│ ├── websocket.h # WebSocket functions library header
│ ├── websocket.c # WebSocket functions library implementation
│ ├── wssh_ssl.h # SSL functions library header
│ ├── wssh_ssl.c # SSL functions library implementation
│ ├── tunnel.h # Tunnel management library header
│ ├── tunnel.c # Tunnel management library implementation
│ ├── configure.sh # C build configuration
│ ├── Makefile # C build system
│ └── debian/ # Debian packaging
......@@ -587,7 +597,51 @@ Your support helps us continue developing and maintaining this open-source proje
## Changelog
### Version 1.4.4 (Latest)
### Version 1.4.6 (Latest)
**Major Refactoring:**
- **Code Architecture Overhaul**: Major refactoring to eliminate code duplication
- Created shared libraries: `wssshlib.h/.c`, `websocket.h/.c`, `wssh_ssl.h/.c`, `tunnel.h/.c`
- Extracted ~1500+ lines of duplicate code between wsssh.c and wsscp.c
- Improved maintainability and code organization with modular architecture
- Enhanced build system with proper library dependencies
**Critical Bug Fixes:**
- **Port Option Behavior Correction**: Fixed critical port option behavior
- `-p`/`-P` options now correctly specify wssshd server port (not SSH/SCP server port)
- Removed misleading "passed through to ssh/scp" documentation
- Updated help text to clearly indicate port options are consumed by wsssh/wsscp
- Fixed argument parsing to properly handle wssshd server port specification
**User Experience Improvements:**
- **Help Documentation Cleanup**: Fixed duplicate "Options:" sections in help output
- Removed duplicate "Options:" sections from both wsssh and wsscp help
- Consolidated all options into single, clean "Options:" section
- Improved user experience with clearer documentation
**Technical Improvements:**
- **Library Dependencies**: New build system properly links wsssh and wsscp against shared libraries
- **Code Deduplication**: Eliminated redundant implementations of WebSocket, SSL, and tunnel functions
- **Port Handling**: Fixed hostname parsing and port precedence logic
- **Build System**: Updated configure.sh and Makefile to handle new library structure
- **Backward Compatibility**: All existing functionality preserved, new features are additive
### Version 1.4.5
**Removed:**
- **Python Implementations**: Removed Python implementations of wssshc, wsssh, and wsscp
- Deleted wssshc.py, wsssh.py, and wsscp.py files
- Removed pyinstaller commands from build.sh for these tools
- Updated Debian packaging to exclude Python script installations
- Cleaned up documentation and project structure references
- Maintained C implementations in wssshtools/ directory
**Changed:**
- **Build System**: Updated build.sh to only build C tools and wssshd daemon
- **Documentation**: Updated README.md, DOCUMENTATION.md, and project structure to reflect C-only implementations
- **Debian Packaging**: Modified wssshtools/debian/rules to only install wssshd.py and C tools
### Version 1.4.4
**New Features:**
- **Dynamic Terminal Sizing**: Web terminal automatically adjusts to browser window dimensions
......
# WebSocket SSH - Future Enhancements Roadmap
## Recently Completed (v1.4.6)
- [x] **Code Refactoring and Library Architecture**: Major refactoring to eliminate code duplication
- Created shared libraries: `wssshlib.h/.c`, `websocket.h/.c`, `wssh_ssl.h/.c`, `tunnel.h/.c`
- Extracted ~1500+ lines of duplicate code between wsssh.c and wsscp.c
- Improved maintainability and code organization with modular architecture
- Enhanced build system with proper library dependencies
- [x] **Port Option Behavior Correction**: Fixed critical port option behavior
- `-p`/`-P` options now correctly specify wssshd server port (not SSH/SCP server port)
- Removed misleading "passed through to ssh/scp" documentation
- Updated help text to clearly indicate port options are consumed by wsssh/wsscp
- Fixed argument parsing to properly handle wssshd server port specification
- [x] **Help Documentation Cleanup**: Fixed duplicate "Options:" sections in help output
- Removed duplicate "Options:" sections from both wsssh and wsscp help
- Consolidated all options into single, clean "Options:" section
- Improved user experience with clearer documentation
- [x] **Documentation Updates**: Updated CHANGELOG.md, README.md, DOCUMENTATION.md, and man pages for version 1.4.6
## Recently Completed (v1.4.5)
- [x] **Python Implementation Removal**: Removed Python implementations of wssshc, wsssh, and wsscp
- Deleted wssshc.py, wsssh.py, and wsscp.py files
- Removed pyinstaller commands from build.sh for these tools
- Updated Debian packaging to exclude Python script installations
- Cleaned up documentation and project structure references
- Maintained C implementations in wssshtools/ directory
### Changed
- [x] **Build System**: Updated build.sh to only build C tools and wssshd daemon
- [x] **Documentation**: Updated README.md, DOCUMENTATION.md, and project structure to reflect C-only implementations
- [x] **Debian Packaging**: Modified wssshtools/debian/rules to only install wssshd.py and C tools
## Recently Completed (v1.4.4)
- [x] **Dynamic Terminal Sizing**: Implemented proper terminal dimension calculation and transmission
- [x] **Terminal Resize Support**: Added real-time terminal resizing when browser window changes
......
......@@ -25,6 +25,7 @@ BUILD_SERVER_ONLY=false
BUILD_NO_SERVER=false
BUILD_WSSSHTOOLS_ONLY=false
BUILD_PACKAGES=false
BUILD_CLEAN=false
while [[ $# -gt 0 ]]; do
case $1 in
--debian)
......@@ -54,6 +55,10 @@ while [[ $# -gt 0 ]]; do
BUILD_WSSSHTOOLS_ONLY=true
shift
;;
--clean)
BUILD_CLEAN=true
shift
;;
--help|-h)
echo "Usage: $0 [options]"
echo "Options:"
......@@ -63,12 +68,13 @@ while [[ $# -gt 0 ]]; do
echo " --server-only Build only the server (wssshd) and wsssh-server Debian package"
echo " --no-server Skip building the server (wssshd) and wsssh-server package"
echo " --wssshtools-only Build only the C tools (wssshtools) and wsssh-tools package"
echo " --clean Clean build artifacts (equivalent to ./clean.sh)"
echo " --help, -h Show this help"
exit 0
;;
*)
echo "Unknown option: $1"
echo "Usage: $0 [--debian] [--debian-only] [--packages] [--server-only] [--no-server] [--wssshtools-only] [--help]"
echo "Usage: $0 [--debian] [--debian-only] [--packages] [--server-only] [--no-server] [--wssshtools-only] [--clean] [--help]"
echo "Try '$0 --help' for more information."
exit 1
;;
......@@ -76,6 +82,81 @@ while [[ $# -gt 0 ]]; do
done
# Handle clean option first
if [ "$BUILD_CLEAN" = true ]; then
echo "Cleaning build artifacts..."
# Remove PyInstaller build artifacts
rm -rf build/
rm -rf dist/
rm -f *.spec
rm -f wssshd # Remove PyInstaller binary
# Remove virtual environment
rm -rf venv/
# Remove SSL certificates
rm -f cert.pem key.pem
# Remove logos and icons
rm -rf logos/
# Remove C version build artifacts
if [ -d "wssshtools" ]; then
cd wssshtools
make clean 2>/dev/null || true
rm -f Makefile
rm -f configure.sh.stamp
rm -f man/*.1.gz 2>/dev/null || true
cd ..
fi
# Remove Debian packaging artifacts
# wsssh-server packages
rm -f dist/wsssh-server*.deb
rm -f dist/wsssh-server*.dsc
rm -f dist/wsssh-server*.tar.gz
rm -f dist/wsssh-server*.changes
rm -f dist/wsssh-server*.buildinfo
rm -f wsssh-server*.deb
rm -f wsssh-server*.dsc
rm -f wsssh-server*.tar.gz
rm -f wsssh-server*.changes
rm -f wsssh-server*.buildinfo
# wsssh-tools packages
rm -f dist/wsssh-tools*.deb
rm -f dist/wsssh-tools*.dsc
rm -f dist/wsssh-tools*.tar.gz
rm -f dist/wsssh-tools*.changes
rm -f dist/wsssh-tools*.buildinfo
rm -f wsssh-tools*.deb
rm -f wsssh-tools*.dsc
rm -f wsssh-tools*.tar.gz
rm -f wsssh-tools*.changes
rm -f wsssh-tools*.buildinfo
# Remove Debian build directory and artifacts
if [ -d "wsssh-server" ]; then
rm -rf wsssh-server/debian/wsssh-server/
rm -f wsssh-server/debian/files
rm -f wsssh-server/debian/*.debhelper*
rm -f wsssh-server/debian/*.substvars
rm -f wsssh-server/debian/debhelper-build-stamp
fi
if [ -d "wssshtools" ]; then
rm -rf wssshtools/debian/wsssh-tools/
rm -f wssshtools/debian/files
rm -f wssshtools/debian/*.debhelper*
rm -f wssshtools/debian/*.substvars
rm -f wssshtools/debian/debhelper-build-stamp
fi
echo "Clean complete. All build artifacts removed."
exit 0
fi
# Create dist directory if not exists
mkdir -p dist
......@@ -99,7 +180,7 @@ if [ "$BUILD_DEBIAN_ONLY" = false ] && [ "$BUILD_WSSSHTOOLS_ONLY" = false ]; the
# Generate SSL certificates if they don't exist
if [ ! -f "cert.pem" ] || [ ! -f "key.pem" ]; then
echo "Generating SSL certificates..."
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost"
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 36500 -nodes -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost"
fi
# Generate logos and icons from image.jpg if it exists
......@@ -136,7 +217,8 @@ if [ "$BUILD_DEBIAN_ONLY" = false ] && [ "$BUILD_WSSSHTOOLS_ONLY" = false ]; the
# Build client binaries
if [ "$BUILD_SERVER_ONLY" = false ]; then
# Client binaries are not built in this version
echo "Client binaries not built in this version"
fi
fi
......
......@@ -18,65 +18,5 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# Remove PyInstaller build artifacts
rm -rf build/
rm -rf dist/
rm -f *.spec
# Remove C version build artifacts
if [ -d "wssshtools" ]; then
cd wssshtools
make clean 2>/dev/null || true
rm -f Makefile
rm -f configure.sh.stamp
rm -f man/*.1.gz 2>/dev/null || true
cd ..
fi
# Remove Debian packaging artifacts
# wsssh-server packages
rm -f dist/wsssh-server*.deb
rm -f dist/wsssh-server*.dsc
rm -f dist/wsssh-server*.tar.gz
rm -f dist/wsssh-server*.changes
rm -f dist/wsssh-server*.buildinfo
rm -f wsssh-server*.deb
rm -f wsssh-server*.dsc
rm -f wsssh-server*.tar.gz
rm -f wsssh-server*.changes
rm -f wsssh-server*.buildinfo
# wsssh-tools packages
rm -f dist/wsssh-tools*.deb
rm -f dist/wsssh-tools*.dsc
rm -f dist/wsssh-tools*.tar.gz
rm -f dist/wsssh-tools*.changes
rm -f dist/wsssh-tools*.buildinfo
rm -f wsssh-tools*.deb
rm -f wsssh-tools*.dsc
rm -f wsssh-tools*.tar.gz
rm -f wsssh-tools*.changes
rm -f wsssh-tools*.buildinfo
# Remove Debian build directory and artifacts
if [ -d "wsssh-server" ]; then
rm -rf wsssh-server/debian/wsssh-server/
rm -f wsssh-server/debian/files
rm -f wsssh-server/debian/*.debhelper*
rm -f wsssh-server/debian/*.substvars
rm -f wsssh-server/debian/debhelper-build-stamp
fi
if [ -d "wssshtools" ]; then
rm -rf wssshtools/debian/wsssh-tools/
rm -f wssshtools/debian/files
rm -f wssshtools/debian/*.debhelper*
rm -f wssshtools/debian/*.substvars
rm -f wssshtools/debian/debhelper-build-stamp
fi
# Optionally remove SSL certificates (uncomment if needed)
# rm -f cert.pem key.pem
echo "Clean complete. Build artifacts removed."
echo "Note: SSL certificates (cert.pem, key.pem) preserved. Uncomment lines in clean.sh to remove them."
\ No newline at end of file
# Use build.sh --clean for consistent cleaning
./build.sh --clean
\ No newline at end of file
logos/banner-800x200.png

52.7 KB | W: | H:

logos/banner-800x200.png

52.7 KB | W: | H:

logos/banner-800x200.png
logos/banner-800x200.png
logos/banner-800x200.png
logos/banner-800x200.png
  • 2-up
  • Swipe
  • Onion skin
logos/icon-128.png

23.2 KB | W: | H:

logos/icon-128.png

23.2 KB | W: | H:

logos/icon-128.png
logos/icon-128.png
logos/icon-128.png
logos/icon-128.png
  • 2-up
  • Swipe
  • Onion skin
logos/icon-16.png

2.34 KB | W: | H:

logos/icon-16.png

2.34 KB | W: | H:

logos/icon-16.png
logos/icon-16.png
logos/icon-16.png
logos/icon-16.png
  • 2-up
  • Swipe
  • Onion skin
logos/icon-256.png

84.6 KB | W: | H:

logos/icon-256.png

84.6 KB | W: | H:

logos/icon-256.png
logos/icon-256.png
logos/icon-256.png
logos/icon-256.png
  • 2-up
  • Swipe
  • Onion skin
logos/icon-32.png

3.24 KB | W: | H:

logos/icon-32.png

3.24 KB | W: | H:

logos/icon-32.png
logos/icon-32.png
logos/icon-32.png
logos/icon-32.png
  • 2-up
  • Swipe
  • Onion skin
logos/icon-48.png

4.91 KB | W: | H:

logos/icon-48.png

4.91 KB | W: | H:

logos/icon-48.png
logos/icon-48.png
logos/icon-48.png
logos/icon-48.png
  • 2-up
  • Swipe
  • Onion skin
logos/icon-64.png

7.23 KB | W: | H:

logos/icon-64.png

7.23 KB | W: | H:

logos/icon-64.png
logos/icon-64.png
logos/icon-64.png
logos/icon-64.png
  • 2-up
  • Swipe
  • Onion skin
logos/logo-128.png

23.2 KB | W: | H:

logos/logo-128.png

23.2 KB | W: | H:

logos/logo-128.png
logos/logo-128.png
logos/logo-128.png
logos/logo-128.png
  • 2-up
  • Swipe
  • Onion skin
logos/logo-256.png

84.6 KB | W: | H:

logos/logo-256.png

84.6 KB | W: | H:

logos/logo-256.png
logos/logo-256.png
logos/logo-256.png
logos/logo-256.png
  • 2-up
  • Swipe
  • Onion skin
logos/logo-512.png

307 KB | W: | H:

logos/logo-512.png

307 KB | W: | H:

logos/logo-512.png
logos/logo-512.png
logos/logo-512.png
logos/logo-512.png
  • 2-up
  • Swipe
  • Onion skin
logos/logo-64.png

7.23 KB | W: | H:

logos/logo-64.png

7.23 KB | W: | H:

logos/logo-64.png
logos/logo-64.png
logos/logo-64.png
logos/logo-64.png
  • 2-up
  • Swipe
  • Onion skin
wsssh-server (1.4.3-1) unstable; urgency=medium
wsssh-server (1.4.4-1) unstable; urgency=medium
* New upstream release 1.4.3
* Fixed Debian package dependencies - removed unnecessary Python packages
* New upstream release 1.4.4
* Dynamic terminal sizing for web interface
* Real-time terminal resize support
* Force echo mode for SSH connections
* Enhanced logging with logrotate integration
* Fixed terminal dimensions and echo handling
* Standalone PyInstaller binary with bundled dependencies
* Fixed Makefile syntax errors and directory creation issues
* Added binary reuse logic for efficient package building
......
# Automatically added by dh_installsystemd/13.26
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then
# The following line should be removed in trixie or trixie+1
deb-systemd-helper unmask 'wssshd.service' >/dev/null || true
# was-enabled defaults to true, so new installations run enable.
if deb-systemd-helper --quiet was-enabled 'wssshd.service'; then
# Enables the unit on first installation, creates new
# symlinks on upgrades if the unit file has changed.
deb-systemd-helper enable 'wssshd.service' >/dev/null || true
else
# Update the statefile to add new symlinks (if any), which need to be
# cleaned up on purge. Also remove old symlinks.
deb-systemd-helper update-state 'wssshd.service' >/dev/null || true
fi
fi
# End automatically added section
# Automatically added by dh_installsystemd/13.26
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then
if [ -d /run/systemd/system ]; then
systemctl --system daemon-reload >/dev/null || true
if [ -n "$2" ]; then
_dh_action=restart
else
_dh_action=start
fi
deb-systemd-invoke $_dh_action 'wssshd.service' >/dev/null || true
fi
fi
# End automatically added section
# Automatically added by dh_installsystemd/13.26
if [ -z "$DPKG_ROOT" ] && [ "$1" = remove ] && [ -d /run/systemd/system ] ; then
deb-systemd-invoke stop 'wssshd.service' >/dev/null || true
fi
# End automatically added section
wsssh-server_1.4.4-1_amd64.buildinfo net optional
wsssh-server_1.4.4-1_amd64.deb net optional
# Automatically added by dh_installsystemd/13.26
if [ "$1" = remove ] && [ -d /run/systemd/system ] ; then
systemctl --system daemon-reload >/dev/null || true
fi
# End automatically added section
# Automatically added by dh_installsystemd/13.26
if [ "$1" = "purge" ]; then
if [ -x "/usr/bin/deb-systemd-helper" ]; then
deb-systemd-helper purge 'wssshd.service' >/dev/null || true
fi
fi
# End automatically added section
# Automatically added by dh_installdebconf/13.26
if [ "$1" = purge ] && [ -e /usr/share/debconf/confmodule ]; then
. /usr/share/debconf/confmodule
db_purge
fi
# End automatically added section
misc:Depends=debconf (>= 0.5) | debconf-2.0
shlibs:Depends=libc6 (>= 2.14), zlib1g (>= 1:1.1.4)
misc:Pre-Depends=
/etc/default/wssshd
/etc/init.d/wssshd
/etc/logrotate.d/wssshd
Package: wsssh-server
Version: 1.4.4-1
Architecture: amd64
Maintainer: Stefy Lanza <stefy@nexlab.net>
Installed-Size: 26704
Depends: libc6 (>= 2.14), zlib1g (>= 1:1.1.4), debconf (>= 0.5) | debconf-2.0, adduser
Section: net
Priority: optional
Homepage: https://git.nexlab.net/nexlab/wsssh
Description: WebSocket SSH Server (wssshd)
A modern SSH tunneling system that provides WebSocket-based SSH/SCP access
to registered client machines. This package contains the server component
that handles WebSocket connections and manages SSH tunnels.
.
This package includes a standalone PyInstaller binary that bundles all
required dependencies, eliminating the need for external Python packages.
.
The wssshd server provides:
- WebSocket SSH tunnel management
- Client registration and authentication
- Web-based management interface
- Secure tunnel establishment between clients and servers
- High availability with watchdog monitoring
.
This is the server component of the WebSocket SSH system.
5b28c390af6ad414bbc6d868f644c127 lib/systemd/system/wssshd.service
d438ff148b99e19c3435b1fe36f24213 usr/bin/wssshd
0ca10988f88229f2118de364159866bd usr/sbin/wssshd-watchdog
81d71d8ec53e6a9ea3daad365eadfb55 usr/share/doc/wsssh-server/changelog.Debian.gz
929c14c9c965f9fca6385eb474c66835 usr/share/doc/wsssh-server/copyright
6771c7ff8f7526f370e92b5234b6611f usr/share/man/man1/wssshd.1.gz
5544f281baa6984b433fe312936524ad usr/share/wsssh/logos/banner-800x200.png
fbae8873c0f4be974ba0710a4431e55c usr/share/wsssh/logos/favicon.ico
52653de9c01c19cd8ebc223a75979736 usr/share/wsssh/logos/icon-128.png
8341d1c90883c14f96fc3c931bff98aa usr/share/wsssh/logos/icon-16.png
0127cf332e8910251c2f8ce1b37af725 usr/share/wsssh/logos/icon-256.png
0985ae2de553ed4628c8a2244922229e usr/share/wsssh/logos/icon-32.png
bcfca95aa4a79b07c1b609d8d91c01f8 usr/share/wsssh/logos/icon-48.png
a6867c9fabaa85db279580b874ba26fb usr/share/wsssh/logos/icon-64.png
52653de9c01c19cd8ebc223a75979736 usr/share/wsssh/logos/logo-128.png
0127cf332e8910251c2f8ce1b37af725 usr/share/wsssh/logos/logo-256.png
283f0c055eff7e299d06120d5913a406 usr/share/wsssh/logos/logo-512.png
a6867c9fabaa85db279580b874ba26fb usr/share/wsssh/logos/logo-64.png
bd58776dc9820c39a46fd51316461fe2 usr/share/wsssh/logos/logo-high-quality.png
07ebae287e8a4882deb22c0223aa6310 usr/share/wsssh/templates/base.html
b98475a970285d499246a8627535a4cf usr/share/wsssh/templates/index.html
dd0dcf5219c6ab94aba6ca0da3186e1e usr/share/wsssh/templates/login.html
f3c419734db44fa03bfaeac866e8287b usr/share/wsssh/templates/terminal.html
42130d3c1a8324daa7cacc004a512650 usr/share/wsssh/templates/users.html
d942cd2a28bf0beee87fe688be0ecc2d usr/share/wsssh/wssshd.conf.example
#!/bin/sh
# postinst script for wsssh-server
set -e
# summary of how this script can be called:
# * <postinst> `configure' <most-recently-configured-version>
# * <old-postinst> `abort-upgrade' <new version>
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
# <new-version>
# * <postinst> `abort-remove'
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
# <package-being-installed> <version> `removing'
# <conflicting-package> <version>
# for details, see http://www.debian.org/doc/debian-policy/ or
# the debian-policy package
case "$1" in
configure)
# Create wssshd user and group if they don't exist
if ! getent group wssshd >/dev/null 2>&1; then
addgroup --system wssshd
fi
if ! getent passwd wssshd >/dev/null 2>&1; then
adduser --system --ingroup wssshd --home /var/lib/wssshd \
--no-create-home --shell /bin/false wssshd
fi
# Create wssshd home directory
if [ ! -d /var/lib/wssshd ]; then
mkdir -p /var/lib/wssshd
fi
# Ensure wssshd user owns its home directory and can write to it
chown wssshd:wssshd /var/lib/wssshd
chmod 755 /var/lib/wssshd
# Ensure the user can actually write to its home directory
# Try to create a test file to verify write permissions
if ! su -s /bin/sh wssshd -c "touch /var/lib/wssshd/.test_write 2>/dev/null && rm /var/lib/wssshd/.test_write 2>/dev/null" 2>/dev/null; then
echo "Warning: wssshd user cannot write to /var/lib/wssshd, fixing permissions"
# Try to fix permissions by making directory writable
chmod 775 /var/lib/wssshd
# Also ensure the user is in the right group
usermod -g wssshd wssshd 2>/dev/null || true
fi
# Create log directory
if [ ! -d /var/log/wssshd ]; then
mkdir -p /var/log/wssshd
chown wssshd:wssshd /var/log/wssshd
chmod 755 /var/log/wssshd
fi
# Create configuration directory
if [ ! -d /etc/wssshd ]; then
mkdir -p /etc/wssshd
chown wssshd:wssshd /etc/wssshd
chmod 755 /etc/wssshd
fi
# Create /etc/default/wssshd if it doesn't exist
if [ ! -f /etc/default/wssshd ]; then
cat > /etc/default/wssshd << EOF
# WebSocket SSH Server (wssshd) configuration
# Set to Y, 1, TRUE, true, YES, or yes to enable the service
START=no
# Additional configuration can be done in /etc/wssshd.conf
EOF
chmod 644 /etc/default/wssshd
fi
# Create example configuration file if it doesn't exist
if [ ! -f /etc/wssshd.conf.example ]; then
if [ -f /usr/share/wsssh/wssshd.conf.example ]; then
cp /usr/share/wsssh/wssshd.conf.example /etc/wssshd.conf.example
chmod 644 /etc/wssshd.conf.example
fi
fi
# Set up init script
if [ -x /etc/init.d/wssshd ]; then
update-rc.d wssshd defaults >/dev/null 2>&1 || true
fi
# Enable systemd service if available and sysv init is not active
if [ -f /lib/systemd/system/wssshd.service ] && command -v systemctl >/dev/null 2>&1; then
if ! systemctl is-active wssshd >/dev/null 2>&1 && [ ! -f /var/run/wssshd-watchdog.pid ]; then
systemctl enable wssshd.service >/dev/null 2>&1 || true
fi
fi
# Set proper permissions on binary
if [ -f /usr/bin/wssshd ]; then
chown wssshd:wssshd /usr/bin/wssshd
chmod 755 /usr/bin/wssshd
fi
# Install watchdog script
if [ -f /usr/sbin/wssshd-watchdog ]; then
chown wssshd:wssshd /usr/sbin/wssshd-watchdog
chmod 755 /usr/sbin/wssshd-watchdog
fi
# Create database directory if it doesn't exist
if [ ! -d /var/lib/wssshd/db ]; then
mkdir -p /var/lib/wssshd/db
chown wssshd:wssshd /var/lib/wssshd/db
chmod 755 /var/lib/wssshd/db
fi
# Restart service if it was running before upgrade
if [ -f /tmp/wsssh-server-upgrade-state ]; then
. /tmp/wsssh-server-upgrade-state
rm -f /tmp/wsssh-server-upgrade-state
if [ "$WSSSHD_WAS_RUNNING" = "1" ]; then
echo "Restarting wssshd service after upgrade..."
if [ -x /etc/init.d/wssshd ]; then
/etc/init.d/wssshd start || true
fi
fi
fi
;;
abort-upgrade|abort-remove|abort-deconfigure)
;;
*)
echo "postinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
# Automatically added by dh_installsystemd/13.26
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then
# The following line should be removed in trixie or trixie+1
deb-systemd-helper unmask 'wssshd.service' >/dev/null || true
# was-enabled defaults to true, so new installations run enable.
if deb-systemd-helper --quiet was-enabled 'wssshd.service'; then
# Enables the unit on first installation, creates new
# symlinks on upgrades if the unit file has changed.
deb-systemd-helper enable 'wssshd.service' >/dev/null || true
else
# Update the statefile to add new symlinks (if any), which need to be
# cleaned up on purge. Also remove old symlinks.
deb-systemd-helper update-state 'wssshd.service' >/dev/null || true
fi
fi
# End automatically added section
# Automatically added by dh_installsystemd/13.26
if [ "$1" = "configure" ] || [ "$1" = "abort-upgrade" ] || [ "$1" = "abort-deconfigure" ] || [ "$1" = "abort-remove" ] ; then
if [ -d /run/systemd/system ]; then
systemctl --system daemon-reload >/dev/null || true
if [ -n "$2" ]; then
_dh_action=restart
else
_dh_action=start
fi
deb-systemd-invoke $_dh_action 'wssshd.service' >/dev/null || true
fi
fi
# End automatically added section
exit 0
\ No newline at end of file
#!/bin/sh
# postrm script for wsssh-server
set -e
# summary of how this script can be called:
# * <postrm> `remove'
# * <postrm> `purge'
# * <old-postrm> `upgrade' <new-version>
# * <new-postrm> `failed-upgrade' <old-version>
# * <new-postrm> `abort-install'
# * <new-postrm> `abort-install' <old-version>
# * <new-postrm> `abort-upgrade' <old-version>
# * <disappearer's-postrm> `disappear' <overwriter>
# <overwriter-version>
# for details, see http://www.debian.org/doc/debian-policy/ or
# the debian-policy package
case "$1" in
purge|remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
# Stop the service if it's running
if [ -x /etc/init.d/wssshd ]; then
invoke-rc.d wssshd stop >/dev/null 2>&1 || true
fi
# Remove init script symlinks
if [ -x /etc/init.d/wssshd ]; then
update-rc.d wssshd remove >/dev/null 2>&1 || true
fi
# Remove user and group on purge
if [ "$1" = "purge" ]; then
# Remove wssshd user and group
if getent passwd wssshd >/dev/null 2>&1; then
deluser wssshd || true
fi
if getent group wssshd >/dev/null 2>&1; then
delgroup wssshd || true
fi
# Remove configuration and data directories
rm -rf /var/lib/wssshd
rm -rf /var/log/wssshd
rm -rf /etc/wssshd
# Remove configuration files
rm -f /etc/default/wssshd
rm -f /etc/wssshd.conf
rm -f /etc/wssshd.conf.example
fi
;;
*)
echo "postrm called with unknown argument \`$1'" >&2
exit 1
;;
esac
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
# Automatically added by dh_installsystemd/13.26
if [ "$1" = remove ] && [ -d /run/systemd/system ] ; then
systemctl --system daemon-reload >/dev/null || true
fi
# End automatically added section
# Automatically added by dh_installsystemd/13.26
if [ "$1" = "purge" ]; then
if [ -x "/usr/bin/deb-systemd-helper" ]; then
deb-systemd-helper purge 'wssshd.service' >/dev/null || true
fi
fi
# End automatically added section
# Automatically added by dh_installdebconf/13.26
if [ "$1" = purge ] && [ -e /usr/share/debconf/confmodule ]; then
. /usr/share/debconf/confmodule
db_purge
fi
# End automatically added section
exit 0
\ No newline at end of file
#!/bin/sh
# preinst script for wsssh-server
set -e
# summary of how this script can be called:
# * <new-preinst> `install'
# * <new-preinst> `install' <old-version>
# * <new-preinst> `upgrade' <old-version>
# * <old-preinst> `abort-upgrade' <new-version>
# for details, see http://www.debian.org/doc/debian-policy/ or
# the debian-policy package
WSSSHD_RUNNING=0
case "$1" in
install|upgrade)
# Check if wssshd service is running before upgrade
if [ -x /etc/init.d/wssshd ]; then
if /etc/init.d/wssshd status >/dev/null 2>&1; then
WSSSHD_RUNNING=1
# Check if we're in a non-interactive environment
if [ -n "$DEBIAN_FRONTEND" ] && [ "$DEBIAN_FRONTEND" = "noninteractive" ]; then
echo "Non-interactive environment detected, stopping wssshd service automatically..."
/etc/init.d/wssshd stop || true
sleep 2
else
# Use debconf to ask user for confirmation
. /usr/share/debconf/confmodule
db_input medium wsssh-server/stop-service || true
db_go || true
# Get the user's answer
db_get wsssh-server/stop-service
if [ "$RET" = "true" ] || [ -z "$RET" ]; then
echo "Stopping wssshd service for upgrade..."
/etc/init.d/wssshd stop || true
# Give it a moment to fully stop
sleep 2
else
echo "User declined to stop wssshd service. Upgrade may fail."
echo "You can manually stop the service with: sudo /etc/init.d/wssshd stop"
echo "Then retry the upgrade."
exit 1
fi
fi
fi
fi
;;
abort-upgrade)
;;
*)
echo "preinst called with unknown argument \`$1'" >&2
exit 1
;;
esac
# Store the running state for postinst
if [ "$WSSSHD_RUNNING" = "1" ]; then
echo "WSSSHD_WAS_RUNNING=1" > /tmp/wsssh-server-upgrade-state
else
echo "WSSSHD_WAS_RUNNING=0" > /tmp/wsssh-server-upgrade-state
fi
# dh_installdeb will replace this with shell code automatically
# generated by other debhelper scripts.
exit 0
\ No newline at end of file
#!/bin/sh
set -e
# Automatically added by dh_installsystemd/13.26
if [ -z "$DPKG_ROOT" ] && [ "$1" = remove ] && [ -d /run/systemd/system ] ; then
deb-systemd-invoke stop 'wssshd.service' >/dev/null || true
fi
# End automatically added section
Template: wsssh-server/stop-service
Type: boolean
Default: true
Description: Stop wssshd service during upgrade?
The wssshd service is currently running. To upgrade the package safely,
the service needs to be stopped temporarily. It will be restarted
automatically after the upgrade is complete.
.
Should the wssshd service be stopped now?
.
This question will be skipped in non-interactive environments, and the
service will be stopped automatically for safe upgrades.
\ No newline at end of file
# WebSocket SSH Server (wssshd) configuration
# Set to Y, 1, TRUE, true, YES, or yes to enable the service
START=no
# Additional configuration can be done in /etc/wssshd.conf
\ No newline at end of file
#!/bin/bash
#
# wssshd Startup script for WebSocket SSH Daemon
#
# chkconfig: 345 85 15
# description: WebSocket SSH Daemon - Handles WebSocket connections from wsssh/wsscp clients
# processname: wssshd
# pidfile: /var/run/wssshd.pid
# config: /etc/wssshd.conf
### BEGIN INIT INFO
# Provides: wssshd
# Required-Start: $local_fs $network $syslog
# Required-Stop: $local_fs $network $syslog
# Default-Start: 3 4 5
# Default-Stop: 0 1 2 6
# Short-Description: WebSocket SSH Daemon
# Description: WebSocket SSH Daemon handles WebSocket connections from wsssh/wsscp clients
### END INIT INFO
# Source function library (Debian/Ubuntu)
. /lib/lsb/init-functions
# Configuration
NAME="wssshd"
DAEMON="/usr/bin/wssshd"
WATCHDOG="/usr/sbin/wssshd-watchdog"
PIDFILE="/var/run/wssshd.pid"
WATCHDOG_PIDFILE="/var/run/wssshd-watchdog.pid"
CONFIG="/etc/wssshd.conf"
LOG_FACILITY="daemon"
USER="wssshd"
GROUP="wssshd"
# Check if we're running as root
if [ $(id -u) != 0 ]; then
echo "Error: This script must be run as root"
exit 1
fi
# Function to check if process is running
is_running() {
if [ -f "$PIDFILE" ]; then
local pid=$(cat "$PIDFILE")
if [ -d "/proc/$pid" ]; then
return 0
else
# Stale PID file
rm -f "$PIDFILE"
return 1
fi
fi
return 1
}
# Function to start the daemon
start() {
echo -n "Starting $NAME: "
# Check if daemon is already running
if is_running; then
local pid=$(cat "$PIDFILE")
echo "already running (PID: $pid)"
return 0
fi
# Check if config file exists
if [ ! -f "$CONFIG" ]; then
echo "configuration file $CONFIG not found"
echo "Please create $CONFIG with the required settings."
echo "You can use the example configuration as a template:"
echo " cp /usr/share/wsssh/wssshd.conf.example $CONFIG"
return 1
fi
# Check if daemon executable exists
if [ ! -x "$DAEMON" ]; then
echo "daemon executable $DAEMON not found or not executable"
return 1
fi
# Create necessary directories
mkdir -p /var/run /var/log/wssshd
chown $USER:$GROUP /var/run /var/log/wssshd 2>/dev/null || true
# Start the daemon directly
echo "Starting daemon..."
if start-stop-daemon --start --quiet --pidfile "$PIDFILE" \
--chuid $USER:$GROUP --background --make-pidfile \
--exec "$DAEMON" --output /var/log/wssshd/wssshd.log; then
echo "Daemon started successfully"
else
echo "start-stop-daemon failed, trying direct execution..."
if "$DAEMON" >> /var/log/wssshd/wssshd.log 2>&1 &
then
echo $! > "$PIDFILE"
echo "Daemon started successfully (direct execution)"
else
echo "FAILED"
echo "Could not start daemon"
return 1
fi
fi
# Wait for daemon to be fully running
local count=0
while [ $count -lt 10 ] && ! is_running; do
echo "Waiting for daemon to start... ($count/10)"
sleep 1
count=$((count + 1))
done
if is_running; then
local pid=$(cat "$PIDFILE")
echo "Daemon is running (PID: $pid)"
# Stop any existing watchdog before starting a new one
if [ -f "$WATCHDOG_PIDFILE" ] || pgrep -f "wssshd-watchdog" >/dev/null 2>&1; then
echo "Stopping existing watchdog..."
$WATCHDOG stop >/dev/null 2>&1
sleep 2
fi
# Now start the watchdog to monitor the running daemon
echo "Starting watchdog..."
if [ -x "$WATCHDOG" ]; then
$WATCHDOG start >/dev/null 2>&1
# Wait for watchdog PID file to be created (max 5 seconds)
local count=0
while [ $count -lt 10 ] && [ ! -f "$WATCHDOG_PIDFILE" ]; do
sleep 0.5
count=$((count + 1))
done
if [ -f "$WATCHDOG_PIDFILE" ]; then
watchdog_pid=$(cat "$WATCHDOG_PIDFILE" 2>/dev/null)
if [ -n "$watchdog_pid" ] && kill -0 "$watchdog_pid" 2>/dev/null; then
echo "OK"
return 0
else
echo "OK (daemon running, watchdog process not responding)"
return 0
fi
else
echo "OK (daemon running, watchdog PID file not found)"
return 0
fi
else
echo "OK (daemon running, no watchdog available)"
return 0
fi
else
echo "FAILED"
echo "Daemon failed to start properly"
return 1
fi
}
# Function to stop the daemon
stop() {
echo -n "Stopping $NAME: "
# First, try to stop the watchdog if it's running
if [ -f "$WATCHDOG_PIDFILE" ]; then
echo "Stopping watchdog..."
$WATCHDOG stop >/dev/null 2>&1
sleep 2
fi
# Check if daemon is running via PID file
if is_running; then
echo "Stopping daemon via PID file..."
local pid=$(cat "$PIDFILE")
# Try to stop gracefully first
kill -TERM "$pid" 2>/dev/null
sleep 2
# Check if it's still running
if kill -0 "$pid" 2>/dev/null; then
# Force kill if still running
kill -KILL "$pid" 2>/dev/null
sleep 1
fi
fi
# Also kill any wssshd processes that might be running (regardless of PID file)
echo "Ensuring all wssshd processes are stopped..."
pkill -TERM -f "^/usr/bin/wssshd" 2>/dev/null || true
sleep 2
pkill -KILL -f "^/usr/bin/wssshd" 2>/dev/null || true
# Clean up PID files
rm -f "$PIDFILE" "$WATCHDOG_PIDFILE"
# Final check
if is_running; then
echo "FAILED"
return 1
else
echo "OK"
return 0
fi
}
# Function to restart the daemon
restart() {
stop
sleep 2
start
}
# Function to check status
status() {
# Check watchdog status
if [ -f "$WATCHDOG_PIDFILE" ]; then
local watchdog_pid=$(cat "$WATCHDOG_PIDFILE")
if kill -0 "$watchdog_pid" 2>/dev/null; then
echo "Watchdog is running (PID: $watchdog_pid)"
else
echo "Watchdog PID file exists but process is not running"
rm -f "$WATCHDOG_PIDFILE"
fi
else
echo "Watchdog is not running"
fi
# Check daemon status
if is_running; then
local pid=$(cat "$PIDFILE")
echo "$NAME daemon is running (PID: $pid)"
return 0
else
echo "$NAME daemon is not running"
return 3
fi
}
# Function to reload configuration
reload() {
echo -n "Reloading $NAME configuration: "
if ! is_running; then
echo "not running"
return 1
fi
local pid=$(cat "$PIDFILE")
kill -HUP $pid 2>/dev/null
if [ $? -eq 0 ]; then
echo "OK"
return 0
else
echo "FAILED"
return 1
fi
}
# Main script logic
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
reload)
reload
;;
status)
status
;;
condrestart|try-restart)
if is_running; then
restart
fi
;;
*)
echo "Usage: $0 {start|stop|restart|reload|status|condrestart}"
exit 2
;;
esac
exit $?
\ No newline at end of file
/var/log/wssshd/wssshd.log {
weekly
missingok
rotate 52
compress
delaycompress
notifempty
create 644 wssshd wssshd
postrotate
if [ -f /var/run/wssshd.pid ]; then
kill -HUP $(cat /var/run/wssshd.pid) 2>/dev/null || true
fi
endscript
}
/var/log/wssshd/watchdog.log {
weekly
missingok
rotate 52
compress
delaycompress
notifempty
create 644 wssshd wssshd
postrotate
if [ -f /var/run/wssshd-watchdog.pid ]; then
kill -HUP $(cat /var/run/wssshd-watchdog.pid) 2>/dev/null || true
fi
endscript
}
\ No newline at end of file
[Unit]
Description=WebSocket SSH Daemon
After=network.target syslog.target
Requires=network.target
ConditionPathExists=!/etc/init.d/wssshd
ConditionPathExists=!/var/run/wssshd-watchdog.pid
[Service]
Type=forking
User=wssshd
Group=wssshd
EnvironmentFile=-/etc/default/wssshd
ExecStart=/usr/sbin/wssshd-watchdog start
ExecStop=/usr/sbin/wssshd-watchdog stop
ExecReload=/usr/sbin/wssshd-watchdog restart
PIDFile=/var/run/wssshd-watchdog.pid
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
\ No newline at end of file
#!/bin/bash
# WebSocket SSH Daemon Watchdog Script
# 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.
# Configuration
DAEMON_NAME="wssshd"
DAEMON_PATH="/usr/bin/wssshd"
PID_FILE="/var/run/wssshd.pid"
WATCHDOG_PID_FILE="/var/run/wssshd-watchdog.pid"
LOG_FILE="/var/log/wssshd/watchdog.log"
CHECK_INTERVAL=30
MAX_RESTARTS=20
RESTART_WINDOW=60 # 1 minute
# Default configuration values (can be overridden by /etc/default/wssshd)
START=yes
DAEMON_ARGS=""
# Load configuration if available
if [ -f /etc/default/wssshd ]; then
. /etc/default/wssshd
echo "Loaded configuration from /etc/default/wssshd"
else
echo "No configuration file found at /etc/default/wssshd, using defaults"
fi
echo "START value after configuration loading: '$START'"
# Function to log messages
log_message() {
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] $*" >> "$LOG_FILE"
logger -t "$DAEMON_NAME-watchdog" "$*"
}
# Function to check if daemon is running (double-check process)
is_daemon_running() {
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
# First check if process exists
if kill -0 "$pid" 2>/dev/null; then
# Double-check: verify the process is actually our daemon
if ps -p "$pid" -o comm= 2>/dev/null | grep -q "^wssshd$"; then
return 0 # Running and correct process
else
log_message "PID file exists but process $pid is not wssshd"
rm -f "$PID_FILE"
fi
else
log_message "PID file exists but process $pid is not running"
rm -f "$PID_FILE"
fi
fi
return 1 # Not running
}
# Function to start daemon
start_daemon() {
log_message "Starting $DAEMON_NAME daemon..."
# Create necessary directories
mkdir -p /var/log/wssshd 2>/dev/null || log_message "Warning: Could not create /var/log/wssshd"
chown wssshd:wssshd /var/log/wssshd 2>/dev/null || log_message "Warning: Could not chown /var/log/wssshd"
# Check if daemon binary exists
if [ ! -x "$DAEMON_PATH" ]; then
log_message "Error: Daemon binary $DAEMON_PATH not found or not executable"
return 1
fi
# Try to start daemon as wssshd user, fallback to current user if that fails
log_message "Attempting to start daemon with start-stop-daemon..."
if [ -n "$DAEMON_ARGS" ]; then
start-stop-daemon --start --quiet --pidfile "$PID_FILE" \
--chuid wssshd:wssshd --background --make-pidfile \
--exec "$DAEMON_PATH" -- $DAEMON_ARGS 2>/dev/null
local result=$?
else
start-stop-daemon --start --quiet --pidfile "$PID_FILE" \
--chuid wssshd:wssshd --background --make-pidfile \
--exec "$DAEMON_PATH" 2>/dev/null
local result=$?
fi
# If start-stop-daemon failed, try running directly
if [ $result -ne 0 ]; then
log_message "start-stop-daemon failed (exit code: $result), trying direct execution..."
if [ -n "$DAEMON_ARGS" ]; then
"$DAEMON_PATH" $DAEMON_ARGS &
echo $! > "$PID_FILE"
else
"$DAEMON_PATH" &
echo $! > "$PID_FILE"
fi
result=$?
fi
if [ $result -eq 0 ]; then
log_message "$DAEMON_NAME started successfully"
return 0
else
log_message "Failed to start $DAEMON_NAME (exit code: $result)"
return 1
fi
}
# Function to check restart limits
check_restart_limits() {
local current_time=$(date +%s)
local restart_count=0
local window_start=$((current_time - RESTART_WINDOW))
# Count successful starts within the restart window
if [ -f "$LOG_FILE" ]; then
# Count starts in the last RESTART_WINDOW seconds
restart_count=$(awk -v window_start="$window_start" '
BEGIN { count = 0 }
{
# Extract timestamp from log line [YYYY-MM-DD HH:MM:SS]
if (match($0, /\[([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2})\]/, arr)) {
timestamp = arr[1]
# Convert to epoch time
cmd = "date -d \"" timestamp "\" +%s 2>/dev/null"
cmd | getline epoch_time
close(cmd)
if (epoch_time >= window_start && $0 ~ /started successfully/) {
count++
}
}
}
END { print count }
' "$LOG_FILE" 2>/dev/null || echo "0")
fi
if [ "$restart_count" -ge "$MAX_RESTARTS" ]; then
log_message "Too many restarts ($restart_count) in $RESTART_WINDOW seconds. Watchdog will exit."
return 1
fi
return 0
}
# Function to stop daemon (double-check process)
stop_daemon() {
log_message "Stopping $DAEMON_NAME daemon..."
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
# Double-check: verify the process is actually our daemon
if ps -p "$pid" -o comm= 2>/dev/null | grep -q "^wssshd$"; then
start-stop-daemon --stop --quiet --pidfile "$PID_FILE" --retry=TERM/30/KILL/5
local result=$?
rm -f "$PID_FILE"
return $result
else
log_message "PID file exists but process $pid is not wssshd"
rm -f "$PID_FILE"
return 1
fi
else
log_message "PID file not found, daemon may not be running"
return 1
fi
}
# Function to cleanup on exit
cleanup() {
log_message "Watchdog shutting down..."
if [ -f "$WATCHDOG_PID_FILE" ]; then
rm -f "$WATCHDOG_PID_FILE"
fi
exit 0
}
# Trap signals
trap cleanup SIGTERM SIGINT
# Main watchdog function (continuous monitoring)
main() {
# Debug: Log the START value
log_message "START configuration value: '$START'"
# Check if START is enabled (accept various forms: yes, YES, Y, 1, true, TRUE)
START_LOWER=$(echo "$START" | tr '[:upper:]' '[:lower:]')
log_message "START_LOWER: '$START_LOWER'"
if [ "$START_LOWER" != "yes" ] && [ "$START_LOWER" != "y" ] && [ "$START_LOWER" != "1" ] && [ "$START_LOWER" != "true" ]; then
log_message "START is not set to a valid enabled value in /etc/default/wssshd. Exiting."
exit 0
fi
log_message "START validation passed, proceeding with watchdog initialization"
log_message "Watchdog started for $DAEMON_NAME"
log_message "Check interval: $CHECK_INTERVAL seconds"
log_message "Max restarts: $MAX_RESTARTS per $RESTART_WINDOW seconds"
log_message "Entering monitoring loop"
local loop_count=0
while true; do
loop_count=$((loop_count + 1))
log_message "Monitoring loop iteration $loop_count"
if ! is_daemon_running; then
log_message "$DAEMON_NAME is not running"
# Check restart limits before attempting to start
if ! check_restart_limits; then
log_message "Restart limits exceeded, watchdog will exit"
break
fi
# Attempt to start daemon
if start_daemon; then
log_message "$DAEMON_NAME restarted successfully"
# Give daemon time to fully start before checking
sleep 3
else
log_message "Failed to restart $DAEMON_NAME"
# If daemon fails to start, exit watchdog
log_message "Watchdog exiting due to daemon restart failure"
break
fi
else
log_message "$DAEMON_NAME is running, monitoring continues"
fi
log_message "Sleeping for $CHECK_INTERVAL seconds"
sleep "$CHECK_INTERVAL"
done
log_message "Watchdog exiting"
cleanup
}
# Handle command line arguments
case "$1" in
start)
echo "Starting watchdog..."
if [ -f "$WATCHDOG_PID_FILE" ]; then
echo "Watchdog is already running"
exit 1
fi
echo "Calling main() function..."
# Run main() function in background and capture its PID
main &
MAIN_PID=$!
# Create PID file with the background process PID
echo $MAIN_PID > "$WATCHDOG_PID_FILE"
# Give it a moment to start
sleep 1
# Check if the process is still running
if kill -0 $MAIN_PID 2>/dev/null; then
echo "Watchdog started successfully (PID: $MAIN_PID)"
exit 0 # Exit immediately to avoid killing background main process
else
echo "Watchdog failed to start"
rm -f "$WATCHDOG_PID_FILE"
exit 1
fi
;;
stop)
# First, try to stop the process from PID file
if [ -f "$WATCHDOG_PID_FILE" ]; then
watchdog_pid=$(cat "$WATCHDOG_PID_FILE")
# Double-check: verify the process is actually our watchdog
if ps -p "$watchdog_pid" -o comm= 2>/dev/null | grep -q "wssshd-watchdog"; then
kill "$watchdog_pid" 2>/dev/null
rm -f "$WATCHDOG_PID_FILE"
echo "Watchdog stopped (from PID file)"
else
echo "PID file exists but process is not wssshd-watchdog"
rm -f "$WATCHDOG_PID_FILE"
fi
fi
# Also kill any other watchdog processes that might be running
# Find all bash processes running our watchdog script
watchdog_processes=$(ps aux | grep "/usr/sbin/wssshd-watchdog start" | grep -v grep | awk '{print $2}')
if [ -n "$watchdog_processes" ]; then
echo "Found additional watchdog processes: $watchdog_processes"
for pid in $watchdog_processes; do
if [ "$pid" != "$$" ]; then # Don't kill ourselves
kill "$pid" 2>/dev/null && echo "Killed watchdog process $pid"
fi
done
fi
# Clean up any remaining PID files
rm -f "$WATCHDOG_PID_FILE" 2>/dev/null
echo "Watchdog stop process completed"
;;
status)
if [ -f "$WATCHDOG_PID_FILE" ]; then
watchdog_pid=$(cat "$WATCHDOG_PID_FILE")
if kill -0 "$watchdog_pid" 2>/dev/null; then
# Double-check: verify the process is actually our watchdog
if ps -p "$watchdog_pid" -o comm= 2>/dev/null | grep -q "wssshd-watchdog"; then
echo "Watchdog is running (PID: $watchdog_pid)"
else
echo "Watchdog PID file exists but process is not wssshd-watchdog"
rm -f "$WATCHDOG_PID_FILE"
fi
else
echo "Watchdog PID file exists but process is not running"
rm -f "$WATCHDOG_PID_FILE"
fi
else
echo "Watchdog is not running"
fi
# Also check daemon status
if is_daemon_running; then
daemon_pid=$(cat "$PID_FILE")
echo "Daemon is running (PID: $daemon_pid)"
else
echo "Daemon is not running"
fi
;;
restart)
$0 stop
sleep 2
$0 start
;;
*)
echo "Usage: $0 {start|stop|status|restart}"
exit 1
;;
esac
exit 0
\ No newline at end of file
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: wsssh-server
Upstream-Contact: Stefy Lanza <stefy@nexlab.net>
Source: https://github.com/stefy/wsssh
Files: *
Copyright: 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
License: GPL-3.0+
Files: debian/*
Copyright: 2024 Stefy Lanza <stefy@nexlab.net>
License: GPL-3.0+
License: GPL-3.0+
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 package 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/>.
.
On Debian systems, the complete text of the GNU General
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
\ No newline at end of file
<!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="/logos/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://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css">
<script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"></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
{% extends "base.html" %}
{% block title %}Terminal - {{ client_id }}{% 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">
<div class="d-flex align-items-center">
<a href="{{ url_for('index') }}" 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 - {{ client_id }}
</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>
{% endblock %}
{% block scripts %}
<script>
let term = 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;
}
// Initialize xterm with proper configuration
if (!term) {
term = new Terminal({
cursorBlink: true,
cursorStyle: 'block',
fontSize: 14,
fontFamily: 'Monaco, Menlo, "Ubuntu Mono", monospace',
theme: {
background: '#1e1e1e',
foreground: '#f8f8f2',
cursor: '#f8f8f2',
cursorAccent: '#1e1e1e',
selection: 'rgba(248, 248, 242, 0.3)'
},
allowTransparency: true,
scrollback: 1000,
tabStopWidth: 4,
convertEol: true,
disableStdin: false,
cursorWidth: 2,
bellStyle: 'none',
rightClickSelectsWord: true,
fastScrollModifier: 'alt',
fastScrollSensitivity: 5,
screenReaderMode: false,
macOptionIsMeta: false,
macOptionClickForcesSelection: false,
minimumContrastRatio: 1
});
term.open(document.getElementById('terminal'));
// Load fit addon
const fitAddon = new FitAddon.FitAddon();
term.loadAddon(fitAddon);
// Fit terminal to container
function fitTerminal() {
fitAddon.fit();
}
// Initial fit after a short delay to ensure DOM is ready
setTimeout(() => {
fitTerminal();
// Calculate dimensions after initial fit
const initialDimensions = fitAddon.proposeDimensions();
term._initialCols = initialDimensions.cols || 80;
term._initialRows = initialDimensions.rows || 24;
}, 100);
// Fit on window resize and update backend terminal size
window.addEventListener('resize', () => {
fitTerminal();
// Update terminal size on backend if connected
if (connected && requestId) {
const newDimensions = fitAddon.proposeDimensions();
const newCols = newDimensions.cols || 80;
const newRows = newDimensions.rows || 24;
fetch('/terminal/{{ client_id }}/resize', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'request_id=' + encodeURIComponent(requestId) +
'&cols=' + encodeURIComponent(newCols) +
'&rows=' + encodeURIComponent(newRows)
}).catch(error => {
console.error('Resize error:', error);
});
}
});
term.focus();
}
term.write('Connecting to ' + username + '@{{ client_id }}...\r\n');
connected = true;
document.getElementById('connectBtn').disabled = true;
document.getElementById('disconnectBtn').disabled = false;
document.getElementById('sshUsername').disabled = true;
// Use calculated dimensions (either from initial fit or current)
const cols = term._initialCols || fitAddon.proposeDimensions().cols || 80;
const rows = term._initialRows || fitAddon.proposeDimensions().rows || 24;
// Send connect request with terminal dimensions
fetch('/terminal/{{ client_id }}/connect', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'username=' + encodeURIComponent(username) +
'&cols=' + encodeURIComponent(cols) +
'&rows=' + encodeURIComponent(rows)
})
.then(response => response.json())
.then(data => {
if (data.request_id) {
requestId = data.request_id;
if (data.command) {
term.write('Launching: ' + data.command + '\r\n');
}
term.write('Connected successfully!\r\n$ ');
// Start polling for data with shorter interval for better responsiveness
pollInterval = setInterval(pollData, 100);
} else {
term.write('Error: ' + data.error + '\r\n');
disconnect();
}
})
.catch(error => {
term.write('Connection failed: ' + error + '\r\n');
disconnect();
});
// Handle input - send all keystrokes to server, let SSH handle echo
term.onData(data => {
if (!connected || !requestId) return;
// Send all input to server, let SSH handle echo and display
fetch('/terminal/{{ client_id }}/data', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'request_id=' + encodeURIComponent(requestId) + '&data=' + encodeURIComponent(data)
});
});
}
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/{{ client_id }}/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/{{ client_id }}/data?request_id=' + encodeURIComponent(requestId))
.then(response => response.text())
.then(data => {
if (data) {
// Let the server handle all echo and display logic
term.write(data.replace(/\n/g, '\r\n'));
}
})
.catch(error => {
console.error('Polling error:', error);
});
}
// Focus on terminal when connected
document.addEventListener('keydown', function(e) {
if (connected && term) {
term.focus();
}
});
</script>
{% endblock %}
\ No newline at end of file
{% 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
[wssshd]
host = 0.0.0.0
port = 9898
password = mysecret
domain = example.com
web-host = 0.0.0.0
web-port = 8080
web-https = false
\ No newline at end of file
Format: 1.0
Source: wsssh-server
Binary: wsssh-server
Architecture: amd64 source
Version: 1.4.4-1
Checksums-Md5:
56d229ac0c75d4d3989fa31efba60bfc 730 wsssh-server_1.4.4-1.dsc
09acdffcb0f2ce1964960c5a00c191ea 26629888 wsssh-server_1.4.4-1_amd64.deb
Checksums-Sha1:
2572508a507c5c51e6771c757880856ee541572c 730 wsssh-server_1.4.4-1.dsc
cacc524558df8a861453333cab40abddc292e1fe 26629888 wsssh-server_1.4.4-1_amd64.deb
Checksums-Sha256:
03e9a4d1e4dd2388c7d6a4254247ea949d79ba1d364e93fd36f993b890ebeefa 730 wsssh-server_1.4.4-1.dsc
e9391f5160b8dd0e5b6cc89a00b0bd99d889467ba78fb9af72d931daf0401b44 26629888 wsssh-server_1.4.4-1_amd64.deb
Build-Origin: Devuan
Build-Architecture: amd64
Build-Date: Tue, 16 Sep 2025 18:34:04 +0200
Build-Tainted-By:
usr-local-has-configs
usr-local-has-libraries
usr-local-has-programs
Installed-Build-Depends:
autoconf (= 2.72-3.1),
automake (= 1:1.17-4),
autopoint (= 0.22.5-4),
autotools-dev (= 20220109.1),
base-files (= 13.6devuan1),
base-passwd (= 3.6.6),
bash (= 5.2.37-2+b5),
binutils (= 2.43.50.20250108-1),
binutils-common (= 2.43.50.20250108-1),
binutils-x86-64-linux-gnu (= 2.43.50.20250108-1),
bsdextrautils (= 2.40.2-14devuan1),
bsdutils (= 1:2.40.2-14devuan1),
build-essential (= 12.12),
bzip2 (= 1.0.8-6),
ca-certificates (= 20241223),
clang-13 (= 1:13.0.0-9+b2),
clang-16 (= 1:16.0.6-27),
clang-19 (= 1:19.1.7-3+b1),
coreutils (= 9.5-1+b1),
cpp (= 4:14.2.0-1),
cpp-10 (= 10.5.0-4),
cpp-11 (= 11.5.0-2),
cpp-13 (= 13.3.0-12),
cpp-13-x86-64-linux-gnu (= 13.3.0-12),
cpp-14 (= 14.2.0-12),
cpp-14-x86-64-linux-gnu (= 14.2.0-12),
cpp-x86-64-linux-gnu (= 4:14.2.0-1),
dash (= 0.5.12-11),
debconf (= 1.5.89),
debhelper (= 13.26),
debianutils (= 5.21),
dh-autoreconf (= 20),
dh-strip-nondeterminism (= 1.14.2-1),
diffutils (= 1:3.10-2),
dpkg (= 1.22.13),
dpkg-dev (= 1.22.13),
dwz (= 0.15-1+b1),
file (= 1:5.45-3+b1),
findutils (= 4.10.0-3),
g++ (= 4:14.2.0-1),
g++-14 (= 14.2.0-12),
g++-14-x86-64-linux-gnu (= 14.2.0-12),
g++-x86-64-linux-gnu (= 4:14.2.0-1),
gawk (= 1:5.2.1-2+b1),
gcc (= 4:14.2.0-1),
gcc-10 (= 10.5.0-4),
gcc-10-base (= 10.5.0-4),
gcc-11 (= 11.5.0-2),
gcc-11-base (= 11.5.0-2),
gcc-13 (= 13.3.0-12),
gcc-13-base (= 13.3.0-12),
gcc-13-x86-64-linux-gnu (= 13.3.0-12),
gcc-14 (= 14.2.0-12),
gcc-14-base (= 14.2.0-12),
gcc-14-x86-64-linux-gnu (= 14.2.0-12),
gcc-x86-64-linux-gnu (= 4:14.2.0-1),
gettext (= 0.22.5-4),
gettext-base (= 0.22.5-4),
grep (= 3.11-4),
groff-base (= 1.23.0-7),
gzip (= 1.12-1.2),
hostname (= 3.25),
init-system-helpers (= 1.68devuan1),
intltool-debian (= 0.35.0+20060710.6),
lib32gcc-s1 (= 14.2.0-12),
lib32stdc++6 (= 14.2.0-12),
libacl1 (= 2.3.2-2+b1),
libarchive-zip-perl (= 1.68-1),
libasan6 (= 11.5.0-2),
libasan8 (= 14.2.0-12),
libatomic1 (= 14.2.0-12),
libattr1 (= 1:2.5.2-2),
libaudit-common (= 1:4.0.2-2),
libaudit1 (= 1:4.0.2-2),
libbinutils (= 2.43.50.20250108-1),
libblkid1 (= 2.40.2-14devuan1),
libbsd0 (= 0.12.2-2),
libbz2-1.0 (= 1.0.8-6),
libc-bin (= 2.40-5),
libc-dev-bin (= 2.40-5),
libc6 (= 2.40-5),
libc6-dev (= 2.40-5),
libc6-i386 (= 2.40-5),
libcap-ng0 (= 0.8.5-4),
libcap2 (= 1:2.66-5+b1),
libcc1-0 (= 14.2.0-12),
libclang-common-13-dev (= 1:13.0.0-9+b2),
libclang-common-16-dev (= 1:16.0.6-27),
libclang-common-19-dev (= 1:19.1.7-3+b1),
libclang-cpp13 (= 1:13.0.0-9+b2),
libclang-cpp16t64 (= 1:16.0.6-27),
libclang-cpp19 (= 1:19.1.7-3+b1),
libclang1-13 (= 1:13.0.0-9+b2),
libclang1-16t64 (= 1:16.0.6-27),
libclang1-19 (= 1:19.1.7-3+b1),
libcrypt-dev (= 1:4.4.36-5),
libcrypt1 (= 1:4.4.36-5),
libctf-nobfd0 (= 2.43.50.20250108-1),
libctf0 (= 2.43.50.20250108-1),
libdb5.3t64 (= 5.3.28+dfsg2-9),
libdebconfclient0 (= 0.277),
libdebhelper-perl (= 13.26),
libdpkg-perl (= 1.22.13),
libedit2 (= 3.1-20240808-1),
libelf1t64 (= 0.192-4),
libelogind-compat (= 255.17-2),
libelogind0 (= 255.17-2),
libeudev1 (= 3.2.14-2),
libexpat1 (= 2.6.4-1),
libffi8 (= 3.4.6-1),
libfile-stripnondeterminism-perl (= 1.14.2-1),
libgc1 (= 1:8.2.8-1),
libgcc-10-dev (= 10.5.0-4),
libgcc-11-dev (= 11.5.0-2),
libgcc-13-dev (= 13.3.0-12),
libgcc-14-dev (= 14.2.0-12),
libgcc-s1 (= 14.2.0-12),
libgdbm-compat4t64 (= 1.24-2),
libgdbm6t64 (= 1.24-2),
libgmp10 (= 2:6.3.0+dfsg-3),
libgomp1 (= 14.2.0-12),
libgprofng0 (= 2.43.50.20250108-1),
libhwasan0 (= 14.2.0-12),
libicu72 (= 72.1-6),
libisl23 (= 0.27-1),
libitm1 (= 14.2.0-12),
libjansson4 (= 2.14-2+b3),
libllvm13 (= 1:13.0.0-9+b2),
libllvm16t64 (= 1:16.0.6-27),
libllvm19 (= 1:19.1.7-3+b1),
liblsan0 (= 14.2.0-12),
liblzma5 (= 5.6.3-1+b1),
libmagic-mgc (= 1:5.45-3+b1),
libmagic1t64 (= 1:5.45-3+b1),
libmd0 (= 1.1.0-2+b1),
libmount1 (= 2.40.2-14devuan1),
libmpc3 (= 1.3.1-1+b3),
libmpfr6 (= 4.2.1-1+b2),
libncursesw6 (= 6.5-2+b1),
libobjc-11-dev (= 11.5.0-2),
libobjc-13-dev (= 13.3.0-12),
libobjc-14-dev (= 14.2.0-12),
libobjc4 (= 14.2.0-12),
libpam-modules (= 1.5.3-7+b1),
libpam-modules-bin (= 1.5.3-7+b1),
libpam-runtime (= 1.5.3-7),
libpam0g (= 1.5.3-7+b1),
libpcre2-8-0 (= 10.44-5),
libperl5.40 (= 5.40.1-5),
libpipeline1 (= 1.5.8-1),
libpython3-stdlib (= 3.13.7-1),
libpython3.13-minimal (= 3.13.7-1),
libpython3.13-stdlib (= 3.13.7-1),
libquadmath0 (= 14.2.0-12),
libreadline8t64 (= 8.2-6),
libseccomp2 (= 2.5.5-2),
libselinux1 (= 3.7-3+b1),
libsframe1 (= 2.43.50.20250108-1),
libsigsegv2 (= 2.14-1+b2),
libsmartcols1 (= 2.40.2-14devuan1),
libsqlite3-0 (= 3.46.1-6),
libssl3t64 (= 3.4.0-2),
libstdc++-11-dev (= 11.5.0-2),
libstdc++-13-dev (= 13.3.0-12),
libstdc++-14-dev (= 14.2.0-12),
libstdc++6 (= 14.2.0-12),
libtinfo6 (= 6.5-2+b1),
libtool (= 2.5.4-2),
libtsan0 (= 11.5.0-2),
libtsan2 (= 14.2.0-12),
libubsan1 (= 14.2.0-12),
libuchardet0 (= 0.0.8-1+b2),
libunistring5 (= 1.3-1),
libuuid1 (= 2.40.2-14devuan1),
libxml2 (= 2.12.7+dfsg+really2.9.14-0.2+b1),
libz3-4 (= 4.13.3-1),
libzstd1 (= 1.5.6+dfsg-2),
linux-libc-dev (= 6.12.15-1),
llvm-16-linker-tools (= 1:16.0.6-27),
llvm-19-linker-tools (= 1:19.1.7-3+b1),
m4 (= 1.4.19-5),
make (= 4.4.1-1),
man-db (= 2.13.0-1),
mawk (= 1.3.4.20240905-1),
media-types (= 10.1.0),
ncurses-base (= 6.5-2),
ncurses-bin (= 6.5-2+b1),
netbase (= 6.4),
openssl (= 3.4.0-2),
openssl-provider-legacy (= 3.4.0-2),
patch (= 2.7.6-7),
perl (= 5.40.1-5),
perl-base (= 5.40.1-5),
perl-modules-5.40 (= 5.40.1-5),
po-debconf (= 1.0.21+nmu1),
python3 (= 3.13.7-1),
python3-autocommand (= 2.2.2-3),
python3-importlib-metadata (= 8.5.0-1),
python3-inflect (= 7.3.1-2),
python3-jaraco.context (= 6.0.0-1),
python3-jaraco.functools (= 4.1.0-1),
python3-jaraco.text (= 4.0.0-1),
python3-minimal (= 3.13.7-1),
python3-more-itertools (= 10.5.0-1),
python3-packaging (= 24.2-1),
python3-pip (= 25.1.1+dfsg-1),
python3-pkg-resources (= 78.1.1-0.1),
python3-setuptools (= 78.1.1-0.1),
python3-typeguard (= 4.4.1-1),
python3-typing-extensions (= 4.12.2-2),
python3-wheel (= 0.46.1-2),
python3-zipp (= 3.21.0-1),
python3.13 (= 3.13.7-1),
python3.13-minimal (= 3.13.7-1),
readline-common (= 8.2-6),
rpcsvc-proto (= 1.4.3-1),
sed (= 4.9-2),
sensible-utils (= 0.0.24),
sysvinit-utils (= 3.13-1devuan1),
tar (= 1.35+dfsg-3.1),
tzdata (= 2024b-5),
util-linux (= 2.40.2-14devuan1),
xz-utils (= 5.6.3-1+b1),
zlib1g (= 1:1.3.dfsg+really1.3.1-1+b1)
Environment:
DEB_BUILD_OPTIONS="parallel=8"
LANG="en_ZA.UTF-8"
SOURCE_DATE_EPOCH="1757827219"
......@@ -560,7 +560,7 @@ async def main():
parser = argparse.ArgumentParser(description='WebSocket SSH 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, help='WebSocket server port')
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)')
......@@ -606,8 +606,6 @@ async def main():
# Check required arguments
if not args.host:
parser.error('--host is required')
if not args.port:
parser.error('--port is required')
if not args.domain:
parser.error('--domain is required')
if not args.password:
......@@ -667,7 +665,7 @@ async def main():
# Generate self-signed certificate if it doesn't exist
if not os.path.exists(web_cert_path) or not os.path.exists(web_key_path):
print("Generating self-signed certificate for web interface...")
os.system(f'openssl req -x509 -newkey rsa:4096 -keyout {web_key_path} -out {web_cert_path} -days 365 -nodes -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost"')
os.system(f'openssl req -x509 -newkey rsa:4096 -keyout {web_key_path} -out {web_cert_path} -days 36500 -nodes -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost"')
ssl_context = (web_cert_path, web_key_path)
......
......@@ -56,6 +56,8 @@ CFLAGS = -Wall -Wextra -O2 $(shell pkg-config --cflags openssl)
LDFLAGS = $(shell pkg-config --libs openssl)
# Source files
LIB_SRCS = wssshlib.c websocket.c wssh_ssl.c tunnel.c
LIB_OBJS = $(LIB_SRCS:.c=.o)
SRCS = wssshc.c wsssh.c wsscp.c
OBJS = $(SRCS:.c=.o)
TARGETS = wssshc wsssh wsscp
......@@ -67,13 +69,13 @@ MANPAGES = man/wssshc.1 man/wsssh.1 man/wsscp.1
all: $(TARGETS)
# Individual targets
wssshc: wssshc.o
wssshc: wssshc.o $(LIB_OBJS)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
wsssh: wsssh.o
wsssh: wsssh.o $(LIB_OBJS)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
wsscp: wsscp.o
wsscp: wsscp.o $(LIB_OBJS)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
# Object files
......@@ -82,7 +84,7 @@ wsscp: wsscp.o
# Clean
clean:
rm -f $(OBJS) $(TARGETS)
rm -f $(OBJS) $(LIB_OBJS) $(TARGETS)
# Install (optional)
install: all
......
......@@ -26,8 +26,8 @@ Display help message and exit
Hostnames in source/destination paths follow the format: [\fIuser\fR@]\fIclient_id\fR.\fIwssshd_host\fR[:\fIport\fR]
.TP
Examples:
- \fBuser@remote.example.com:/path/file\fR → client: \fBremote\fR, server: \fBexample.com:22\fR
- \fBserver.datacenter.com:9898/file\fR → client: \fBserver\fR, server: \fBdatacenter.com:9898\fR
- \fBuser@remote.example.com:/path/file\fR → client: \fBremote\fR, server: \fBexample.com:9898\fR
- \fBserver.datacenter.com:2222/file\fR → client: \fBserver\fR, server: \fBdatacenter.com:2222\fR
.SH EXAMPLES
.TP
Copy file to remote:
......
......@@ -25,8 +25,8 @@ Display help message and exit
The hostname follows the format: \fIclient_id\fR.\fIwssshd_host\fR[:\fIport\fR]
.TP
Examples:
- \fBremote.example.com\fR → client: \fBremote\fR, server: \fBexample.com:22\fR
- \fBserver.datacenter.com:9898\fR → client: \fBserver\fR, server: \fBdatacenter.com:9898\fR
- \fBremote.example.com\fR → client: \fBremote\fR, server: \fBexample.com:9898\fR
- \fBserver.datacenter.com:2222\fR → client: \fBserver\fR, server: \fBdatacenter.com:2222\fR
.SH EXAMPLES
.TP
Basic SSH connection:
......
/*
* WebSocket SSH Library - Tunnel management 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/>.
*/
#include "tunnel.h"
#include "wssshlib.h"
#include "websocket.h"
#include "wssh_ssl.h"
#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 <netdb.h>
#include <fcntl.h>
#include <errno.h>
#define INITIAL_FRAME_BUFFER_SIZE 8192
// Global variables
tunnel_t *active_tunnel = NULL;
pthread_mutex_t tunnel_mutex;
frame_buffer_t *frame_buffer_init() {
frame_buffer_t *fb = malloc(sizeof(frame_buffer_t));
if (!fb) return NULL;
fb->buffer = malloc(INITIAL_FRAME_BUFFER_SIZE);
if (!fb->buffer) {
free(fb);
return NULL;
}
fb->size = INITIAL_FRAME_BUFFER_SIZE;
fb->used = 0;
return fb;
}
void frame_buffer_free(frame_buffer_t *fb) {
if (fb) {
free(fb->buffer);
free(fb);
}
}
int frame_buffer_resize(frame_buffer_t *fb, size_t new_size) {
if (new_size <= fb->size) return 1;
char *new_buffer = realloc(fb->buffer, new_size);
if (!new_buffer) return 0;
fb->buffer = new_buffer;
fb->size = new_size;
return 1;
}
int frame_buffer_append(frame_buffer_t *fb, const char *data, size_t len) {
if (fb->used + len > fb->size) {
size_t new_size = fb->size * 2;
while (new_size < fb->used + len) {
new_size *= 2;
}
if (!frame_buffer_resize(fb, new_size)) {
return 0;
}
}
memcpy(fb->buffer + fb->used, data, len);
fb->used += len;
return 1;
}
int frame_buffer_consume(frame_buffer_t *fb, size_t len) {
if (len > fb->used) return 0;
memmove(fb->buffer, fb->buffer + len, fb->used - len);
fb->used -= len;
return 1;
}
void handle_tunnel_request(SSL *ssl, const char *request_id, int debug) {
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel) {
close(active_tunnel->sock);
free(active_tunnel);
}
active_tunnel = malloc(sizeof(tunnel_t));
if (!active_tunnel) {
perror("Memory allocation failed");
pthread_mutex_unlock(&tunnel_mutex);
return;
}
active_tunnel->sock = -1; // Not connected yet
strcpy(active_tunnel->request_id, request_id);
pthread_mutex_unlock(&tunnel_mutex);
if (debug) {
printf("[DEBUG] Tunnel %s prepared (not connected yet)\n", request_id);
}
// Send tunnel_ack back to server
char ack_msg[256];
snprintf(ack_msg, sizeof(ack_msg), "{\"type\":\"tunnel_ack\",\"request_id\":\"%s\"}", request_id);
if (debug) {
printf("[DEBUG] Sending tunnel_ack: %s\n", ack_msg);
}
char frame[512];
frame[0] = 0x81; // FIN + text opcode
int msg_len = strlen(ack_msg);
frame[1] = 0x80 | msg_len; // MASK + length
char mask_key[4];
for (int i = 0; i < 4; i++) {
mask_key[i] = rand() % 256;
frame[2 + i] = mask_key[i];
}
for (int i = 0; i < msg_len; i++) {
frame[6 + i] = ack_msg[i] ^ mask_key[i % 4];
}
int frame_len = 6 + msg_len;
if (SSL_write(ssl, frame, frame_len) <= 0) {
fprintf(stderr, "Send tunnel_ack failed\n");
}
}
void cleanup_tunnel(int debug) {
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel) {
if (active_tunnel->sock >= 0) {
// Check if SSH connection is still valid before closing
char test_buf[1];
int result = recv(active_tunnel->sock, test_buf, 1, MSG_PEEK | MSG_DONTWAIT);
if (result == 0 || (result < 0 && (errno == ECONNRESET || errno == EPIPE))) {
// SSH connection is closed or broken, safe to close
close(active_tunnel->sock);
if (debug) {
printf("[DEBUG] [TCP Tunnel] Closed broken SSH connection\n");
}
} else {
// SSH connection appears valid, don't close it
if (debug) {
printf("[DEBUG] [TCP Tunnel] Keeping SSH connection alive for potential reuse\n");
}
// Reset socket to -1 so it will be reconnected if needed
active_tunnel->sock = -1;
}
}
free(active_tunnel);
active_tunnel = NULL;
}
pthread_mutex_unlock(&tunnel_mutex);
}
void *forward_tcp_to_ws(void *arg) {
thread_args_t *args = (thread_args_t *)arg;
SSL *ssl = args->ssl;
int debug = args->debug;
char buffer[BUFFER_SIZE];
int bytes_read;
fd_set readfds;
struct timeval tv;
while (1) {
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || !active_tunnel->active) {
pthread_mutex_unlock(&tunnel_mutex);
break;
}
int sock = active_tunnel->local_sock;
char request_id[37];
strcpy(request_id, active_tunnel->request_id);
// Send pending data from outgoing buffer to local socket (wsscp only)
if (active_tunnel->outgoing_buffer && active_tunnel->outgoing_buffer->used > 0) {
ssize_t sent = send(sock, active_tunnel->outgoing_buffer->buffer, active_tunnel->outgoing_buffer->used, MSG_DONTWAIT);
if (sent > 0) {
frame_buffer_consume(active_tunnel->outgoing_buffer, sent);
if (debug) {
printf("[DEBUG] Sent %zd bytes from buffer to local socket\n", sent);
fflush(stdout);
}
} else if (sent == -1 && errno != EAGAIN && errno != EWOULDBLOCK) {
if (debug) {
perror("[DEBUG] send to local socket failed");
fflush(stdout);
}
pthread_mutex_unlock(&tunnel_mutex);
break;
}
}
pthread_mutex_unlock(&tunnel_mutex);
// Use select to wait for data on local socket
FD_ZERO(&readfds);
FD_SET(sock, &readfds);
tv.tv_sec = 0; // 0 seconds
tv.tv_usec = 50000; // 50ms timeout
int retval = select(sock + 1, &readfds, NULL, NULL, &tv);
if (retval == -1) {
if (debug) {
perror("[DEBUG] select failed");
fflush(stdout);
}
break;
} else if (retval == 0) {
// Timeout, continue loop
continue;
}
if (FD_ISSET(sock, &readfds)) {
bytes_read = recv(sock, buffer, sizeof(buffer), 0);
if (bytes_read <= 0) {
if (debug) {
printf("[DEBUG] TCP connection closed or error\n");
fflush(stdout);
}
break;
}
if (debug) {
printf("[DEBUG] Forwarding %d bytes from TCP to WebSocket\n", bytes_read);
fflush(stdout);
}
// Convert to hex
char hex_data[bytes_read * 2 + 1];
for (int i = 0; i < bytes_read; i++) {
sprintf(hex_data + i * 2, "%02x", (unsigned char)buffer[i]);
}
hex_data[bytes_read * 2] = '\0';
// Send as tunnel_data
char message[BUFFER_SIZE];
snprintf(message, sizeof(message),
"{\"type\":\"tunnel_data\",\"request_id\":\"%s\",\"data\":\"%s\"}",
request_id, hex_data);
if (!send_websocket_frame(ssl, message)) {
if (debug) {
printf("[DEBUG] Failed to send WebSocket frame\n");
fflush(stdout);
}
break;
}
}
}
if (debug) {
printf("[DEBUG] TCP to WebSocket forwarding thread exiting\n");
fflush(stdout);
}
free(args);
return NULL;
}
void handle_tunnel_data(SSL *ssl __attribute__((unused)), const char *request_id, const char *data_hex, int debug __attribute__((unused))) {
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || strcmp(active_tunnel->request_id, request_id) != 0) {
pthread_mutex_unlock(&tunnel_mutex);
return;
}
pthread_mutex_unlock(&tunnel_mutex);
// Decode hex data
size_t data_len = strlen(data_hex) / 2;
char *data = malloc(data_len);
if (!data) return;
for (size_t i = 0; i < data_len; i++) {
sscanf(data_hex + i * 2, "%2hhx", &data[i]);
}
if (active_tunnel->outgoing_buffer) {
// wsscp: Append to outgoing buffer
pthread_mutex_lock(&tunnel_mutex);
if (!frame_buffer_append(active_tunnel->outgoing_buffer, data, data_len)) {
if (debug) {
printf("[DEBUG] Failed to append to outgoing buffer, dropping %zu bytes\n", data_len);
fflush(stdout);
}
}
pthread_mutex_unlock(&tunnel_mutex);
} else {
// wsssh: Send directly to local socket
send(active_tunnel->local_sock, data, data_len, 0);
}
free(data);
}
void handle_tunnel_close(SSL *ssl __attribute__((unused)), const char *request_id, int debug) {
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel && strcmp(active_tunnel->request_id, request_id) == 0) {
active_tunnel->active = 0;
close(active_tunnel->local_sock);
if (active_tunnel->outgoing_buffer) {
frame_buffer_free(active_tunnel->outgoing_buffer);
}
free(active_tunnel);
active_tunnel = NULL;
if (debug) {
printf("[DEBUG] Tunnel %s closed\n", request_id);
}
}
pthread_mutex_unlock(&tunnel_mutex);
}
int reconnect_websocket(tunnel_t *tunnel, const char *wssshd_host, int wssshd_port, const char *client_id, const char *request_id, int debug) {
struct sockaddr_in server_addr;
struct hostent *he;
int sock;
SSL_CTX *ssl_ctx;
SSL *ssl;
char buffer[BUFFER_SIZE];
int bytes_read;
if (debug) {
printf("[DEBUG] Attempting to reconnect WebSocket connection...\n");
fflush(stdout);
}
// Resolve hostname
if ((he = gethostbyname(wssshd_host)) == NULL) {
herror("gethostbyname");
return -1;
}
// Create socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(wssshd_port);
server_addr.sin_addr = *((struct in_addr *)he->h_addr);
// Connect to server
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Connection failed");
close(sock);
return -1;
}
// Create SSL context and connection
ssl_ctx = create_ssl_context();
if (!ssl_ctx) {
close(sock);
return -1;
}
ssl = create_ssl_connection(ssl_ctx, sock, debug);
if (!ssl) {
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Perform WebSocket handshake
if (!websocket_handshake(ssl, wssshd_host, wssshd_port, "/")) {
fprintf(stderr, "WebSocket handshake failed\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Send tunnel request
if (!send_json_message(ssl, "tunnel_request", client_id, request_id)) {
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Read acknowledgment
bytes_read = SSL_read(ssl, buffer, sizeof(buffer));
if (bytes_read <= 0) {
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Check if it's a close frame
if (bytes_read >= 2 && (buffer[0] & 0x8F) == 0x88) {
if (debug) {
printf("[DEBUG] Server sent close frame during reconnection\n");
fflush(stdout);
}
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Parse WebSocket frame
char *payload;
int payload_len;
if (!parse_websocket_frame(buffer, bytes_read, &payload, &payload_len)) {
fprintf(stderr, "Failed to parse WebSocket frame during reconnection\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Null terminate payload
payload[payload_len] = '\0';
// Check for tunnel acknowledgment
if (strstr(payload, "tunnel_ack") == NULL) {
fprintf(stderr, "Tunnel request denied during reconnection: %s\n", payload);
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Success - update the tunnel's SSL connection
if (tunnel->ssl) {
SSL_free(tunnel->ssl);
}
tunnel->ssl = ssl;
// Clean up old SSL context
SSL_CTX_free(ssl_ctx);
if (debug) {
printf("[DEBUG] WebSocket reconnection successful\n");
fflush(stdout);
}
return 0;
}
int setup_tunnel(const char *wssshd_host, int wssshd_port, const char *client_id, int local_port, int debug, int use_buffer) {
struct sockaddr_in server_addr;
struct hostent *he;
int sock;
SSL_CTX *ssl_ctx;
SSL *ssl;
char buffer[BUFFER_SIZE];
int bytes_read;
// Generate request ID
char request_id[37];
generate_request_id(request_id, sizeof(request_id));
// Resolve hostname
if ((he = gethostbyname(wssshd_host)) == NULL) {
herror("gethostbyname");
return -1;
}
// Create socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(wssshd_port);
server_addr.sin_addr = *((struct in_addr *)he->h_addr);
// Connect to server
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Connection failed");
close(sock);
return -1;
}
// Create SSL context and connection
ssl_ctx = create_ssl_context();
if (!ssl_ctx) {
close(sock);
return -1;
}
ssl = create_ssl_connection(ssl_ctx, sock, debug);
if (!ssl) {
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Perform WebSocket handshake
if (debug) {
printf("[DEBUG] Performing WebSocket handshake to %s:%d\n", wssshd_host, wssshd_port);
fflush(stdout);
}
if (!websocket_handshake(ssl, wssshd_host, wssshd_port, "/")) {
fprintf(stderr, "WebSocket handshake failed\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (debug) {
printf("[DEBUG] WebSocket handshake successful\n");
fflush(stdout);
}
// Send tunnel request
if (!send_json_message(ssl, "tunnel_request", client_id, request_id)) {
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (debug) {
printf("[DEBUG] Tunnel request sent for client: %s, request_id: %s\n", client_id, request_id);
}
// Read acknowledgment
bytes_read = SSL_read(ssl, buffer, sizeof(buffer));
if (bytes_read <= 0) {
if (debug) {
printf("[DEBUG] No acknowledgment received\n");
}
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (debug) {
printf("[DEBUG] Read %d bytes acknowledgment\n", bytes_read);
printf("[DEBUG] Raw acknowledgment: ");
for (int i = 0; i < bytes_read && i < 50; i++) {
if (buffer[i] >= 32 && buffer[i] < 127) {
printf("%c", buffer[i]);
} else {
printf("\\x%02x", (unsigned char)buffer[i]);
}
}
printf("\n");
fflush(stdout);
}
// Check if it's a close frame
if (bytes_read >= 2 && (buffer[0] & 0x8F) == 0x88) {
if (debug) {
printf("[DEBUG] Server sent close frame\n");
if (bytes_read >= 6) {
int close_code = (buffer[2] << 8) | buffer[3];
printf("[DEBUG] Close code: %d\n", close_code);
if (bytes_read > 6) {
printf("[DEBUG] Close reason: %.*s\n", bytes_read - 6, buffer + 6);
}
}
fflush(stdout);
}
fprintf(stderr, "Tunnel request rejected by server\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Parse WebSocket frame
char *payload;
int payload_len;
if (!parse_websocket_frame(buffer, bytes_read, &payload, &payload_len)) {
fprintf(stderr, "Failed to parse WebSocket frame\n");
if (debug) {
printf("[DEBUG] Frame parsing failed. First few bytes: ");
for (int i = 0; i < bytes_read && i < 10; i++) {
printf("%02x ", (unsigned char)buffer[i]);
}
printf("\n");
fflush(stdout);
}
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Null terminate payload
payload[payload_len] = '\0';
if (debug) {
printf("[DEBUG] Received payload: '%s' (length: %d)\n", payload, payload_len);
printf("[DEBUG] Looking for tunnel_ack in: ");
for (int i = 0; i < payload_len && i < 50; i++) {
if (payload[i] >= 32 && payload[i] < 127) {
printf("%c", payload[i]);
} else {
printf("\\x%02x", (unsigned char)payload[i]);
}
}
printf("\n");
fflush(stdout);
}
// Check for tunnel acknowledgment
if (strstr(payload, "tunnel_ack") == NULL) {
fprintf(stderr, "Tunnel request denied or failed: %s\n", payload);
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (debug) {
printf("[DEBUG] Tunnel established, local port: %d\n", local_port);
}
// Create tunnel structure
active_tunnel = malloc(sizeof(tunnel_t));
if (!active_tunnel) {
perror("Memory allocation failed");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (use_buffer) {
active_tunnel->outgoing_buffer = frame_buffer_init();
if (!active_tunnel->outgoing_buffer) {
perror("Failed to initialize outgoing buffer");
free(active_tunnel);
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
} else {
active_tunnel->outgoing_buffer = NULL;
}
strcpy(active_tunnel->request_id, request_id);
active_tunnel->local_sock = -1;
active_tunnel->active = 1;
active_tunnel->ssl = ssl;
// Start listening on local port
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0) {
perror("Local socket creation failed");
if (use_buffer) frame_buffer_free(active_tunnel->outgoing_buffer);
free(active_tunnel);
active_tunnel = NULL;
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
struct sockaddr_in local_addr;
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(local_port);
local_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (bind(listen_sock, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) {
perror("Local bind failed");
close(listen_sock);
if (use_buffer) frame_buffer_free(active_tunnel->outgoing_buffer);
free(active_tunnel);
active_tunnel = NULL;
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (listen(listen_sock, 1) < 0) {
perror("Local listen failed");
close(listen_sock);
if (use_buffer) frame_buffer_free(active_tunnel->outgoing_buffer);
free(active_tunnel);
active_tunnel = NULL;
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (debug) {
printf("[DEBUG] Listening on localhost:%d\n", local_port);
fflush(stdout);
}
// Clean up SSL context
SSL_CTX_free(ssl_ctx);
// Return success - tunnel is set up and listening
return listen_sock;
}
\ No newline at end of file
/*
* WebSocket SSH Library - Tunnel management
*
* 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 TUNNEL_H
#define TUNNEL_H
#include <openssl/ssl.h>
#include <pthread.h>
// Frame buffer for wsscp
typedef struct {
char *buffer;
size_t size;
size_t used;
} frame_buffer_t;
// Tunnel structure
typedef struct {
int local_sock; // Local TCP connection socket
int sock; // Alternative socket member for wssshc compatibility
char request_id[37]; // UUID string
int active;
SSL *ssl; // WebSocket SSL connection
frame_buffer_t *outgoing_buffer; // Buffer for data to send to local socket (wsscp only)
} tunnel_t;
// Global variables
extern tunnel_t *active_tunnel;
extern pthread_mutex_t tunnel_mutex;
// Function declarations
frame_buffer_t *frame_buffer_init(void);
void frame_buffer_free(frame_buffer_t *fb);
int frame_buffer_resize(frame_buffer_t *fb, size_t new_size);
int frame_buffer_append(frame_buffer_t *fb, const char *data, size_t len);
int frame_buffer_consume(frame_buffer_t *fb, size_t len);
void *forward_tcp_to_ws(void *arg);
void *tunnel_thread(void *arg);
void handle_tunnel_request(SSL *ssl, const char *request_id, int debug);
void handle_tunnel_data(SSL *ssl, const char *request_id, const char *data_hex, int debug);
void handle_tunnel_close(SSL *ssl, const char *request_id, int debug);
void cleanup_tunnel(int debug);
int reconnect_websocket(tunnel_t *tunnel, const char *wssshd_host, int wssshd_port, const char *client_id, const char *request_id, int debug);
int setup_tunnel(const char *wssshd_host, int wssshd_port, const char *client_id, int local_port, int debug, int use_buffer);
#endif // TUNNEL_H
\ No newline at end of file
/*
* WebSocket SSH Library - WebSocket functions 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/>.
*/
#include "websocket.h"
#include "wssshlib.h"
#include <openssl/err.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
int websocket_handshake(SSL *ssl, const char *host, int port, const char *path) {
char request[1024];
char response[BUFFER_SIZE];
int bytes_read;
// Send WebSocket handshake
snprintf(request, sizeof(request),
"GET %s HTTP/1.1\r\n"
"Host: %s:%d\r\n"
"Upgrade: websocket\r\n"
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n",
path, host, port);
if (SSL_write(ssl, request, strlen(request)) <= 0) {
ERR_print_errors_fp(stderr);
fprintf(stderr, "WebSocket handshake send failed\n");
return 0;
}
// Read response
bytes_read = SSL_read(ssl, response, sizeof(response) - 1);
if (bytes_read <= 0) {
ERR_print_errors_fp(stderr);
fprintf(stderr, "WebSocket handshake recv failed\n");
return 0;
}
response[bytes_read] = '\0';
// Check for successful handshake
if (strstr(response, "101 Switching Protocols") == NULL) {
fprintf(stderr, "WebSocket handshake failed\n");
return 0;
}
return 1;
}
int send_json_message(SSL *ssl, const char *type, const char *client_id, const char *request_id) {
char message[1024];
if (request_id) {
snprintf(message, sizeof(message),
"{\"type\":\"%s\",\"client_id\":\"%s\",\"request_id\":\"%s\"}",
type, client_id, request_id);
} else {
snprintf(message, sizeof(message),
"{\"type\":\"%s\",\"client_id\":\"%s\"}",
type, client_id);
}
// Send as WebSocket frame
return send_websocket_frame(ssl, message);
}
int send_websocket_frame(SSL *ssl, const char *data) {
char frame[BUFFER_SIZE];
frame[0] = 0x81; // FIN + text opcode
int msg_len = strlen(data);
int header_len = 2;
if (msg_len <= 125) {
frame[1] = 0x80 | msg_len; // MASK + length
} else if (msg_len <= 65535) {
frame[1] = 0x80 | 126; // MASK + extended length
frame[2] = (msg_len >> 8) & 0xFF;
frame[3] = msg_len & 0xFF;
header_len = 4;
} else {
frame[1] = 0x80 | 127; // MASK + extended length
frame[2] = 0;
frame[3] = 0;
frame[4] = 0;
frame[5] = 0;
frame[6] = (msg_len >> 24) & 0xFF;
frame[7] = (msg_len >> 16) & 0xFF;
frame[8] = (msg_len >> 8) & 0xFF;
frame[9] = msg_len & 0xFF;
header_len = 10;
}
// Add mask key
char mask_key[4];
for (int i = 0; i < 4; i++) {
mask_key[i] = rand() % 256;
frame[header_len + i] = mask_key[i];
}
header_len += 4;
// Mask payload
for (int i = 0; i < msg_len; i++) {
frame[header_len + i] = data[i] ^ mask_key[i % 4];
}
int frame_len = header_len + msg_len;
return SSL_write(ssl, frame, frame_len) > 0;
}
int send_pong_frame(SSL *ssl, const char *ping_payload, int payload_len) {
char frame[BUFFER_SIZE];
frame[0] = 0x8A; // FIN + pong opcode
int header_len = 2;
if (payload_len <= 125) {
frame[1] = 0x80 | payload_len; // MASK + length
} else if (payload_len <= 65535) {
frame[1] = 0x80 | 126; // MASK + extended length
frame[2] = (payload_len >> 8) & 0xFF;
frame[3] = payload_len & 0xFF;
header_len = 4;
} else {
frame[1] = 0x80 | 127; // MASK + extended length
frame[2] = 0;
frame[3] = 0;
frame[4] = 0;
frame[5] = 0;
frame[6] = (payload_len >> 24) & 0xFF;
frame[7] = (payload_len >> 16) & 0xFF;
frame[8] = (payload_len >> 8) & 0xFF;
frame[9] = payload_len & 0xFF;
header_len = 10;
}
// Add mask key
char mask_key[4];
for (int i = 0; i < 4; i++) {
mask_key[i] = rand() % 256;
frame[header_len + i] = mask_key[i];
}
header_len += 4;
// Mask payload
for (int i = 0; i < payload_len; i++) {
frame[header_len + i] = ping_payload[i] ^ mask_key[i % 4];
}
int frame_len = header_len + payload_len;
return SSL_write(ssl, frame, frame_len) > 0;
}
int parse_websocket_frame(const char *buffer, int bytes_read, char **payload, int *payload_len) {
if (bytes_read < 2) {
return 0; // Not enough data for a frame
}
unsigned char frame_type = buffer[0] & 0x8F;
if (frame_type != 0x81 && frame_type != 0x82 && frame_type != 0x88 && frame_type != 0x89 && frame_type != 0x8A) {
return 0; // Not a supported frame type (text, binary, close, ping, pong)
}
int masked = buffer[1] & 0x80;
int len_indicator = buffer[1] & 0x7F;
int header_len = 2;
if (len_indicator <= 125) {
*payload_len = len_indicator;
} else if (len_indicator == 126) {
if (bytes_read < 4) return 0;
*payload_len = ((unsigned char)buffer[2] << 8) | (unsigned char)buffer[3];
header_len = 4;
} else if (len_indicator == 127) {
if (bytes_read < 10) return 0;
unsigned long long full_len = ((unsigned long long)(unsigned char)buffer[2] << 56) |
((unsigned long long)(unsigned char)buffer[3] << 48) |
((unsigned long long)(unsigned char)buffer[4] << 40) |
((unsigned long long)(unsigned char)buffer[5] << 32) |
((unsigned long long)(unsigned char)buffer[6] << 24) |
((unsigned long long)(unsigned char)buffer[7] << 16) |
((unsigned long long)(unsigned char)buffer[8] << 8) |
(unsigned char)buffer[9];
if (full_len > INT_MAX) {
return 0; // Payload too large
}
*payload_len = (int)full_len;
header_len = 10;
} else {
// Invalid length indicator
return 0;
}
if (masked) {
header_len += 4;
}
if (bytes_read < header_len + *payload_len) {
return 0; // Incomplete frame
}
*payload = (char *)buffer + header_len;
if (masked) {
char *mask_key = (char *)buffer + header_len - 4;
for (int i = 0; i < *payload_len; i++) {
(*payload)[i] ^= mask_key[i % 4];
}
}
return 1;
}
\ No newline at end of file
/*
* WebSocket SSH Library - WebSocket functions
*
* 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 WEBSOCKET_H
#define WEBSOCKET_H
#include <openssl/ssl.h>
// Function declarations
int websocket_handshake(SSL *ssl, const char *host, int port, const char *path);
int send_json_message(SSL *ssl, const char *type, const char *client_id, const char *request_id);
int send_websocket_frame(SSL *ssl, const char *data);
int send_pong_frame(SSL *ssl, const char *ping_payload, int payload_len);
int parse_websocket_frame(const char *buffer, int bytes_read, char **payload, int *payload_len);
#endif // WEBSOCKET_H
\ No newline at end of file
......@@ -26,8 +26,6 @@
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <getopt.h>
#include <sys/wait.h>
#include <fcntl.h>
......@@ -35,122 +33,14 @@
#include <errno.h>
#include <sys/select.h>
#define BUFFER_SIZE 1048576
#define DEFAULT_PORT 22
#define INITIAL_FRAME_BUFFER_SIZE 8192
char *read_config_value(const char *key) {
char *home = getenv("HOME");
if (!home) return NULL;
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s/.config/wsssh/wsssh.conf", home);
FILE *f = fopen(path, "r");
if (!f) return NULL;
char line[256];
while (fgets(line, sizeof(line), f)) {
if (strncmp(line, key, strlen(key)) == 0 && line[strlen(key)] == '=') {
char *value = strdup(line + strlen(key) + 1);
// remove newline
value[strcspn(value, "\n")] = 0;
fclose(f);
return value;
}
}
fclose(f);
return NULL;
}
#include "wssshlib.h"
#include "websocket.h"
#include "wssh_ssl.h"
#include "tunnel.h"
typedef struct {
char *local_port;
int debug;
int interval; // Connection retry interval in seconds
} wsscp_config_t;
typedef struct {
char *buffer;
size_t size;
size_t used;
} frame_buffer_t;
typedef struct {
int local_sock; // Local TCP connection socket
char request_id[37]; // UUID string
int active;
SSL *ssl; // WebSocket SSL connection
frame_buffer_t *outgoing_buffer; // Buffer for data to send to local socket
} tunnel_t;
typedef struct {
SSL *ssl;
int debug;
} thread_args_t;
tunnel_t *active_tunnel = NULL;
pthread_mutex_t tunnel_mutex;
int send_websocket_frame(SSL *ssl, const char *data);
void print_trans_flag(void) {
// Transgender pride flag colors using ANSI escape codes
const char *colors[] = {
"\033[48;5;117m", // Light blue background
"\033[48;5;218m", // Pink background
"\033[48;5;231m", // White background
"\033[48;5;218m", // Pink background
"\033[48;5;117m" // Light blue background
};
const char *reset = "\033[0m";
// Print 5 rows of colored blocks
for (int i = 0; i < 5; i++) {
printf("%s", colors[i]);
for (int j = 0; j < 40; j++) {
printf(" ");
}
printf("%s\n", reset);
}
printf("\n");
}
void print_palestinian_flag(void) {
const char *reset = "\033[0m";
const char *red = "\033[41m";
const char *black = "\033[40m";
const char *white = "\033[47m";
const char *green = "\033[42m";
int height = 12;
int width = 40;
for (int i = 0; i < height; i++) {
int triangle_width;
if (i <= 5) {
triangle_width = (i + 1) * 2;
} else {
triangle_width = (height - i) * 2;
}
const char *stripe_color;
if (i < 4) {
stripe_color = black;
} else if (i < 8) {
stripe_color = white;
} else {
stripe_color = green;
}
printf("%s", red);
for (int j = 0; j < triangle_width; j++) {
printf(" ");
}
printf("%s", stripe_color);
for (int j = triangle_width; j < width; j++) {
printf(" ");
}
printf("%s\n", reset);
}
printf("\n");
}
void print_usage(const char *program_name) {
fprintf(stderr, "Usage: %s [options] [scp_options...] source destination\n", program_name);
......@@ -161,6 +51,8 @@ void print_usage(const char *program_name) {
fprintf(stderr, " --interval SEC Connection retry interval in seconds (default: 30)\n");
fprintf(stderr, " --debug Enable debug output\n");
fprintf(stderr, " --help Show this help\n");
fprintf(stderr, " -P PORT wssshd server port (default: 9898)\n");
fprintf(stderr, " --port PORT Same as -P\n");
fprintf(stderr, "\nDonations:\n");
fprintf(stderr, " BTC: bc1q3zlkpu95amtcltsk85y0eacyzzk29v68tgc5hx\n");
fprintf(stderr, " ETH: 0xdA6dAb526515b5cb556d20269207D43fcc760E51\n");
......@@ -174,6 +66,12 @@ int parse_args(int argc, char *argv[], wsscp_config_t *config, int *remaining_ar
if (strcmp(argv[i], "--local-port") == 0 && i + 1 < argc) {
config->local_port = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
config->wssshd_port = atoi(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--port") == 0 && i + 1 < argc) {
config->wssshd_port = atoi(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--interval") == 0 && i + 1 < argc) {
config->interval = atoi(argv[i + 1]);
i++; // Skip the argument
......@@ -241,30 +139,18 @@ int parse_hostname(const char *hostname, char **client_id, char **wssshd_host, c
return 1;
}
int parse_scp_args(int argc, char *argv[], char **destination, int *scp_port, int config_port, int debug) {
int parse_scp_args(int argc, char *argv[], char **destination, int debug) {
*destination = NULL;
*scp_port = DEFAULT_PORT;
int skip_next = 0;
for (int i = 0; i < argc; i++) {
if (skip_next) {
skip_next = 0;
continue;
}
if ((strcmp(argv[i], "-P") == 0 || strcmp(argv[i], "--port") == 0) && i + 1 < argc) {
*scp_port = atoi(argv[i + 1]);
skip_next = 1;
if (debug) {
printf("[DEBUG] Found SCP port: %d\n", *scp_port);
}
} else if (argv[i][0] != '-') {
if (argv[i][0] != '-') {
// Check if this looks like a remote destination (contains @ or :)
if (strchr(argv[i], '@') || strchr(argv[i], ':')) {
*destination = argv[i];
if (debug) {
printf("[DEBUG] Found SCP destination: %s\n", *destination);
}
break;
}
}
}
......@@ -274,11 +160,6 @@ int parse_scp_args(int argc, char *argv[], char **destination, int *scp_port, in
return 0;
}
// If no port found in args, use config
if (*scp_port == DEFAULT_PORT && config_port != 0) {
*scp_port = config_port;
}
return 1;
}
......@@ -314,19 +195,7 @@ char **modify_scp_args(int argc, char *argv[], const char *original_host, int lo
snprintf(port_str, 16, "%d", local_port);
new_args[idx++] = port_str;
int skip_next = 0;
for (int i = 0; i < argc; i++) {
if (skip_next) {
skip_next = 0;
continue;
}
if (strcmp(argv[i], "-P") == 0 || strcmp(argv[i], "--port") == 0 ||
strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--host") == 0) {
skip_next = 1;
continue;
}
// Check if this argument is the original host:path
if (strcmp(argv[i], original_host) == 0) {
// Replace user@host:path with user@localhost:path or host:path with localhost:path
......@@ -375,881 +244,17 @@ char **modify_scp_args(int argc, char *argv[], const char *original_host, int lo
return new_args;
}
int find_available_port() {
struct sockaddr_in addr;
int sock;
int port = 0;
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
return 0;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = 0;
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("Bind failed");
close(sock);
return 0;
}
socklen_t len = sizeof(addr);
if (getsockname(sock, (struct sockaddr *)&addr, &len) < 0) {
perror("Getsockname failed");
close(sock);
return 0;
}
port = ntohs(addr.sin_port);
close(sock);
return port;
}
void generate_request_id(char *request_id, size_t size) {
const char charset[] = "0123456789abcdef";
for (size_t i = 0; i < size - 1; i++) {
request_id[i] = charset[rand() % (sizeof(charset) - 1)];
}
request_id[size - 1] = '\0';
}
int websocket_handshake(SSL *ssl, const char *host, int port, const char *path) {
char request[1024];
char response[BUFFER_SIZE];
int bytes_read;
// Send WebSocket handshake
snprintf(request, sizeof(request),
"GET %s HTTP/1.1\r\n"
"Host: %s:%d\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n",
path, host, port);
if (SSL_write(ssl, request, strlen(request)) <= 0) {
ERR_print_errors_fp(stderr);
fprintf(stderr, "WebSocket handshake send failed\n");
return 0;
}
// Read response
bytes_read = SSL_read(ssl, response, sizeof(response) - 1);
if (bytes_read <= 0) {
ERR_print_errors_fp(stderr);
fprintf(stderr, "WebSocket handshake recv failed\n");
return 0;
}
response[bytes_read] = '\0';
// Check for successful handshake
if (strstr(response, "101 Switching Protocols") == NULL) {
fprintf(stderr, "WebSocket handshake failed\n");
return 0;
}
return 1;
}
int send_json_message(SSL *ssl, const char *type, const char *client_id, const char *request_id) {
char message[1024];
if (request_id) {
snprintf(message, sizeof(message),
"{\"type\":\"%s\",\"client_id\":\"%s\",\"request_id\":\"%s\"}",
type, client_id, request_id);
} else {
snprintf(message, sizeof(message),
"{\"type\":\"%s\",\"client_id\":\"%s\"}",
type, client_id);
}
// Send as WebSocket frame
return send_websocket_frame(ssl, message);
}
int send_websocket_frame(SSL *ssl, const char *data) {
char frame[BUFFER_SIZE];
frame[0] = 0x81; // FIN + text opcode
int msg_len = strlen(data);
int header_len = 2;
if (msg_len <= 125) {
frame[1] = 0x80 | msg_len; // MASK + length
} else if (msg_len <= 65535) {
frame[1] = 0x80 | 126; // MASK + extended length
frame[2] = (msg_len >> 8) & 0xFF;
frame[3] = msg_len & 0xFF;
header_len = 4;
} else {
frame[1] = 0x80 | 127; // MASK + extended length
frame[2] = 0;
frame[3] = 0;
frame[4] = 0;
frame[5] = 0;
frame[6] = (msg_len >> 24) & 0xFF;
frame[7] = (msg_len >> 16) & 0xFF;
frame[8] = (msg_len >> 8) & 0xFF;
frame[9] = msg_len & 0xFF;
header_len = 10;
}
// Add mask key
char mask_key[4];
for (int i = 0; i < 4; i++) {
mask_key[i] = rand() % 256;
frame[header_len + i] = mask_key[i];
}
header_len += 4;
// Mask payload
for (int i = 0; i < msg_len; i++) {
frame[header_len + i] = data[i] ^ mask_key[i % 4];
}
int frame_len = header_len + msg_len;
return SSL_write(ssl, frame, frame_len) > 0;
}
int send_pong_frame(SSL *ssl, const char *ping_payload, int payload_len) {
char frame[BUFFER_SIZE];
frame[0] = 0x8A; // FIN + pong opcode
int header_len = 2;
if (payload_len <= 125) {
frame[1] = 0x80 | payload_len; // MASK + length
} else if (payload_len <= 65535) {
frame[1] = 0x80 | 126; // MASK + extended length
frame[2] = (payload_len >> 8) & 0xFF;
frame[3] = payload_len & 0xFF;
header_len = 4;
} else {
frame[1] = 0x80 | 127; // MASK + extended length
frame[2] = 0;
frame[3] = 0;
frame[4] = 0;
frame[5] = 0;
frame[6] = (payload_len >> 24) & 0xFF;
frame[7] = (payload_len >> 16) & 0xFF;
frame[8] = (payload_len >> 8) & 0xFF;
frame[9] = payload_len & 0xFF;
header_len = 10;
}
// Add mask key
char mask_key[4];
for (int i = 0; i < 4; i++) {
mask_key[i] = rand() % 256;
frame[header_len + i] = mask_key[i];
}
header_len += 4;
// Mask payload
for (int i = 0; i < payload_len; i++) {
frame[header_len + i] = ping_payload[i] ^ mask_key[i % 4];
}
int frame_len = header_len + payload_len;
return SSL_write(ssl, frame, frame_len) > 0;
}
frame_buffer_t *frame_buffer_init() {
frame_buffer_t *fb = malloc(sizeof(frame_buffer_t));
if (!fb) return NULL;
fb->buffer = malloc(INITIAL_FRAME_BUFFER_SIZE);
if (!fb->buffer) {
free(fb);
return NULL;
}
fb->size = INITIAL_FRAME_BUFFER_SIZE;
fb->used = 0;
return fb;
}
void frame_buffer_free(frame_buffer_t *fb) {
if (fb) {
free(fb->buffer);
free(fb);
}
}
int frame_buffer_resize(frame_buffer_t *fb, size_t new_size) {
if (new_size <= fb->size) return 1;
char *new_buffer = realloc(fb->buffer, new_size);
if (!new_buffer) return 0;
fb->buffer = new_buffer;
fb->size = new_size;
return 1;
}
int frame_buffer_append(frame_buffer_t *fb, const char *data, size_t len) {
if (fb->used + len > fb->size) {
size_t new_size = fb->size * 2;
while (new_size < fb->used + len) {
new_size *= 2;
}
if (!frame_buffer_resize(fb, new_size)) {
return 0;
}
}
memcpy(fb->buffer + fb->used, data, len);
fb->used += len;
return 1;
}
int frame_buffer_consume(frame_buffer_t *fb, size_t len) {
if (len > fb->used) return 0;
memmove(fb->buffer, fb->buffer + len, fb->used - len);
fb->used -= len;
return 1;
}
int parse_websocket_frame(const char *buffer, int bytes_read, char **payload, int *payload_len) {
if (bytes_read < 2) {
return 0; // Not enough data for a frame
}
unsigned char frame_type = buffer[0] & 0x8F;
if (frame_type != 0x81 && frame_type != 0x82 && frame_type != 0x88 && frame_type != 0x89 && frame_type != 0x8A) {
return 0; // Not a supported frame type (text, binary, close, ping, pong)
}
int masked = buffer[1] & 0x80;
int len_indicator = buffer[1] & 0x7F;
int header_len = 2;
if (len_indicator <= 125) {
*payload_len = len_indicator;
} else if (len_indicator == 126) {
if (bytes_read < 4) return 0;
*payload_len = ((unsigned char)buffer[2] << 8) | (unsigned char)buffer[3];
header_len = 4;
} else if (len_indicator == 127) {
if (bytes_read < 10) return 0;
unsigned long long full_len = ((unsigned long long)(unsigned char)buffer[2] << 56) |
((unsigned long long)(unsigned char)buffer[3] << 48) |
((unsigned long long)(unsigned char)buffer[4] << 40) |
((unsigned long long)(unsigned char)buffer[5] << 32) |
((unsigned long long)(unsigned char)buffer[6] << 24) |
((unsigned long long)(unsigned char)buffer[7] << 16) |
((unsigned long long)(unsigned char)buffer[8] << 8) |
(unsigned char)buffer[9];
if (full_len > INT_MAX) {
return 0; // Payload too large
}
*payload_len = (int)full_len;
header_len = 10;
} else {
// Invalid length indicator
return 0;
}
if (masked) {
header_len += 4;
}
if (bytes_read < header_len + *payload_len) {
return 0; // Incomplete frame
}
*payload = (char *)buffer + header_len;
if (masked) {
char *mask_key = (char *)buffer + header_len - 4;
for (int i = 0; i < *payload_len; i++) {
(*payload)[i] ^= mask_key[i % 4];
}
}
return 1;
}
void *forward_tcp_to_ws(void *arg) {
thread_args_t *args = (thread_args_t *)arg;
SSL *ssl = args->ssl;
int debug = args->debug;
char buffer[BUFFER_SIZE];
int bytes_read;
fd_set readfds;
struct timeval tv;
while (1) {
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || !active_tunnel->active) {
pthread_mutex_unlock(&tunnel_mutex);
break;
}
int sock = active_tunnel->local_sock;
char request_id[37];
strcpy(request_id, active_tunnel->request_id);
// Send pending data from outgoing buffer to local socket
if (active_tunnel->outgoing_buffer->used > 0) {
ssize_t sent = send(sock, active_tunnel->outgoing_buffer->buffer, active_tunnel->outgoing_buffer->used, MSG_DONTWAIT);
if (sent > 0) {
frame_buffer_consume(active_tunnel->outgoing_buffer, sent);
if (debug) {
printf("[DEBUG] Sent %zd bytes from buffer to local socket\n", sent);
fflush(stdout);
}
} else if (sent == -1 && errno != EAGAIN && errno != EWOULDBLOCK) {
if (debug) {
perror("[DEBUG] send to local socket failed");
fflush(stdout);
}
pthread_mutex_unlock(&tunnel_mutex);
break;
}
}
pthread_mutex_unlock(&tunnel_mutex);
// Use select to wait for data on local socket
FD_ZERO(&readfds);
FD_SET(sock, &readfds);
tv.tv_sec = 0; // 0 seconds
tv.tv_usec = 50000; // 50ms timeout
int retval = select(sock + 1, &readfds, NULL, NULL, &tv);
if (retval == -1) {
if (debug) {
perror("[DEBUG] select failed");
fflush(stdout);
}
break;
} else if (retval == 0) {
// Timeout, continue loop
continue;
}
if (FD_ISSET(sock, &readfds)) {
bytes_read = recv(sock, buffer, sizeof(buffer), 0);
if (bytes_read <= 0) {
if (debug) {
printf("[DEBUG] TCP connection closed or error\n");
fflush(stdout);
}
break;
}
if (debug) {
printf("[DEBUG] Forwarding %d bytes from TCP to WebSocket\n", bytes_read);
fflush(stdout);
}
// Convert to hex
char hex_data[bytes_read * 2 + 1];
for (int i = 0; i < bytes_read; i++) {
sprintf(hex_data + i * 2, "%02x", (unsigned char)buffer[i]);
}
hex_data[bytes_read * 2] = '\0';
// Send as tunnel_data
char message[BUFFER_SIZE];
snprintf(message, sizeof(message),
"{\"type\":\"tunnel_data\",\"request_id\":\"%s\",\"data\":\"%s\"}",
request_id, hex_data);
if (!send_websocket_frame(ssl, message)) {
if (debug) {
printf("[DEBUG] Failed to send WebSocket frame\n");
fflush(stdout);
}
break;
}
}
}
if (debug) {
printf("[DEBUG] TCP to WebSocket forwarding thread exiting\n");
fflush(stdout);
}
free(args);
return NULL;
}
void handle_tunnel_data(SSL *ssl __attribute__((unused)), const char *request_id, const char *data_hex, int debug __attribute__((unused))) {
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || strcmp(active_tunnel->request_id, request_id) != 0) {
pthread_mutex_unlock(&tunnel_mutex);
return;
}
pthread_mutex_unlock(&tunnel_mutex);
// Decode hex data
size_t data_len = strlen(data_hex) / 2;
char *data = malloc(data_len);
if (!data) return;
for (size_t i = 0; i < data_len; i++) {
sscanf(data_hex + i * 2, "%2hhx", &data[i]);
}
// Append to outgoing buffer
pthread_mutex_lock(&tunnel_mutex);
if (!frame_buffer_append(active_tunnel->outgoing_buffer, data, data_len)) {
if (debug) {
printf("[DEBUG] Failed to append to outgoing buffer, dropping %zu bytes\n", data_len);
fflush(stdout);
}
}
pthread_mutex_unlock(&tunnel_mutex);
free(data);
}
void handle_tunnel_close(SSL *ssl __attribute__((unused)), const char *request_id, int debug) {
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel && strcmp(active_tunnel->request_id, request_id) == 0) {
active_tunnel->active = 0;
close(active_tunnel->local_sock);
frame_buffer_free(active_tunnel->outgoing_buffer);
free(active_tunnel);
active_tunnel = NULL;
if (debug) {
printf("[DEBUG] Tunnel %s closed\n", request_id);
}
}
pthread_mutex_unlock(&tunnel_mutex);
}
int reconnect_websocket(tunnel_t *tunnel, const char *wssshd_host, int wssshd_port, const char *client_id, const char *request_id, int debug) {
struct sockaddr_in server_addr;
struct hostent *he;
int sock;
SSL_CTX *ssl_ctx;
SSL *ssl;
char buffer[BUFFER_SIZE];
int bytes_read;
if (debug) {
printf("[DEBUG] Attempting to reconnect WebSocket connection...\n");
fflush(stdout);
}
// Resolve hostname
if ((he = gethostbyname(wssshd_host)) == NULL) {
herror("gethostbyname");
return -1;
}
// Create socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(wssshd_port);
server_addr.sin_addr = *((struct in_addr *)he->h_addr);
// Connect to server
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Connection failed");
close(sock);
return -1;
}
// Initialize SSL
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ssl_ctx = SSL_CTX_new(TLS_client_method());
if (!ssl_ctx) {
ERR_print_errors_fp(stderr);
close(sock);
return -1;
}
// Allow self-signed certificates
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
ssl = SSL_new(ssl_ctx);
SSL_set_fd(ssl, sock);
if (SSL_connect(ssl) <= 0) {
ERR_print_errors_fp(stderr);
fprintf(stderr, "SSL connection failed\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Perform WebSocket handshake
if (!websocket_handshake(ssl, wssshd_host, wssshd_port, "/")) {
fprintf(stderr, "WebSocket handshake failed\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Send tunnel request
if (!send_json_message(ssl, "tunnel_request", client_id, request_id)) {
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Read acknowledgment
bytes_read = SSL_read(ssl, buffer, sizeof(buffer));
if (bytes_read <= 0) {
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Check if it's a close frame
if (bytes_read >= 2 && (buffer[0] & 0x8F) == 0x88) {
if (debug) {
printf("[DEBUG] Server sent close frame during reconnection\n");
fflush(stdout);
}
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Parse WebSocket frame
char *payload;
int payload_len;
if (!parse_websocket_frame(buffer, bytes_read, &payload, &payload_len)) {
fprintf(stderr, "Failed to parse WebSocket frame during reconnection\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Null terminate payload
payload[payload_len] = '\0';
// Check for tunnel acknowledgment
if (strstr(payload, "tunnel_ack") == NULL) {
fprintf(stderr, "Tunnel request denied during reconnection: %s\n", payload);
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Success - update the tunnel's SSL connection
if (tunnel->ssl) {
SSL_free(tunnel->ssl);
}
tunnel->ssl = ssl;
// Clean up old SSL context
SSL_CTX_free(ssl_ctx);
if (debug) {
printf("[DEBUG] WebSocket reconnection successful\n");
fflush(stdout);
}
return 0;
}
int setup_tunnel(const char *wssshd_host, int wssshd_port, const char *client_id, int local_port, int debug) {
struct sockaddr_in server_addr;
struct hostent *he;
int sock;
SSL_CTX *ssl_ctx;
SSL *ssl;
char buffer[BUFFER_SIZE];
int bytes_read;
// Generate request ID
char request_id[37];
generate_request_id(request_id, sizeof(request_id));
// Resolve hostname
if ((he = gethostbyname(wssshd_host)) == NULL) {
herror("gethostbyname");
return -1;
}
// Create socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(wssshd_port);
server_addr.sin_addr = *((struct in_addr *)he->h_addr);
// Connect to server
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Connection failed");
close(sock);
return -1;
}
// Initialize SSL
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ssl_ctx = SSL_CTX_new(TLS_client_method());
if (!ssl_ctx) {
ERR_print_errors_fp(stderr);
close(sock);
return 0;
}
// Allow self-signed certificates
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
ssl = SSL_new(ssl_ctx);
SSL_set_fd(ssl, sock);
if (debug) {
printf("[DEBUG] Establishing SSL connection...\n");
fflush(stdout);
}
if (SSL_connect(ssl) <= 0) {
ERR_print_errors_fp(stderr);
fprintf(stderr, "SSL connection failed\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (debug) {
printf("[DEBUG] SSL connection established\n");
fflush(stdout);
}
// Perform WebSocket handshake
if (debug) {
printf("[DEBUG] Performing WebSocket handshake to %s:%d\n", wssshd_host, wssshd_port);
fflush(stdout);
}
if (!websocket_handshake(ssl, wssshd_host, wssshd_port, "/")) {
fprintf(stderr, "WebSocket handshake failed\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (debug) {
printf("[DEBUG] WebSocket handshake successful\n");
fflush(stdout);
}
// Send tunnel request
if (!send_json_message(ssl, "tunnel_request", client_id, request_id)) {
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (debug) {
printf("[DEBUG] Tunnel request sent for client: %s, request_id: %s\n", client_id, request_id);
}
// Read acknowledgment
bytes_read = SSL_read(ssl, buffer, sizeof(buffer));
if (bytes_read <= 0) {
if (debug) {
printf("[DEBUG] No acknowledgment received\n");
}
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (debug) {
printf("[DEBUG] Read %d bytes acknowledgment\n", bytes_read);
printf("[DEBUG] Raw acknowledgment: ");
for (int i = 0; i < bytes_read && i < 50; i++) {
if (buffer[i] >= 32 && buffer[i] < 127) {
printf("%c", buffer[i]);
} else {
printf("\\x%02x", (unsigned char)buffer[i]);
}
}
printf("\n");
fflush(stdout);
}
// Check if it's a close frame
if (bytes_read >= 2 && (buffer[0] & 0x8F) == 0x88) {
if (debug) {
printf("[DEBUG] Server sent close frame\n");
if (bytes_read >= 6) {
int close_code = (buffer[2] << 8) | buffer[3];
printf("[DEBUG] Close code: %d\n", close_code);
if (bytes_read > 6) {
printf("[DEBUG] Close reason: %.*s\n", bytes_read - 6, buffer + 6);
}
}
fflush(stdout);
}
fprintf(stderr, "Tunnel request rejected by server\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Parse WebSocket frame
char *payload;
int payload_len;
if (!parse_websocket_frame(buffer, bytes_read, &payload, &payload_len)) {
fprintf(stderr, "Failed to parse WebSocket frame\n");
if (debug) {
printf("[DEBUG] Frame parsing failed. First few bytes: ");
for (int i = 0; i < bytes_read && i < 10; i++) {
printf("%02x ", (unsigned char)buffer[i]);
}
printf("\n");
fflush(stdout);
}
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Null terminate payload
payload[payload_len] = '\0';
if (debug) {
printf("[DEBUG] Received payload: '%s' (length: %d)\n", payload, payload_len);
printf("[DEBUG] Looking for tunnel_ack in: ");
for (int i = 0; i < payload_len && i < 50; i++) {
if (payload[i] >= 32 && payload[i] < 127) {
printf("%c", payload[i]);
} else {
printf("\\x%02x", (unsigned char)payload[i]);
}
}
printf("\n");
fflush(stdout);
}
// Check for tunnel acknowledgment
if (strstr(payload, "tunnel_ack") == NULL) {
fprintf(stderr, "Tunnel request denied or failed: %s\n", payload);
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (debug) {
printf("[DEBUG] Tunnel established, local port: %d\n", local_port);
}
// Create tunnel structure
active_tunnel = malloc(sizeof(tunnel_t));
if (!active_tunnel) {
perror("Memory allocation failed");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
active_tunnel->outgoing_buffer = frame_buffer_init();
if (!active_tunnel->outgoing_buffer) {
perror("Failed to initialize outgoing buffer");
free(active_tunnel);
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
strcpy(active_tunnel->request_id, request_id);
active_tunnel->local_sock = -1;
active_tunnel->active = 1;
active_tunnel->ssl = ssl;
// Start listening on local port
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0) {
perror("Local socket creation failed");
free(active_tunnel);
active_tunnel = NULL;
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
struct sockaddr_in local_addr;
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(local_port);
local_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (bind(listen_sock, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) {
perror("Local bind failed");
close(listen_sock);
frame_buffer_free(active_tunnel->outgoing_buffer);
free(active_tunnel);
active_tunnel = NULL;
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (listen(listen_sock, 1) < 0) {
perror("Local listen failed");
close(listen_sock);
frame_buffer_free(active_tunnel->outgoing_buffer);
free(active_tunnel);
active_tunnel = NULL;
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (debug) {
printf("[DEBUG] Listening on localhost:%d\n", local_port);
fflush(stdout);
}
// Return success - tunnel is set up and listening
return listen_sock;
}
int main(int argc, char *argv[]) {
// Read config
char *config_port_str = read_config_value("port");
int config_port = config_port_str ? atoi(config_port_str) : 0;
free(config_port_str);
char *config_domain = read_config_value("domain");
wsscp_config_t config = {
.local_port = NULL,
.wssshd_port = 9898,
.debug = 0,
.interval = 30
};
......@@ -1293,10 +298,9 @@ int main(int argc, char *argv[]) {
return 1;
}
// Parse SCP arguments to extract destination and port
// Parse SCP arguments to extract destination
char *scp_destination = NULL;
int scp_port = DEFAULT_PORT;
if (!parse_scp_args(remaining_argc, remaining_argv, &scp_destination, &scp_port, config_port, config.debug)) {
if (!parse_scp_args(remaining_argc, remaining_argv, &scp_destination, config.debug)) {
pthread_mutex_destroy(&tunnel_mutex);
free(config_domain);
return 1;
......@@ -1305,7 +309,7 @@ int main(int argc, char *argv[]) {
// Parse the SCP destination to extract client_id and wssshd_host
char *client_id = NULL;
char *wssshd_host = NULL;
int wssshd_port = scp_port; // The -P port becomes the WebSocket server port
int wssshd_port = config.wssshd_port; // Use the wssshd port from config
if (!parse_hostname(scp_destination, &client_id, &wssshd_host, config_domain)) {
pthread_mutex_destroy(&tunnel_mutex);
......@@ -1373,7 +377,7 @@ int main(int argc, char *argv[]) {
fflush(stdout);
}
listen_sock = setup_tunnel(wssshd_host, wssshd_port, client_id, local_port, config.debug);
listen_sock = setup_tunnel(wssshd_host, wssshd_port, client_id, local_port, config.debug, 1);
if (listen_sock < 0) {
setup_attempts++;
......
/*
* WebSocket SSH Library - SSL functions 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/>.
*/
#include "wssh_ssl.h"
#include <openssl/err.h>
#include <stdio.h>
SSL_CTX *create_ssl_context(void) {
SSL_CTX *ssl_ctx;
// Initialize SSL
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ssl_ctx = SSL_CTX_new(TLS_client_method());
if (!ssl_ctx) {
ERR_print_errors_fp(stderr);
return NULL;
}
// Allow self-signed certificates
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
return ssl_ctx;
}
SSL *create_ssl_connection(SSL_CTX *ssl_ctx, int sock, int debug) {
SSL *ssl = SSL_new(ssl_ctx);
SSL_set_fd(ssl, sock);
if (debug) {
printf("[DEBUG] Establishing SSL connection...\n");
fflush(stdout);
}
if (SSL_connect(ssl) <= 0) {
ERR_print_errors_fp(stderr);
fprintf(stderr, "SSL connection failed\n");
SSL_free(ssl);
return NULL;
}
if (debug) {
printf("[DEBUG] SSL connection established\n");
fflush(stdout);
}
return ssl;
}
\ No newline at end of file
/*
* WebSocket SSH Library - SSL functions
*
* 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 WSSH_SSL_H
#define WSSH_SSL_H
#include <openssl/ssl.h>
// Function declarations
SSL_CTX *create_ssl_context(void);
SSL *create_ssl_connection(SSL_CTX *ssl_ctx, int sock, int debug);
#endif // WSSH_SSL_H
\ No newline at end of file
......@@ -26,165 +26,21 @@
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <getopt.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/select.h>
#define BUFFER_SIZE 1048576
#define DEFAULT_PORT 22
char *read_config_value(const char *key) {
char *home = getenv("HOME");
if (!home) return NULL;
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s/.config/wsssh/wsssh.conf", home);
FILE *f = fopen(path, "r");
if (!f) return NULL;
char line[256];
while (fgets(line, sizeof(line), f)) {
if (strncmp(line, key, strlen(key)) == 0 && line[strlen(key)] == '=') {
char *value = strdup(line + strlen(key) + 1);
// remove newline
value[strcspn(value, "\n")] = 0;
fclose(f);
return value;
}
}
fclose(f);
return NULL;
}
typedef struct {
char *local_port;
int debug;
int interval; // Reconnection interval in seconds
} wsssh_config_t;
typedef struct {
int local_sock; // Local TCP connection socket
char request_id[37]; // UUID string
int active;
SSL *ssl; // WebSocket SSL connection
} tunnel_t;
typedef struct {
SSL *ssl;
int debug;
} thread_args_t;
tunnel_t *active_tunnel = NULL;
pthread_mutex_t tunnel_mutex;
int send_websocket_frame(SSL *ssl, const char *data);
int send_pong_frame(SSL *ssl, const char *ping_payload, int payload_len);
int send_pong_frame(SSL *ssl, const char *ping_payload, int payload_len) {
char frame[BUFFER_SIZE];
frame[0] = 0x8A; // FIN + pong opcode
int header_len = 2;
if (payload_len <= 125) {
frame[1] = 0x80 | payload_len; // MASK + length
} else if (payload_len <= 65535) {
frame[1] = 0x80 | 126; // MASK + extended length
frame[2] = (payload_len >> 8) & 0xFF;
frame[3] = payload_len & 0xFF;
header_len = 4;
} else {
frame[1] = 0x80 | 127; // MASK + extended length
frame[2] = 0;
frame[3] = 0;
frame[4] = 0;
frame[5] = 0;
frame[6] = (payload_len >> 24) & 0xFF;
frame[7] = (payload_len >> 16) & 0xFF;
frame[8] = (payload_len >> 8) & 0xFF;
frame[9] = payload_len & 0xFF;
header_len = 10;
}
#include "wssshlib.h"
#include "websocket.h"
#include "wssh_ssl.h"
#include "tunnel.h"
// Add mask key
char mask_key[4];
for (int i = 0; i < 4; i++) {
mask_key[i] = rand() % 256;
frame[header_len + i] = mask_key[i];
}
header_len += 4;
// Mask payload
for (int i = 0; i < payload_len; i++) {
frame[header_len + i] = ping_payload[i] ^ mask_key[i % 4];
}
int frame_len = header_len + payload_len;
return SSL_write(ssl, frame, frame_len) > 0;
}
void print_trans_flag(void) {
// Transgender pride flag colors using ANSI escape codes
const char *colors[] = {
"\033[48;5;117m", // Light blue background
"\033[48;5;218m", // Pink background
"\033[48;5;231m", // White background
"\033[48;5;218m", // Pink background
"\033[48;5;117m" // Light blue background
};
const char *reset = "\033[0m";
// Print 5 rows of colored blocks
for (int i = 0; i < 5; i++) {
printf("%s", colors[i]);
for (int j = 0; j < 40; j++) {
printf(" ");
}
printf("%s\n", reset);
}
printf("\n");
}
void print_palestinian_flag(void) {
const char *reset = "\033[0m";
const char *red = "\033[41m";
const char *black = "\033[40m";
const char *white = "\033[47m";
const char *green = "\033[42m";
int height = 12;
int width = 40;
for (int i = 0; i < height; i++) {
int triangle_width;
if (i <= 5) {
triangle_width = (i + 1) * 2;
} else {
triangle_width = (height - i) * 2;
}
const char *stripe_color;
if (i < 4) {
stripe_color = black;
} else if (i < 8) {
stripe_color = white;
} else {
stripe_color = green;
}
printf("%s", red);
for (int j = 0; j < triangle_width; j++) {
printf(" ");
}
printf("%s", stripe_color);
for (int j = triangle_width; j < width; j++) {
printf(" ");
}
printf("%s\n", reset);
}
printf("\n");
}
void print_usage(const char *program_name) {
fprintf(stderr, "Usage: %s [options] user@client.domain [ssh_options...]\n", program_name);
......@@ -195,6 +51,8 @@ void print_usage(const char *program_name) {
fprintf(stderr, " --interval SEC Connection retry interval in seconds (default: 30)\n");
fprintf(stderr, " --debug Enable debug output\n");
fprintf(stderr, " --help Show this help\n");
fprintf(stderr, " -p PORT wssshd server port (default: 9898)\n");
fprintf(stderr, " --port PORT Same as -p\n");
fprintf(stderr, "\nDonations:\n");
fprintf(stderr, " BTC: bc1q3zlkpu95amtcltsk85y0eacyzzk29v68tgc5hx\n");
fprintf(stderr, " ETH: 0xdA6dAb526515b5cb556d20269207D43fcc760E51\n");
......@@ -208,6 +66,12 @@ int parse_args(int argc, char *argv[], wsssh_config_t *config, int *remaining_ar
if (strcmp(argv[i], "--local-port") == 0 && i + 1 < argc) {
config->local_port = strdup(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "-p") == 0 && i + 1 < argc) {
config->wssshd_port = atoi(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--port") == 0 && i + 1 < argc) {
config->wssshd_port = atoi(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--interval") == 0 && i + 1 < argc) {
config->interval = atoi(argv[i + 1]);
i++; // Skip the argument
......@@ -274,29 +138,17 @@ int parse_hostname(const char *hostname, char **client_id, char **wssshd_host, c
return 1;
}
int parse_ssh_args(int argc, char *argv[], char **host, int *ssh_port, int config_port, int debug) {
int parse_ssh_args(int argc, char *argv[], char **host, int debug) {
*host = NULL;
*ssh_port = DEFAULT_PORT;
int skip_next = 0;
for (int i = 0; i < argc; i++) {
if (skip_next) {
skip_next = 0;
continue;
}
if ((strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0) && i + 1 < argc) {
*ssh_port = atoi(argv[i + 1]);
skip_next = 1;
if (debug) {
printf("[DEBUG] Found SSH port: %d\n", *ssh_port);
}
} else if (argv[i][0] != '-' && !*host) {
if (argv[i][0] != '-' && !*host) {
// First non-option argument should be the host
*host = argv[i];
if (debug) {
printf("[DEBUG] Found SSH host: %s\n", *host);
}
break;
}
}
......@@ -305,11 +157,6 @@ int parse_ssh_args(int argc, char *argv[], char **host, int *ssh_port, int confi
return 0;
}
// If no port found in args, use config
if (*ssh_port == DEFAULT_PORT && config_port != 0) {
*ssh_port = config_port;
}
return 1;
}
......@@ -345,19 +192,7 @@ char **modify_ssh_args(int argc, char *argv[], const char *original_host, int lo
snprintf(port_str, 16, "%d", local_port);
new_args[idx++] = port_str;
int skip_next = 0;
for (int i = 0; i < argc; i++) {
if (skip_next) {
skip_next = 0;
continue;
}
if (strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "--port") == 0 ||
strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--host") == 0) {
skip_next = 1;
continue;
}
// Check if this argument is the original host
if (strcmp(argv[i], original_host) == 0) {
// Replace user@host with user@localhost or host with localhost
......@@ -389,749 +224,24 @@ char **modify_ssh_args(int argc, char *argv[], const char *original_host, int lo
return new_args;
}
int find_available_port() {
struct sockaddr_in addr;
int sock;
int port = 0;
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
return 0;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = 0;
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("Bind failed");
close(sock);
return 0;
}
socklen_t len = sizeof(addr);
if (getsockname(sock, (struct sockaddr *)&addr, &len) < 0) {
perror("Getsockname failed");
close(sock);
return 0;
}
port = ntohs(addr.sin_port);
close(sock);
return port;
}
void generate_request_id(char *request_id, size_t size) {
const char charset[] = "0123456789abcdef";
for (size_t i = 0; i < size - 1; i++) {
request_id[i] = charset[rand() % (sizeof(charset) - 1)];
}
request_id[size - 1] = '\0';
}
int websocket_handshake(SSL *ssl, const char *host, int port, const char *path) {
char request[1024];
char response[BUFFER_SIZE];
int bytes_read;
// Send WebSocket handshake
snprintf(request, sizeof(request),
"GET %s HTTP/1.1\r\n"
"Host: %s:%d\r\n"
"Upgrade: websocket\r\n"
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n",
path, host, port);
if (SSL_write(ssl, request, strlen(request)) <= 0) {
ERR_print_errors_fp(stderr);
fprintf(stderr, "WebSocket handshake send failed\n");
return 0;
}
// Read response
bytes_read = SSL_read(ssl, response, sizeof(response) - 1);
if (bytes_read <= 0) {
ERR_print_errors_fp(stderr);
fprintf(stderr, "WebSocket handshake recv failed\n");
return 0;
}
response[bytes_read] = '\0';
// Check for successful handshake
if (strstr(response, "101 Switching Protocols") == NULL) {
fprintf(stderr, "WebSocket handshake failed\n");
return 0;
}
return 1;
}
int send_json_message(SSL *ssl, const char *type, const char *client_id, const char *request_id) {
char message[1024];
if (request_id) {
snprintf(message, sizeof(message),
"{\"type\":\"%s\",\"client_id\":\"%s\",\"request_id\":\"%s\"}",
type, client_id, request_id);
} else {
snprintf(message, sizeof(message),
"{\"type\":\"%s\",\"client_id\":\"%s\"}",
type, client_id);
}
// Send as WebSocket frame
return send_websocket_frame(ssl, message);
}
int send_all(SSL *ssl, const char *data, int len) {
int total_sent = 0;
while (total_sent < len) {
int sent = SSL_write(ssl, data + total_sent, len - total_sent);
if (sent <= 0) {
ERR_print_errors_fp(stderr);
return 0;
}
total_sent += sent;
}
return 1;
}
int send_websocket_frame(SSL *ssl, const char *data) {
int msg_len = strlen(data);
int header_len = 2;
if (msg_len <= 125) {
header_len = 6; // 2 + 4 for mask
} else if (msg_len <= 65535) {
header_len = 8; // 4 + 4 for mask
} else {
header_len = 14; // 10 + 4 for mask
}
char *frame = malloc(header_len + msg_len);
if (!frame) {
perror("Memory allocation failed for frame");
return 0;
}
frame[0] = 0x81; // FIN + text opcode
if (msg_len <= 125) {
frame[1] = 0x80 | msg_len; // MASK + length
} else if (msg_len <= 65535) {
frame[1] = 0x80 | 126; // MASK + extended length
frame[2] = (msg_len >> 8) & 0xFF;
frame[3] = msg_len & 0xFF;
} else {
frame[1] = 0x80 | 127; // MASK + extended length
frame[2] = 0;
frame[3] = 0;
frame[4] = 0;
frame[5] = 0;
frame[6] = (msg_len >> 24) & 0xFF;
frame[7] = (msg_len >> 16) & 0xFF;
frame[8] = (msg_len >> 8) & 0xFF;
frame[9] = msg_len & 0xFF;
}
// Add mask key
char mask_key[4];
for (int i = 0; i < 4; i++) {
mask_key[i] = rand() % 256;
frame[header_len - 4 + i] = mask_key[i];
}
// Mask payload
for (int i = 0; i < msg_len; i++) {
frame[header_len + i] = data[i] ^ mask_key[i % 4];
}
int frame_len = header_len + msg_len;
int result = send_all(ssl, frame, frame_len);
free(frame);
return result;
}
int parse_websocket_frame(const char *buffer, int bytes_read, char **payload, int *payload_len) {
if (bytes_read < 2 || ((buffer[0] & 0x8F) != 0x81 && (buffer[0] & 0x8F) != 0x82 && (buffer[0] & 0x8F) != 0x89)) { // Not a text, binary, or ping frame
return 0;
}
int masked = buffer[1] & 0x80;
int len_indicator = buffer[1] & 0x7F;
int header_len = 2;
if (len_indicator <= 125) {
*payload_len = len_indicator;
} else if (len_indicator == 126) {
if (bytes_read < 4) return 0;
*payload_len = (buffer[2] << 8) | buffer[3];
header_len = 4;
} else if (len_indicator == 127) {
if (bytes_read < 10) return 0;
*payload_len = (buffer[6] << 24) | (buffer[7] << 16) | (buffer[8] << 8) | buffer[9];
header_len = 10;
}
if (masked) {
header_len += 4;
}
if (bytes_read < header_len + *payload_len) {
return 0;
}
*payload = (char *)buffer + header_len;
if (masked) {
char *mask_key = (char *)buffer + header_len - 4;
for (int i = 0; i < *payload_len; i++) {
(*payload)[i] ^= mask_key[i % 4];
}
}
return 1;
}
void *forward_tcp_to_ws(void *arg) {
thread_args_t *args = (thread_args_t *)arg;
SSL *ssl = args->ssl;
int debug = args->debug;
char buffer[BUFFER_SIZE];
int bytes_read;
fd_set readfds;
struct timeval tv;
while (1) {
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || !active_tunnel->active) {
pthread_mutex_unlock(&tunnel_mutex);
break;
}
int sock = active_tunnel->local_sock;
char request_id[37];
strcpy(request_id, active_tunnel->request_id);
pthread_mutex_unlock(&tunnel_mutex);
// Use select to wait for data on local socket
FD_ZERO(&readfds);
FD_SET(sock, &readfds);
tv.tv_sec = 0; // 0 seconds
tv.tv_usec = 50000; // 50ms timeout
int retval = select(sock + 1, &readfds, NULL, NULL, &tv);
if (retval == -1) {
if (debug) {
perror("[DEBUG] select failed");
fflush(stdout);
}
break;
} else if (retval == 0) {
// Timeout, continue loop
continue;
}
if (FD_ISSET(sock, &readfds)) {
bytes_read = recv(sock, buffer, sizeof(buffer), 0);
if (bytes_read <= 0) {
if (debug) {
printf("[DEBUG] TCP connection closed or error\n");
fflush(stdout);
}
break;
}
if (debug) {
printf("[DEBUG] Forwarding %d bytes from TCP to WebSocket\n", bytes_read);
fflush(stdout);
}
// Convert to hex
char hex_data[bytes_read * 2 + 1];
for (int i = 0; i < bytes_read; i++) {
sprintf(hex_data + i * 2, "%02x", (unsigned char)buffer[i]);
}
hex_data[bytes_read * 2] = '\0';
// Send as tunnel_data
char message[BUFFER_SIZE];
snprintf(message, sizeof(message),
"{\"type\":\"tunnel_data\",\"request_id\":\"%s\",\"data\":\"%s\"}",
request_id, hex_data);
if (!send_websocket_frame(ssl, message)) {
if (debug) {
printf("[DEBUG] Failed to send WebSocket frame\n");
fflush(stdout);
}
break;
}
}
}
if (debug) {
printf("[DEBUG] TCP to WebSocket forwarding thread exiting\n");
fflush(stdout);
}
free(args);
return NULL;
}
void handle_tunnel_data(SSL *ssl __attribute__((unused)), const char *request_id, const char *data_hex, int debug __attribute__((unused))) {
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || strcmp(active_tunnel->request_id, request_id) != 0) {
pthread_mutex_unlock(&tunnel_mutex);
return;
}
int sock = active_tunnel->local_sock;
pthread_mutex_unlock(&tunnel_mutex);
// Decode hex data
size_t data_len = strlen(data_hex) / 2;
char *data = malloc(data_len);
if (!data) return;
for (size_t i = 0; i < data_len; i++) {
sscanf(data_hex + i * 2, "%2hhx", &data[i]);
}
// Send to local socket
send(sock, data, data_len, 0);
free(data);
}
void handle_tunnel_close(SSL *ssl __attribute__((unused)), const char *request_id, int debug) {
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel && strcmp(active_tunnel->request_id, request_id) == 0) {
active_tunnel->active = 0;
close(active_tunnel->local_sock);
free(active_tunnel);
active_tunnel = NULL;
if (debug) {
printf("[DEBUG] Tunnel %s closed\n", request_id);
}
}
pthread_mutex_unlock(&tunnel_mutex);
}
int reconnect_websocket(tunnel_t *tunnel, const char *wssshd_host, int wssshd_port, const char *client_id, const char *request_id, int debug) {
struct sockaddr_in server_addr;
struct hostent *he;
int sock;
SSL_CTX *ssl_ctx;
SSL *ssl;
char buffer[BUFFER_SIZE];
int bytes_read;
if (debug) {
printf("[DEBUG] Attempting to reconnect WebSocket connection...\n");
fflush(stdout);
}
// Resolve hostname
if ((he = gethostbyname(wssshd_host)) == NULL) {
herror("gethostbyname");
return -1;
}
// Create socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(wssshd_port);
server_addr.sin_addr = *((struct in_addr *)he->h_addr);
// Connect to server
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Connection failed");
close(sock);
return -1;
}
// Initialize SSL
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ssl_ctx = SSL_CTX_new(TLS_client_method());
if (!ssl_ctx) {
ERR_print_errors_fp(stderr);
close(sock);
return -1;
}
// Allow self-signed certificates
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
ssl = SSL_new(ssl_ctx);
SSL_set_fd(ssl, sock);
if (SSL_connect(ssl) <= 0) {
ERR_print_errors_fp(stderr);
fprintf(stderr, "SSL connection failed\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Perform WebSocket handshake
if (!websocket_handshake(ssl, wssshd_host, wssshd_port, "/")) {
fprintf(stderr, "WebSocket handshake failed\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Send tunnel request
if (!send_json_message(ssl, "tunnel_request", client_id, request_id)) {
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Read acknowledgment
bytes_read = SSL_read(ssl, buffer, sizeof(buffer));
if (bytes_read <= 0) {
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Check if it's a close frame
if (bytes_read >= 2 && (buffer[0] & 0x8F) == 0x88) {
if (debug) {
printf("[DEBUG] Server sent close frame during reconnection\n");
fflush(stdout);
}
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Parse WebSocket frame
char *payload;
int payload_len;
if (!parse_websocket_frame(buffer, bytes_read, &payload, &payload_len)) {
fprintf(stderr, "Failed to parse WebSocket frame during reconnection\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Null terminate payload
payload[payload_len] = '\0';
// Check for tunnel acknowledgment
if (strstr(payload, "tunnel_ack") == NULL) {
fprintf(stderr, "Tunnel request denied during reconnection: %s\n", payload);
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Success - update the tunnel's SSL connection
if (tunnel->ssl) {
SSL_free(tunnel->ssl);
}
tunnel->ssl = ssl;
// Clean up old SSL context
SSL_CTX_free(ssl_ctx);
if (debug) {
printf("[DEBUG] WebSocket reconnection successful\n");
fflush(stdout);
}
return 0;
}
int setup_tunnel(const char *wssshd_host, int wssshd_port, const char *client_id, int local_port, int debug) {
struct sockaddr_in server_addr;
struct hostent *he;
int sock;
SSL_CTX *ssl_ctx;
SSL *ssl;
char buffer[BUFFER_SIZE];
int bytes_read;
// Generate request ID
char request_id[37];
generate_request_id(request_id, sizeof(request_id));
// Resolve hostname
if ((he = gethostbyname(wssshd_host)) == NULL) {
herror("gethostbyname");
return -1;
}
// Create socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(wssshd_port);
server_addr.sin_addr = *((struct in_addr *)he->h_addr);
// Connect to server
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Connection failed");
close(sock);
return -1;
}
// Initialize SSL
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ssl_ctx = SSL_CTX_new(TLS_client_method());
if (!ssl_ctx) {
ERR_print_errors_fp(stderr);
close(sock);
return -1;
}
// Allow self-signed certificates
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
ssl = SSL_new(ssl_ctx);
SSL_set_fd(ssl, sock);
if (debug) {
printf("[DEBUG] Establishing SSL connection...\n");
fflush(stdout);
}
if (SSL_connect(ssl) <= 0) {
ERR_print_errors_fp(stderr);
fprintf(stderr, "SSL connection failed\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (debug) {
printf("[DEBUG] SSL connection established\n");
fflush(stdout);
}
// Perform WebSocket handshake
if (debug) {
printf("[DEBUG] Performing WebSocket handshake to %s:%d\n", wssshd_host, wssshd_port);
fflush(stdout);
}
if (!websocket_handshake(ssl, wssshd_host, wssshd_port, "/")) {
fprintf(stderr, "WebSocket handshake failed\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (debug) {
printf("[DEBUG] WebSocket handshake successful\n");
fflush(stdout);
}
// Send tunnel request
if (!send_json_message(ssl, "tunnel_request", client_id, request_id)) {
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (debug) {
printf("[DEBUG] Tunnel request sent for client: %s, request_id: %s\n", client_id, request_id);
}
// Read acknowledgment
bytes_read = SSL_read(ssl, buffer, sizeof(buffer));
if (bytes_read <= 0) {
if (debug) {
printf("[DEBUG] No acknowledgment received\n");
}
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (debug) {
printf("[DEBUG] Read %d bytes acknowledgment\n", bytes_read);
printf("[DEBUG] Raw acknowledgment: ");
for (int i = 0; i < bytes_read && i < 50; i++) {
if (buffer[i] >= 32 && buffer[i] < 127) {
printf("%c", buffer[i]);
} else {
printf("\\x%02x", (unsigned char)buffer[i]);
}
}
printf("\n");
fflush(stdout);
}
// Check if it's a close frame
if (bytes_read >= 2 && (buffer[0] & 0x8F) == 0x88) {
if (debug) {
printf("[DEBUG] Server sent close frame\n");
if (bytes_read >= 6) {
int close_code = (buffer[2] << 8) | buffer[3];
printf("[DEBUG] Close code: %d\n", close_code);
if (bytes_read > 6) {
printf("[DEBUG] Close reason: %.*s\n", bytes_read - 6, buffer + 6);
}
}
fflush(stdout);
}
fprintf(stderr, "Tunnel request rejected by server\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Parse WebSocket frame
char *payload;
int payload_len;
if (!parse_websocket_frame(buffer, bytes_read, &payload, &payload_len)) {
fprintf(stderr, "Failed to parse WebSocket frame\n");
if (debug) {
printf("[DEBUG] Frame parsing failed. First few bytes: ");
for (int i = 0; i < bytes_read && i < 10; i++) {
printf("%02x ", (unsigned char)buffer[i]);
}
printf("\n");
fflush(stdout);
}
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Null terminate payload
payload[payload_len] = '\0';
if (debug) {
printf("[DEBUG] Received payload: '%s' (length: %d)\n", payload, payload_len);
printf("[DEBUG] Looking for tunnel_ack in: ");
for (int i = 0; i < payload_len && i < 50; i++) {
if (payload[i] >= 32 && payload[i] < 127) {
printf("%c", payload[i]);
} else {
printf("\\x%02x", (unsigned char)payload[i]);
}
}
printf("\n");
fflush(stdout);
}
// Check for tunnel acknowledgment
if (strstr(payload, "tunnel_ack") == NULL) {
fprintf(stderr, "Tunnel request denied or failed: %s\n", payload);
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (debug) {
printf("[DEBUG] Tunnel established, local port: %d\n", local_port);
}
// Create tunnel structure
active_tunnel = malloc(sizeof(tunnel_t));
if (!active_tunnel) {
perror("Memory allocation failed");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
strcpy(active_tunnel->request_id, request_id);
active_tunnel->local_sock = -1;
active_tunnel->active = 1;
active_tunnel->ssl = ssl;
// Start listening on local port
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0) {
perror("Local socket creation failed");
free(active_tunnel);
active_tunnel = NULL;
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
struct sockaddr_in local_addr;
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(local_port);
local_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (bind(listen_sock, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) {
perror("Local bind failed");
close(listen_sock);
free(active_tunnel);
active_tunnel = NULL;
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (listen(listen_sock, 1) < 0) {
perror("Local listen failed");
close(listen_sock);
free(active_tunnel);
active_tunnel = NULL;
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
if (debug) {
printf("[DEBUG] Listening on localhost:%d\n", local_port);
fflush(stdout);
}
// Return success - tunnel is set up and listening
return listen_sock;
}
int main(int argc, char *argv[]) {
// Read config
char *config_port_str = read_config_value("port");
int config_port = config_port_str ? atoi(config_port_str) : 0;
free(config_port_str);
char *config_domain = read_config_value("domain");
wsssh_config_t config = {
.local_port = NULL,
.wssshd_port = 9898,
.debug = 0,
.interval = 30
};
......@@ -1175,10 +285,9 @@ int main(int argc, char *argv[]) {
return 1;
}
// Parse SSH arguments to extract host and port
// Parse SSH arguments to extract host
char *ssh_host = NULL;
int ssh_port = DEFAULT_PORT;
if (!parse_ssh_args(remaining_argc, remaining_argv, &ssh_host, &ssh_port, config_port, config.debug)) {
if (!parse_ssh_args(remaining_argc, remaining_argv, &ssh_host, config.debug)) {
pthread_mutex_destroy(&tunnel_mutex);
free(config_domain);
return 1;
......@@ -1187,7 +296,7 @@ int main(int argc, char *argv[]) {
// Parse the SSH host to extract client_id and wssshd_host
char *client_id = NULL;
char *wssshd_host = NULL;
int wssshd_port = ssh_port; // The -p port becomes the WebSocket server port
int wssshd_port = config.wssshd_port; // Use the wssshd port from config
if (!parse_hostname(ssh_host, &client_id, &wssshd_host, config_domain)) {
pthread_mutex_destroy(&tunnel_mutex);
......@@ -1255,7 +364,7 @@ int main(int argc, char *argv[]) {
fflush(stdout);
}
listen_sock = setup_tunnel(wssshd_host, wssshd_port, client_id, local_port, config.debug);
listen_sock = setup_tunnel(wssshd_host, wssshd_port, client_id, local_port, config.debug, 0);
if (listen_sock < 0) {
setup_attempts++;
......
......@@ -26,24 +26,19 @@
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <getopt.h>
#include <pthread.h>
#include <errno.h>
#define BUFFER_SIZE 4096
#define DEFAULT_PORT 9898
#include "wssshlib.h"
#include "websocket.h"
#include "wssh_ssl.h"
#include "tunnel.h"
int send_all(SSL *ssl, const char *data, int len);
int global_debug = 0;
pthread_mutex_t tunnel_mutex;
typedef struct {
int sock; // Local SSH connection socket
char request_id[64];
} tunnel_t;
typedef struct {
char *server_ip;
......@@ -113,367 +108,8 @@ void load_config(wssshc_config_t *config) {
}
}
tunnel_t *active_tunnel = NULL;
void *tunnel_thread(void *arg);
void cleanup_tunnel();
void handle_tunnel_request(SSL *ssl, const char *request_id, int debug) {
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel) {
close(active_tunnel->sock);
free(active_tunnel);
}
active_tunnel = malloc(sizeof(tunnel_t));
if (!active_tunnel) {
perror("Memory allocation failed");
pthread_mutex_unlock(&tunnel_mutex);
return;
}
active_tunnel->sock = -1; // Not connected yet
strcpy(active_tunnel->request_id, request_id);
pthread_mutex_unlock(&tunnel_mutex);
if (debug) {
printf("[DEBUG] Tunnel %s prepared (not connected yet)\n", request_id);
}
// Send tunnel_ack back to server
char ack_msg[256];
snprintf(ack_msg, sizeof(ack_msg), "{\"type\":\"tunnel_ack\",\"request_id\":\"%s\"}", request_id);
if (debug) {
printf("[DEBUG] Sending tunnel_ack: %s\n", ack_msg);
}
char frame[512];
frame[0] = 0x81; // FIN + text opcode
int msg_len = strlen(ack_msg);
frame[1] = 0x80 | msg_len; // MASK + length
char mask_key[4];
for (int i = 0; i < 4; i++) {
mask_key[i] = rand() % 256;
frame[2 + i] = mask_key[i];
}
for (int i = 0; i < msg_len; i++) {
frame[6 + i] = ack_msg[i] ^ mask_key[i % 4];
}
int frame_len = 6 + msg_len;
if (!send_all(ssl, frame, frame_len)) {
fprintf(stderr, "Send tunnel_ack failed\n");
}
}
void handle_tunnel_data(SSL *ssl, const char *request_id, const char *data_hex, int debug) {
printf("handle_tunnel_data called with id: %s, data len: %zu\n", request_id, strlen(data_hex));
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || strcmp(active_tunnel->request_id, request_id) != 0) {
printf("No active tunnel or id mismatch\n");
pthread_mutex_unlock(&tunnel_mutex);
return;
}
int sock_fd = active_tunnel->sock;
pthread_mutex_unlock(&tunnel_mutex);
// Open local SSH connection if not already open
if (sock_fd == -1) {
struct sockaddr_in addr;
int sock;
if (debug) {
printf("[DEBUG] [TCP Tunnel] Opening local SSH connection for tunnel %s\n", request_id);
}
// Create socket for local SSH connection
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Local socket creation failed");
return;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(22);
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
// Connect to local SSH
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("Local SSH connection failed");
close(sock);
return;
}
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel && strcmp(active_tunnel->request_id, request_id) == 0) {
active_tunnel->sock = sock;
sock_fd = sock;
// Start tunnel thread
pthread_t thread;
pthread_create(&thread, NULL, tunnel_thread, ssl);
pthread_detach(thread);
if (debug) {
printf("[DEBUG] [TCP Tunnel] Local SSH connection established and tunnel thread started\n");
}
} else {
close(sock);
}
pthread_mutex_unlock(&tunnel_mutex);
} else {
// SSH connection already exists, verify it's still valid
char test_buf[1];
int result = recv(sock_fd, test_buf, 1, MSG_PEEK | MSG_DONTWAIT);
if (result < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
// SSH connection is broken, close it and create a new one
if (debug) {
printf("[DEBUG] [TCP Tunnel] Existing SSH connection is broken, creating new one\n");
}
close(sock_fd);
struct sockaddr_in addr;
int sock;
// Create new socket for local SSH connection
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Local socket creation failed");
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel && strcmp(active_tunnel->request_id, request_id) == 0) {
active_tunnel->sock = -1;
}
pthread_mutex_unlock(&tunnel_mutex);
return;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(22);
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
// Connect to local SSH
if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("Local SSH connection failed");
close(sock);
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel && strcmp(active_tunnel->request_id, request_id) == 0) {
active_tunnel->sock = -1;
}
pthread_mutex_unlock(&tunnel_mutex);
return;
}
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel && strcmp(active_tunnel->request_id, request_id) == 0) {
active_tunnel->sock = sock;
sock_fd = sock;
// Start tunnel thread
pthread_t thread;
pthread_create(&thread, NULL, tunnel_thread, ssl);
pthread_detach(thread);
if (debug) {
printf("[DEBUG] [TCP Tunnel] New SSH connection established and tunnel thread started\n");
}
} else {
close(sock);
}
pthread_mutex_unlock(&tunnel_mutex);
} else if (debug) {
printf("[DEBUG] [TCP Tunnel] Reusing existing SSH connection for tunnel %s\n", request_id);
}
}
if (debug) {
printf("[DEBUG] Handling tunnel_data, hex len: %zu, hex data: %.100s...\n", strlen(data_hex), data_hex);
}
// Decode hex data
size_t data_len = strlen(data_hex) / 2;
char *data = malloc(data_len);
if (!data) {
fprintf(stderr, "Memory allocation failed for tunnel data\n");
return;
}
for (size_t i = 0; i < data_len; i++) {
sscanf(data_hex + i * 2, "%2hhx", &data[i]);
}
if (debug) {
printf("[DEBUG] Decoded data len: %zu, sending to local SSH: ", data_len);
for (size_t i = 0; i < data_len && i < 50; i++) {
if (data[i] >= 32 && data[i] < 127) {
printf("%c", data[i]);
} else {
printf("\\x%02x", (unsigned char)data[i]);
}
}
printf("\n");
}
// Send to local SSH
ssize_t sent = send(sock_fd, data, data_len, 0);
if (sent < 0) {
perror("Failed to send data to local SSH");
} else if ((size_t)sent != data_len) {
fprintf(stderr, "Partial send to local SSH: sent %zd of %zu bytes\n", sent, data_len);
}
free(data);
}
void handle_tunnel_close(SSL *ssl __attribute__((unused)), const char *request_id, int debug) {
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel && strcmp(active_tunnel->request_id, request_id) == 0) {
close(active_tunnel->sock);
free(active_tunnel);
active_tunnel = NULL;
if (debug) {
printf("[DEBUG] [TCP Tunnel] Tunnel %s closed\n", request_id);
}
}
pthread_mutex_unlock(&tunnel_mutex);
}
void *tunnel_thread(void *arg) {
SSL *ssl = (SSL *)arg;
char buffer[BUFFER_SIZE];
int bytes_read;
while (1) {
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel) {
pthread_mutex_unlock(&tunnel_mutex);
break;
}
int sock = active_tunnel->sock;
char request_id[64];
strcpy(request_id, active_tunnel->request_id);
pthread_mutex_unlock(&tunnel_mutex);
bytes_read = recv(sock, buffer, sizeof(buffer), 0);
if (bytes_read <= 0) {
if (bytes_read < 0) {
perror("Error reading from local SSH");
// Check if this is due to tunnel cleanup (ECONNRESET, EPIPE, etc.)
if (errno == ECONNRESET || errno == EPIPE || errno == EBADF) {
if (global_debug) {
printf("[DEBUG] [TCP Tunnel] SSH connection closed due to tunnel reset\n");
}
}
} else {
if (global_debug) {
printf("[DEBUG] [TCP Tunnel] Local SSH connection closed normally\n");
}
}
break;
}
if (global_debug) {
printf("[DEBUG] [TCP Tunnel] Read %d bytes from local SSH: ", bytes_read);
for (int i = 0; i < bytes_read && i < 50; i++) {
if (buffer[i] >= 32 && buffer[i] < 127) {
printf("%c", buffer[i]);
} else {
printf("\\x%02x", (unsigned char)buffer[i]);
}
}
printf("\n");
}
// Send as tunnel_response
char *hex_data = malloc(bytes_read * 2 + 1);
if (!hex_data) {
fprintf(stderr, "Memory allocation failed for hex_data in tunnel_thread\n");
break;
}
for (int i = 0; i < bytes_read; i++) {
sprintf(hex_data + i * 2, "%02x", (unsigned char)buffer[i]);
}
hex_data[bytes_read * 2] = '\0';
if (global_debug) {
printf("[DEBUG] [WebSocket] Sending tunnel_response, hex len: %zu, hex: %.100s...\n", strlen(hex_data), hex_data);
}
char response[9000];
int response_len = snprintf(response, sizeof(response), "{\"type\":\"tunnel_response\",\"request_id\":\"%s\",\"data\":\"%s\"}", request_id, hex_data);
if (response_len < 0 || (size_t)response_len >= sizeof(response)) {
perror("JSON construction failed");
free(hex_data);
break;
}
if (global_debug) {
printf("[DEBUG] Sending tunnel_response, len: %d\n", response_len);
}
// Send as WebSocket frame with proper length encoding
int msg_len = response_len;
int header_len = 2;
char mask_key[4];
if (msg_len <= 125) {
header_len = 6; // 2 + 4 for mask
} else if (msg_len <= 65535) {
header_len = 8; // 4 + 4 for mask
} else {
header_len = 14; // 10 + 4 for mask
}
char *frame = malloc(header_len + msg_len);
if (!frame) {
fprintf(stderr, "Memory allocation failed for WebSocket frame in tunnel_thread\n");
free(hex_data);
break;
}
frame[0] = 0x81; // FIN + text opcode
if (msg_len <= 125) {
frame[1] = 0x80 | msg_len; // MASK + length
} else if (msg_len <= 65535) {
frame[1] = 0x80 | 126; // MASK + extended length
frame[2] = (msg_len >> 8) & 0xFF;
frame[3] = msg_len & 0xFF;
} else {
frame[1] = 0x80 | 127; // MASK + extended length
// For simplicity, assume length fits in 32 bits
frame[2] = 0;
frame[3] = 0;
frame[4] = 0;
frame[5] = 0;
frame[6] = (msg_len >> 24) & 0xFF;
frame[7] = (msg_len >> 16) & 0xFF;
frame[8] = (msg_len >> 8) & 0xFF;
frame[9] = msg_len & 0xFF;
}
// Add mask key
for (int i = 0; i < 4; i++) {
mask_key[i] = rand() % 256;
frame[header_len - 4 + i] = mask_key[i];
}
// Mask payload
for (int i = 0; i < msg_len; i++) {
frame[header_len + i] = response[i] ^ mask_key[i % 4];
}
int frame_len = header_len + msg_len;
if (!send_all(ssl, frame, frame_len)) {
fprintf(stderr, "Send tunnel_response failed - tunnel may hang\n");
free(frame);
free(hex_data);
break;
}
free(frame);
free(hex_data);
}
return NULL;
}
void print_usage(const char *program_name) {
fprintf(stderr, "Usage: %s [options]\n", program_name);
......@@ -562,139 +198,9 @@ int parse_args(int argc, char *argv[], wssshc_config_t *config) {
return 1;
}
SSL_CTX* init_ssl_ctx() {
SSL_CTX *ctx;
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ctx = SSL_CTX_new(TLS_client_method());
if (!ctx) {
ERR_print_errors_fp(stderr);
return NULL;
}
// Allow self-signed certificates
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
return ctx;
}
int websocket_handshake(SSL *ssl, const char *host, int port, const char *path) {
char request[1024];
char response[BUFFER_SIZE];
int bytes_read;
// Send WebSocket handshake
snprintf(request, sizeof(request),
"GET %s HTTP/1.1\r\n"
"Host: %s:%d\r\n"
"Upgrade: websocket\r\n"
"Connection: upgrade\r\n"
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
"Sec-WebSocket-Version: 13\r\n"
"\r\n",
path, host, port);
if (!send_all(ssl, request, strlen(request))) {
fprintf(stderr, "WebSocket handshake send failed\n");
return 0;
}
// Read response
bytes_read = SSL_read(ssl, response, sizeof(response) - 1);
if (bytes_read <= 0) {
ERR_print_errors_fp(stderr);
fprintf(stderr, "WebSocket handshake recv failed\n");
return 0;
}
response[bytes_read] = '\0';
// Check for successful handshake
if (strstr(response, "101 Switching Protocols") == NULL) {
fprintf(stderr, "WebSocket handshake failed\n");
return 0;
}
return 1;
}
int send_all(SSL *ssl, const char *data, int len) {
int total_sent = 0;
while (total_sent < len) {
int sent = SSL_write(ssl, data + total_sent, len - total_sent);
if (sent <= 0) {
ERR_print_errors_fp(stderr);
return 0;
}
total_sent += sent;
}
return 1;
}
int send_json_message(SSL *ssl, const char *type, const char *id, const char *password) {
char message[1024];
char frame[1024];
int message_len;
if (password) {
message_len = snprintf(message, sizeof(message),
"{\"type\":\"%s\",\"id\":\"%s\",\"password\":\"%s\"}",
type, id, password);
} else {
message_len = snprintf(message, sizeof(message),
"{\"type\":\"%s\",\"id\":\"%s\"}",
type, id);
}
// Send as WebSocket frame with proper length encoding
frame[0] = 0x81; // FIN + text opcode
int header_len = 2;
if (message_len <= 125) {
frame[1] = 0x80 | message_len; // MASK + length
} else if (message_len <= 65535) {
frame[1] = 0x80 | 126; // MASK + extended length
frame[2] = (message_len >> 8) & 0xFF;
frame[3] = message_len & 0xFF;
header_len = 4;
} else {
frame[1] = 0x80 | 127; // MASK + extended length
frame[2] = 0;
frame[3] = 0;
frame[4] = 0;
frame[5] = 0;
frame[6] = (message_len >> 24) & 0xFF;
frame[7] = (message_len >> 16) & 0xFF;
frame[8] = (message_len >> 8) & 0xFF;
frame[9] = message_len & 0xFF;
header_len = 10;
}
// Add mask key
char mask_key[4];
for (int i = 0; i < 4; i++) {
mask_key[i] = rand() % 256;
frame[header_len + i] = mask_key[i];
}
header_len += 4;
// Mask payload
for (int i = 0; i < message_len; i++) {
frame[header_len + i] = message[i] ^ mask_key[i % 4];
}
int frame_len = header_len + message_len;
if (!send_all(ssl, frame, frame_len)) {
fprintf(stderr, "Send failed\n");
return 0;
}
return 1;
}
int connect_to_server(const wssshc_config_t *config) {
struct sockaddr_in server_addr;
......@@ -706,7 +212,7 @@ int connect_to_server(const wssshc_config_t *config) {
int bytes_read;
// Clean up any leftover tunnel state from previous connection
cleanup_tunnel();
cleanup_tunnel(config->debug);
// Resolve hostname
if ((he = gethostbyname(config->server_ip)) == NULL) {
......@@ -733,7 +239,7 @@ int connect_to_server(const wssshc_config_t *config) {
}
// Initialize SSL
ssl_ctx = init_ssl_ctx();
ssl_ctx = create_ssl_context();
if (!ssl_ctx) {
close(sock);
return 1;
......@@ -869,7 +375,7 @@ int connect_to_server(const wssshc_config_t *config) {
}
}
// Clean up tunnel resources before breaking
cleanup_tunnel();
cleanup_tunnel(config->debug);
break;
}
......@@ -1013,7 +519,7 @@ int connect_to_server(const wssshc_config_t *config) {
printf("[DEBUG] [WebSocket] Received close frame - cleaning up and reconnecting...\n");
}
// Clean up tunnel resources before reconnecting
cleanup_tunnel();
cleanup_tunnel(config->debug);
return 0;
} else if (bytes_read >= 2 && (buffer[0] & 0x8F) == 0x89) { // Ping frame
if (config->debug) {
......@@ -1116,7 +622,7 @@ int connect_to_server(const wssshc_config_t *config) {
if (config->debug) {
printf("[DEBUG] [WebSocket] Sending pong frame, len: %d\n", pong_frame_len);
}
if (!send_all(ssl, pong_frame, pong_frame_len)) {
if (SSL_write(ssl, pong_frame, pong_frame_len) <= 0) {
fprintf(stderr, "[ERROR] [WebSocket] Send pong failed\n");
}
}
......@@ -1130,33 +636,6 @@ int connect_to_server(const wssshc_config_t *config) {
return 1;
}
void cleanup_tunnel() {
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel) {
if (active_tunnel->sock >= 0) {
// Check if SSH connection is still valid before closing
char test_buf[1];
int result = recv(active_tunnel->sock, test_buf, 1, MSG_PEEK | MSG_DONTWAIT);
if (result == 0 || (result < 0 && (errno == ECONNRESET || errno == EPIPE))) {
// SSH connection is closed or broken, safe to close
close(active_tunnel->sock);
if (global_debug) {
printf("[DEBUG] [TCP Tunnel] Closed broken SSH connection\n");
}
} else {
// SSH connection appears valid, don't close it
if (global_debug) {
printf("[DEBUG] [TCP Tunnel] Keeping SSH connection alive for potential reuse\n");
}
// Reset socket to -1 so it will be reconnected if needed
active_tunnel->sock = -1;
}
}
free(active_tunnel);
active_tunnel = NULL;
}
pthread_mutex_unlock(&tunnel_mutex);
}
int main(int argc, char *argv[]) {
wssshc_config_t config = {
......
/*
* WebSocket SSH Library - Shared utilities 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/>.
*/
#include "wssshlib.h"
char *read_config_value(const char *key) {
char *home = getenv("HOME");
if (!home) return NULL;
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s/.config/wsssh/wsssh.conf", home);
FILE *f = fopen(path, "r");
if (!f) return NULL;
char line[256];
while (fgets(line, sizeof(line), f)) {
if (strncmp(line, key, strlen(key)) == 0 && line[strlen(key)] == '=') {
char *value = strdup(line + strlen(key) + 1);
// remove newline
value[strcspn(value, "\n")] = 0;
fclose(f);
return value;
}
}
fclose(f);
return NULL;
}
void print_trans_flag(void) {
// Transgender pride flag colors using ANSI escape codes
const char *colors[] = {
"\033[48;5;117m", // Light blue background
"\033[48;5;218m", // Pink background
"\033[48;5;231m", // White background
"\033[48;5;218m", // Pink background
"\033[48;5;117m" // Light blue background
};
const char *reset = "\033[0m";
// Print 10 rows of colored blocks (double height)
for (int i = 0; i < 10; i++) {
int color_index = i / 2; // Each color appears twice for double height
if (color_index >= 5) color_index = 4; // Ensure we don't go out of bounds
printf("%s", colors[color_index]);
for (int j = 0; j < 40; j++) {
printf(" ");
}
printf("%s\n", reset);
}
printf("\n");
}
void print_palestinian_flag(void) {
const char *reset = "\033[0m";
const char *red = "\033[41m";
const char *black = "\033[40m";
const char *white = "\033[47m";
const char *green = "\033[42m";
int height = 12;
int width = 40;
for (int i = 0; i < height; i++) {
int triangle_width;
if (i <= 5) {
triangle_width = (i + 1) * 2;
} else {
triangle_width = (height - i) * 2;
}
const char *stripe_color;
if (i < 4) {
stripe_color = black;
} else if (i < 8) {
stripe_color = white;
} else {
stripe_color = green;
}
printf("%s", red);
for (int j = 0; j < triangle_width; j++) {
printf(" ");
}
printf("%s", stripe_color);
for (int j = triangle_width; j < width; j++) {
printf(" ");
}
printf("%s\n", reset);
}
printf("\n");
}
int find_available_port() {
struct sockaddr_in addr;
int sock;
int port = 0;
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
return 0;
}
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = 0;
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("Bind failed");
close(sock);
return 0;
}
socklen_t len = sizeof(addr);
if (getsockname(sock, (struct sockaddr *)&addr, &len) < 0) {
perror("Getsockname failed");
close(sock);
return 0;
}
port = ntohs(addr.sin_port);
close(sock);
return port;
}
void generate_request_id(char *request_id, size_t size) {
const char charset[] = "0123456789abcdef";
for (size_t i = 0; i < size - 1; i++) {
request_id[i] = charset[rand() % (sizeof(charset) - 1)];
}
request_id[size - 1] = '\0';
}
\ No newline at end of file
/*
* WebSocket SSH Library - Shared utilities
*
* 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 WSSH_LIB_H
#define WSSH_LIB_H
#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 <netdb.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <getopt.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/select.h>
#define BUFFER_SIZE 1048576
#define DEFAULT_PORT 22
// Config structures
typedef struct {
char *local_port;
int wssshd_port; // wssshd server port
int debug;
int interval; // Reconnection interval in seconds
} wsssh_config_t;
typedef struct {
char *local_port;
int wssshd_port; // wssshd server port
int debug;
int interval; // Connection retry interval in seconds
} wsscp_config_t;
// Thread arguments
typedef struct {
SSL *ssl;
int debug;
} thread_args_t;
// Function declarations
char *read_config_value(const char *key);
void print_trans_flag(void);
void print_palestinian_flag(void);
int find_available_port(void);
void generate_request_id(char *request_id, size_t size);
#endif // WSSH_LIB_H
\ No newline at end of file
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