Add RDP support and fix VNC WebSocket issues

parent c3b379fb
diff --git a/wssshd2/templates/index.html b/wssshd2/templates/index.html
index 078b174..46f4ebe 100644
--- a/wssshd2/templates/index.html
+++ b/wssshd2/templates/index.html
@@ -90,6 +90,7 @@ function updateClients() {
if (data.count === 0) {
clientListHtml = '<div class="text-center py-5"><i class="fas fa-server fa-4x text-muted mb-3"></i><h4 class="text-muted">No clients registered</h4><p class="text-muted">Clients will appear here when they register.</p></div>';
} else {
+ clientListHtml = '<div class="row">';
for (const [clientId, clientData] of Object.entries(data.clients)) {
const statusIcon = clientData.status === 'connected' ? 'fa-desktop text-success' : 'fa-server text-warning';
const statusText = clientData.status === 'connected' ? 'Connected' : 'Registered';
@@ -114,6 +115,7 @@ function updateClients() {
</div>
</div>`;
}
+ clientListHtml += '</div>';
}
// Update client list only if changed
diff --git a/wssshd2/templates/novnc/input/util.js b/wssshd2/templates/novnc/input/util.js
index c6951ff..d19112a 100644
--- a/wssshd2/templates/novnc/input/util.js
+++ b/wssshd2/templates/novnc/input/util.js
@@ -185,3 +185,9 @@ function getKeysym(evt) {
}
window.getKeycode = getKeycode;
window.getKeysym = getKeysym;
+
+// Create KeyboardUtil object for compatibility
+window.KeyboardUtil = {
+ getKeycode: getKeycode,
+ getKeysym: getKeysym
+};
diff --git a/wssshd2/templates/novnc/rfb.js b/wssshd2/templates/novnc/rfb.js
index 9a1d561..68a3545 100644
--- a/wssshd2/templates/novnc/rfb.js
+++ b/wssshd2/templates/novnc/rfb.js
@@ -175,7 +175,6 @@ class RFB extends EventTargetMixin {
// main setup
Log.Debug(">> RFB.constructor");
- console.log("[VNC-CLIENT] RFB constructor called");
// Create DOM elements
this._screen = document.createElement('div');
@@ -192,10 +191,6 @@ class RFB extends EventTargetMixin {
this._canvas.height = 0;
this._canvas.tabIndex = -1;
this._screen.appendChild(this._canvas);
- console.log("[VNC-CLIENT] DOM elements created, canvas size:", this._canvas.width, "x", this._canvas.height);
- console.log("[VNC-CLIENT] Canvas element:", this._canvas);
- console.log("[VNC-CLIENT] Screen element:", this._screen);
- console.log("[VNC-CLIENT] Target element:", this._target);
// Cursor
this._cursor = new Cursor();
@@ -224,16 +219,12 @@ class RFB extends EventTargetMixin {
// NB: nothing that needs explicit teardown should be done
// before this point, since this can throw an exception
try {
- console.log("[VNC-CLIENT] Creating Display object with canvas:", this._canvas);
this._display = new Display(this._canvas);
- console.log("[VNC-CLIENT] Display object created successfully:", this._display);
} catch (exc) {
- console.error("[VNC-CLIENT] Display exception:", exc);
Log.Error("Display exception: " + exc);
throw exc;
}
this._display.onflush = this._onFlush.bind(this);
- console.log("[VNC-CLIENT] Display onflush handler set");
this._keyboard = new Keyboard(this._canvas);
this._keyboard.onkeyevent = this._handleKeyEvent.bind(this);
@@ -610,17 +601,11 @@ class RFB extends EventTargetMixin {
}
_socketOpen() {
- console.log("[VNC-CLIENT] WebSocket opened, connectionState:", this._rfbConnectionState, "initState:", this._rfbInitState);
- // console.log("[VNC-DEBUG] WebSocket opened, starting VNC handshake");
if ((this._rfbConnectionState === 'connecting') &&
(this._rfbInitState === '')) {
this._rfbInitState = 'ProtocolVersion';
Log.Debug("Starting VNC handshake");
- console.log("[VNC-CLIENT] Starting VNC handshake, set init state to ProtocolVersion");
- // console.log("[VNC-DEBUG] Set init state to ProtocolVersion");
} else {
- console.log("[VNC-CLIENT] Unexpected socket open - connectionState:", this._rfbConnectionState, "initState:", this._rfbInitState);
- // console.log("[VNC-DEBUG] Unexpected socket open - connectionState:", this._rfbConnectionState, "initState:", this._rfbInitState);
this._fail("Unexpected server connection while " +
this._rfbConnectionState);
}
@@ -887,7 +872,6 @@ class RFB extends EventTargetMixin {
break;
case 'connected':
- console.log("[VNC-CLIENT] Connection state changed to CONNECTED");
this.dispatchEvent(new CustomEvent("connect", { detail: {} }));
break;
@@ -944,7 +928,6 @@ class RFB extends EventTargetMixin {
}
_handleMessage() {
- console.log("[VNC-MESSAGE] _handleMessage called, rQlen:", this._sock.rQlen, "connectionState:", this._rfbConnectionState, "initState:", this._rfbInitState);
if (this._sock.rQlen === 0) {
Log.Warn("handleMessage called on an empty receive queue");
return;
@@ -955,37 +938,21 @@ class RFB extends EventTargetMixin {
Log.Error("Got data while disconnected");
break;
case 'connected':
- console.log("[VNC-MESSAGE] Processing messages in connected state");
- let msgCount = 0;
while (true) {
if (this._flushing) {
- console.log("[VNC-MESSAGE] Flushing, breaking message loop");
break;
}
- console.log("[VNC-MESSAGE] Calling _normalMsg, rQlen before:", this._sock.rQlen);
- const result = this._normalMsg();
- console.log("[VNC-MESSAGE] _normalMsg returned:", result, "rQlen after:", this._sock.rQlen);
- if (!result) {
- console.log("[VNC-MESSAGE] _normalMsg returned false, breaking");
+ if (!this._normalMsg()) {
break;
}
if (this._sock.rQlen === 0) {
- console.log("[VNC-MESSAGE] No more data in queue");
- break;
- }
- msgCount++;
- if (msgCount > 10) { // Prevent infinite loops
- console.log("[VNC-MESSAGE] Too many messages processed, breaking to prevent infinite loop");
break;
}
}
break;
case 'connecting':
- console.log("[VNC-MESSAGE] Processing messages in connecting state");
while (this._rfbConnectionState === 'connecting') {
- console.log("[VNC-MESSAGE] Calling _initMsg, initState:", this._rfbInitState);
if (!this._initMsg()) {
- console.log("[VNC-MESSAGE] _initMsg returned false, breaking");
break;
}
}
@@ -1350,29 +1317,23 @@ class RFB extends EventTargetMixin {
// Message Handlers
_negotiateProtocolVersion() {
- console.log("[VNC-DEBUG] _negotiateProtocolVersion called");
if (this._sock.rQwait("version", 12)) {
- console.log("[VNC-DEBUG] Waiting for version data");
return false;
}
const versionStr = this._sock.rQshiftStr(12);
- console.log("[VNC-DEBUG] Received version string:", versionStr);
const sversion = versionStr.substr(4, 7);
Log.Info("Server ProtocolVersion: " + sversion);
let isRepeater = 0;
switch (sversion) {
case "000.000": // UltraVNC repeater
- console.log("[VNC-DEBUG] Detected UltraVNC repeater");
isRepeater = 1;
break;
case "003.003":
case "003.006": // UltraVNC
- console.log("[VNC-DEBUG] Setting RFB version to 3.3");
this._rfbVersion = 3.3;
break;
case "003.007":
- console.log("[VNC-DEBUG] Setting RFB version to 3.7");
this._rfbVersion = 3.7;
break;
case "003.008":
@@ -1380,11 +1341,9 @@ class RFB extends EventTargetMixin {
case "004.000": // Intel AMT KVM
case "004.001": // RealVNC 4.6
case "005.000": // RealVNC 5.3
- console.log("[VNC-DEBUG] Setting RFB version to 3.8");
this._rfbVersion = 3.8;
break;
default:
- console.log("[VNC-DEBUG] Invalid server version:", sversion);
return this._fail("Invalid server version " + sversion);
}
@@ -2058,16 +2017,13 @@ class RFB extends EventTargetMixin {
}
_negotiateServerInit() {
- console.log("[VNC-DEBUG] _negotiateServerInit called");
if (this._sock.rQwait("server initialization", 24)) {
- console.log("[VNC-DEBUG] Waiting for server initialization data");
return false;
}
/* Screen size */
const width = this._sock.rQshift16();
const height = this._sock.rQshift16();
- console.log("[VNC-DEBUG] Server screen size:", width, "x", height);
/* PIXEL_FORMAT */
const bpp = this._sock.rQshift8();
@@ -2083,8 +2039,6 @@ class RFB extends EventTargetMixin {
const blueShift = this._sock.rQshift8();
this._sock.rQskipBytes(3); // padding
- console.log("[VNC-DEBUG] Server pixel format - bpp:", bpp, "depth:", depth, "trueColor:", trueColor);
-
// NB(directxman12): we don't want to call any callbacks or print messages until
// *after* we're past the point where we could backtrack
@@ -2133,13 +2087,11 @@ class RFB extends EventTargetMixin {
// we're past the point where we could backtrack, so it's safe to call this
this._setDesktopName(name);
- console.log("[VNC-CLIENT] Server init complete - screen:", width, "x", height, "name:", name);
this._resize(width, height);
if (!this._viewOnly) { this._keyboard.grab(); }
this._fbDepth = 24;
- console.log("[VNC-CLIENT] Keyboard grabbed, fbDepth set to 24");
if (this._fbName === "Intel(r) AMT KVM") {
Log.Warn("Intel AMT KVM only supports 8/16 bit depths. Using low color mode.");
@@ -2493,13 +2445,12 @@ class RFB extends EventTargetMixin {
msgType = 0;
} else {
if (this._sock.rQlen < 1) {
- console.log("[VNC-MESSAGE] Not enough data for message type, rQlen:", this._sock.rQlen);
return false;
}
msgType = this._sock.rQshift8();
}
- console.log("[VNC-MESSAGE] Processing message type:", msgType, "FBU.rects:", this._FBU.rects, "rQlen:", this._sock.rQlen);
+ // console.log("[VNC-MESSAGE] Processing message type:", msgType, "FBU.rects:", this._FBU.rects, "rQlen:", this._sock.rQlen);
let first, ret;
switch (msgType) {
@@ -2569,24 +2520,15 @@ class RFB extends EventTargetMixin {
this._sock.rQskipBytes(1); // Padding
this._FBU.rects = this._sock.rQshift16();
- console.log("[VNC-CLIENT] FramebufferUpdate header - rects:", this._FBU.rects);
-
- // Debug: check if we have enough data for the rectangles
- const expectedBytes = this._FBU.rects * 12; // Each rect header is 12 bytes
- console.log("[VNC-DEBUG] Expected bytes for rect headers:", expectedBytes, "available rQlen:", this._sock.rQlen);
-
// Validate rectangle count - should be reasonable (not 65534 or similar corruption)
if (this._FBU.rects > 10000 || this._FBU.rects < 0) {
- console.log("[VNC-DEBUG] Invalid rectangle count:", this._FBU.rects, "- likely corrupted data, skipping message");
Log.Warn("Invalid rectangle count in FramebufferUpdate (" + this._FBU.rects + "), skipping corrupted message");
// Skip the entire FramebufferUpdate message by consuming all remaining data for this message
// We need to skip past all the rectangle data
const skipBytes = this._FBU.rects * 12; // Each rectangle header is 12 bytes
if (this._sock.rQlen >= skipBytes) {
this._sock.rQskipBytes(skipBytes);
- console.log("[VNC-DEBUG] Skipped", skipBytes, "bytes of corrupted rectangle data");
} else {
- console.log("[VNC-DEBUG] Not enough data to skip, waiting for more data");
return false; // Wait for more data
}
this._FBU.rects = 0; // Reset for next message
@@ -2605,7 +2547,6 @@ class RFB extends EventTargetMixin {
while (this._FBU.rects > 0) {
if (this._FBU.encoding === null) {
if (this._sock.rQwait("rect header", 12)) {
- console.log("[VNC-DEBUG] Waiting for rectangle header data");
return false;
}
/* New FramebufferUpdate */
@@ -2617,13 +2558,9 @@ class RFB extends EventTargetMixin {
this._FBU.height = (hdr[6] << 8) + hdr[7];
this._FBU.encoding = parseInt((hdr[8] << 24) + (hdr[9] << 16) +
(hdr[10] << 8) + hdr[11], 10);
- console.log("[VNC-DEBUG] Processing rectangle:", this._FBU.x, this._FBU.y, this._FBU.width, "x", this._FBU.height, "encoding:", this._FBU.encoding);
}
- const rectResult = this._handleRect();
- console.log("[VNC-DEBUG] _handleRect returned:", rectResult, "remaining rects:", this._FBU.rects - 1);
- if (!rectResult) {
- console.log("[VNC-DEBUG] Rectangle processing waiting for more data");
+ if (!this._handleRect()) {
return false;
}
@@ -2917,7 +2854,6 @@ class RFB extends EventTargetMixin {
_handleDataRect() {
let decoder = this._decoders[this._FBU.encoding];
if (!decoder) {
- console.log("[VNC-DEBUG] Unsupported encoding:", this._FBU.encoding, "- skipping rectangle data");
Log.Warn("Unsupported encoding (encoding: " +
this._FBU.encoding + "), skipping rectangle");
@@ -2929,9 +2865,7 @@ class RFB extends EventTargetMixin {
// Skip a reasonable amount of data for this rectangle
const skipSize = Math.min(minDataSize, this._sock.rQlen);
this._sock.rQskipBytes(skipSize);
- console.log("[VNC-DEBUG] Skipped", skipSize, "bytes for unsupported encoding");
} else {
- console.log("[VNC-DEBUG] Not enough data to skip rectangle, waiting for more data");
return false; // Wait for more data
}
return true;
@@ -2943,14 +2877,12 @@ class RFB extends EventTargetMixin {
this._sock, this._display,
this._fbDepth);
} catch (err) {
- console.log("[VNC-DEBUG] Error decoding rect:", err, "- skipping corrupted rectangle");
Log.Warn("Error decoding rect: " + err + " - skipping corrupted rectangle");
// For decoding errors, also skip data to avoid getting stuck
const minDataSize = this._FBU.width * this._FBU.height;
if (this._sock.rQlen >= minDataSize) {
const skipSize = Math.min(minDataSize, this._sock.rQlen);
this._sock.rQskipBytes(skipSize);
- console.log("[VNC-DEBUG] Skipped", skipSize, "bytes after decode error");
}
return true; // Continue processing instead of failing
}
@@ -2964,13 +2896,10 @@ class RFB extends EventTargetMixin {
}
_resize(width, height) {
- console.log("[VNC-CLIENT] Resizing display to", width, "x", height);
- console.log("[VNC-CLIENT] Canvas before resize:", this._canvas.width, "x", this._canvas.height);
this._fbWidth = width;
this._fbHeight = height;
this._display.resize(this._fbWidth, this._fbHeight);
- console.log("[VNC-CLIENT] Display resized, canvas after:", this._canvas.width, "x", this._canvas.height);
// Adjust the visible viewport based on the new dimensions
this._updateClip();
@@ -2980,7 +2909,6 @@ class RFB extends EventTargetMixin {
// Keep this size until browser client size changes
this._saveExpectedClientSize();
- console.log("[VNC-CLIENT] Resize complete, final canvas size:", this._canvas.width, "x", this._canvas.height);
}
_xvpOp(ver, op) {
diff --git a/wssshd2/templates/novnc/websock.js b/wssshd2/templates/novnc/websock.js
index 7755ddd..b1e0031 100644
--- a/wssshd2/templates/novnc/websock.js
+++ b/wssshd2/templates/novnc/websock.js
@@ -338,16 +338,8 @@ class Websock {
}
_recvMessage(e) {
- console.log("[VNC-WS] Received WebSocket message, data length:", e.data.byteLength || e.data.length);
- // Log first 32 bytes for debugging
- const dataArray = new Uint8Array(e.data);
- const hexData = Array.from(dataArray.slice(0, Math.min(32, dataArray.length))).map(b => b.toString(16).padStart(2, '0')).join(' ');
- console.log("[VNC-WS] First 32 bytes (hex):", hexData);
-
this._DecodeMessage(e.data);
- console.log("[VNC-WS] After decode, rQlen:", this.rQlen, "rQi:", this._rQi);
-
if (this.rQlen > 0) {
this._eventHandlers.message();
if (this._rQlen == this._rQi) {
diff --git a/wssshd2/templates/vnc.html b/wssshd2/templates/vnc.html
index c471702..63d9687 100644
--- a/wssshd2/templates/vnc.html
+++ b/wssshd2/templates/vnc.html
@@ -63,6 +63,15 @@
overflow: hidden;
position: relative;
}
+ .vnc-container.full-size {
+ overflow: auto;
+ max-height: none;
+ height: auto;
+ }
+ .vnc-container.full-size #noVNC_screen {
+ min-width: fit-content;
+ min-height: fit-content;
+ }
/* GNOME-like window decorations */
.vnc-window {
border: 1px solid #ccc;
@@ -127,6 +136,19 @@
.minimize-btn:hover {
background: #ffad2c;
}
+ .zoom-btn {
+ background: #17a2b8;
+ color: white;
+ }
+ .zoom-btn:hover {
+ background: #138496;
+ }
+ .zoom-btn.active {
+ background: #007bff;
+ }
+ .zoom-btn.active:hover {
+ background: #0056b3;
+ }
.window-content {
background: #1e1e1e;
padding: 8px;
@@ -180,6 +202,9 @@
<button id="disconnectBtn" class="btn btn-danger btn-sm me-1" disabled style="height: 24px; font-size: 12px; padding: 0 8px;">
<i class="fas fa-stop"></i> Disconnect
</button>
+ <button class="window-btn zoom-btn" title="Toggle Zoom (Scale/Fit)" id="zoomBtn">
+ <i class="fas fa-search-plus"></i>
+ </button>
<button class="window-btn minimize-btn" title="Exit Fullscreen">_</button>
<button class="window-btn maximize-btn" title="Fullscreen"><span class="maximize-icon">□</span></button>
<button class="window-btn close-btn" title="Disconnect">×</button>
@@ -189,6 +214,15 @@
<div id="vnc" class="vnc-container w-100">
<div id="noVNC_screen">
<div id="noVNC_status">Click Connect to start VNC session</div>
+ <div id="noVNC_loading" style="display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; color: #f8f8f2;">
+ <div class="spinner-border text-primary mb-2" role="status">
+ <span class="visually-hidden">Loading...</span>
+ </div>
+ <div>Connecting to VNC server...</div>
+ <button id="cancelConnectBtn" class="btn btn-outline-danger btn-sm mt-2" style="display: none;">
+ <i class="fas fa-times"></i> Cancel
+ </button>
+ </div>
</div>
</div>
</div>
@@ -199,15 +233,10 @@
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
-console.log('VNC page loaded');
-
// Check if Base64 is loaded
setTimeout(function() {
- console.log('[VNC-DEBUG] Checking if Base64 is defined:', typeof window.Base64);
if (typeof window.Base64 === 'undefined') {
- console.error('[VNC-DEBUG] Base64 is not defined! This will cause JPEG decoding to fail.');
- } else {
- console.log('[VNC-DEBUG] Base64 is properly defined');
+ console.error('[VNC-ERROR] Base64 is not defined! This will cause JPEG decoding to fail.');
}
}, 100);
@@ -217,11 +246,13 @@ let socketFrameSeq = 0;
document.getElementById('connectBtn').addEventListener('click', connect);
document.getElementById('disconnectBtn').addEventListener('click', disconnect);
+document.getElementById('cancelConnectBtn').addEventListener('click', cancelConnect);
// Window control buttons
document.querySelector('.close-btn').addEventListener('click', disconnect);
document.querySelector('.maximize-btn').addEventListener('click', toggleFullscreen);
document.querySelector('.minimize-btn').addEventListener('click', toggleFullscreen);
+document.getElementById('zoomBtn').addEventListener('click', toggleZoom);
function showNotification(message, type = 'info') {
const notificationArea = document.getElementById('notification-area');
@@ -254,95 +285,93 @@ function toggleFullscreen() {
}
}
+function toggleZoom() {
+ if (!rfb || !connected) return;
+
+ const zoomBtn = document.getElementById('zoomBtn');
+ const zoomIcon = zoomBtn.querySelector('i');
+ const vncContainer = document.getElementById('vnc');
+
+ if (zoomBtn.classList.contains('active')) {
+ // Switch to scaled view (fit to window)
+ zoomBtn.classList.remove('active');
+ zoomBtn.title = 'Toggle Zoom (Scale/Fit)';
+ zoomIcon.className = 'fas fa-search-plus';
+ vncContainer.classList.remove('full-size');
+
+ // Enable scaling, disable clipping
+ rfb.scaleViewport = true;
+ rfb.clipViewport = false;
+
+ showNotification('Switched to scaled view (fit to window)', 'info');
+ } else {
+ // Switch to full-size view with scrollbars
+ zoomBtn.classList.add('active');
+ zoomBtn.title = 'Toggle Zoom (Actual Size)';
+ zoomIcon.className = 'fas fa-search-minus';
+ vncContainer.classList.add('full-size');
+
+ // Disable scaling, enable clipping
+ rfb.scaleViewport = false;
+ rfb.clipViewport = true;
+
+ // Force a resize to ensure canvas is properly sized for scrolling
+ setTimeout(() => {
+ const canvas = document.querySelector('#noVNC_screen canvas');
+ if (canvas) {
+ console.log('Canvas size after zoom toggle:', canvas.width, 'x', canvas.height);
+ console.log('Container size:', vncContainer.clientWidth, 'x', vncContainer.clientHeight);
+ }
+ }, 100);
+
+ showNotification('Switched to actual size view with scrollbars', 'info');
+ }
+}
+
function connect() {
- console.log('Connect button clicked');
if (connected) return;
+ // Hide the status text and show loading message
+ document.getElementById('noVNC_status').style.display = 'none';
+ document.getElementById('noVNC_loading').style.display = 'block';
+ document.getElementById('cancelConnectBtn').style.display = 'inline-block';
+
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = wsProtocol + '//' + window.location.host + '/vnc/%s/ws';
- console.log('Connecting VNC to:', wsUrl);
try {
rfb = new RFB(document.getElementById('noVNC_screen'), wsUrl, {
credentials: {},
shared: true,
- repeaterID: '',
- wsProtocols: ['binary']
+ repeaterID: ''
});
rfb.addEventListener('connect', () => {
- console.log('[VNC-DEBUG] VNC connected event fired');
- console.log('[VNC-DEBUG] Connection state:', rfb._rfbConnectionState);
- console.log('[VNC-DEBUG] Init state:', rfb._rfbInitState);
connected = true;
document.getElementById('connectBtn').disabled = true;
document.getElementById('disconnectBtn').disabled = false;
+ // Hide loading message
+ document.getElementById('noVNC_loading').style.display = 'none';
showNotification('VNC session connected', 'success');
- // Override WebSocket onmessage after connection to intercept messages
- const originalOnMessage = rfb._sock.onmessage;
- rfb._sock.onmessage = function(event) {
- // Debug VNC socket data logging with base64 encoding and sequence numbers (first 20 frames only)
- if (socketFrameSeq < 20) {
- const bytes = event.data.byteLength || event.data.length;
- const uint8 = new Uint8Array(event.data);
- let binary = '';
- for (let i = 0; i < uint8.length; i++) {
- binary += String.fromCharCode(uint8[i]);
- }
- const b64Data = btoa(binary);
- console.log(`[VNC-SOCKET-SERVER] Frame ${socketFrameSeq}: ${bytes} bytes -> base64: ${b64Data}`);
- socketFrameSeq++;
- }
-
- // Call original handler
- return originalOnMessage.call(this, event);
- };
+ // Reset frame sequence for new connection
+ socketFrameSeq = 0;
});
- // Add debug logging for WebSocket messages
- const originalSend = rfb._sock.send;
- rfb._sock.send = function(data) {
- console.log('[VNC-DEBUG] JavaScript sending data to WebSocket:', data.length, 'bytes');
- if (data.length <= 32) {
- const hex = Array.from(new Uint8Array(data)).map(b => b.toString(16).padStart(2, '0')).join(' ');
- console.log('[VNC-DEBUG] Data (hex):', hex);
- } else {
- const hex = Array.from(new Uint8Array(data.slice(0, 32))).map(b => b.toString(16).padStart(2, '0')).join(' ');
- console.log('[VNC-DEBUG] Data (first 32 bytes hex):', hex + '...');
- }
-
- // Log VNC message types
- if (data.length > 0) {
- const msgType = new Uint8Array(data)[0];
- console.log('[VNC-DEBUG] JavaScript message type:', msgType);
- switch (msgType) {
- case 0: console.log('[VNC-DEBUG] -> SetPixelFormat'); break;
- case 2: console.log('[VNC-DEBUG] -> SetEncodings'); break;
- case 3: console.log('[VNC-DEBUG] -> FramebufferUpdateRequest'); break;
- case 4: console.log('[VNC-DEBUG] -> KeyEvent'); break;
- case 5: console.log('[VNC-DEBUG] -> PointerEvent'); break;
- case 6: console.log('[VNC-DEBUG] -> ClientCutText'); break;
- default: console.log('[VNC-DEBUG] -> Unknown message type'); break;
- }
- }
-
- return originalSend.call(this, data);
- };
rfb.addEventListener('disconnect', () => {
- console.log('[VNC-DEBUG] VNC disconnected event fired');
- console.log('[VNC-DEBUG] Connection state:', rfb._rfbConnectionState);
- console.log('[VNC-DEBUG] Init state:', rfb._rfbInitState);
connected = false;
document.getElementById('connectBtn').disabled = false;
document.getElementById('disconnectBtn').disabled = true;
+ // Reset UI to initial state
+ document.getElementById('noVNC_status').style.display = 'block';
+ document.getElementById('noVNC_loading').style.display = 'none';
+ document.getElementById('cancelConnectBtn').style.display = 'none';
showNotification('VNC session disconnected', 'info');
});
rfb.addEventListener('credentialsrequired', (e) => {
- console.log('VNC credentials required');
// For now, assume no credentials needed
});
@@ -352,7 +381,7 @@ function connect() {
});
rfb.addEventListener('clipboard', (e) => {
- console.log('VNC clipboard:', e.detail.text);
+ // Handle clipboard data if needed
});
} catch (e) {
@@ -369,10 +398,29 @@ function disconnect() {
connected = false;
document.getElementById('connectBtn').disabled = false;
document.getElementById('disconnectBtn').disabled = true;
+ // Reset UI to initial state
+ document.getElementById('noVNC_status').style.display = 'block';
+ document.getElementById('noVNC_loading').style.display = 'none';
+ document.getElementById('cancelConnectBtn').style.display = 'none';
setTimeout(() => {
location.reload();
}, 3000);
}
+
+function cancelConnect() {
+ if (rfb) {
+ rfb.disconnect();
+ rfb = null;
+ }
+ connected = false;
+ document.getElementById('connectBtn').disabled = false;
+ document.getElementById('disconnectBtn').disabled = true;
+ // Reset UI to initial state
+ document.getElementById('noVNC_status').style.display = 'block';
+ document.getElementById('noVNC_loading').style.display = 'none';
+ document.getElementById('cancelConnectBtn').style.display = 'none';
+ showNotification('VNC connection cancelled', 'warning');
+}
</script>
</body>
</html>
# Makefile for wssshd2 - Generated by configure.sh
# Do not edit manually, run ./configure.sh instead
CC = gcc
CFLAGS = -Wall -Wextra -O2 -I. -pthread
LDFLAGS = -lssl -lcrypto -lm -luuid -lsqlite3
PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
MANDIR = $(PREFIX)/share/man
CONFIGDIR = /etc
# Source files
SRCS = main.c config.c tunnel.c terminal.c vnc.c websocket.c websocket_protocol.c web.c assets.c ssl.c
OBJS = $(SRCS:.c=.o)
# Target
TARGET = wssshd
.PHONY: all clean install uninstall
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# Dependencies
main.o: main.c config.h websocket.h web.h
config.o: config.c config.h
tunnel.o: tunnel.c tunnel.h websocket.h
terminal.o: terminal.c terminal.h config.h
vnc.o: vnc.c vnc.h config.h
websocket.o: websocket.c websocket.h websocket_protocol.h config.h
websocket_protocol.o: websocket_protocol.c websocket_protocol.h
web.o: web.c web.h terminal.h vnc.h assets.h websocket.h html_pages/index_page.h html_pages/login_page.h html_pages/terminal_page.h html_pages/vnc_page.h html_pages/users_page.h
assets.o: assets.c assets.h
ssl.o: ssl.c ssl.h
# Asset embedding (run before compilation)
assets.o: image_data.h
image_data.h: embed_assets.sh
./embed_assets.sh
# HTML page generation
html_pages/index_page.h html_pages/login_page.h html_pages/terminal_page.h html_pages/vnc_page.h: embed_assets.sh
./embed_assets.sh
clean:
rm -f $(OBJS) $(TARGET) image_data.h favicon_data.h
install: $(TARGET)
install -d $(DESTDIR)$(BINDIR)
install -m 755 $(TARGET) $(DESTDIR)$(BINDIR)/
install -d $(DESTDIR)$(CONFIGDIR)
[ -f $(DESTDIR)$(CONFIGDIR)/wssshd.conf ] || install -m 644 wssshd.conf.example $(DESTDIR)$(CONFIGDIR)/wssshd.conf
install -d $(DESTDIR)$(MANDIR)/man8
install -m 644 wssshd.8 $(DESTDIR)$(MANDIR)/man8/
uninstall:
rm -f $(DESTDIR)$(BINDIR)/$(TARGET)
rm -f $(DESTDIR)$(MANDIR)/man8/wssshd.8
# Development targets
debug: CFLAGS += -g -DDEBUG
debug: clean all
test: $(TARGET)
@echo "Running basic functionality test..."
./$(TARGET) --help || true
distclean: clean
rm -f Makefile
# Help target
help:
@echo "Available targets:"
@echo " all - Build wssshd2 (default)"
@echo " clean - Remove build artifacts"
@echo " install - Install wssshd2 to system"
@echo " uninstall - Remove wssshd2 from system"
@echo " debug - Build with debug symbols"
@echo " test - Run basic tests"
@echo " distclean - Remove all generated files"
@echo " help - Show this help"
......@@ -26,6 +26,7 @@
#include "html_pages/vnc_page.h"
#include "html_pages/xterm_page.h"
#include "html_pages/xterm_addon_page.h"
#include "html_pages/mstsc_page.h"
#include "html_pages/novnc_css_page.h"
#include "novnc_asset_map.c"
......@@ -65,6 +66,9 @@ const char *get_embedded_asset(const char *path, size_t *size) {
} else if (strcmp(path, "/xterm-addon-fit.js") == 0) {
if (size) *size = strlen(xterm_addon_fit_js);
return xterm_addon_fit_js;
} else if (strcmp(path, "/mstsc.js") == 0) {
if (size) *size = strlen(mstsc_js);
return mstsc_js;
} else if (strcmp(path, "/novnc.css") == 0) {
if (size) *size = strlen(novnc_css);
return novnc_css;
......
......@@ -42,6 +42,7 @@ static void set_default_config(wssshd_config_t *config) {
config->debug_web = false;
config->debug_database = false;
config->debug_vnc = false;
config->debug_rdp = false;
}
static void load_config_file(wssshd_config_t *config, const char *config_file) {
......@@ -158,6 +159,7 @@ wssshd_config_t *load_config(int argc, char *argv[]) {
{"debug-web", no_argument, 0, 'E'},
{"debug-database", no_argument, 0, 'F'},
{"debug-vnc", no_argument, 0, 'G'},
{"debug-rdp", no_argument, 0, 'H'},
{"help", no_argument, 0, '?'},
{0, 0, 0, 0}
};
......@@ -165,7 +167,7 @@ wssshd_config_t *load_config(int argc, char *argv[]) {
int opt;
int option_index = 0;
while ((opt = getopt_long(argc, argv, "c:h:p:d:P:w:W:stDEFG?", long_options, &option_index)) != -1) {
while ((opt = getopt_long(argc, argv, "c:h:p:d:P:w:W:stDEFGH?", long_options, &option_index)) != -1) {
switch (opt) {
case 'c':
if (config->config_file) free(config->config_file);
......@@ -211,6 +213,9 @@ wssshd_config_t *load_config(int argc, char *argv[]) {
case 'G':
config->debug_vnc = true;
break;
case 'H':
config->debug_rdp = true;
break;
case '?':
printf("Usage: %s [OPTIONS]\n", argv[0]);
printf("Options:\n");
......@@ -227,6 +232,7 @@ wssshd_config_t *load_config(int argc, char *argv[]) {
printf(" --debug-web Enable comprehensive web interface debug output\n");
printf(" --debug-database Enable database debug output\n");
printf(" --debug-vnc Enable VNC debug output\n");
printf(" --debug-rdp Enable RDP debug output\n");
printf(" --help Show this help\n");
free_config(config);
exit(0);
......@@ -304,4 +310,5 @@ void print_config(const wssshd_config_t *config) {
printf(" Debug Web: %s\n", config->debug_web ? "yes" : "no");
printf(" Debug Database: %s\n", config->debug_database ? "yes" : "no");
printf(" Debug VNC: %s\n", config->debug_vnc ? "yes" : "no");
printf(" Debug RDP: %s\n", config->debug_rdp ? "yes" : "no");
}
\ No newline at end of file
......@@ -37,6 +37,7 @@ typedef struct {
bool debug_web;
bool debug_database;
bool debug_vnc;
bool debug_rdp;
} wssshd_config_t;
// Function declarations
......
......@@ -65,7 +65,7 @@ MANDIR = $(PREFIX)/share/man
CONFIGDIR = /etc
# Source files
SRCS = main.c config.c tunnel.c terminal.c vnc.c websocket.c websocket_protocol.c web.c assets.c ssl.c
SRCS = main.c config.c tunnel.c terminal.c vnc.c rdp.c websocket.c websocket_protocol.c web.c assets.c ssl.c
OBJS = $(SRCS:.c=.o)
# Target
......@@ -87,9 +87,10 @@ config.o: config.c config.h
tunnel.o: tunnel.c tunnel.h websocket.h
terminal.o: terminal.c terminal.h config.h
vnc.o: vnc.c vnc.h config.h
rdp.o: rdp.c rdp.h config.h
websocket.o: websocket.c websocket.h websocket_protocol.h config.h
websocket_protocol.o: websocket_protocol.c websocket_protocol.h
web.o: web.c web.h terminal.h vnc.h assets.h websocket.h html_pages/index_page.h html_pages/login_page.h html_pages/terminal_page.h html_pages/vnc_page.h html_pages/users_page.h
web.o: web.c web.h terminal.h vnc.h rdp.h assets.h websocket.h html_pages/index_page.h html_pages/login_page.h html_pages/terminal_page.h html_pages/vnc_page.h html_pages/rdp_page.h html_pages/users_page.h
assets.o: assets.c assets.h
ssl.o: ssl.c ssl.h
......@@ -100,7 +101,7 @@ image_data.h: embed_assets.sh
./embed_assets.sh
# HTML page generation
html_pages/index_page.h html_pages/login_page.h html_pages/terminal_page.h html_pages/vnc_page.h: embed_assets.sh
html_pages/index_page.h html_pages/login_page.h html_pages/terminal_page.h html_pages/vnc_page.h html_pages/rdp_page.h: embed_assets.sh
./embed_assets.sh
clean:
......
......@@ -172,6 +172,59 @@ static const char *xterm_addon_fit_js = "";
EOF
fi
# Embed mstsc.js
if [ -f templates/mstsc.min.js ]; then
echo "Embedding mstsc.min.js..."
mkdir -p html_pages
HEADER_FILE="html_pages/mstsc_page.h"
cat > "$HEADER_FILE" << EOF
/**
* mstsc.js library for wssshd
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MSTSC_PAGE_H
#define MSTSC_PAGE_H
// mstsc.js library
const char *mstsc_js =
EOF
# Process the JS file, escape quotes and backslashes
sed 's/\\/\\\\/g; s/"/\\"/g; s/^/"/; s/$/\\n"/' templates/mstsc.min.js >> "$HEADER_FILE"
# Close the string and header
cat >> "$HEADER_FILE" << EOF
;
#endif /* MSTSC_PAGE_H */
EOF
else
echo "Warning: mstsc.min.js not found, creating empty placeholder"
mkdir -p html_pages
cat > html_pages/mstsc_page.h << 'EOF'
#ifndef MSTSC_PAGE_H
#define MSTSC_PAGE_H
static const char *mstsc_js = "";
#endif /* MSTSC_PAGE_H */
EOF
fi
# Embed noVNC CSS
if [ -f templates/novnc.css ]; then
echo "Embedding novnc.css..."
......
unsigned char favicon_ico[] = {
0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x10, 0x10, 0x00, 0x00, 0x01, 0x00,
0x08, 0x00, 0x68, 0x05, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x20, 0x20,
0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0xa8, 0x10, 0x00, 0x00, 0x8e, 0x05,
0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00,
0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0xe9, 0xdc, 0xd4, 0x00, 0xe9, 0xdd,
0xd5, 0x00, 0xea, 0xdd, 0xd5, 0x00, 0xe8, 0xdc, 0xd4, 0x00, 0xea, 0xde,
0xd6, 0x00, 0xeb, 0xde, 0xd6, 0x00, 0xe9, 0xde, 0xd5, 0x00, 0xe8, 0xda,
0xd1, 0x00, 0xea, 0xdd, 0xd6, 0x00, 0xeb, 0xde, 0xd7, 0x00, 0xeb, 0xdd,
0xd5, 0x00, 0xe9, 0xdc, 0xd5, 0x00, 0xf3, 0xe9, 0xe7, 0x00, 0xf1, 0xe5,
0xe2, 0x00, 0xef, 0xe3, 0xde, 0x00, 0xf5, 0xed, 0xed, 0x00, 0xed, 0xdf,
0xd9, 0x00, 0xe8, 0xda, 0xd2, 0x00, 0xf3, 0xea, 0xe9, 0x00, 0xde, 0xcc,
0xbc, 0x00, 0xc8, 0xad, 0x94, 0x00, 0xc8, 0xae, 0x96, 0x00, 0xd4, 0xb9,
0xa3, 0x00, 0xf5, 0xec, 0xeb, 0x00, 0xe8, 0xdb, 0xd2, 0x00, 0xea, 0xdc,
0xd4, 0x00, 0xe9, 0xdd, 0xd4, 0x00, 0xe9, 0xdc, 0xd3, 0x00, 0xf1, 0xe6,
0xe4, 0x00, 0xe7, 0xd6, 0xc9, 0x00, 0xc3, 0xa8, 0x8d, 0x00, 0xd3, 0xce,
0xcb, 0x00, 0xd5, 0xd1, 0xcf, 0x00, 0xc8, 0xb2, 0xa2, 0x00, 0xd7, 0xbe,
0xac, 0x00, 0xfb, 0xf7, 0xfa, 0x00, 0xee, 0xe2, 0xdc, 0x00, 0xec, 0xe2,
0xdb, 0x00, 0xeb, 0xe0, 0xd8, 0x00, 0xeb, 0xe0, 0xd9, 0x00, 0xea, 0xdf,
0xd8, 0x00, 0xec, 0xe2, 0xdc, 0x00, 0xf8, 0xf0, 0xf0, 0x00, 0xd5, 0xba,
0xa4, 0x00, 0xd7, 0xcd, 0xc6, 0x00, 0xc1, 0xb5, 0xae, 0x00, 0xb6, 0xa4,
0x98, 0x00, 0xdc, 0xd9, 0xd8, 0x00, 0xc3, 0xa2, 0x8a, 0x00, 0xd5, 0xbc,
0xac, 0x00, 0xd1, 0xb2, 0x9d, 0x00, 0xc5, 0xa3, 0x8b, 0x00, 0xcd, 0xb1,
0x9e, 0x00, 0xc9, 0xac, 0x97, 0x00, 0xc9, 0xaa, 0x94, 0x00, 0xcd, 0xaf,
0x9a, 0x00, 0xd1, 0xb3, 0x9f, 0x00, 0xe6, 0xd9, 0xd1, 0x00, 0xf8, 0xef,
0xf0, 0x00, 0xdc, 0xc6, 0xb5, 0x00, 0xd0, 0xbf, 0xb1, 0x00, 0xd5, 0xd4,
0xd4, 0x00, 0xcf, 0xc9, 0xc6, 0x00, 0xd7, 0xce, 0xc8, 0x00, 0xd3, 0xb7,
0xa3, 0x00, 0xbe, 0x94, 0x75, 0x00, 0xb3, 0x82, 0x5e, 0x00, 0xc1, 0xa0,
0x88, 0x00, 0xba, 0x94, 0x79, 0x00, 0xb9, 0x94, 0x79, 0x00, 0xb9, 0x92,
0x77, 0x00, 0xae, 0x7d, 0x5a, 0x00, 0xbf, 0x95, 0x78, 0x00, 0xea, 0xdc,
0xd5, 0x00, 0xf2, 0xe4, 0xe1, 0x00, 0xf4, 0xee, 0xed, 0x00, 0xc5, 0xa6,
0x8f, 0x00, 0xcc, 0xb9, 0xaa, 0x00, 0xd3, 0xc4, 0xb8, 0x00, 0xc3, 0xa6,
0x90, 0x00, 0xde, 0xd2, 0xc9, 0x00, 0xdb, 0xce, 0xc6, 0x00, 0xd4, 0xc2,
0xb6, 0x00, 0xcf, 0xb8, 0xaa, 0x00, 0xcb, 0xb0, 0x9e, 0x00, 0xcb, 0xb1,
0xa0, 0x00, 0xcb, 0xb1, 0x9f, 0x00, 0xca, 0xb0, 0x9f, 0x00, 0xe2, 0xd2,
0xca, 0x00, 0xf0, 0xe0, 0xdc, 0x00, 0xf0, 0xdf, 0xdb, 0x00, 0xf5, 0xf2,
0xf1, 0x00, 0xe2, 0xd3, 0xca, 0x00, 0xbf, 0x9b, 0x81, 0x00, 0xcc, 0xba,
0xac, 0x00, 0xee, 0xec, 0xec, 0x00, 0xed, 0xe3, 0xe0, 0x00, 0xf4, 0xea,
0xe9, 0x00, 0xf7, 0xee, 0xef, 0x00, 0xf7, 0xf0, 0xf1, 0x00, 0xf7, 0xed,
0xed, 0x00, 0xf4, 0xe6, 0xe3, 0x00, 0xee, 0xde, 0xd9, 0x00, 0xf0, 0xdf,
0xda, 0x00, 0xf2, 0xe4, 0xe2, 0x00, 0xf4, 0xec, 0xec, 0x00, 0xfe, 0xfe,
0xff, 0x00, 0xe6, 0xe0, 0xdb, 0x00, 0xe2, 0xd7, 0xd1, 0x00, 0xf7, 0xf4,
0xf4, 0x00, 0xf6, 0xed, 0xed, 0x00, 0xf3, 0xe3, 0xe0, 0x00, 0xf2, 0xe2,
0xde, 0x00, 0xf2, 0xe1, 0xde, 0x00, 0xf2, 0xe3, 0xdf, 0x00, 0xf1, 0xe1,
0xde, 0x00, 0xf1, 0xe0, 0xdd, 0x00, 0xee, 0xde, 0xd8, 0x00, 0xef, 0xdf,
0xd9, 0x00, 0xf3, 0xe6, 0xe4, 0x00, 0xf4, 0xeb, 0xeb, 0x00, 0xee, 0xe8,
0xe7, 0x00, 0xf8, 0xf4, 0xf5, 0x00, 0xf5, 0xee, 0xee, 0x00, 0xf4, 0xe8,
0xe7, 0x00, 0xf3, 0xe2, 0xde, 0x00, 0xf2, 0xe1, 0xdd, 0x00, 0xf2, 0xe2,
0xdf, 0x00, 0xf1, 0xe0, 0xdc, 0x00, 0xef, 0xdf, 0xda, 0x00, 0xf3, 0xe4,
0xe2, 0x00, 0xf4, 0xe8, 0xe6, 0x00, 0xf6, 0xed, 0xec, 0x00, 0xf5, 0xeb,
0xea, 0x00, 0xf4, 0xe6, 0xe4, 0x00, 0xf3, 0xe2, 0xdf, 0x00, 0xf1, 0xe0,
0xdb, 0x00, 0xf0, 0xe0, 0xda, 0x00, 0xf1, 0xe1, 0xdd, 0x00, 0xf5, 0xe9,
0xe7, 0x00, 0xf5, 0xea, 0xe8, 0x00, 0xf3, 0xe3, 0xdf, 0x00, 0xf2, 0xe2,
0xdd, 0x00, 0xf3, 0xe4, 0xe0, 0x00, 0xf5, 0xe8, 0xe6, 0x00, 0xf4, 0xe7,
0xe4, 0x00, 0xf4, 0xe4, 0xe0, 0x00, 0xf1, 0xe1, 0xdc, 0x00, 0xf3, 0xe3,
0xdd, 0x00, 0xf4, 0xe4, 0xe1, 0x00, 0xf5, 0xe7, 0xe4, 0x00, 0xf6, 0xea,
0xe8, 0x00, 0xf6, 0xeb, 0xe9, 0x00, 0xf7, 0xec, 0xea, 0x00, 0xf7, 0xea,
0xe8, 0x00, 0xf5, 0xe6, 0xe3, 0x00, 0xf4, 0xe5, 0xe1, 0x00, 0xf5, 0xe4,
0xe1, 0x00, 0xf5, 0xe4, 0xe0, 0x00, 0xf3, 0xe3, 0xde, 0x00, 0xff, 0xff,
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89, 0x94,
0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x92, 0x9f,
0x93, 0x81, 0x89, 0x8e, 0x8f, 0x65, 0x90, 0x8c, 0x8b, 0x91, 0x92, 0x8d,
0x8d, 0x8d, 0x8d, 0x7d, 0x93, 0x75, 0x89, 0x8a, 0x6f, 0x86, 0x8b, 0x85,
0x8c, 0x65, 0x8d, 0x7d, 0x70, 0x87, 0x87, 0x70, 0x80, 0x75, 0x81, 0x7e,
0x82, 0x83, 0x84, 0x0f, 0x85, 0x86, 0x87, 0x7d, 0x70, 0x70, 0x7f, 0x70,
0x88, 0x75, 0x76, 0x70, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x6f, 0x7d,
0x7e, 0x70, 0x7f, 0x7f, 0x80, 0x75, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c,
0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x72, 0x73, 0x74, 0x75, 0x5a, 0x0f,
0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x0d, 0x61, 0x62, 0x63, 0x63, 0x64,
0x65, 0x66, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53,
0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x2a, 0x2b,
0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x38, 0x39, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
0x26, 0x27, 0x28, 0x27, 0x29, 0x03, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
0x17, 0x10, 0x18, 0x19, 0x1a, 0x00, 0x19, 0x00, 0x1b, 0x0b, 0x00, 0x00,
0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x00, 0x04, 0x05, 0x02, 0x02, 0x05, 0x02,
0x01, 0x0b, 0x02, 0x01, 0x07, 0x08, 0x09, 0x07, 0x00, 0x02, 0x0a, 0x02,
0x05, 0x0a, 0x02, 0x05, 0x02, 0x0b, 0x01, 0x04, 0x04, 0x02, 0x02, 0x04,
0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x01, 0x00, 0x01,
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x20, 0x00,
0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xe0,
0xda, 0xff, 0xf1, 0xe1, 0xdb, 0xff, 0xf3, 0xe2, 0xdd, 0xff, 0xf4, 0xe3,
0xdf, 0xff, 0xf4, 0xe4, 0xe0, 0xff, 0xf5, 0xe5, 0xe1, 0xff, 0xf6, 0xe7,
0xe3, 0xff, 0xf6, 0xe8, 0xe5, 0xff, 0xf6, 0xea, 0xe8, 0xff, 0xf7, 0xeb,
0xe9, 0xff, 0xf7, 0xeb, 0xe9, 0xff, 0xf7, 0xeb, 0xea, 0xff, 0xf8, 0xed,
0xec, 0xff, 0xf7, 0xed, 0xec, 0xff, 0xf7, 0xec, 0xeb, 0xff, 0xf7, 0xea,
0xe8, 0xff, 0xf6, 0xe8, 0xe5, 0xff, 0xf5, 0xe6, 0xe2, 0xff, 0xf5, 0xe5,
0xe1, 0xff, 0xf5, 0xe5, 0xe1, 0xff, 0xf5, 0xe5, 0xe0, 0xff, 0xf5, 0xe5,
0xe1, 0xff, 0xf5, 0xe5, 0xe1, 0xff, 0xf5, 0xe4, 0xe0, 0xff, 0xf5, 0xe4,
0xe1, 0xff, 0xf5, 0xe4, 0xe0, 0xff, 0xf4, 0xe3, 0xde, 0xff, 0xf3, 0xe3,
0xde, 0xff, 0xf2, 0xe2, 0xdc, 0xff, 0xf1, 0xe1, 0xdc, 0xff, 0xf0, 0xe1,
0xdc, 0xff, 0xee, 0xe0, 0xda, 0xff, 0xef, 0xe0, 0xda, 0xff, 0xf1, 0xe1,
0xdb, 0xff, 0xf2, 0xe2, 0xdc, 0xff, 0xf3, 0xe3, 0xde, 0xff, 0xf4, 0xe4,
0xe0, 0xff, 0xf4, 0xe5, 0xe1, 0xff, 0xf4, 0xe6, 0xe3, 0xff, 0xf5, 0xe7,
0xe4, 0xff, 0xf5, 0xe9, 0xe6, 0xff, 0xf6, 0xea, 0xe8, 0xff, 0xf6, 0xea,
0xe8, 0xff, 0xf6, 0xeb, 0xe9, 0xff, 0xf6, 0xeb, 0xe9, 0xff, 0xf6, 0xeb,
0xe9, 0xff, 0xf6, 0xea, 0xe8, 0xff, 0xf6, 0xe8, 0xe6, 0xff, 0xf5, 0xe6,
0xe3, 0xff, 0xf5, 0xe5, 0xe1, 0xff, 0xf4, 0xe4, 0xe1, 0xff, 0xf4, 0xe4,
0xe0, 0xff, 0xf4, 0xe4, 0xe0, 0xff, 0xf4, 0xe4, 0xe1, 0xff, 0xf4, 0xe4,
0xe0, 0xff, 0xf5, 0xe4, 0xe0, 0xff, 0xf4, 0xe3, 0xe0, 0xff, 0xf4, 0xe3,
0xe0, 0xff, 0xf3, 0xe3, 0xdf, 0xff, 0xf3, 0xe3, 0xde, 0xff, 0xf2, 0xe2,
0xdc, 0xff, 0xf1, 0xe1, 0xdb, 0xff, 0xef, 0xe0, 0xda, 0xff, 0xed, 0xde,
0xd8, 0xff, 0xf0, 0xe0, 0xd9, 0xff, 0xf1, 0xe1, 0xdb, 0xff, 0xf1, 0xe2,
0xdc, 0xff, 0xf2, 0xe2, 0xde, 0xff, 0xf3, 0xe3, 0xe0, 0xff, 0xf4, 0xe4,
0xe0, 0xff, 0xf4, 0xe5, 0xe2, 0xff, 0xf5, 0xe7, 0xe4, 0xff, 0xf5, 0xe7,
0xe4, 0xff, 0xf5, 0xe9, 0xe7, 0xff, 0xf5, 0xe9, 0xe7, 0xff, 0xf5, 0xe9,
0xe7, 0xff, 0xf5, 0xe9, 0xe7, 0xff, 0xf6, 0xe9, 0xe7, 0xff, 0xf5, 0xe9,
0xe7, 0xff, 0xf4, 0xe7, 0xe4, 0xff, 0xf4, 0xe5, 0xe1, 0xff, 0xf4, 0xe4,
0xe0, 0xff, 0xf3, 0xe4, 0xe0, 0xff, 0xf3, 0xe3, 0xdf, 0xff, 0xf3, 0xe3,
0xdf, 0xff, 0xf3, 0xe3, 0xdf, 0xff, 0xf4, 0xe4, 0xe0, 0xff, 0xf3, 0xe3,
0xdf, 0xff, 0xf3, 0xe3, 0xe0, 0xff, 0xf4, 0xe3, 0xdf, 0xff, 0xf3, 0xe2,
0xdf, 0xff, 0xf3, 0xe2, 0xdf, 0xff, 0xf2, 0xe1, 0xdd, 0xff, 0xf0, 0xe0,
0xda, 0xff, 0xef, 0xdf, 0xd9, 0xff, 0xed, 0xde, 0xd8, 0xff, 0xef, 0xdf,
0xd9, 0xff, 0xf0, 0xe1, 0xdb, 0xff, 0xf1, 0xe1, 0xdc, 0xff, 0xf2, 0xe2,
0xde, 0xff, 0xf3, 0xe3, 0xdf, 0xff, 0xf4, 0xe4, 0xe1, 0xff, 0xf4, 0xe5,
0xe2, 0xff, 0xf4, 0xe6, 0xe3, 0xff, 0xf5, 0xe8, 0xe5, 0xff, 0xf5, 0xe8,
0xe6, 0xff, 0xf5, 0xea, 0xe9, 0xff, 0xf5, 0xeb, 0xea, 0xff, 0xf5, 0xe9,
0xe7, 0xff, 0xf4, 0xe8, 0xe6, 0xff, 0xf4, 0xe7, 0xe5, 0xff, 0xf4, 0xe5,
0xe2, 0xff, 0xf3, 0xe4, 0xe0, 0xff, 0xf3, 0xe3, 0xdf, 0xff, 0xf3, 0xe3,
0xde, 0xff, 0xf3, 0xe3, 0xdf, 0xff, 0xf3, 0xe3, 0xde, 0xff, 0xf2, 0xe2,
0xde, 0xff, 0xf3, 0xe3, 0xde, 0xff, 0xf3, 0xe3, 0xde, 0xff, 0xf3, 0xe3,
0xdf, 0xff, 0xf3, 0xe3, 0xdf, 0xff, 0xf3, 0xe2, 0xde, 0xff, 0xf2, 0xe2,
0xdd, 0xff, 0xf2, 0xe1, 0xdd, 0xff, 0xf1, 0xe1, 0xdb, 0xff, 0xef, 0xdf,
0xd9, 0xff, 0xed, 0xde, 0xd7, 0xff, 0xef, 0xdf, 0xd9, 0xff, 0xf0, 0xe0,
0xdb, 0xff, 0xf1, 0xe1, 0xdc, 0xff, 0xf2, 0xe2, 0xdd, 0xff, 0xf3, 0xe3,
0xdf, 0xff, 0xf4, 0xe4, 0xe1, 0xff, 0xf4, 0xe5, 0xe2, 0xff, 0xf4, 0xe7,
0xe4, 0xff, 0xf5, 0xe8, 0xe6, 0xff, 0xf5, 0xe9, 0xe8, 0xff, 0xf5, 0xeb,
0xea, 0xff, 0xf6, 0xeb, 0xea, 0xff, 0xf5, 0xea, 0xe8, 0xff, 0xf5, 0xe9,
0xe7, 0xff, 0xf4, 0xe7, 0xe4, 0xff, 0xf4, 0xe5, 0xe2, 0xff, 0xf4, 0xe3,
0xe0, 0xff, 0xf3, 0xe2, 0xdf, 0xff, 0xf3, 0xe2, 0xde, 0xff, 0xf3, 0xe2,
0xde, 0xff, 0xf2, 0xe2, 0xdd, 0xff, 0xf3, 0xe3, 0xde, 0xff, 0xf3, 0xe3,
0xdf, 0xff, 0xf3, 0xe3, 0xdf, 0xff, 0xf3, 0xe2, 0xdf, 0xff, 0xf3, 0xe2,
0xde, 0xff, 0xf3, 0xe2, 0xdf, 0xff, 0xf2, 0xe1, 0xdd, 0xff, 0xf2, 0xe1,
0xdc, 0xff, 0xf1, 0xe0, 0xdc, 0xff, 0xef, 0xdf, 0xd9, 0xff, 0xed, 0xdd,
0xd7, 0xff, 0xef, 0xdf, 0xd9, 0xff, 0xf1, 0xe0, 0xdb, 0xff, 0xf1, 0xe1,
0xdc, 0xff, 0xf1, 0xe1, 0xdd, 0xff, 0xf3, 0xe3, 0xe0, 0xff, 0xf3, 0xe4,
0xe1, 0xff, 0xf4, 0xe6, 0xe3, 0xff, 0xf4, 0xe8, 0xe6, 0xff, 0xf5, 0xe9,
0xe8, 0xff, 0xf5, 0xea, 0xe8, 0xff, 0xf5, 0xeb, 0xea, 0xff, 0xf6, 0xeb,
0xea, 0xff, 0xf5, 0xeb, 0xea, 0xff, 0xf5, 0xe9, 0xe8, 0xff, 0xf4, 0xe7,
0xe4, 0xff, 0xf4, 0xe5, 0xe2, 0xff, 0xf3, 0xe3, 0xe0, 0xff, 0xf3, 0xe2,
0xdf, 0xff, 0xf2, 0xe2, 0xde, 0xff, 0xf2, 0xe2, 0xde, 0xff, 0xf2, 0xe2,
0xde, 0xff, 0xf2, 0xe2, 0xde, 0xff, 0xf2, 0xe2, 0xde, 0xff, 0xf3, 0xe2,
0xdf, 0xff, 0xf3, 0xe2, 0xde, 0xff, 0xf3, 0xe2, 0xde, 0xff, 0xf3, 0xe2,
0xdf, 0xff, 0xf2, 0xe1, 0xdd, 0xff, 0xf1, 0xe0, 0xdc, 0xff, 0xf1, 0xe0,
0xdc, 0xff, 0xf0, 0xdf, 0xd9, 0xff, 0xed, 0xdd, 0xd7, 0xff, 0xef, 0xdf,
0xd9, 0xff, 0xf0, 0xe0, 0xdb, 0xff, 0xf1, 0xe1, 0xdc, 0xff, 0xf2, 0xe1,
0xde, 0xff, 0xf3, 0xe3, 0xe0, 0xff, 0xf3, 0xe5, 0xe2, 0xff, 0xf3, 0xe6,
0xe4, 0xff, 0xf4, 0xe8, 0xe7, 0xff, 0xf5, 0xe9, 0xe9, 0xff, 0xf5, 0xeb,
0xeb, 0xff, 0xf5, 0xed, 0xed, 0xff, 0xf5, 0xed, 0xed, 0xff, 0xf5, 0xec,
0xeb, 0xff, 0xf5, 0xe9, 0xe8, 0xff, 0xf4, 0xe7, 0xe5, 0xff, 0xf4, 0xe5,
0xe2, 0xff, 0xf3, 0xe3, 0xdf, 0xff, 0xf3, 0xe2, 0xde, 0xff, 0xf3, 0xe2,
0xde, 0xff, 0xf3, 0xe2, 0xde, 0xff, 0xf3, 0xe2, 0xde, 0xff, 0xf2, 0xe1,
0xdd, 0xff, 0xf2, 0xe1, 0xde, 0xff, 0xf3, 0xe2, 0xde, 0xff, 0xf2, 0xe2,
0xde, 0xff, 0xf2, 0xe2, 0xde, 0xff, 0xf2, 0xe2, 0xdf, 0xff, 0xf2, 0xe2,
0xde, 0xff, 0xf1, 0xe0, 0xdc, 0xff, 0xf1, 0xdf, 0xdb, 0xff, 0xf0, 0xde,
0xd8, 0xff, 0xee, 0xdd, 0xd7, 0xff, 0xef, 0xdf, 0xd9, 0xff, 0xf0, 0xe0,
0xdb, 0xff, 0xf1, 0xe1, 0xdd, 0xff, 0xf2, 0xe2, 0xde, 0xff, 0xf3, 0xe4,
0xe1, 0xff, 0xf4, 0xe6, 0xe3, 0xff, 0xf4, 0xe8, 0xe6, 0xff, 0xf5, 0xeb,
0xea, 0xff, 0xf4, 0xeb, 0xeb, 0xff, 0xf6, 0xef, 0xef, 0xff, 0xf6, 0xef,
0xee, 0xff, 0xf6, 0xef, 0xee, 0xff, 0xf5, 0xed, 0xec, 0xff, 0xf5, 0xe9,
0xe8, 0xff, 0xf4, 0xe7, 0xe5, 0xff, 0xf4, 0xe6, 0xe3, 0xff, 0xf3, 0xe3,
0xe0, 0xff, 0xf3, 0xe2, 0xdf, 0xff, 0xf2, 0xe2, 0xde, 0xff, 0xf3, 0xe2,
0xde, 0xff, 0xf2, 0xe2, 0xdd, 0xff, 0xf2, 0xe2, 0xdd, 0xff, 0xf2, 0xe1,
0xde, 0xff, 0xf2, 0xe2, 0xdf, 0xff, 0xf2, 0xe2, 0xdf, 0xff, 0xf3, 0xe2,
0xdf, 0xff, 0xf2, 0xe2, 0xde, 0xff, 0xf2, 0xe1, 0xde, 0xff, 0xf2, 0xe1,
0xdd, 0xff, 0xf1, 0xdf, 0xda, 0xff, 0xef, 0xde, 0xd8, 0xff, 0xed, 0xdd,
0xd7, 0xff, 0xee, 0xde, 0xd8, 0xff, 0xf0, 0xdf, 0xdb, 0xff, 0xf1, 0xe0,
0xdd, 0xff, 0xf2, 0xe2, 0xdf, 0xff, 0xf3, 0xe4, 0xe2, 0xff, 0xf4, 0xe7,
0xe4, 0xff, 0xf5, 0xea, 0xe8, 0xff, 0xf5, 0xec, 0xec, 0xff, 0xf5, 0xee,
0xee, 0xff, 0xf2, 0xed, 0xec, 0xff, 0xf6, 0xf0, 0xf0, 0xff, 0xf5, 0xee,
0xee, 0xff, 0xf5, 0xee, 0xee, 0xff, 0xf5, 0xed, 0xed, 0xff, 0xf5, 0xea,
0xe8, 0xff, 0xf4, 0xe7, 0xe5, 0xff, 0xf3, 0xe4, 0xe1, 0xff, 0xf3, 0xe2,
0xdf, 0xff, 0xf3, 0xe2, 0xde, 0xff, 0xf2, 0xe2, 0xde, 0xff, 0xf2, 0xe2,
0xdd, 0xff, 0xf2, 0xe1, 0xdd, 0xff, 0xf2, 0xe2, 0xde, 0xff, 0xf2, 0xe2,
0xdf, 0xff, 0xf3, 0xe2, 0xdf, 0xff, 0xf2, 0xe2, 0xdf, 0xff, 0xf2, 0xe2,
0xdf, 0xff, 0xf2, 0xe2, 0xde, 0xff, 0xf1, 0xe1, 0xdc, 0xff, 0xf0, 0xdf,
0xda, 0xff, 0xef, 0xdf, 0xd9, 0xff, 0xed, 0xde, 0xd8, 0xff, 0xee, 0xde,
0xd8, 0xff, 0xf0, 0xdf, 0xdb, 0xff, 0xf1, 0xe1, 0xde, 0xff, 0xf2, 0xe4,
0xe0, 0xff, 0xf3, 0xe6, 0xe3, 0xff, 0xf4, 0xe9, 0xe8, 0xff, 0xf4, 0xec,
0xec, 0xff, 0xf6, 0xf1, 0xf1, 0xff, 0xf1, 0xec, 0xec, 0xff, 0xdd, 0xd6,
0xd2, 0xff, 0xf7, 0xf3, 0xf3, 0xff, 0xf6, 0xf1, 0xf1, 0xff, 0xf6, 0xf1,
0xf1, 0xff, 0xf5, 0xee, 0xee, 0xff, 0xf5, 0xeb, 0xea, 0xff, 0xf4, 0xe7,
0xe5, 0xff, 0xf3, 0xe4, 0xe1, 0xff, 0xf2, 0xe3, 0xdf, 0xff, 0xf3, 0xe2,
0xdf, 0xff, 0xf2, 0xe2, 0xde, 0xff, 0xf2, 0xe1, 0xdd, 0xff, 0xf2, 0xe1,
0xdd, 0xff, 0xf3, 0xe2, 0xde, 0xff, 0xf2, 0xe2, 0xdf, 0xff, 0xf3, 0xe2,
0xdf, 0xff, 0xf2, 0xe2, 0xdf, 0xff, 0xf2, 0xe2, 0xdf, 0xff, 0xf2, 0xe2,
0xdf, 0xff, 0xf2, 0xe1, 0xde, 0xff, 0xf0, 0xe0, 0xdb, 0xff, 0xef, 0xdf,
0xd9, 0xff, 0xed, 0xdd, 0xd7, 0xff, 0xef, 0xdf, 0xd9, 0xff, 0xf0, 0xe0,
0xdb, 0xff, 0xf1, 0xe2, 0xde, 0xff, 0xf3, 0xe6, 0xe3, 0xff, 0xf4, 0xea,
0xe9, 0xff, 0xf4, 0xed, 0xec, 0xff, 0xf5, 0xef, 0xef, 0xff, 0xf9, 0xf6,
0xf8, 0xff, 0xec, 0xe5, 0xe3, 0xff, 0xd9, 0xce, 0xc6, 0xff, 0xff, 0xff,
0xff, 0xff, 0xf3, 0xef, 0xef, 0xff, 0xf4, 0xef, 0xef, 0xff, 0xf6, 0xf0,
0xf0, 0xff, 0xf5, 0xec, 0xec, 0xff, 0xf4, 0xe7, 0xe6, 0xff, 0xf3, 0xe4,
0xe1, 0xff, 0xf2, 0xe2, 0xdf, 0xff, 0xf3, 0xe2, 0xde, 0xff, 0xf3, 0xe2,
0xde, 0xff, 0xf2, 0xe2, 0xdf, 0xff, 0xf3, 0xe2, 0xde, 0xff, 0xf3, 0xe2,
0xdf, 0xff, 0xf3, 0xe3, 0xe0, 0xff, 0xf3, 0xe3, 0xe0, 0xff, 0xf2, 0xe3,
0xe0, 0xff, 0xf2, 0xe2, 0xdf, 0xff, 0xf2, 0xe2, 0xdf, 0xff, 0xf1, 0xe2,
0xdf, 0xff, 0xf1, 0xe0, 0xdc, 0xff, 0xef, 0xdf, 0xd9, 0xff, 0xed, 0xdd,
0xd7, 0xff, 0xee, 0xde, 0xd8, 0xff, 0xf1, 0xe1, 0xdd, 0xff, 0xf2, 0xe4,
0xe1, 0xff, 0xf3, 0xe8, 0xe7, 0xff, 0xf4, 0xed, 0xed, 0xff, 0xf5, 0xef,
0xef, 0xff, 0xf5, 0xf0, 0xf0, 0xff, 0xf7, 0xf3, 0xf3, 0xff, 0xf0, 0xeb,
0xe9, 0xff, 0xd1, 0xc4, 0xb9, 0xff, 0xd0, 0xba, 0xab, 0xff, 0xcc, 0xbd,
0xb1, 0xff, 0xf3, 0xf1, 0xf2, 0xff, 0xf5, 0xef, 0xef, 0xff, 0xf4, 0xed,
0xec, 0xff, 0xf4, 0xe9, 0xe8, 0xff, 0xf3, 0xe5, 0xe2, 0xff, 0xf2, 0xe2,
0xdf, 0xff, 0xf3, 0xe3, 0xdf, 0xff, 0xf3, 0xe3, 0xe0, 0xff, 0xf3, 0xe2,
0xe0, 0xff, 0xf2, 0xe3, 0xe0, 0xff, 0xf3, 0xe4, 0xe2, 0xff, 0xf3, 0xe4,
0xe2, 0xff, 0xf3, 0xe5, 0xe2, 0xff, 0xf2, 0xe5, 0xe2, 0xff, 0xf2, 0xe4,
0xe1, 0xff, 0xf2, 0xe3, 0xe0, 0xff, 0xf1, 0xe2, 0xdf, 0xff, 0xf1, 0xe1,
0xdd, 0xff, 0xf0, 0xdf, 0xda, 0xff, 0xed, 0xdd, 0xd7, 0xff, 0xef, 0xde,
0xd9, 0xff, 0xf1, 0xe1, 0xde, 0xff, 0xf3, 0xe7, 0xe5, 0xff, 0xf5, 0xed,
0xed, 0xff, 0xf4, 0xef, 0xef, 0xff, 0xf7, 0xf2, 0xf3, 0xff, 0xfd, 0xfd,
0xfe, 0xff, 0xfd, 0xfd, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb6, 0x92,
0x78, 0xff, 0xb4, 0x92, 0x7a, 0xff, 0xf7, 0xf7, 0xf8, 0xff, 0xfc, 0xfa,
0xfb, 0xff, 0xfa, 0xf7, 0xf9, 0xff, 0xf8, 0xf2, 0xf3, 0xff, 0xf5, 0xeb,
0xea, 0xff, 0xf3, 0xe5, 0xe3, 0xff, 0xf1, 0xe2, 0xdf, 0xff, 0xf1, 0xe3,
0xe0, 0xff, 0xf1, 0xe4, 0xe1, 0xff, 0xf2, 0xe4, 0xe2, 0xff, 0xf3, 0xe6,
0xe5, 0xff, 0xf2, 0xe7, 0xe6, 0xff, 0xf2, 0xe7, 0xe6, 0xff, 0xf3, 0xe8,
0xe6, 0xff, 0xf2, 0xe6, 0xe4, 0xff, 0xf2, 0xe4, 0xe2, 0xff, 0xf2, 0xe4,
0xe1, 0xff, 0xf1, 0xe2, 0xdf, 0xff, 0xf1, 0xe1, 0xde, 0xff, 0xf0, 0xdf,
0xdb, 0xff, 0xec, 0xdd, 0xd7, 0xff, 0xef, 0xdf, 0xd9, 0xff, 0xf2, 0xe3,
0xe0, 0xff, 0xf3, 0xea, 0xe9, 0xff, 0xf4, 0xee, 0xee, 0xff, 0xf9, 0xf7,
0xf7, 0xff, 0xf6, 0xf7, 0xf7, 0xff, 0xd3, 0xbe, 0xb0, 0xff, 0xbc, 0x97,
0x7e, 0xff, 0xb7, 0x8b, 0x6b, 0xff, 0xa0, 0x60, 0x33, 0xff, 0xd3, 0xca,
0xc4, 0xff, 0xdb, 0xda, 0xda, 0xff, 0xd5, 0xca, 0xc3, 0xff, 0xde, 0xd4,
0xce, 0xff, 0xe9, 0xe0, 0xdc, 0xff, 0xf1, 0xe9, 0xe7, 0xff, 0xf6, 0xed,
0xec, 0xff, 0xf8, 0xef, 0xf0, 0xff, 0xfa, 0xf4, 0xf6, 0xff, 0xfa, 0xf5,
0xf8, 0xff, 0xfb, 0xf7, 0xf9, 0xff, 0xfc, 0xf8, 0xfb, 0xff, 0xfb, 0xf6,
0xf9, 0xff, 0xfb, 0xf7, 0xfa, 0xff, 0xfb, 0xf8, 0xfb, 0xff, 0xfa, 0xf5,
0xf8, 0xff, 0xfa, 0xf4, 0xf6, 0xff, 0xfa, 0xf3, 0xf4, 0xff, 0xf7, 0xed,
0xed, 0xff, 0xf1, 0xe2, 0xdf, 0xff, 0xf0, 0xdf, 0xda, 0xff, 0xec, 0xdd,
0xd7, 0xff, 0xf0, 0xe0, 0xda, 0xff, 0xf3, 0xe5, 0xe2, 0xff, 0xf4, 0xec,
0xeb, 0xff, 0xf9, 0xf8, 0xf9, 0xff, 0xeb, 0xe5, 0xe1, 0xff, 0xb2, 0x7f,
0x5a, 0xff, 0xb2, 0x83, 0x60, 0xff, 0xc8, 0xad, 0x99, 0xff, 0xc0, 0xa9,
0x96, 0xff, 0xc7, 0xa9, 0x92, 0xff, 0xaa, 0x76, 0x50, 0xff, 0xcb, 0xae,
0x9a, 0xff, 0xef, 0xef, 0xf1, 0xff, 0xd8, 0xcc, 0xc3, 0xff, 0xcc, 0xb9,
0xac, 0xff, 0xca, 0xb6, 0xaa, 0xff, 0xc9, 0xb2, 0xa3, 0xff, 0xce, 0xb5,
0xa6, 0xff, 0xcc, 0xb4, 0xa4, 0xff, 0xd5, 0xc2, 0xb6, 0xff, 0xd5, 0xbf,
0xb1, 0xff, 0xd1, 0xb9, 0xaa, 0xff, 0xd7, 0xc6, 0xbc, 0xff, 0xd6, 0xc3,
0xb8, 0xff, 0xd6, 0xc2, 0xb6, 0xff, 0xdc, 0xcc, 0xc2, 0xff, 0xd7, 0xc3,
0xb8, 0xff, 0xd9, 0xc3, 0xb7, 0xff, 0xe1, 0xcf, 0xc5, 0xff, 0xf4, 0xe9,
0xe8, 0xff, 0xf0, 0xe0, 0xdb, 0xff, 0xec, 0xdc, 0xd6, 0xff, 0xf1, 0xe0,
0xdc, 0xff, 0xf3, 0xe6, 0xe5, 0xff, 0xf6, 0xf1, 0xf1, 0xff, 0xf3, 0xf1,
0xf0, 0xff, 0xac, 0x74, 0x49, 0xff, 0xb3, 0x95, 0x80, 0xff, 0xf5, 0xfc,
0xff, 0xff, 0xee, 0xf2, 0xf4, 0xff, 0xd5, 0xd4, 0xd4, 0xff, 0xf6, 0xfa,
0xfe, 0xff, 0xea, 0xea, 0xe9, 0xff, 0xa0, 0x6d, 0x47, 0xff, 0xcd, 0xb0,
0x9b, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xe9, 0xe5, 0xff, 0xdb, 0xcc,
0xc2, 0xff, 0xe2, 0xd9, 0xd4, 0xff, 0xd1, 0xbd, 0xb0, 0xff, 0xda, 0xca,
0xc1, 0xff, 0xb0, 0x87, 0x69, 0xff, 0xb9, 0x96, 0x7c, 0xff, 0xcc, 0xb4,
0xa3, 0xff, 0xa8, 0x7a, 0x59, 0xff, 0xbb, 0x9a, 0x82, 0xff, 0xc4, 0xa7,
0x92, 0xff, 0xa6, 0x76, 0x54, 0xff, 0xc0, 0xa1, 0x8c, 0xff, 0xba, 0x97,
0x7f, 0xff, 0xc5, 0xab, 0x98, 0xff, 0xdf, 0xd0, 0xc8, 0xff, 0xf3, 0xe3,
0xe0, 0xff, 0xea, 0xdb, 0xd4, 0xff, 0xf2, 0xe1, 0xdd, 0xff, 0xf3, 0xe7,
0xe5, 0xff, 0xfd, 0xfe, 0xff, 0xff, 0xca, 0xa7, 0x8a, 0xff, 0xbd, 0x94,
0x77, 0xff, 0xe3, 0xea, 0xf0, 0xff, 0xc8, 0xbf, 0xba, 0xff, 0xd9, 0xd5,
0xd1, 0xff, 0xc9, 0xc2, 0xbd, 0xff, 0xd7, 0xd2, 0xce, 0xff, 0xce, 0xc9,
0xc7, 0xff, 0xd5, 0xd1, 0xcd, 0xff, 0xab, 0x73, 0x48, 0xff, 0xf2, 0xf1,
0xf1, 0xff, 0xcd, 0xae, 0x98, 0xff, 0xa0, 0x5e, 0x2f, 0xff, 0xbf, 0x9a,
0x7f, 0xff, 0xb0, 0x78, 0x51, 0xff, 0xe2, 0xdb, 0xd7, 0xff, 0xbf, 0x9f,
0x88, 0xff, 0xab, 0x75, 0x4f, 0xff, 0xcb, 0xb4, 0xa4, 0xff, 0xc7, 0xad,
0x99, 0xff, 0xac, 0x78, 0x54, 0xff, 0xc4, 0xa9, 0x96, 0xff, 0xc1, 0x9d,
0x84, 0xff, 0xa9, 0x74, 0x50, 0xff, 0xba, 0x92, 0x78, 0xff, 0xda, 0xc6,
0xb9, 0xff, 0xbd, 0x90, 0x71, 0xff, 0xf5, 0xea, 0xe9, 0xff, 0xe8, 0xd9,
0xd1, 0xff, 0xf2, 0xe2, 0xde, 0xff, 0xf3, 0xe9, 0xe8, 0xff, 0xf8, 0xf9,
0xfa, 0xff, 0xb9, 0x84, 0x5a, 0xff, 0xe0, 0xd4, 0xcc, 0xff, 0xf4, 0xf7,
0xf9, 0xff, 0xce, 0xca, 0xc6, 0xff, 0xbe, 0xae, 0xa4, 0xff, 0xc2, 0xb2,
0xa8, 0xff, 0xc2, 0xb4, 0xab, 0xff, 0xd8, 0xd3, 0xd0, 0xff, 0xfd, 0xff,
0xff, 0xff, 0xbd, 0x91, 0x72, 0xff, 0xd6, 0xbe, 0xac, 0xff, 0xc7, 0xa4,
0x89, 0xff, 0xac, 0x77, 0x51, 0xff, 0xa8, 0x6e, 0x43, 0xff, 0xaf, 0x7c,
0x56, 0xff, 0xda, 0xc9, 0xbe, 0xff, 0xbe, 0x99, 0x7e, 0xff, 0xb7, 0x8a,
0x68, 0xff, 0xd7, 0xc6, 0xba, 0xff, 0xbe, 0x9a, 0x81, 0xff, 0xb8, 0x8d,
0x6d, 0xff, 0xd5, 0xc2, 0xb5, 0xff, 0xba, 0x90, 0x73, 0xff, 0xb9, 0x91,
0x73, 0xff, 0xad, 0x74, 0x4e, 0xff, 0xa6, 0x68, 0x3c, 0xff, 0xbe, 0x8d,
0x6c, 0xff, 0xf0, 0xe7, 0xe4, 0xff, 0xe6, 0xd8, 0xd1, 0xff, 0xf1, 0xe1,
0xdd, 0xff, 0xf4, 0xeb, 0xea, 0xff, 0xf3, 0xee, 0xec, 0xff, 0xb3, 0x7e,
0x53, 0xff, 0xd0, 0xc8, 0xc3, 0xff, 0xd1, 0xcd, 0xcb, 0xff, 0xc8, 0xbc,
0xb3, 0xff, 0xb8, 0xa7, 0x9d, 0xff, 0xb6, 0xb2, 0xaf, 0xff, 0xc1, 0xae,
0xa0, 0xff, 0xd4, 0xcd, 0xc8, 0xff, 0xe1, 0xe2, 0xe4, 0xff, 0xc1, 0x9e,
0x85, 0xff, 0xbf, 0x98, 0x7a, 0xff, 0xbc, 0x96, 0x7a, 0xff, 0xce, 0xb0,
0x9c, 0xff, 0xb2, 0x7a, 0x50, 0xff, 0xca, 0xaa, 0x93, 0xff, 0xa8, 0x73,
0x4d, 0xff, 0xa5, 0x6c, 0x43, 0xff, 0xcb, 0xb4, 0xa2, 0xff, 0xc1, 0xa1,
0x8a, 0xff, 0x9e, 0x63, 0x39, 0xff, 0xce, 0xba, 0xaa, 0xff, 0xb2, 0x84,
0x64, 0xff, 0xa7, 0x72, 0x4b, 0xff, 0xd1, 0xbc, 0xae, 0xff, 0xb7, 0x87,
0x68, 0xff, 0xd0, 0xb4, 0xa1, 0xff, 0xb3, 0x83, 0x5f, 0xff, 0xee, 0xe6,
0xe1, 0xff, 0xe6, 0xd9, 0xd1, 0xff, 0xef, 0xe0, 0xdc, 0xff, 0xf4, 0xe9,
0xe7, 0xff, 0xf5, 0xf3, 0xf4, 0xff, 0xb6, 0x81, 0x55, 0xff, 0xd7, 0xcc,
0xc5, 0xff, 0xe5, 0xe6, 0xe5, 0xff, 0xce, 0xc7, 0xc2, 0xff, 0xb5, 0xa1,
0x93, 0xff, 0xb1, 0x9f, 0x92, 0xff, 0xc3, 0xb3, 0xa9, 0xff, 0xcf, 0xc8,
0xc3, 0xff, 0xde, 0xdf, 0xe1, 0xff, 0xb2, 0x89, 0x6b, 0xff, 0xc9, 0xa8,
0x8f, 0xff, 0xee, 0xea, 0xea, 0xff, 0xed, 0xe3, 0xe0, 0xff, 0xe3, 0xd0,
0xc7, 0xff, 0xe6, 0xd6, 0xcd, 0xff, 0xde, 0xcd, 0xc1, 0xff, 0xd2, 0xb7,
0xa4, 0xff, 0xcc, 0xae, 0x98, 0xff, 0xe6, 0xda, 0xd2, 0xff, 0xcd, 0xb2,
0x9e, 0xff, 0xd2, 0xb8, 0xa5, 0xff, 0xe3, 0xd5, 0xcc, 0xff, 0xc7, 0xa7,
0x8f, 0xff, 0xd3, 0xbb, 0xa9, 0xff, 0xdc, 0xca, 0xbe, 0xff, 0xe3, 0xd4,
0xca, 0xff, 0xda, 0xc6, 0xb8, 0xff, 0xe9, 0xde, 0xd7, 0xff, 0xe6, 0xda,
0xd4, 0xff, 0xed, 0xde, 0xd7, 0xff, 0xf1, 0xe4, 0xe0, 0xff, 0xfb, 0xfb,
0xff, 0xff, 0xc8, 0xa1, 0x7b, 0xff, 0xc6, 0xa2, 0x84, 0xff, 0xe9, 0xee,
0xf4, 0xff, 0xba, 0xad, 0xa4, 0xff, 0xd2, 0xca, 0xc5, 0xff, 0xcd, 0xc1,
0xb9, 0xff, 0xc2, 0xb8, 0xb2, 0xff, 0xd9, 0xd5, 0xd2, 0xff, 0xf3, 0xf7,
0xf9, 0xff, 0xac, 0x72, 0x47, 0xff, 0xe8, 0xdc, 0xd4, 0xff, 0xfb, 0xf9,
0xfc, 0xff, 0xf3, 0xe7, 0xe6, 0xff, 0xf0, 0xe3, 0xdf, 0xff, 0xeb, 0xde,
0xd6, 0xff, 0xeb, 0xe0, 0xd9, 0xff, 0xed, 0xe4, 0xdf, 0xff, 0xee, 0xe5,
0xe0, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xee, 0xe5, 0xdf, 0xff, 0xed, 0xe3,
0xdd, 0xff, 0xea, 0xdd, 0xd6, 0xff, 0xef, 0xe6, 0xe2, 0xff, 0xed, 0xe2,
0xdc, 0xff, 0xec, 0xe0, 0xd9, 0xff, 0xea, 0xde, 0xd7, 0xff, 0xeb, 0xe1,
0xda, 0xff, 0xe7, 0xdb, 0xd3, 0xff, 0xe7, 0xdb, 0xd4, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xee, 0xe0, 0xda, 0xff, 0xf6, 0xed, 0xee, 0xff, 0xec, 0xe6,
0xe0, 0xff, 0xb3, 0x82, 0x4c, 0xff, 0xb5, 0x9b, 0x87, 0xff, 0xec, 0xf1,
0xf6, 0xff, 0xe0, 0xe0, 0xe1, 0xff, 0xd1, 0xcd, 0xcb, 0xff, 0xf2, 0xf5,
0xf8, 0xff, 0xcd, 0xcf, 0xd1, 0xff, 0xaf, 0x7e, 0x5a, 0xff, 0xc6, 0x9f,
0x80, 0xff, 0xfb, 0xfd, 0xff, 0xff, 0xf2, 0xe7, 0xe4, 0xff, 0xf0, 0xe2,
0xde, 0xff, 0xeb, 0xdc, 0xd5, 0xff, 0xe9, 0xdc, 0xd4, 0xff, 0xe9, 0xdc,
0xd4, 0xff, 0xe9, 0xdb, 0xd2, 0xff, 0xe8, 0xdb, 0xd3, 0xff, 0xe9, 0xdc,
0xd4, 0xff, 0xe8, 0xdb, 0xd2, 0xff, 0xe8, 0xdb, 0xd3, 0xff, 0xe8, 0xdc,
0xd4, 0xff, 0xe6, 0xda, 0xd1, 0xff, 0xe8, 0xdb, 0xd3, 0xff, 0xe9, 0xdc,
0xd3, 0xff, 0xe9, 0xdc, 0xd4, 0xff, 0xe9, 0xdb, 0xd3, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xe8, 0xdc, 0xd5, 0xff, 0xe9, 0xdc, 0xd4, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xf0, 0xe2, 0xde, 0xff, 0xf8, 0xf2, 0xf4, 0xff, 0xe4, 0xdb,
0xd0, 0xff, 0xb4, 0x87, 0x53, 0xff, 0xbf, 0x9b, 0x7b, 0xff, 0xc9, 0xb6,
0xa6, 0xff, 0xc5, 0xb5, 0xa8, 0xff, 0xcf, 0xba, 0xa7, 0xff, 0xae, 0x78,
0x4b, 0xff, 0xc2, 0x95, 0x70, 0xff, 0xf7, 0xf7, 0xf9, 0xff, 0xf4, 0xec,
0xec, 0xff, 0xf2, 0xe6, 0xe3, 0xff, 0xee, 0xdf, 0xd9, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xe9, 0xdc, 0xd4, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xeb, 0xdd, 0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xe9, 0xdc, 0xd4, 0xff, 0xea, 0xdd, 0xd4, 0xff, 0xe9, 0xdc,
0xd5, 0xff, 0xe8, 0xdc, 0xd4, 0xff, 0xe9, 0xdc, 0xd4, 0xff, 0xed, 0xde,
0xd8, 0xff, 0xf2, 0xe4, 0xe1, 0xff, 0xf8, 0xf2, 0xf5, 0xff, 0xee, 0xea,
0xe8, 0xff, 0xcc, 0xb2, 0x98, 0xff, 0xb8, 0x8f, 0x67, 0xff, 0xb5, 0x89,
0x60, 0xff, 0xbe, 0x96, 0x71, 0xff, 0xdf, 0xcc, 0xba, 0xff, 0xfa, 0xf9,
0xfc, 0xff, 0xf4, 0xeb, 0xea, 0xff, 0xf2, 0xe5, 0xe3, 0xff, 0xee, 0xe0,
0xda, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xea, 0xde,
0xd6, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xea, 0xde,
0xd6, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xe9, 0xde, 0xd6, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xea, 0xdc,
0xd4, 0xff, 0xea, 0xdd, 0xd4, 0xff, 0xe9, 0xdc, 0xd5, 0xff, 0xe9, 0xdc,
0xd4, 0xff, 0xe8, 0xdd, 0xd5, 0xff, 0xe9, 0xdc, 0xd4, 0xff, 0xed, 0xdf,
0xd9, 0xff, 0xf1, 0xe3, 0xe0, 0xff, 0xf5, 0xea, 0xe9, 0xff, 0xfa, 0xf6,
0xf9, 0xff, 0xf8, 0xf5, 0xf8, 0xff, 0xf7, 0xf4, 0xf6, 0xff, 0xfb, 0xfa,
0xfe, 0xff, 0xf9, 0xf5, 0xf8, 0xff, 0xf3, 0xe8, 0xe6, 0xff, 0xf1, 0xe3,
0xdf, 0xff, 0xed, 0xde, 0xd8, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xe9, 0xdd,
0xd4, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xeb, 0xdd, 0xd5, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xeb, 0xdd, 0xd5, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xea, 0xdc, 0xd5, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xea, 0xdd,
0xd4, 0xff, 0xe9, 0xdc, 0xd5, 0xff, 0xea, 0xdd, 0xd4, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdc, 0xd4, 0xff, 0xeb, 0xdd,
0xd5, 0xff, 0xed, 0xde, 0xd8, 0xff, 0xef, 0xe0, 0xdc, 0xff, 0xf2, 0xe4,
0xe1, 0xff, 0xf2, 0xe5, 0xe2, 0xff, 0xf0, 0xe3, 0xde, 0xff, 0xee, 0xe1,
0xdc, 0xff, 0xed, 0xdf, 0xd9, 0xff, 0xeb, 0xdd, 0xd5, 0xff, 0xe9, 0xdc,
0xd5, 0xff, 0xe8, 0xdc, 0xd5, 0xff, 0xea, 0xdd, 0xd4, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xea, 0xde,
0xd6, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xea, 0xdd, 0xd4, 0xff, 0xe9, 0xdc,
0xd5, 0xff, 0xea, 0xdc, 0xd4, 0xff, 0xea, 0xdc, 0xd4, 0xff, 0xea, 0xdc,
0xd4, 0xff, 0xea, 0xdc, 0xd4, 0xff, 0xe9, 0xdc, 0xd4, 0xff, 0xe9, 0xdc,
0xd4, 0xff, 0xea, 0xdc, 0xd5, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xec, 0xde,
0xd6, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xe9, 0xdc, 0xd4, 0xff, 0xe9, 0xdc,
0xd4, 0xff, 0xe9, 0xdc, 0xd4, 0xff, 0xe9, 0xdc, 0xd5, 0xff, 0xe8, 0xdc,
0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xeb, 0xde,
0xd6, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xea, 0xde,
0xd6, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xeb, 0xde,
0xd6, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xeb, 0xdd,
0xd5, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xea, 0xdd, 0xd4, 0xff, 0xe9, 0xdc, 0xd5, 0xff, 0xea, 0xdd,
0xd4, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdc,
0xd4, 0xff, 0xe9, 0xdc, 0xd4, 0xff, 0xe9, 0xdc, 0xd4, 0xff, 0xe9, 0xdc,
0xd4, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xeb, 0xde,
0xd6, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xea, 0xde,
0xd6, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xeb, 0xde,
0xd6, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xe9, 0xdc, 0xd5, 0xff, 0xe9, 0xdd, 0xd4, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xea, 0xde,
0xd6, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xeb, 0xde,
0xd6, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xea, 0xde,
0xd6, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xea, 0xde,
0xd6, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xea, 0xde,
0xd6, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xeb, 0xdd, 0xd5, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xe8, 0xdd, 0xd6, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdc,
0xd5, 0xff, 0xea, 0xdd, 0xd4, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xea, 0xde,
0xd6, 0xff, 0xeb, 0xdd, 0xd5, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xeb, 0xde,
0xd6, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xeb, 0xde,
0xd6, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xeb, 0xde,
0xd6, 0xff, 0xeb, 0xdd, 0xd5, 0xff, 0xeb, 0xdd, 0xd5, 0xff, 0xeb, 0xdd,
0xd5, 0xff, 0xeb, 0xdd, 0xd5, 0xff, 0xeb, 0xde, 0xd5, 0xff, 0xeb, 0xde,
0xd5, 0xff, 0xeb, 0xde, 0xd5, 0xff, 0xeb, 0xde, 0xd5, 0xff, 0xeb, 0xde,
0xd5, 0xff, 0xeb, 0xde, 0xd5, 0xff, 0xeb, 0xde, 0xd5, 0xff, 0xeb, 0xde,
0xd5, 0xff, 0xeb, 0xde, 0xd5, 0xff, 0xeb, 0xde, 0xd5, 0xff, 0xeb, 0xde,
0xd6, 0xff, 0xeb, 0xde, 0xd6, 0xff, 0xe9, 0xde, 0xd6, 0xff, 0xe8, 0xdd,
0xd5, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xe9, 0xdc, 0xd5, 0xff, 0xea, 0xdc,
0xd4, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xea, 0xdd,
0xd6, 0xff, 0xea, 0xdd, 0xd6, 0xff, 0xea, 0xdd, 0xd6, 0xff, 0xea, 0xde,
0xd6, 0xff, 0xea, 0xdd, 0xd6, 0xff, 0xea, 0xdd, 0xd6, 0xff, 0xea, 0xde,
0xd6, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xea, 0xdd,
0xd6, 0xff, 0xea, 0xde, 0xd6, 0xff, 0xea, 0xdd, 0xd6, 0xff, 0xea, 0xdd,
0xd6, 0xff, 0xea, 0xdd, 0xd6, 0xff, 0xea, 0xdd, 0xd6, 0xff, 0xea, 0xdd,
0xd6, 0xff, 0xea, 0xdd, 0xd6, 0xff, 0xea, 0xdd, 0xd6, 0xff, 0xea, 0xdd,
0xd6, 0xff, 0xea, 0xdd, 0xd6, 0xff, 0xea, 0xdd, 0xd5, 0xff, 0xea, 0xdd,
0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdc,
0xd4, 0xff, 0xe8, 0xdc, 0xd5, 0xff, 0xe8, 0xdc, 0xd4, 0xff, 0xe8, 0xdc,
0xd4, 0xff, 0xe9, 0xdc, 0xd4, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd,
0xd5, 0xff, 0xe9, 0xdd, 0xd5, 0xff, 0xe9, 0xdd, 0xd4, 0xff, 0xe9, 0xdc,
0xd4, 0xff, 0xe9, 0xdc, 0xd4, 0xff, 0xe8, 0xdc, 0xd4, 0xff, 0xe8, 0xdb,
0xd4, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
unsigned int favicon_ico_len = 5686;
/**
* base page HTML template for wssshd
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef BASE_PAGE_H
#define BASE_PAGE_H
// base page HTML template
static const char *base_page_html =
"<!DOCTYPE html>\n"
"<html lang=\"en\">\n"
"<head>\n"
" <meta charset=\"UTF-8\">\n"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
" <title>{% block title %}WSSSHD control panel{% endblock %}</title>\n"
" <link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\">\n"
" <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n"
" <link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" rel=\"stylesheet\">\n"
" <link rel=\"stylesheet\" href=\"https://unpkg.com/xterm@5.3.0/css/xterm.css\">\n"
" <script src=\"https://unpkg.com/xterm@5.3.0/lib/xterm.js\"></script>\n"
" <script src=\"https://unpkg.com/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js\"></script>\n"
" <script>\n"
" // Ensure libraries are loaded\n"
" function checkLibraries() {\n"
" if (typeof Terminal === 'undefined') {\n"
" console.error('Terminal not loaded from CDN');\n"
" return false;\n"
" }\n"
" if (typeof FitAddon === 'undefined') {\n"
" console.error('FitAddon not loaded from CDN');\n"
" return false;\n"
" }\n"
" console.log('All xterm libraries loaded successfully');\n"
" return true;\n"
" }\n"
"\n"
" // Check immediately and after a delay\n"
" if (!checkLibraries()) {\n"
" setTimeout(checkLibraries, 1000);\n"
" }\n"
" </script>\n"
" <style>\n"
" .navbar-brand {\n"
" font-weight: bold;\n"
" }\n"
" .client-card {\n"
" transition: transform 0.2s;\n"
" }\n"
" .client-card:hover {\n"
" transform: translateY(-2px);\n"
" box-shadow: 0 4px 8px rgba(0,0,0,0.1);\n"
" }\n"
" .terminal-container {\n"
" background-color: #1e1e1e;\n"
" color: #f8f8f2;\n"
" font-family: 'Courier New', monospace;\n"
" border-radius: 8px;\n"
" height: calc(100vh - 200px);\n"
" min-height: 400px;\n"
" overflow: hidden;\n"
" position: relative;\n"
" }\n"
" .terminal-input {\n"
" background: transparent;\n"
" border: none;\n"
" color: #f8f8f2;\n"
" font-family: 'Courier New', monospace;\n"
" width: 100%;\n"
" outline: none;\n"
" }\n"
" .terminal-input:focus {\n"
" box-shadow: none;\n"
" }\n"
" </style>\n"
"</head>\n"
"<body>\n"
" <nav class=\"navbar navbar-expand-lg navbar-dark bg-primary\">\n"
" <div class=\"container\">\n"
" <a class=\"navbar-brand\" href=\"{{ url_for('index') }}\">\n"
" <i class=\"fas fa-terminal\"></i> WSSSHD control panel\n"
" </a>\n"
" <div class=\"navbar-nav ms-auto\">\n"
" {% if current_user.is_authenticated %}\n"
" <span class=\"navbar-text me-3\">\n"
" Welcome, {{ current_user.username }}!\n"
" </span>\n"
" <button class=\"btn btn-outline-warning btn-sm me-2\" data-bs-toggle=\"modal\" data-bs-target=\"#donationModal\">\n"
" <i class=\"fas fa-heart\"></i> Donate\n"
" </button>\n"
" <a class=\"nav-link\" href=\"{{ url_for('logout') }}\">\n"
" <i class=\"fas fa-sign-out-alt\"></i> Logout\n"
" </a>\n"
" {% endif %}\n"
" </div>\n"
" </div>\n"
" </nav>\n"
"\n"
" <div class=\"container mt-4\">\n"
" {% with messages = get_flashed_messages(with_categories=true) %}\n"
" {% if messages %}\n"
" {% for category, message in messages %}\n"
" <div class=\"alert alert-{{ 'danger' if category == 'error' else 'info' }} alert-dismissible fade show\" role=\"alert\">\n"
" {{ message }}\n"
" <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\"></button>\n"
" </div>\n"
" {% endfor %}\n"
" {% endif %}\n"
" {% endwith %}\n"
"\n"
" {% block content %}{% endblock %}\n"
" </div>\n"
"\n"
" <!-- Donation Modal -->\n"
" <div class=\"modal fade\" id=\"donationModal\" tabindex=\"-1\">\n"
" <div class=\"modal-dialog\">\n"
" <div class=\"modal-content\">\n"
" <div class=\"modal-header\">\n"
" <h5 class=\"modal-title\">\n"
" <i class=\"fas fa-heart text-danger\"></i> Support WebSocket SSH Development\n"
" </h5>\n"
" <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\"></button>\n"
" </div>\n"
" <div class=\"modal-body\">\n"
" <p class=\"text-muted\">Your support helps us continue developing and maintaining this open-source project!</p>\n"
"\n"
" <div class=\"row\">\n"
" <div class=\"col-md-4 text-center mb-3\">\n"
" <h6><i class=\"fab fa-paypal text-primary\"></i> PayPal</h6>\n"
" <a href=\"https://www.paypal.com/paypalme/nexlab\" target=\"_blank\" class=\"btn btn-primary btn-sm\">\n"
" <i class=\"fab fa-paypal\"></i> Donate via PayPal\n"
" </a>\n"
" <small class=\"d-block text-muted mt-1\">info@nexlab.net</small>\n"
" </div>\n"
"\n"
" <div class=\"col-md-4 text-center mb-3\">\n"
" <h6><i class=\"fab fa-bitcoin text-warning\"></i> Bitcoin</h6>\n"
" <div class=\"mb-2\">\n"
" <img src=\"https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=bitcoin:bc1q3zlkpu95amtcltsk85y0eacyzzk29v68tgc5hx\" alt=\"BTC QR Code\" class=\"img-fluid rounded\">\n"
" </div>\n"
" <div class=\"input-group input-group-sm\">\n"
" <input type=\"text\" class=\"form-control form-control-sm font-monospace\" value=\"bc1q3zlkpu95amtcltsk85y0eacyzzk29v68tgc5hx\" readonly style=\"font-size: 0.75rem;\">\n"
" <button class=\"btn btn-outline-secondary btn-sm\" type=\"button\" onclick=\"copyToClipboard('bc1q3zlkpu95amtcltsk85y0eacyzzk29v68tgc5hx')\">\n"
" <i class=\"fas fa-copy\"></i>\n"
" </button>\n"
" </div>\n"
" </div>\n"
"\n"
" <div class=\"col-md-4 text-center mb-3\">\n"
" <h6><i class=\"fab fa-ethereum text-secondary\"></i> Ethereum</h6>\n"
" <div class=\"mb-2\">\n"
" <img src=\"https://api.qrserver.com/v1/create-qr-code/?size=120x120&data=ethereum:0xdA6dAb526515b5cb556d20269207D43fcc760E51\" alt=\"ETH QR Code\" class=\"img-fluid rounded\">\n"
" </div>\n"
" <div class=\"input-group input-group-sm\">\n"
" <input type=\"text\" class=\"form-control form-control-sm font-monospace\" value=\"0xdA6dAb526515b5cb556d20269207D43fcc760E51\" readonly style=\"font-size: 0.75rem;\">\n"
" <button class=\"btn btn-outline-secondary btn-sm\" type=\"button\" onclick=\"copyToClipboard('0xdA6dAb526515b5cb556d20269207D43fcc760E51')\">\n"
" <i class=\"fas fa-copy\"></i>\n"
" </button>\n"
" </div>\n"
" </div>\n"
" </div>\n"
"\n"
" <hr>\n"
" <p class=\"text-center mb-0\">\n"
" <small class=\"text-muted\">\n"
" Thank you for your support! ❤️\n"
" </small>\n"
" </p>\n"
" </div>\n"
" </div>\n"
" </div>\n"
" </div>\n"
"\n"
" <script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js\"></script>\n"
" <script src=\"https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js\"></script>\n"
" <script>\n"
" function copyToClipboard(text) {\n"
" navigator.clipboard.writeText(text).then(function() {\n"
" // Show a temporary success message\n"
" const btn = event.target.closest('button');\n"
" const originalHtml = btn.innerHTML;\n"
" btn.innerHTML = '<i class=\"fas fa-check\"></i>';\n"
" btn.classList.remove('btn-outline-secondary');\n"
" btn.classList.add('btn-success');\n"
" setTimeout(() => {\n"
" btn.innerHTML = originalHtml;\n"
" btn.classList.remove('btn-success');\n"
" btn.classList.add('btn-outline-secondary');\n"
" }, 1000);\n"
" });\n"
" }\n"
" </script>\n"
" {% block scripts %}{% endblock %}\n"
"</body>\n"
"</html>\n";
#endif /* BASE_PAGE_H */
/**
* index page HTML template for wssshd
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef INDEX_PAGE_H
#define INDEX_PAGE_H
// index page HTML template
static const char *index_page_html =
"<!DOCTYPE html>\n"
"<html lang=\"en\">\n"
"<head>\n"
" <meta charset=\"UTF-8\">\n"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
" <title>Dashboard - WSSSHD control panel</title>\n"
" <link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\">\n"
" <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n"
" <link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" rel=\"stylesheet\">\n"
"</head>\n"
"<body>\n"
"<nav class=\"navbar navbar-expand-lg navbar-dark bg-primary\">\n"
"<div class=\"container\">\n"
"<a class=\"navbar-brand\" href=\"/\">\n"
"<i class=\"fas fa-terminal\"></i> WSSSHD control panel</a>\n"
"<div class=\"navbar-nav ms-auto\">\n"
"<span class=\"navbar-text me-3\">%s</span>\n"
"<a class=\"nav-link\" href=\"/logout\">Logout</a>\n"
"</div></div></nav>\n"
"%s\n"
"<div class=\"container mt-4\">\n"
"<div class=\"row\">\n"
" <div class=\"col-md-8\">\n"
" <div class=\"card\">\n"
" <div class=\"card-header\">\n"
" <h3 class=\"card-title mb-0\">\n"
" <i class=\"fas fa-server\"></i> Registered Clients\n"
" </h3>\n"
" </div>\n"
" <div class=\"card-body\">\n"
" <div id=\"client-list\">%s</div>\n"
" </div>\n"
" </div>\n"
" </div>\n"
"\n"
" <div class=\"col-md-4\">\n"
" <div class=\"card\">\n"
" <div class=\"card-header\">\n"
" <h3 class=\"card-title mb-0\">\n"
" <i class=\"fas fa-cogs\"></i> Quick Actions\n"
" </h3>\n"
" </div>\n"
" <div class=\"card-body\">\n"
" %s\n"
" <button class=\"btn btn-outline-secondary btn-sm w-100\" onclick=\"location.reload()\">\n"
" <i class=\"fas fa-sync\"></i> Refresh Status\n"
" </button>\n"
" </div>\n"
" </div>\n"
"\n"
" <div class=\"card mt-3\">\n"
" <div class=\"card-header\">\n"
" <h3 class=\"card-title mb-0\">\n"
" <i class=\"fas fa-info-circle\"></i> System Info\n"
" </h3>\n"
" </div>\n"
" <div class=\"card-body\">\n"
" <p class=\"mb-1\"><strong>WebSocket Port:</strong> <span id=\"websocket-port\">%d</span></p>\n"
" <p class=\"mb-1\"><strong>Domain:</strong> <span id=\"domain\">%s</span></p>\n"
" <p class=\"mb-0\"><strong>Connected Clients:</strong> <span id=\"client-count\">%d</span></p>\n"
" </div>\n"
" </div>\n"
" </div>\n"
"</div>\n"
"</div>\n"
"<script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js\"></script>\n"
"<script>\n"
"function showNotification(message, type = 'info') {\n"
" const notificationArea = document.getElementById('notification-area');\n"
" const notification = document.createElement('div');\n"
" notification.className = `alert alert-${type} alert-dismissible fade show`;\n"
" notification.innerHTML = `${message}<button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\"></button>`;\n"
" notificationArea.appendChild(notification);\n"
" setTimeout(() => {\n"
" if (notification.parentNode) {\n"
" notification.remove();\n"
" }\n"
" }, 5000);\n"
"}\n"
"\n"
"function updateClients() {\n"
" fetch('/api/clients')\n"
" .then(response => response.json())\n"
" .then(data => {\n"
" // Update client count\n"
" document.getElementById('client-count').textContent = data.count;\n"
"\n"
" // Generate HTML for client list\n"
" let clientListHtml = '';\n"
" if (data.count === 0) {\n"
" clientListHtml = '<div class=\"text-center py-5\"><i class=\"fas fa-server fa-4x text-muted mb-3\"></i><h4 class=\"text-muted\">No clients registered</h4><p class=\"text-muted\">Clients will appear here when they register.</p></div>';\n"
" } else {\n"
" for (const [clientId, clientData] of Object.entries(data.clients)) {\n"
" const statusIcon = clientData.status === 'connected' ? 'fa-desktop text-success' : 'fa-server text-warning';\n"
" const statusText = clientData.status === 'connected' ? 'Connected' : 'Registered';\n"
" const services = clientData.services;\n"
" let actionsHtml = '';\n"
" if (services.includes('ssh')) {\n"
" actionsHtml += `<a href=\"/terminal/${clientId}\" class=\"btn btn-primary btn-sm me-1\"><i class=\"fas fa-terminal\"></i> SSH</a>`;\n"
" }\n"
" if (services.includes('vnc')) {\n"
" actionsHtml += `<a href=\"/vnc/${clientId}\" class=\"btn btn-info btn-sm me-1\"><i class=\"fas fa-desktop\"></i> VNC</a>`;\n"
" }\n"
" clientListHtml += `\n"
" <div class=\"col-md-4 mb-3\">\n"
" <div class=\"card client-card h-100\">\n"
" <div class=\"card-body text-center\">\n"
" <i class=\"fas ${statusIcon} fa-3x mb-3\"></i>\n"
" <h5 class=\"card-title\">${clientId}</h5>\n"
" <p class=\"card-text text-muted\">${statusText}</p>\n"
" <p class=\"card-text small\">Services: ${services}</p>\n"
" <div class=\"d-flex justify-content-center\">${actionsHtml}</div>\n"
" </div>\n"
" </div>\n"
" </div>`;\n"
" }\n"
" }\n"
"\n"
" // Update client list only if changed\n"
" const clientListDiv = document.getElementById('client-list');\n"
" if (clientListDiv.innerHTML !== clientListHtml) {\n"
" clientListDiv.innerHTML = clientListHtml;\n"
" }\n"
" })\n"
" .catch(error => {\n"
" console.log('Error fetching client data:', error);\n"
" });\n"
"}\n"
"\n"
"// Update every 5 seconds\n"
"setInterval(updateClients, 5000);\n"
"\n"
"// Initial update after 1 second\n"
"setTimeout(updateClients, 1000);\n"
"</script>\n"
"</body>\n"
"</html>\n";
#endif /* INDEX_PAGE_H */
/**
* login page HTML template for wssshd
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef LOGIN_PAGE_H
#define LOGIN_PAGE_H
// login page HTML template
static const char *login_page_html =
"<!DOCTYPE html>\n"
"<html lang=\"en\">\n"
"<head>\n"
" <meta charset=\"UTF-8\">\n"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
" <title>Login - WSSSHD control panel</title>\n"
" <link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\">\n"
" <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n"
" <link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" rel=\"stylesheet\">\n"
"</head>\n"
"<body>\n"
"<nav class=\"navbar navbar-expand-lg navbar-dark bg-primary\">\n"
"<div class=\"container\">\n"
"<a class=\"navbar-brand\" href=\"/\">\n"
"<i class=\"fas fa-terminal\"></i> WSSSHD control panel</a>\n"
"</div></nav>\n"
"<div class=\"container mt-4\">\n"
"<div class=\"row justify-content-center\">\n"
" <div class=\"col-md-6\">\n"
" <div class=\"card\">\n"
" <div class=\"card-header\">\n"
" <h3 class=\"card-title mb-0\"><i class=\"fas fa-sign-in-alt\"></i> Login</h3>\n"
" </div>\n"
" <div class=\"card-body\">\n"
" <form method=\"post\">\n"
" <div class=\"mb-3\">\n"
" <label for=\"username\" class=\"form-label\">Username</label>\n"
" <input type=\"text\" class=\"form-control\" id=\"username\" name=\"username\" required>\n"
" </div>\n"
" <div class=\"mb-3\">\n"
" <label for=\"password\" class=\"form-label\">Password</label>\n"
" <input type=\"password\" class=\"form-control\" id=\"password\" name=\"password\" required>\n"
" </div>\n"
" <button type=\"submit\" class=\"btn btn-primary\">\n"
" <i class=\"fas fa-sign-in-alt\"></i> Login\n"
" </button>\n"
" </form>\n"
" <div class=\"mt-3\">\n"
" <small class=\"text-muted\">\n"
" Default credentials: admin / admin123\n"
" </small>\n"
" </div>\n"
" </div>\n"
" </div>\n"
" </div>\n"
"</div>\n"
"</div>\n"
"<script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js\"></script>\n"
"</body>\n"
"</html>\n";
#endif /* LOGIN_PAGE_H */
#ifndef NOVNC_BASE64_JS_PAGE_H
#define NOVNC_BASE64_JS_PAGE_H
extern const char *novnc_base64_js;
#endif /* NOVNC_BASE64_JS_PAGE_H */
#ifndef NOVNC_DECODERS_COPYRECT_JS_PAGE_H
#define NOVNC_DECODERS_COPYRECT_JS_PAGE_H
extern const char *novnc_decoders_copyrect_js;
#endif /* NOVNC_DECODERS_COPYRECT_JS_PAGE_H */
#ifndef NOVNC_DECODERS_HEXTILE_JS_PAGE_H
#define NOVNC_DECODERS_HEXTILE_JS_PAGE_H
extern const char *novnc_decoders_hextile_js;
#endif /* NOVNC_DECODERS_HEXTILE_JS_PAGE_H */
#ifndef NOVNC_DECODERS_JPEG_JS_PAGE_H
#define NOVNC_DECODERS_JPEG_JS_PAGE_H
extern const char *novnc_decoders_jpeg_js;
#endif /* NOVNC_DECODERS_JPEG_JS_PAGE_H */
#ifndef NOVNC_DECODERS_RAW_JS_PAGE_H
#define NOVNC_DECODERS_RAW_JS_PAGE_H
extern const char *novnc_decoders_raw_js;
#endif /* NOVNC_DECODERS_RAW_JS_PAGE_H */
#ifndef NOVNC_DECODERS_RRE_JS_PAGE_H
#define NOVNC_DECODERS_RRE_JS_PAGE_H
extern const char *novnc_decoders_rre_js;
#endif /* NOVNC_DECODERS_RRE_JS_PAGE_H */
#ifndef NOVNC_DECODERS_TIGHT_JS_PAGE_H
#define NOVNC_DECODERS_TIGHT_JS_PAGE_H
extern const char *novnc_decoders_tight_js;
#endif /* NOVNC_DECODERS_TIGHT_JS_PAGE_H */
#ifndef NOVNC_DECODERS_TIGHTPNG_JS_PAGE_H
#define NOVNC_DECODERS_TIGHTPNG_JS_PAGE_H
extern const char *novnc_decoders_tightpng_js;
#endif /* NOVNC_DECODERS_TIGHTPNG_JS_PAGE_H */
#ifndef NOVNC_DECODERS_ZRLE_JS_PAGE_H
#define NOVNC_DECODERS_ZRLE_JS_PAGE_H
extern const char *novnc_decoders_zrle_js;
#endif /* NOVNC_DECODERS_ZRLE_JS_PAGE_H */
#ifndef NOVNC_DEFLATOR_JS_PAGE_H
#define NOVNC_DEFLATOR_JS_PAGE_H
extern const char *novnc_deflator_js;
#endif /* NOVNC_DEFLATOR_JS_PAGE_H */
#ifndef NOVNC_DES_JS_PAGE_H
#define NOVNC_DES_JS_PAGE_H
extern const char *novnc_des_js;
#endif /* NOVNC_DES_JS_PAGE_H */
#ifndef NOVNC_DISPLAY_JS_PAGE_H
#define NOVNC_DISPLAY_JS_PAGE_H
extern const char *novnc_display_js;
#endif /* NOVNC_DISPLAY_JS_PAGE_H */
#ifndef NOVNC_ENCODINGS_JS_PAGE_H
#define NOVNC_ENCODINGS_JS_PAGE_H
extern const char *novnc_encodings_js;
#endif /* NOVNC_ENCODINGS_JS_PAGE_H */
#ifndef NOVNC_INFLATOR_JS_PAGE_H
#define NOVNC_INFLATOR_JS_PAGE_H
extern const char *novnc_inflator_js;
#endif /* NOVNC_INFLATOR_JS_PAGE_H */
#ifndef NOVNC_INPUT_DOMKEYTABLE_JS_PAGE_H
#define NOVNC_INPUT_DOMKEYTABLE_JS_PAGE_H
extern const char *novnc_input_domkeytable_js;
#endif /* NOVNC_INPUT_DOMKEYTABLE_JS_PAGE_H */
#ifndef NOVNC_INPUT_FIXEDKEYS_JS_PAGE_H
#define NOVNC_INPUT_FIXEDKEYS_JS_PAGE_H
extern const char *novnc_input_fixedkeys_js;
#endif /* NOVNC_INPUT_FIXEDKEYS_JS_PAGE_H */
#ifndef NOVNC_INPUT_GESTUREHANDLER_JS_PAGE_H
#define NOVNC_INPUT_GESTUREHANDLER_JS_PAGE_H
extern const char *novnc_input_gesturehandler_js;
#endif /* NOVNC_INPUT_GESTUREHANDLER_JS_PAGE_H */
#ifndef NOVNC_INPUT_KEYBOARD_JS_PAGE_H
#define NOVNC_INPUT_KEYBOARD_JS_PAGE_H
extern const char *novnc_input_keyboard_js;
#endif /* NOVNC_INPUT_KEYBOARD_JS_PAGE_H */
#ifndef NOVNC_INPUT_KEYSYM_JS_PAGE_H
#define NOVNC_INPUT_KEYSYM_JS_PAGE_H
extern const char *novnc_input_keysym_js;
#endif /* NOVNC_INPUT_KEYSYM_JS_PAGE_H */
#ifndef NOVNC_INPUT_KEYSYMDEF_JS_PAGE_H
#define NOVNC_INPUT_KEYSYMDEF_JS_PAGE_H
extern const char *novnc_input_keysymdef_js;
#endif /* NOVNC_INPUT_KEYSYMDEF_JS_PAGE_H */
#ifndef NOVNC_INPUT_UTIL_JS_PAGE_H
#define NOVNC_INPUT_UTIL_JS_PAGE_H
extern const char *novnc_input_util_js;
#endif /* NOVNC_INPUT_UTIL_JS_PAGE_H */
#ifndef NOVNC_INPUT_VKEYS_JS_PAGE_H
#define NOVNC_INPUT_VKEYS_JS_PAGE_H
extern const char *novnc_input_vkeys_js;
#endif /* NOVNC_INPUT_VKEYS_JS_PAGE_H */
#ifndef NOVNC_INPUT_XTSCANCODES_JS_PAGE_H
#define NOVNC_INPUT_XTSCANCODES_JS_PAGE_H
extern const char *novnc_input_xtscancodes_js;
#endif /* NOVNC_INPUT_XTSCANCODES_JS_PAGE_H */
#ifndef NOVNC_RA2_JS_PAGE_H
#define NOVNC_RA2_JS_PAGE_H
extern const char *novnc_ra2_js;
#endif /* NOVNC_RA2_JS_PAGE_H */
#ifndef NOVNC_RFB_JS_PAGE_H
#define NOVNC_RFB_JS_PAGE_H
extern const char *novnc_rfb_js;
#endif /* NOVNC_RFB_JS_PAGE_H */
#ifndef NOVNC_UTIL_BROWSER_JS_PAGE_H
#define NOVNC_UTIL_BROWSER_JS_PAGE_H
extern const char *novnc_util_browser_js;
#endif /* NOVNC_UTIL_BROWSER_JS_PAGE_H */
#ifndef NOVNC_UTIL_CURSOR_JS_PAGE_H
#define NOVNC_UTIL_CURSOR_JS_PAGE_H
extern const char *novnc_util_cursor_js;
#endif /* NOVNC_UTIL_CURSOR_JS_PAGE_H */
#ifndef NOVNC_UTIL_ELEMENT_JS_PAGE_H
#define NOVNC_UTIL_ELEMENT_JS_PAGE_H
extern const char *novnc_util_element_js;
#endif /* NOVNC_UTIL_ELEMENT_JS_PAGE_H */
#ifndef NOVNC_UTIL_EVENTS_JS_PAGE_H
#define NOVNC_UTIL_EVENTS_JS_PAGE_H
extern const char *novnc_util_events_js;
#endif /* NOVNC_UTIL_EVENTS_JS_PAGE_H */
#ifndef NOVNC_UTIL_EVENTTARGET_JS_PAGE_H
#define NOVNC_UTIL_EVENTTARGET_JS_PAGE_H
extern const char *novnc_util_eventtarget_js;
#endif /* NOVNC_UTIL_EVENTTARGET_JS_PAGE_H */
#ifndef NOVNC_UTIL_INT_JS_PAGE_H
#define NOVNC_UTIL_INT_JS_PAGE_H
extern const char *novnc_util_int_js;
#endif /* NOVNC_UTIL_INT_JS_PAGE_H */
#ifndef NOVNC_UTIL_LOGGING_JS_PAGE_H
#define NOVNC_UTIL_LOGGING_JS_PAGE_H
extern const char *novnc_util_logging_js;
#endif /* NOVNC_UTIL_LOGGING_JS_PAGE_H */
#ifndef NOVNC_UTIL_MD5_JS_PAGE_H
#define NOVNC_UTIL_MD5_JS_PAGE_H
extern const char *novnc_util_md5_js;
#endif /* NOVNC_UTIL_MD5_JS_PAGE_H */
#ifndef NOVNC_UTIL_STRINGS_JS_PAGE_H
#define NOVNC_UTIL_STRINGS_JS_PAGE_H
extern const char *novnc_util_strings_js;
#endif /* NOVNC_UTIL_STRINGS_JS_PAGE_H */
#ifndef NOVNC_VENDOR_PAKO_LIB_ZLIB_ADLER32_JS_PAGE_H
#define NOVNC_VENDOR_PAKO_LIB_ZLIB_ADLER32_JS_PAGE_H
extern const char *novnc_vendor_pako_lib_zlib_adler32_js;
#endif /* NOVNC_VENDOR_PAKO_LIB_ZLIB_ADLER32_JS_PAGE_H */
#ifndef NOVNC_VENDOR_PAKO_LIB_ZLIB_CONSTANTS_JS_PAGE_H
#define NOVNC_VENDOR_PAKO_LIB_ZLIB_CONSTANTS_JS_PAGE_H
extern const char *novnc_vendor_pako_lib_zlib_constants_js;
#endif /* NOVNC_VENDOR_PAKO_LIB_ZLIB_CONSTANTS_JS_PAGE_H */
#ifndef NOVNC_VENDOR_PAKO_LIB_ZLIB_CRC32_JS_PAGE_H
#define NOVNC_VENDOR_PAKO_LIB_ZLIB_CRC32_JS_PAGE_H
extern const char *novnc_vendor_pako_lib_zlib_crc32_js;
#endif /* NOVNC_VENDOR_PAKO_LIB_ZLIB_CRC32_JS_PAGE_H */
#ifndef NOVNC_VENDOR_PAKO_LIB_ZLIB_DEFLATE_JS_PAGE_H
#define NOVNC_VENDOR_PAKO_LIB_ZLIB_DEFLATE_JS_PAGE_H
extern const char *novnc_vendor_pako_lib_zlib_deflate_js;
#endif /* NOVNC_VENDOR_PAKO_LIB_ZLIB_DEFLATE_JS_PAGE_H */
#ifndef NOVNC_VENDOR_PAKO_LIB_ZLIB_INFFAST_JS_PAGE_H
#define NOVNC_VENDOR_PAKO_LIB_ZLIB_INFFAST_JS_PAGE_H
extern const char *novnc_vendor_pako_lib_zlib_inffast_js;
#endif /* NOVNC_VENDOR_PAKO_LIB_ZLIB_INFFAST_JS_PAGE_H */
#ifndef NOVNC_VENDOR_PAKO_LIB_ZLIB_INFLATE_JS_PAGE_H
#define NOVNC_VENDOR_PAKO_LIB_ZLIB_INFLATE_JS_PAGE_H
extern const char *novnc_vendor_pako_lib_zlib_inflate_js;
#endif /* NOVNC_VENDOR_PAKO_LIB_ZLIB_INFLATE_JS_PAGE_H */
#ifndef NOVNC_VENDOR_PAKO_LIB_ZLIB_INFTREES_JS_PAGE_H
#define NOVNC_VENDOR_PAKO_LIB_ZLIB_INFTREES_JS_PAGE_H
extern const char *novnc_vendor_pako_lib_zlib_inftrees_js;
#endif /* NOVNC_VENDOR_PAKO_LIB_ZLIB_INFTREES_JS_PAGE_H */
#ifndef NOVNC_VENDOR_PAKO_LIB_ZLIB_MESSAGES_JS_PAGE_H
#define NOVNC_VENDOR_PAKO_LIB_ZLIB_MESSAGES_JS_PAGE_H
extern const char *novnc_vendor_pako_lib_zlib_messages_js;
#endif /* NOVNC_VENDOR_PAKO_LIB_ZLIB_MESSAGES_JS_PAGE_H */
#ifndef NOVNC_VENDOR_PAKO_LIB_ZLIB_TREES_JS_PAGE_H
#define NOVNC_VENDOR_PAKO_LIB_ZLIB_TREES_JS_PAGE_H
extern const char *novnc_vendor_pako_lib_zlib_trees_js;
#endif /* NOVNC_VENDOR_PAKO_LIB_ZLIB_TREES_JS_PAGE_H */
#ifndef NOVNC_VENDOR_PAKO_LIB_ZLIB_ZSTREAM_JS_PAGE_H
#define NOVNC_VENDOR_PAKO_LIB_ZLIB_ZSTREAM_JS_PAGE_H
extern const char *novnc_vendor_pako_lib_zlib_zstream_js;
#endif /* NOVNC_VENDOR_PAKO_LIB_ZLIB_ZSTREAM_JS_PAGE_H */
#ifndef NOVNC_WEBSOCK_JS_PAGE_H
#define NOVNC_WEBSOCK_JS_PAGE_H
extern const char *novnc_websock_js;
#endif /* NOVNC_WEBSOCK_JS_PAGE_H */
/**
* terminal page HTML template for wssshd
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef TERMINAL_PAGE_H
#define TERMINAL_PAGE_H
// terminal page HTML template
static const char *terminal_page_html =
"<!DOCTYPE html>\n"
"<html lang=\"en\">\n"
"<head>\n"
" <meta charset=\"UTF-8\">\n"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
" <title>Terminal - %s</title>\n"
" <link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\">\n"
" <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n"
" <link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" rel=\"stylesheet\">\n"
" <link rel=\"stylesheet\" href=\"https://unpkg.com/xterm@5.3.0/css/xterm.css\">\n"
" <script src=\"/xterm.js\"></script>\n"
" <script src=\"/xterm-addon-fit.js\"></script>\n"
" <script>\n"
" // Ensure libraries are loaded\n"
" function checkLibraries() {\n"
" if (typeof Terminal === 'undefined') {\n"
" console.error('Terminal not loaded from CDN');\n"
" return false;\n"
" }\n"
" if (typeof FitAddon === 'undefined') {\n"
" console.error('FitAddon not loaded from CDN');\n"
" return false;\n"
" }\n"
" console.log('All xterm libraries loaded successfully');\n"
" return true;\n"
" }\n"
"\n"
" // Check immediately and after a delay\n"
" if (!checkLibraries()) {\n"
" setTimeout(checkLibraries, 1000);\n"
" }\n"
" </script>\n"
" <style>\n"
" .navbar-brand {\n"
" font-weight: bold;\n"
" }\n"
" .client-card {\n"
" transition: transform 0.2s;\n"
" }\n"
" .client-card:hover {\n"
" transform: translateY(-2px);\n"
" box-shadow: 0 4px 8px rgba(0,0,0,0.1);\n"
" }\n"
" .terminal-container {\n"
" background-color: #1e1e1e;\n"
" color: #f8f8f2;\n"
" font-family: 'Courier New', monospace;\n"
" border-radius: 8px;\n"
" height: calc(100vh - 200px);\n"
" min-height: 400px;\n"
" overflow: hidden;\n"
" position: relative;\n"
" }\n"
" .terminal-input {\n"
" background: transparent;\n"
" border: none;\n"
" color: #f8f8f2;\n"
" font-family: 'Courier New', monospace;\n"
" width: 100%;\n"
" outline: none;\n"
" }\n"
" .terminal-input:focus {\n"
" box-shadow: none;\n"
" }\n"
" /* GNOME-like window decorations */\n"
" .terminal-window {\n"
" border: 1px solid #ccc;\n"
" border-radius: 8px;\n"
" overflow: hidden;\n"
" box-shadow: 0 2px 10px rgba(0,0,0,0.2);\n"
" }\n"
" .window-titlebar {\n"
" background: linear-gradient(to bottom, #f0f0f0, #e0e0e0);\n"
" border-bottom: 1px solid #ccc;\n"
" padding: 4px 8px;\n"
" display: flex;\n"
" justify-content: space-between;\n"
" align-items: center;\n"
" height: 32px;\n"
" }\n"
" .window-title {\n"
" font-weight: bold;\n"
" color: #333;\n"
" font-size: 14px;\n"
" }\n"
" .window-controls {\n"
" display: flex;\n"
" align-items: center;\n"
" gap: 4px;\n"
" }\n"
" .window-btn {\n"
" width: 24px;\n"
" height: 24px;\n"
" border: none;\n"
" border-radius: 50%;\n"
" display: flex;\n"
" align-items: center;\n"
" justify-content: center;\n"
" font-size: 14px;\n"
" font-weight: bold;\n"
" cursor: pointer;\n"
" transition: all 0.2s;\n"
" }\n"
" .window-btn:hover {\n"
" transform: scale(1.1);\n"
" }\n"
" .close-btn {\n"
" background: #ff605c;\n"
" color: white;\n"
" }\n"
" .close-btn:hover {\n"
" background: #ff403c;\n"
" }\n"
" .maximize-btn {\n"
" background: #28ca42;\n"
" color: white;\n"
" }\n"
" .maximize-btn:hover {\n"
" background: #24b83a;\n"
" }\n"
" .minimize-btn {\n"
" background: #ffbd44;\n"
" color: white;\n"
" display: none; /* Hidden by default, shown in fullscreen */\n"
" }\n"
" .minimize-btn:hover {\n"
" background: #ffad2c;\n"
" }\n"
" .window-content {\n"
" background: #1e1e1e;\n"
" padding: 8px;\n"
" }\n"
" /* Fullscreen styles */\n"
" .terminal-window.fullscreen {\n"
" position: fixed;\n"
" top: 0;\n"
" left: 0;\n"
" width: 100vw;\n"
" height: 100vh;\n"
" border: none;\n"
" border-radius: 0;\n"
" z-index: 9999;\n"
" }\n"
" .terminal-window.fullscreen .terminal-container {\n"
" height: calc(100vh - 40px) !important;\n"
" min-height: calc(100vh - 40px) !important;\n"
" }\n"
" .terminal-window.fullscreen .minimize-btn {\n"
" display: flex;\n"
" }\n"
" </style>\n"
"</head>\n"
"<body>\n"
"<nav class=\"navbar navbar-expand-lg navbar-dark bg-primary\">\n"
"<div class=\"container\">\n"
"<a class=\"navbar-brand\" href=\"/\">\n"
"<i class=\"fas fa-terminal\"></i> WSSSHD control panel</a>\n"
"<div class=\"navbar-nav ms-auto\">\n"
"<span class=\"navbar-text me-3\">SSH Terminal - %s</span>\n"
"<a class=\"nav-link\" href=\"/logout\">Logout</a>\n"
"</div></div></nav>\n"
"<div id=\"notification-area\" class=\"position-fixed top-0 end-0 p-3\" style=\"z-index: 1050;\"></div>\n"
"<div class=\"container mt-4\">\n"
"<div class=\"row\">\n"
" <div class=\"col-12\">\n"
" <div class=\"terminal-window\">\n"
" <div class=\"window-titlebar\">\n"
" <div class=\"window-title\">\n"
" <i class=\"fas fa-terminal\"></i> SSH Terminal - %s\n"
" </div>\n"
" <div class=\"window-controls\">\n"
" <input type=\"text\" id=\"sshUsername\" class=\"form-control form-control-sm d-inline-block w-auto me-2\" placeholder=\"Username\" value=\"root\" style=\"height: 24px; font-size: 12px; border: none; background: rgba(255,255,255,0.1); color: white;\">\n"
" <button id=\"connectBtn\" class=\"btn btn-success btn-sm me-1\" style=\"height: 24px; font-size: 12px; padding: 0 8px;\">\n"
" <i class=\"fas fa-play\"></i> Connect\n"
" </button>\n"
" <button id=\"disconnectBtn\" class=\"btn btn-danger btn-sm me-1\" disabled style=\"height: 24px; font-size: 12px; padding: 0 8px;\">\n"
" <i class=\"fas fa-stop\"></i> Disconnect\n"
" </button>\n"
" <button class=\"window-btn minimize-btn\" title=\"Exit Fullscreen\">_</button>\n"
" <button class=\"window-btn maximize-btn\" title=\"Fullscreen\"><span class=\"maximize-icon\">□</span></button>\n"
" <button class=\"window-btn close-btn\" title=\"Disconnect\">×</button>\n"
" </div>\n"
" </div>\n"
" <div class=\"window-content\">\n"
" <div id=\"terminal\" class=\"terminal-container w-100\"></div>\n"
" </div>\n"
" </div>\n"
" </div>\n"
"</div>\n"
"</div>\n"
"\n"
"<script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js\"></script>\n"
"<script>\n"
"console.log('Terminal script starting...');\n"
"console.log('xterm available:', typeof Terminal);\n"
"console.log('xterm-fit available:', typeof FitAddon);\n"
"\n"
"let term = null;\n"
"let fitAddon = null;\n"
"let connected = false;\n"
"let requestId = null;\n"
"let websocket = null;\n"
"\n"
"console.log('Terminal page loaded, adding event listeners');\n"
"document.getElementById('connectBtn').addEventListener('click', connect);\n"
"document.getElementById('disconnectBtn').addEventListener('click', disconnect);\n"
"\n"
"// Window control buttons\n"
"document.querySelector('.close-btn').addEventListener('click', disconnect);\n"
"document.querySelector('.maximize-btn').addEventListener('click', toggleFullscreen);\n"
"document.querySelector('.minimize-btn').addEventListener('click', toggleFullscreen);\n"
"console.log('Event listeners added');\n"
"\n"
"// Fullscreen functionality\n"
"function toggleFullscreen() {\n"
" const terminalWindow = document.querySelector('.terminal-window');\n"
" const maximizeBtn = document.querySelector('.maximize-btn');\n"
" const maximizeIcon = maximizeBtn.querySelector('.maximize-icon');\n"
"\n"
" if (terminalWindow.classList.contains('fullscreen')) {\n"
" // Exit fullscreen\n"
" terminalWindow.classList.remove('fullscreen');\n"
" maximizeIcon.textContent = '□';\n"
" maximizeBtn.title = 'Fullscreen';\n"
" } else {\n"
" // Enter fullscreen\n"
" terminalWindow.classList.add('fullscreen');\n"
" maximizeIcon.textContent = '▭'; // Restore icon\n"
" maximizeBtn.title = 'Exit Fullscreen';\n"
" }\n"
"\n"
" // Resize terminal after fullscreen change\n"
" setTimeout(() => {\n"
" if (window.fitTerminal) {\n"
" window.fitTerminal();\n"
" }\n"
" // Update backend terminal size if connected\n"
" if (connected && requestId && fitAddon) {\n"
" const newDimensions = fitAddon.proposeDimensions();\n"
" const newCols = newDimensions.cols || 80;\n"
" const newRows = newDimensions.rows || 24;\n"
"\n"
" fetch('/terminal/%s/xterm/resize', {\n"
" method: 'POST',\n"
" headers: {\n"
" 'Content-Type': 'application/x-www-form-urlencoded',\n"
" },\n"
" body: 'request_id=' + encodeURIComponent(requestId) +\n"
" '&cols=' + encodeURIComponent(newCols) +\n"
" '&rows=' + encodeURIComponent(newRows)\n"
" }).catch(error => {\n"
" console.error('Resize error during fullscreen toggle:', error);\n"
" });\n"
" }\n"
" }, 100);\n"
"}\n"
"\n"
"\n"
"function connect() {\n"
" console.log('Connect button clicked');\n"
" console.log('Connect function called');\n"
" if (typeof Terminal === 'undefined') {\n"
" alert('xterm.js library failed to load. Please check your internet connection or refresh the page.');\n"
" return;\n"
" }\n"
" const username = document.getElementById('sshUsername').value;\n"
" console.log('Username value:', username);\n"
" if (!username) {\n"
" alert('Please enter a username');\n"
" return;\n"
" }\n"
" console.log('Username validation passed');\n"
"\n"
" // Initialize xterm with proper configuration\n"
" if (!term) {\n"
" term = new Terminal({\n"
" cursorBlink: true,\n"
" cursorStyle: 'block',\n"
" fontSize: 14,\n"
" fontFamily: 'Monaco, Menlo, \"Ubuntu Mono\", monospace',\n"
" theme: {\n"
" background: '#1e1e1e',\n"
" foreground: '#f8f8f2',\n"
" cursor: '#f8f8f2',\n"
" cursorAccent: '#1e1e1e',\n"
" selection: 'rgba(248, 248, 242, 0.3)'\n"
" },\n"
" allowTransparency: true,\n"
" scrollback: 1000,\n"
" tabStopWidth: 4,\n"
" convertEol: true,\n"
" disableStdin: false,\n"
" cursorWidth: 2,\n"
" bellStyle: 'none',\n"
" rightClickSelectsWord: true,\n"
" fastScrollModifier: 'alt',\n"
" fastScrollSensitivity: 5,\n"
" screenReaderMode: false,\n"
" macOptionIsMeta: false,\n"
" macOptionClickForcesSelection: false,\n"
" minimumContrastRatio: 1\n"
" });\n"
" term.open(document.getElementById('terminal'));\n"
"\n"
" // Load fit addon\n"
" try {\n"
" if (typeof FitAddon !== 'undefined') {\n"
" fitAddon = new FitAddon.FitAddon();\n"
" term.loadAddon(fitAddon);\n"
" console.log('FitAddon loaded successfully');\n"
" } else {\n"
" console.error('FitAddon is not available');\n"
" throw new Error('FitAddon not loaded');\n"
" }\n"
" } catch (e) {\n"
" console.error('Failed to load FitAddon:', e);\n"
" term.write('Warning: Terminal auto-resizing not available\\r\\n');\n"
" // Continue without fit addon - terminal will still work\n"
" }\n"
"\n"
" // Initial fit after a short delay to ensure DOM is ready\n"
" setTimeout(() => {\n"
" window.fitTerminal();\n"
" // Calculate dimensions after initial fit\n"
" let initialDimensions = { cols: 80, rows: 24 };\n"
" if (fitAddon) {\n"
" initialDimensions = fitAddon.proposeDimensions();\n"
" }\n"
" term._initialCols = initialDimensions.cols || 80;\n"
" term._initialRows = initialDimensions.rows || 24;\n"
" }, 100);\n"
"\n"
" // Fit on window resize and update backend terminal size\n"
" window.addEventListener('resize', () => {\n"
" window.fitTerminal();\n"
" // Update terminal size on backend if connected\n"
" if (connected && requestId && fitAddon) {\n"
" const newDimensions = fitAddon.proposeDimensions();\n"
" const newCols = newDimensions.cols || 80;\n"
" const newRows = newDimensions.rows || 24;\n"
"\n"
" fetch('/terminal/%s/xterm/resize', {\n"
" method: 'POST',\n"
" headers: {\n"
" 'Content-Type': 'application/x-www-form-urlencoded',\n"
" },\n"
" body: 'request_id=' + encodeURIComponent(requestId) +\n"
" '&cols=' + encodeURIComponent(newCols) +\n"
" '&rows=' + encodeURIComponent(newRows)\n"
" }).catch(error => {\n"
" console.error('Resize error:', error);\n"
" });\n"
" }\n"
" });\n"
"\n"
" term.focus();\n"
" }\n"
"\n"
" // Define fitTerminal function globally for fullscreen handling\n"
" window.fitTerminal = function() {\n"
" if (fitAddon) {\n"
" fitAddon.fit();\n"
" }\n"
" };\n"
"\n"
" term.write('Connecting to ' + username + '@%s...\\r\\n');\n"
"\n"
" connected = true;\n"
" document.getElementById('connectBtn').disabled = true;\n"
" document.getElementById('disconnectBtn').disabled = false;\n"
" document.getElementById('sshUsername').disabled = true;\n"
"\n"
" // Use calculated dimensions (either from initial fit or current)\n"
" let cols = term._initialCols || 80;\n"
" let rows = term._initialRows || 24;\n"
" if (fitAddon) {\n"
" const dimensions = fitAddon.proposeDimensions();\n"
" cols = dimensions.cols || cols;\n"
" rows = dimensions.rows || rows;\n"
" }\n"
"\n"
" // Establish WebSocket connection first\n"
" const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\n"
" const wsUrl = wsProtocol + '//' + window.location.host + '/terminal/%s/ws';\n"
" console.log('Connecting WebSocket to:', wsUrl);\n"
"\n"
" websocket = new WebSocket(wsUrl);\n"
" websocket.binaryType = 'arraybuffer';\n"
"\n"
" websocket.onopen = function(event) {\n"
" console.log('WebSocket connected');\n"
" term.write('WebSocket connected, establishing terminal...\\r\\n');\n"
"\n"
" // Now send connect request with terminal dimensions\n"
" const connectUrl = '/terminal/%s/xterm/connect';\n"
" console.log('Sending connect request to:', connectUrl);\n"
"\n"
" fetch(connectUrl, {\n"
" method: 'POST',\n"
" headers: {\n"
" 'Content-Type': 'application/x-www-form-urlencoded',\n"
" },\n"
" body: 'username=' + encodeURIComponent(username) +\n"
" '&cols=' + encodeURIComponent(cols) +\n"
" '&rows=' + encodeURIComponent(rows),\n"
" credentials: 'same-origin'\n"
" })\n"
" .then(response => {\n"
" console.log('Connect response status:', response.status);\n"
" if (!response.ok) {\n"
" throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n"
" }\n"
" return response.json();\n"
" })\n"
" .then(data => {\n"
" console.log('Connect response data:', data);\n"
" if (data.request_id) {\n"
" requestId = data.request_id;\n"
" if (data.command) {\n"
" console.log('Launching command:', data.command);\n"
" }\n"
" term.write('Terminal connected successfully!\\r\\n');\n"
"\n"
" // Send resize with current dimensions to ensure correct sizing\n"
" if (fitAddon) {\n"
" const dimensions = fitAddon.proposeDimensions();\n"
" const newCols = dimensions.cols || 80;\n"
" const newRows = dimensions.rows || 24;\n"
" fetch('/terminal/%s/xterm/resize', {\n"
" method: 'POST',\n"
" headers: {\n"
" 'Content-Type': 'application/x-www-form-urlencoded',\n"
" },\n"
" body: 'request_id=' + encodeURIComponent(requestId) +\n"
" '&cols=' + encodeURIComponent(newCols) +\n"
" '&rows=' + encodeURIComponent(newRows)\n"
" }).catch(error => {\n"
" console.error('Initial resize error:', error);\n"
" });\n"
" }\n"
" } else {\n"
" term.write('Error: ' + (data.error || 'Unknown error') + '\\r\\n');\n"
" disconnect();\n"
" }\n"
" })\n"
" .catch(error => {\n"
" console.error('Terminal connection failed:', error);\n"
" term.write('Terminal connection failed: ' + error.message + '\\r\\n');\n"
" disconnect();\n"
" });\n"
" };\n"
"\n"
" websocket.onmessage = function(event) {\n"
" console.log('WebSocket message received:', event.data);\n"
" if (typeof event.data === 'string') {\n"
" // JSON message (like session ended)\n"
" try {\n"
" const data = JSON.parse(event.data);\n"
" if (data.ended) {\n"
" console.log('Session ended');\n"
" disconnect();\n"
" }\n"
" } catch (e) {\n"
" console.error('Failed to parse JSON message:', e);\n"
" }\n"
" } else {\n"
" // Binary data (terminal output)\n"
" term.write(new Uint8Array(event.data));\n"
" }\n"
" };\n"
"\n"
" websocket.onerror = function(error) {\n"
" console.error('WebSocket error:', error);\n"
" term.write('WebSocket error occurred\\r\\n');\n"
" disconnect();\n"
" };\n"
"\n"
" websocket.onclose = function(event) {\n"
" console.log('WebSocket closed:', event.code, event.reason);\n"
" term.write('Connection closed\\r\\n');\n"
" disconnect();\n"
" };\n"
"\n"
" // Handle input - send all keystrokes to server via WebSocket\n"
" term.onKey(e => {\n"
" if (!connected || !websocket || websocket.readyState !== WebSocket.OPEN) return;\n"
"\n"
" let data = e.key;\n"
" console.log('Sending input data via WebSocket:', data);\n"
"\n"
" // Send input data via WebSocket\n"
" websocket.send(data);\n"
"\n"
" // Prevent local display of input\n"
" e.domEvent.preventDefault();\n"
" });\n"
"}\n"
"\n"
"function disconnect() {\n"
" connected = false;\n"
" document.getElementById('connectBtn').disabled = false;\n"
" document.getElementById('disconnectBtn').disabled = true;\n"
" document.getElementById('sshUsername').disabled = false;\n"
"\n"
" if (websocket && websocket.readyState === WebSocket.OPEN) {\n"
" websocket.close();\n"
" }\n"
" websocket = null;\n"
"\n"
" if (requestId) {\n"
" fetch('/terminal/%s/xterm/disconnect', {\n"
" method: 'POST',\n"
" headers: {\n"
" 'Content-Type': 'application/x-www-form-urlencoded',\n"
" },\n"
" body: 'request_id=' + encodeURIComponent(requestId)\n"
" });\n"
" requestId = null;\n"
" }\n"
"\n"
" if (term) {\n"
" term.write('\\r\\nDisconnected\\r\\n');\n"
" setTimeout(() => {\n"
" location.reload();\n"
" }, 3000);\n"
" }\n"
"}\n"
"\n"
"\n"
"// Focus on terminal when connected\n"
"document.addEventListener('keydown', function(e) {\n"
" if (connected && term) {\n"
" term.focus();\n"
" }\n"
"});\n"
"</script>\n"
"</body>\n"
"</html>\n"
;
#endif /* TERMINAL_PAGE_H */
/**
* users page HTML template for wssshd
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef USERS_PAGE_H
#define USERS_PAGE_H
// users page HTML template
static const char *users_page_html =
"<!DOCTYPE html>\n"
"<html lang=\"en\">\n"
"<head>\n"
" <meta charset=\"UTF-8\">\n"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
" <title>Users - WSSSHD control panel</title>\n"
" <link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\">\n"
" <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n"
" <link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" rel=\"stylesheet\">\n"
"</head>\n"
"<body>\n"
"<nav class=\"navbar navbar-expand-lg navbar-dark bg-primary\">\n"
"<div class=\"container\">\n"
"<a class=\"navbar-brand\" href=\"/\">\n"
"<i class=\"fas fa-terminal\"></i> WSSSHD control panel</a>\n"
"<div class=\"navbar-nav ms-auto\">\n"
"<span class=\"navbar-text me-3\">%s</span>\n"
"<a class=\"nav-link\" href=\"/logout\">Logout</a>\n"
"</div></div></nav>\n"
"<div class=\"container mt-4\">\n"
" <div class=\"card\">\n"
" <div class=\"card-header d-flex justify-content-between align-items-center\">\n"
" <h3 class=\"card-title mb-0\">\n"
" <i class=\"fas fa-users\"></i> User Management\n"
" </h3>\n"
" <div>\n"
" <a href=\"/\" class=\"btn btn-outline-secondary btn-sm me-2\">\n"
" <i class=\"fas fa-home\"></i> Back to Home\n"
" </a>\n"
" <button class=\"btn btn-primary btn-sm\" data-bs-toggle=\"modal\" data-bs-target=\"#addUserModal\">\n"
" <i class=\"fas fa-plus\"></i> Add User\n"
" </button>\n"
" </div>\n"
" </div>\n"
" <div class=\"card-body\">\n"
" <div class=\"table-responsive\">\n"
" <table class=\"table table-striped\">\n"
" <thead>\n"
" <tr>\n"
" <th>Username</th>\n"
" <th>Role</th>\n"
" <th>Actions</th>\n"
" </tr>\n"
" </thead>\n"
" <tbody>\n"
" %s\n"
" </tbody>\n"
" </table>\n"
" </div>\n"
" </div>\n"
" </div>\n"
"</div>\n"
"\n"
"<!-- Add User Modal -->\n"
"<div class=\"modal fade\" id=\"addUserModal\" tabindex=\"-1\">\n"
" <div class=\"modal-dialog\">\n"
" <div class=\"modal-content\">\n"
" <div class=\"modal-header\">\n"
" <h5 class=\"modal-title\">Add New User</h5>\n"
" <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\"></button>\n"
" </div>\n"
" <form id=\"addUserForm\">\n"
" <div class=\"modal-body\">\n"
" <div class=\"mb-3\">\n"
" <label for=\"addUsername\" class=\"form-label\">Username</label>\n"
" <input type=\"text\" class=\"form-control\" id=\"addUsername\" name=\"username\" required>\n"
" </div>\n"
" <div class=\"mb-3\">\n"
" <label for=\"addPassword\" class=\"form-label\">Password</label>\n"
" <input type=\"password\" class=\"form-control\" id=\"addPassword\" name=\"password\" required>\n"
" </div>\n"
" <div class=\"mb-3 form-check\">\n"
" <input type=\"checkbox\" class=\"form-check-input\" id=\"addIsAdmin\" name=\"is_admin\">\n"
" <label class=\"form-check-label\" for=\"addIsAdmin\">Administrator</label>\n"
" </div>\n"
" </div>\n"
" <div class=\"modal-footer\">\n"
" <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button>\n"
" <button type=\"submit\" class=\"btn btn-primary\">Add User</button>\n"
" </div>\n"
" </form>\n"
" </div>\n"
" </div>\n"
"</div>\n"
"\n"
"<!-- Edit User Modal -->\n"
"<div class=\"modal fade\" id=\"editUserModal\" tabindex=\"-1\">\n"
" <div class=\"modal-dialog\">\n"
" <div class=\"modal-content\">\n"
" <div class=\"modal-header\">\n"
" <h5 class=\"modal-title\">Edit User</h5>\n"
" <button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\"></button>\n"
" </div>\n"
" <form id=\"editUserForm\">\n"
" <input type=\"hidden\" id=\"editUserId\" name=\"user_id\">\n"
" <div class=\"modal-body\">\n"
" <div class=\"mb-3\">\n"
" <label for=\"editUsername\" class=\"form-label\">Username</label>\n"
" <input type=\"text\" class=\"form-control\" id=\"editUsername\" name=\"username\" required>\n"
" </div>\n"
" <div class=\"mb-3\">\n"
" <label for=\"editPassword\" class=\"form-label\">New Password (leave empty to keep current)</label>\n"
" <input type=\"password\" class=\"form-control\" id=\"editPassword\" name=\"password\">\n"
" </div>\n"
" <div class=\"mb-3 form-check\">\n"
" <input type=\"checkbox\" class=\"form-check-input\" id=\"editIsAdmin\" name=\"is_admin\">\n"
" <label class=\"form-check-label\" for=\"editIsAdmin\">Administrator</label>\n"
" </div>\n"
" </div>\n"
" <div class=\"modal-footer\">\n"
" <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button>\n"
" <button type=\"submit\" class=\"btn btn-primary\">Update User</button>\n"
" </div>\n"
" </form>\n"
" </div>\n"
" </div>\n"
"</div>\n"
"\n"
"<script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js\"></script>\n"
"<script>\n"
"function editUser(userId, username, isAdmin) {\n"
" document.getElementById('editUserId').value = userId;\n"
" document.getElementById('editUsername').value = username;\n"
" document.getElementById('editPassword').value = '';\n"
" document.getElementById('editIsAdmin').checked = isAdmin;\n"
" new bootstrap.Modal(document.getElementById('editUserModal')).show();\n"
"}\n"
"\n"
"function deleteUser(userId, username) {\n"
" if (confirm(`Are you sure you want to delete user \"${username}\"?`)) {\n"
" fetch(`/delete_user/${userId}`, {\n"
" method: 'POST',\n"
" headers: {'Content-Type': 'application/x-www-form-urlencoded'}\n"
" })\n"
" .then(response => response.json())\n"
" .then(data => {\n"
" if (data.success) location.reload();\n"
" else alert('Error: ' + data.error);\n"
" });\n"
" }\n"
"}\n"
"\n"
"document.getElementById('addUserForm').addEventListener('submit', function(e) {\n"
" e.preventDefault();\n"
" const formData = new FormData(this);\n"
" fetch('/add_user', {method: 'POST', body: formData})\n"
" .then(response => response.json())\n"
" .then(data => {\n"
" if (data.success) {\n"
" bootstrap.Modal.getInstance(document.getElementById('addUserModal')).hide();\n"
" location.reload();\n"
" } else alert('Error: ' + data.error);\n"
" });\n"
"});\n"
"\n"
"document.getElementById('editUserForm').addEventListener('submit', function(e) {\n"
" e.preventDefault();\n"
" const formData = new FormData(this);\n"
" const userId = document.getElementById('editUserId').value;\n"
" fetch(`/edit_user/${userId}`, {method: 'POST', body: formData})\n"
" .then(response => response.json())\n"
" .then(data => {\n"
" if (data.success) {\n"
" bootstrap.Modal.getInstance(document.getElementById('editUserModal')).hide();\n"
" location.reload();\n"
" } else alert('Error: ' + data.error);\n"
" });\n"
"});\n"
"</script>\n"
"</body>\n"
"</html>\n";
#endif /* USERS_PAGE_H */
/**
* vnc page HTML template for wssshd
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef VNC_PAGE_H
#define VNC_PAGE_H
// vnc page HTML template
static const char *vnc_page_html __attribute__((used)) =
"<!DOCTYPE html>\n"
"<html lang=\"en\">\n"
"<head>\n"
" <meta charset=\"UTF-8\">\n"
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n"
" <title>VNC - %s</title>\n"
" <link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\">\n"
" <link href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n"
" <link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" rel=\"stylesheet\">\n"
" <link rel=\"stylesheet\" href=\"/novnc.css\">\n"
" <script src=\"/novnc/util/strings.js\"></script>\n"
" <script src=\"/novnc/util/logging.js\"></script>\n"
" <script src=\"/novnc/util/browser.js\"></script>\n"
" <script src=\"/novnc/util/element.js\"></script>\n"
" <script src=\"/novnc/util/events.js\"></script>\n"
" <script src=\"/novnc/util/eventtarget.js\"></script>\n"
" <script src=\"/novnc/util/int.js\"></script>\n"
" <script src=\"/novnc/util/cursor.js\"></script>\n"
" <script src=\"/novnc/util/md5.js\"></script>\n"
" <script src=\"/novnc/websock.js\"></script>\n"
" <script src=\"/novnc/des.js\"></script>\n"
" <script src=\"/novnc/input/keysym.js\"></script>\n"
" <script src=\"/novnc/input/keysymdef.js\"></script>\n"
" <script src=\"/novnc/input/xtscancodes.js\"></script>\n"
" <script src=\"/novnc/input/util.js\"></script>\n"
" <script src=\"/novnc/input/keyboard.js\"></script>\n"
" <script src=\"/novnc/input/gesturehandler.js\"></script>\n"
" <script src=\"/novnc/display.js\"></script>\n"
" <script src=\"/novnc/inflator.js\"></script>\n"
" <script src=\"/novnc/deflator.js\"></script>\n"
" <script src=\"/novnc/encodings.js\"></script>\n"
" <script src=\"/novnc/ra2.js\"></script>\n"
" <script src=\"/novnc/decoders/raw.js\"></script>\n"
" <script src=\"/novnc/decoders/copyrect.js\"></script>\n"
" <script src=\"/novnc/decoders/rre.js\"></script>\n"
" <script src=\"/novnc/decoders/hextile.js\"></script>\n"
" <script src=\"/novnc/decoders/tight.js\"></script>\n"
" <script src=\"/novnc/decoders/tightpng.js\"></script>\n"
" <script src=\"/novnc/decoders/zrle.js\"></script>\n"
" <script src=\"/novnc/decoders/jpeg.js\"></script>\n"
" <script src=\"/novnc/vendor/pako/lib/zlib/adler32.js\"></script>\n"
" <script src=\"/novnc/vendor/pako/lib/zlib/constants.js\"></script>\n"
" <script src=\"/novnc/vendor/pako/lib/zlib/crc32.js\"></script>\n"
" <script src=\"/novnc/vendor/pako/lib/zlib/deflate.js\"></script>\n"
" <script src=\"/novnc/vendor/pako/lib/zlib/inffast.js\"></script>\n"
" <script src=\"/novnc/vendor/pako/lib/zlib/inflate.js\"></script>\n"
" <script src=\"/novnc/vendor/pako/lib/zlib/inftrees.js\"></script>\n"
" <script src=\"/novnc/vendor/pako/lib/zlib/messages.js\"></script>\n"
" <script src=\"/novnc/vendor/pako/lib/zlib/trees.js\"></script>\n"
" <script src=\"/novnc/vendor/pako/lib/zlib/zstream.js\"></script>\n"
" <script src=\"/novnc/rfb.js\"></script>\n"
" <style>\n"
" .navbar-brand {\n"
" font-weight: bold;\n"
" }\n"
" .vnc-container {\n"
" background-color: #1e1e1e;\n"
" color: #f8f8f2;\n"
" border-radius: 8px;\n"
" height: calc(100vh - 200px);\n"
" min-height: 400px;\n"
" overflow: hidden;\n"
" position: relative;\n"
" }\n"
" /* GNOME-like window decorations */\n"
" .vnc-window {\n"
" border: 1px solid #ccc;\n"
" border-radius: 8px;\n"
" overflow: hidden;\n"
" box-shadow: 0 2px 10px rgba(0,0,0,0.2);\n"
" }\n"
" .window-titlebar {\n"
" background: linear-gradient(to bottom, #f0f0f0, #e0e0e0);\n"
" border-bottom: 1px solid #ccc;\n"
" padding: 4px 8px;\n"
" display: flex;\n"
" justify-content: space-between;\n"
" align-items: center;\n"
" height: 32px;\n"
" }\n"
" .window-title {\n"
" font-weight: bold;\n"
" color: #333;\n"
" font-size: 14px;\n"
" }\n"
" .window-controls {\n"
" display: flex;\n"
" align-items: center;\n"
" gap: 4px;\n"
" }\n"
" .window-btn {\n"
" width: 24px;\n"
" height: 24px;\n"
" border: none;\n"
" border-radius: 50%;\n"
" display: flex;\n"
" align-items: center;\n"
" justify-content: center;\n"
" font-size: 14px;\n"
" font-weight: bold;\n"
" cursor: pointer;\n"
" transition: all 0.2s;\n"
" }\n"
" .window-btn:hover {\n"
" transform: scale(1.1);\n"
" }\n"
" .close-btn {\n"
" background: #ff605c;\n"
" color: white;\n"
" }\n"
" .close-btn:hover {\n"
" background: #ff403c;\n"
" }\n"
" .maximize-btn {\n"
" background: #28ca42;\n"
" color: white;\n"
" }\n"
" .maximize-btn:hover {\n"
" background: #24b83a;\n"
" }\n"
" .minimize-btn {\n"
" background: #ffbd44;\n"
" color: white;\n"
" display: none; /* Hidden by default, shown in fullscreen */\n"
" }\n"
" .minimize-btn:hover {\n"
" background: #ffad2c;\n"
" }\n"
" .window-content {\n"
" background: #1e1e1e;\n"
" padding: 8px;\n"
" }\n"
" /* Fullscreen styles */\n"
" .vnc-window.fullscreen {\n"
" position: fixed;\n"
" top: 0;\n"
" left: 0;\n"
" width: 100vw;\n"
" height: 100vh;\n"
" border: none;\n"
" border-radius: 0;\n"
" z-index: 9999;\n"
" }\n"
" .vnc-window.fullscreen .vnc-container {\n"
" height: calc(100vh - 40px) !important;\n"
" min-height: calc(100vh - 40px) !important;\n"
" }\n"
" .vnc-window.fullscreen .minimize-btn {\n"
" display: flex;\n"
" }\n"
" #noVNC_screen {\n"
" width: 100%;\n"
" height: 100%;\n"
" }\n"
" </style>\n"
"</head>\n"
"<body>\n"
"<nav class=\"navbar navbar-expand-lg navbar-dark bg-primary\">\n"
"<div class=\"container\">\n"
"<a class=\"navbar-brand\" href=\"/\">\n"
"<i class=\"fas fa-desktop\"></i> WSSSHD control panel</a>\n"
"<div class=\"navbar-nav ms-auto\">\n"
"<span class=\"navbar-text me-3\">VNC Session - %s</span>\n"
"<a class=\"nav-link\" href=\"/logout\">Logout</a>\n"
"</div></div></nav>\n"
"<div id=\"notification-area\" class=\"position-fixed top-0 end-0 p-3\" style=\"z-index: 1050;\"></div>\n"
"<div class=\"container mt-4\">\n"
"<div class=\"row\">\n"
" <div class=\"col-12\">\n"
" <div class=\"vnc-window\">\n"
" <div class=\"window-titlebar\">\n"
" <div class=\"window-title\">\n"
" <i class=\"fas fa-desktop\"></i> VNC Session - %s\n"
" </div>\n"
" <div class=\"window-controls\">\n"
" <button id=\"connectBtn\" class=\"btn btn-success btn-sm me-1\" style=\"height: 24px; font-size: 12px; padding: 0 8px;\">\n"
" <i class=\"fas fa-play\"></i> Connect\n"
" </button>\n"
" <button id=\"disconnectBtn\" class=\"btn btn-danger btn-sm me-1\" disabled style=\"height: 24px; font-size: 12px; padding: 0 8px;\">\n"
" <i class=\"fas fa-stop\"></i> Disconnect\n"
" </button>\n"
" <button class=\"window-btn minimize-btn\" title=\"Exit Fullscreen\">_</button>\n"
" <button class=\"window-btn maximize-btn\" title=\"Fullscreen\"><span class=\"maximize-icon\">□</span></button>\n"
" <button class=\"window-btn close-btn\" title=\"Disconnect\">×</button>\n"
" </div>\n"
" </div>\n"
" <div class=\"window-content\">\n"
" <div id=\"vnc\" class=\"vnc-container w-100\">\n"
" <div id=\"noVNC_screen\">\n"
" <div id=\"noVNC_status\">Click Connect to start VNC session</div>\n"
" </div>\n"
" </div>\n"
" </div>\n"
" </div>\n"
" </div>\n"
"</div>\n"
"</div>\n"
"\n"
"<script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js\"></script>\n"
"<script>\n"
"console.log('VNC page loaded');\n"
"\n"
"let rfb = null;\n"
"let connected = false;\n"
"\n"
"document.getElementById('connectBtn').addEventListener('click', connect);\n"
"document.getElementById('disconnectBtn').addEventListener('click', disconnect);\n"
"\n"
"// Window control buttons\n"
"document.querySelector('.close-btn').addEventListener('click', disconnect);\n"
"document.querySelector('.maximize-btn').addEventListener('click', toggleFullscreen);\n"
"document.querySelector('.minimize-btn').addEventListener('click', toggleFullscreen);\n"
"\n"
"function showNotification(message, type = 'info') {\n"
" const notificationArea = document.getElementById('notification-area');\n"
" const notification = document.createElement('div');\n"
" notification.className = `alert alert-${type} alert-dismissible fade show`;\n"
" notification.innerHTML = `${message}<button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"alert\"></button>`;\n"
" notificationArea.appendChild(notification);\n"
" setTimeout(() => {\n"
" if (notification.parentNode) {\n"
" notification.remove();\n"
" }\n"
" }, 5000);\n"
"}\n"
"\n"
"function toggleFullscreen() {\n"
" const vncWindow = document.querySelector('.vnc-window');\n"
" const maximizeBtn = document.querySelector('.maximize-btn');\n"
" const maximizeIcon = maximizeBtn.querySelector('.maximize-icon');\n"
"\n"
" if (vncWindow.classList.contains('fullscreen')) {\n"
" // Exit fullscreen\n"
" vncWindow.classList.remove('fullscreen');\n"
" maximizeIcon.textContent = '□';\n"
" maximizeBtn.title = 'Fullscreen';\n"
" } else {\n"
" // Enter fullscreen\n"
" vncWindow.classList.add('fullscreen');\n"
" maximizeIcon.textContent = '▭'; // Restore icon\n"
" maximizeBtn.title = 'Exit Fullscreen';\n"
" }\n"
"}\n"
"\n"
"function connect() {\n"
" console.log('Connect button clicked');\n"
" if (connected) return;\n"
"\n"
" const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\n"
" const wsUrl = wsProtocol + '//' + window.location.host + '/vnc/%s/ws';\n"
" console.log('Connecting VNC to:', wsUrl);\n"
"\n"
" try {\n"
" rfb = new RFB(document.getElementById('noVNC_screen'), wsUrl, {\n"
" credentials: {},\n"
" shared: true,\n"
" repeaterID: '',\n"
" wsProtocols: ['binary']\n"
" });\n"
"\n"
" rfb.addEventListener('connect', () => {\n"
" console.log('[VNC-DEBUG] VNC connected event fired');\n"
" console.log('[VNC-DEBUG] Connection state:', rfb._rfbConnectionState);\n"
" console.log('[VNC-DEBUG] Init state:', rfb._rfbInitState);\n"
" connected = true;\n"
" document.getElementById('connectBtn').disabled = true;\n"
" document.getElementById('disconnectBtn').disabled = false;\n"
" showNotification('VNC session connected', 'success');\n"
" });\n"
"\n"
" rfb.addEventListener('disconnect', () => {\n"
" console.log('[VNC-DEBUG] VNC disconnected event fired');\n"
" console.log('[VNC-DEBUG] Connection state:', rfb._rfbConnectionState);\n"
" console.log('[VNC-DEBUG] Init state:', rfb._rfbInitState);\n"
" connected = false;\n"
" document.getElementById('connectBtn').disabled = false;\n"
" document.getElementById('disconnectBtn').disabled = true;\n"
" showNotification('VNC session disconnected', 'info');\n"
" });\n"
"\n"
" rfb.addEventListener('credentialsrequired', (e) => {\n"
" console.log('VNC credentials required');\n"
" // For now, assume no credentials needed\n"
" });\n"
"\n"
" rfb.addEventListener('securityfailure', (e) => {\n"
" console.error('VNC security failure:', e.detail);\n"
" showNotification('VNC security failure: ' + e.detail.reason, 'danger');\n"
" });\n"
"\n"
" rfb.addEventListener('clipboard', (e) => {\n"
" console.log('VNC clipboard:', e.detail.text);\n"
" });\n"
"\n"
" } catch (e) {\n"
" console.error('Failed to create RFB:', e);\n"
" showNotification('Failed to connect VNC: ' + e.message, 'danger');\n"
" }\n"
"}\n"
"\n"
"function disconnect() {\n"
" if (rfb) {\n"
" rfb.disconnect();\n"
" rfb = null;\n"
" }\n"
" connected = false;\n"
" document.getElementById('connectBtn').disabled = false;\n"
" document.getElementById('disconnectBtn').disabled = true;\n"
" setTimeout(() => {\n"
" location.reload();\n"
" }, 3000);\n"
"}\n"
"</script>\n"
"</body>\n"
"</html>\n"
;
#endif /* VNC_PAGE_H */
/**
* xterm-addon-fit.js library for wssshd
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef XTERM_ADDON_PAGE_H
#define XTERM_ADDON_PAGE_H
// xterm-addon-fit.js library
const char *xterm_addon_fit_js =
"!function(e,t){\"object\"==typeof exports&&\"object\"==typeof module?module.exports=t():\"function\"==typeof define&&define.amd?define([],t):\"object\"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(self,(()=>(()=>{\"use strict\";var e={};return(()=>{var t=e;Object.defineProperty(t,\"__esModule\",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core,t=e._renderService.dimensions;if(0===t.css.cell.width||0===t.css.cell.height)return;const r=0===this._terminal.options.scrollback?0:e.viewport.scrollBarWidth,i=window.getComputedStyle(this._terminal.element.parentElement),o=parseInt(i.getPropertyValue(\"height\")),s=Math.max(0,parseInt(i.getPropertyValue(\"width\"))),n=window.getComputedStyle(this._terminal.element),l=o-(parseInt(n.getPropertyValue(\"padding-top\"))+parseInt(n.getPropertyValue(\"padding-bottom\"))),a=s-(parseInt(n.getPropertyValue(\"padding-right\"))+parseInt(n.getPropertyValue(\"padding-left\")))-r;return{cols:Math.max(2,Math.floor(a/t.css.cell.width)),rows:Math.max(1,Math.floor(l/t.css.cell.height))}}}})(),e})()));\n"
"//# sourceMappingURL=xterm-addon-fit.js.map\n";
#endif /* XTERM_ADDON_PAGE_H */
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -18,8 +18,8 @@
#include "html_pages/novnc_input_fixedkeys_js_page.h"
#include "html_pages/novnc_input_gesturehandler_js_page.h"
#include "html_pages/novnc_input_keyboard_js_page.h"
#include "html_pages/novnc_input_keysym_js_page.h"
#include "html_pages/novnc_input_keysymdef_js_page.h"
#include "html_pages/novnc_input_keysym_js_page.h"
#include "html_pages/novnc_input_util_js_page.h"
#include "html_pages/novnc_input_vkeys_js_page.h"
#include "html_pages/novnc_input_xtscancodes_js_page.h"
......
/**
* RDP and WebSocket proxy handling implementation for wssshd
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/wait.h>
#include <signal.h>
#include <uuid/uuid.h>
#include <pthread.h>
#include "rdp.h"
#include "config.h"
// Thread function to read pipe output
static void *read_pipe_output(void *arg) {
rdp_session_t *session = (rdp_session_t *)arg;
char buffer[1024];
fd_set readfds;
struct timeval tv;
while (1) {
FD_ZERO(&readfds);
FD_SET(session->read_fd, &readfds);
tv.tv_sec = 0;
tv.tv_usec = 100000; // 100ms timeout
int ret = select(session->read_fd + 1, &readfds, NULL, NULL, &tv);
if (ret < 0) break;
if (ret == 0) continue; // timeout
if (FD_ISSET(session->read_fd, &readfds)) {
ssize_t bytes_read = read(session->read_fd, buffer, sizeof(buffer));
if (bytes_read <= 0) break;
if (session->debug_rdp) {
printf("RDP: Received %zd bytes from pipe\n", bytes_read);
// Debug: print first 20 bytes in hex
printf("RDP received data (first 20 bytes): ");
for (ssize_t i = 0; i < bytes_read && i < 20; i++) {
printf("%02x ", (unsigned char)buffer[i]);
}
printf("\n");
}
// Append to session output buffer
pthread_mutex_lock(&session->output_mutex);
if (session->output_used + bytes_read > session->output_size) {
size_t new_size = session->output_size * 2;
while (new_size < session->output_used + bytes_read) {
new_size *= 2;
}
char *new_buf = realloc(session->output_buffer, new_size);
if (new_buf) {
session->output_buffer = new_buf;
session->output_size = new_size;
} else {
// Out of memory, skip this data
pthread_mutex_unlock(&session->output_mutex);
continue;
}
}
memcpy(session->output_buffer + session->output_used, buffer, bytes_read);
session->output_used += bytes_read;
pthread_mutex_unlock(&session->output_mutex);
}
}
return NULL;
}
rdp_session_t *rdp_create_session(const wssshd_config_t *config, const char *client_id, const char *username, bool debug_rdp) {
rdp_session_t *session = calloc(1, sizeof(rdp_session_t));
if (!session) return NULL;
// Generate UUID for request_id
uuid_t uuid;
uuid_generate(uuid);
uuid_unparse(uuid, session->request_id);
// Copy client_id and username
if (client_id) {
strncpy(session->client_id, client_id, sizeof(session->client_id) - 1);
}
if (username) {
strncpy(session->username, username, sizeof(session->username) - 1);
}
// Initialize output buffer
session->output_buffer = malloc(4096);
if (!session->output_buffer) {
free(session);
return NULL;
}
session->output_size = 4096;
session->output_used = 0;
session->debug_rdp = debug_rdp;
pthread_mutex_init(&session->output_mutex, NULL);
// Create pipes for bidirectional communication
int stdout_pipe[2]; // parent reads from child stdout
int stdin_pipe[2]; // parent writes to child stdin
if (pipe(stdout_pipe) < 0 || pipe(stdin_pipe) < 0) {
if (stdout_pipe[0] >= 0) close(stdout_pipe[0]);
if (stdout_pipe[1] >= 0) close(stdout_pipe[1]);
if (stdin_pipe[0] >= 0) close(stdin_pipe[0]);
if (stdin_pipe[1] >= 0) close(stdin_pipe[1]);
free(session->output_buffer);
free(session);
return NULL;
}
session->read_fd = stdout_pipe[0]; // parent reads from child stdout
session->write_fd = stdin_pipe[1]; // parent writes to child stdin
// Build command: wsssht --pipe --wssshd-port 9898 rdp://{client_id}@mbetter.nexlab.net
char command[1024];
snprintf(command, sizeof(command),
"wsssht --pipe --wssshd-port %d rdp://%s@%s",
config->port, client_id, config->domain);
// Fork and execute
pid_t pid = fork();
if (pid < 0) {
close(stdout_pipe[0]);
close(stdout_pipe[1]);
close(stdin_pipe[0]);
close(stdin_pipe[1]);
free(session->output_buffer);
free(session);
return NULL;
}
if (pid == 0) { // Child process
// Redirect stdout to stdout_pipe
dup2(stdout_pipe[1], 1);
// Redirect stdin from stdin_pipe
dup2(stdin_pipe[0], 0);
close(stdout_pipe[0]);
close(stdout_pipe[1]);
close(stdin_pipe[0]);
close(stdin_pipe[1]);
// Execute command
execl("/bin/sh", "sh", "-c", command, NULL);
_exit(1); // Should not reach here
}
// Parent process
close(stdout_pipe[1]); // close write end of stdout pipe
close(stdin_pipe[0]); // close read end of stdin pipe
session->proc_pid = pid;
// Start output reading thread
pthread_t thread;
pthread_create(&thread, NULL, read_pipe_output, session);
pthread_detach(thread);
return session;
}
void rdp_free_session(rdp_session_t *session) {
if (!session) return;
if (session->read_fd >= 0) {
close(session->read_fd);
session->read_fd = -1;
}
if (session->write_fd >= 0) {
close(session->write_fd);
session->write_fd = -1;
}
if (session->output_buffer) {
free(session->output_buffer);
session->output_buffer = NULL;
}
pthread_mutex_destroy(&session->output_mutex);
free(session);
}
bool rdp_send_data(rdp_session_t *session, const char *data, size_t len) {
if (!session || session->write_fd < 0) return false;
// Check if process is still running
if (waitpid(session->proc_pid, NULL, WNOHANG) > 0) {
return false;
}
if (session->debug_rdp) {
printf("RDP: Sending %zu bytes to pipe\n", len);
// Debug: print first 20 bytes in hex
printf("RDP send data (first 20 bytes): ");
for (size_t i = 0; i < len && i < 20; i++) {
printf("%02x ", (unsigned char)data[i]);
}
printf("\n");
}
ssize_t written = write(session->write_fd, data, len);
if (session->debug_rdp) {
printf("RDP: Wrote %zd bytes to pipe\n", written);
}
return written > 0;
}
char *rdp_get_output(rdp_session_t *session, size_t *len) {
if (!session) {
*len = 0;
return NULL;
}
pthread_mutex_lock(&session->output_mutex);
if (session->output_used == 0) {
// For WebSocket connections, don't wait - return immediately
pthread_mutex_unlock(&session->output_mutex);
*len = 0;
return NULL;
}
// Return the buffer and length
*len = session->output_used;
char *output = session->output_buffer;
session->output_buffer = malloc(4096);
if (session->output_buffer) {
session->output_size = 4096;
} else {
// Out of memory
*len = 0;
pthread_mutex_unlock(&session->output_mutex);
return NULL;
}
session->output_used = 0;
pthread_mutex_unlock(&session->output_mutex);
return output;
}
bool rdp_disconnect(rdp_session_t *session) {
if (!session) return false;
if (session->proc_pid > 0) {
kill(session->proc_pid, SIGTERM);
// Wait for graceful termination
for (int i = 0; i < 30; i++) { // Wait up to 3 seconds
if (waitpid(session->proc_pid, NULL, WNOHANG) > 0) {
break;
}
usleep(100000); // 100ms
}
// Force kill if still running
if (waitpid(session->proc_pid, NULL, WNOHANG) == 0) {
kill(session->proc_pid, SIGKILL);
waitpid(session->proc_pid, NULL, 0);
}
}
return true;
}
bool rdp_is_running(rdp_session_t *session) {
if (!session || session->proc_pid <= 0) return false;
return waitpid(session->proc_pid, NULL, WNOHANG) == 0;
}
\ No newline at end of file
/**
* noVNC CSS for wssshd
* RDP and WebSocket proxy handling implementation for wssshd
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
......@@ -17,11 +17,33 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef NOVNC_CSS_PAGE_H
#define NOVNC_CSS_PAGE_H
#ifndef RDP_H
#define RDP_H
// noVNC CSS
const char *novnc_css =
"Couldn't find the requested file /lib/rfb.css in @novnc/novnc.\n";
#include <stdbool.h>
#include <uuid/uuid.h>
#include "config.h"
#endif /* NOVNC_CSS_PAGE_H */
typedef struct {
char request_id[37];
char client_id[256];
char username[50];
int proc_pid;
int read_fd; // pipe read end
int write_fd; // pipe write end
bool active;
bool debug_rdp;
char *output_buffer;
size_t output_size;
size_t output_used;
pthread_mutex_t output_mutex;
} rdp_session_t;
rdp_session_t *rdp_create_session(const wssshd_config_t *config, const char *client_id, const char *username, bool debug_rdp);
void rdp_free_session(rdp_session_t *session);
bool rdp_send_data(rdp_session_t *session, const char *data, size_t len);
char *rdp_get_output(rdp_session_t *session, size_t *len);
bool rdp_disconnect(rdp_session_t *session);
bool rdp_is_running(rdp_session_t *session);
#endif /* RDP_H */
\ No newline at end of file
/*
* Copyright (c) 2015 Sylvain Peyrefitte
*
* This file is part of mstsc.js.
*
* mstsc.j 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 <http://www.gnu.org/licenses/>.
*/
(function() {
/**
* Use for domain declaration
*/
Mstsc = function () {
}
Mstsc.prototype = {
// shortcut
$ : function (id) {
return document.getElementById(id);
},
/**
* Compute screen offset for a target element
* @param el {DOM element}
* @return {top : {integer}, left {integer}}
*/
elementOffset : function (el) {
var x = 0;
var y = 0;
while (el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop )) {
x += el.offsetLeft - el.scrollLeft;
y += el.offsetTop - el.scrollTop;
el = el.offsetParent;
}
return { top: y, left: x };
},
/**
* Try to detect browser
* @returns {String} [firefox|chrome|ie]
*/
browser : function () {
if (typeof InstallTrigger !== 'undefined') {
return 'firefox';
}
if (!!window.chrome) {
return 'chrome';
}
if (!!document.docuemntMode) {
return 'ie';
}
return null;
},
/**
* Try to detect language
* @returns
*/
locale : function () {
return window.navigator.userLanguage || window.navigator.language;
}
}
})();
this.Mstsc = new Mstsc();
\ No newline at end of file
......@@ -986,6 +986,11 @@ class RFB extends EventTargetMixin {
return;
}
// Debug: log received data
const debugLen = Math.min(50, this._sock.rQlen);
const debugData = new Uint8Array(this._sock.rQ.buffer, this._sock.rQi, debugLen);
console.log("[VNC-CLIENT] Received data (first", debugLen, "bytes):", Array.from(debugData).map(b => b.toString(16).padStart(2, '0')).join(' '));
console.log("[VNC-CLIENT] Processing message in state:", this._rfbConnectionState);
switch (this._rfbConnectionState) {
......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RDP - %s</title>
<link rel="icon" href="/favicon.ico" type="image/x-icon">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<script src="/mstsc.js"></script>
<style>
.navbar-brand {
font-weight: bold;
}
.rdp-container {
background-color: #1e1e1e;
color: #f8f8f2;
border-radius: 8px;
height: calc(100vh - 200px);
min-height: 400px;
overflow: hidden;
position: relative;
}
.rdp-container.full-size {
overflow: auto;
max-height: none;
height: auto;
}
.rdp-container.full-size #mstsc_screen {
min-width: fit-content;
min-height: fit-content;
}
/* GNOME-like window decorations */
.rdp-window {
border: 1px solid #ccc;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
}
.window-titlebar {
background: linear-gradient(to bottom, #f0f0f0, #e0e0e0);
border-bottom: 1px solid #ccc;
padding: 4px 8px;
display: flex;
justify-content: space-between;
align-items: center;
height: 32px;
}
.window-title {
font-weight: bold;
color: #333;
font-size: 14px;
}
.window-controls {
display: flex;
align-items: center;
gap: 4px;
}
.window-btn {
width: 24px;
height: 24px;
border: none;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: bold;
cursor: pointer;
transition: all 0.2s;
}
.window-btn:hover {
transform: scale(1.1);
}
.close-btn {
background: #ff605c;
color: white;
}
.close-btn:hover {
background: #ff403c;
}
.maximize-btn {
background: #28ca42;
color: white;
}
.maximize-btn:hover {
background: #24b83a;
}
.minimize-btn {
background: #ffbd44;
color: white;
display: none; /* Hidden by default, shown in fullscreen */
}
.minimize-btn:hover {
background: #ffad2c;
}
.zoom-btn {
background: #17a2b8;
color: white;
}
.zoom-btn:hover {
background: #138496;
}
.zoom-btn.active {
background: #007bff;
}
.zoom-btn.active:hover {
background: #0056b3;
}
.window-content {
background: #1e1e1e;
padding: 8px;
}
/* Fullscreen styles */
.rdp-window.fullscreen {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
border: none;
border-radius: 0;
z-index: 9999;
}
.rdp-window.fullscreen .rdp-container {
height: calc(100vh - 40px) !important;
min-height: calc(100vh - 40px) !important;
}
.rdp-window.fullscreen .minimize-btn {
display: flex;
}
#mstsc_screen {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">
<i class="fas fa-desktop"></i> WSSSHD control panel</a>
<div class="navbar-nav ms-auto">
<span class="navbar-text me-3">RDP Session - %s</span>
<a class="nav-link" href="/logout">Logout</a>
</div></div></nav>
<div id="notification-area" class="position-fixed top-0 end-0 p-3" style="z-index: 1050;"></div>
<div class="container mt-4">
<div class="row">
<div class="col-12">
<div class="rdp-window">
<div class="window-titlebar">
<div class="window-title">
<i class="fas fa-desktop"></i> RDP Session - %s
</div>
<div class="window-controls">
<button id="connectBtn" class="btn btn-success btn-sm me-1" style="height: 24px; font-size: 12px; padding: 0 8px;">
<i class="fas fa-play"></i> Connect
</button>
<button id="disconnectBtn" class="btn btn-danger btn-sm me-1" disabled style="height: 24px; font-size: 12px; padding: 0 8px;">
<i class="fas fa-stop"></i> Disconnect
</button>
<button class="window-btn zoom-btn" title="Toggle Zoom (Scale/Fit)" id="zoomBtn">
<i class="fas fa-search-plus"></i>
</button>
<button class="window-btn minimize-btn" title="Exit Fullscreen">_</button>
<button class="window-btn maximize-btn" title="Fullscreen"><span class="maximize-icon"></span></button>
<button class="window-btn close-btn" title="Disconnect">×</button>
</div>
</div>
<div class="window-content">
<div id="rdp" class="rdp-container w-100">
<div id="mstsc_screen">
<div id="mstsc_status">Click Connect to start RDP session</div>
<div id="mstsc_loading" style="display: none; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; color: #f8f8f2;">
<div class="spinner-border text-primary mb-2" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<div>Connecting to RDP server...</div>
<button id="cancelConnectBtn" class="btn btn-outline-danger btn-sm mt-2" style="display: none;">
<i class="fas fa-times"></i> Cancel
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
let rdp = null;
let connected = false;
document.getElementById('connectBtn').addEventListener('click', connect);
document.getElementById('disconnectBtn').addEventListener('click', disconnect);
document.getElementById('cancelConnectBtn').addEventListener('click', cancelConnect);
// Window control buttons
document.querySelector('.close-btn').addEventListener('click', disconnect);
document.querySelector('.maximize-btn').addEventListener('click', toggleFullscreen);
document.querySelector('.minimize-btn').addEventListener('click', toggleFullscreen);
document.getElementById('zoomBtn').addEventListener('click', toggleZoom);
function showNotification(message, type = 'info') {
const notificationArea = document.getElementById('notification-area');
const notification = document.createElement('div');
notification.className = `alert alert-${type} alert-dismissible fade show`;
notification.innerHTML = `${message}<button type="button" class="btn-close" data-bs-dismiss="alert"></button>`;
notificationArea.appendChild(notification);
setTimeout(() => {
if (notification.parentNode) {
notification.remove();
}
}, 5000);
}
function toggleFullscreen() {
const rdpWindow = document.querySelector('.rdp-window');
const maximizeBtn = document.querySelector('.maximize-btn');
const maximizeIcon = maximizeBtn.querySelector('.maximize-icon');
if (rdpWindow.classList.contains('fullscreen')) {
// Exit fullscreen
rdpWindow.classList.remove('fullscreen');
maximizeIcon.textContent = '□';
maximizeBtn.title = 'Fullscreen';
} else {
// Enter fullscreen
rdpWindow.classList.add('fullscreen');
maximizeIcon.textContent = '▭'; // Restore icon
maximizeBtn.title = 'Exit Fullscreen';
}
}
function toggleZoom() {
if (!rdp || !connected) return;
const zoomBtn = document.getElementById('zoomBtn');
const zoomIcon = zoomBtn.querySelector('i');
const rdpContainer = document.getElementById('rdp');
if (zoomBtn.classList.contains('active')) {
// Switch to scaled view (fit to window)
zoomBtn.classList.remove('active');
zoomBtn.title = 'Toggle Zoom (Scale/Fit)';
zoomIcon.className = 'fas fa-search-plus';
rdpContainer.classList.remove('full-size');
showNotification('Switched to scaled view (fit to window)', 'info');
} else {
// Switch to full-size view with scrollbars
zoomBtn.classList.add('active');
zoomBtn.title = 'Toggle Zoom (Actual Size)';
zoomIcon.className = 'fas fa-search-minus';
rdpContainer.classList.add('full-size');
showNotification('Switched to actual size view with scrollbars', 'info');
}
}
function connect() {
if (connected) return;
// Hide the status text and show loading message
document.getElementById('mstsc_status').style.display = 'none';
document.getElementById('mstsc_loading').style.display = 'block';
document.getElementById('cancelConnectBtn').style.display = 'inline-block';
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = wsProtocol + '//' + window.location.host + '/rdp/%s/ws';
try {
rdp = new Mstsc(document.getElementById('mstsc_screen'), {
url: wsUrl,
width: 1024,
height: 768,
domain: '',
username: '',
password: '',
keyboard: true,
mouse: true,
clipboard: true
});
rdp.addEventListener('connect', () => {
connected = true;
document.getElementById('connectBtn').disabled = true;
document.getElementById('disconnectBtn').disabled = false;
// Hide loading message
document.getElementById('mstsc_loading').style.display = 'none';
showNotification('RDP session connected', 'success');
});
rdp.addEventListener('disconnect', () => {
connected = false;
document.getElementById('connectBtn').disabled = false;
document.getElementById('disconnectBtn').disabled = true;
// Reset UI to initial state
document.getElementById('mstsc_status').style.display = 'block';
document.getElementById('mstsc_loading').style.display = 'none';
document.getElementById('cancelConnectBtn').style.display = 'none';
showNotification('RDP session disconnected', 'info');
});
rdp.addEventListener('error', (e) => {
console.error('RDP error:', e.detail);
showNotification('RDP error: ' + e.detail, 'danger');
});
} catch (e) {
console.error('Failed to create RDP client:', e);
showNotification('Failed to connect RDP: ' + e.message, 'danger');
}
}
function disconnect() {
if (rdp) {
rdp.disconnect();
rdp = null;
}
connected = false;
document.getElementById('connectBtn').disabled = false;
document.getElementById('disconnectBtn').disabled = true;
// Reset UI to initial state
document.getElementById('mstsc_status').style.display = 'block';
document.getElementById('mstsc_loading').style.display = 'none';
document.getElementById('cancelConnectBtn').style.display = 'none';
setTimeout(() => {
location.reload();
}, 3000);
}
function cancelConnect() {
if (rdp) {
rdp.disconnect();
rdp = null;
}
connected = false;
document.getElementById('connectBtn').disabled = false;
document.getElementById('disconnectBtn').disabled = true;
// Reset UI to initial state
document.getElementById('mstsc_status').style.display = 'block';
document.getElementById('mstsc_loading').style.display = 'none';
document.getElementById('cancelConnectBtn').style.display = 'none';
showNotification('RDP connection cancelled', 'warning');
}
</script>
</body>
</html>
\ No newline at end of file
......@@ -66,12 +66,13 @@
}
.vnc-container.full-size {
overflow: auto;
max-height: none;
max-height: calc(100vh - 200px);
height: auto;
}
.vnc-container.full-size #noVNC_screen {
min-width: fit-content;
min-height: fit-content;
height: auto;
}
/* GNOME-like window decorations */
.vnc-window {
......
[wssshd]
host = 0.0.0.0
port = 9898
password = mbetter4ntan1
domain = mbetter.nexlab.net
web-host = 0.0.0.0
web-port = 9899
web-https = false
[wssshd]
host = 127.0.0.1
port = 19998
password = test123
domain = test.local
......@@ -38,7 +38,7 @@ static void *read_pipe_output(void *arg) {
fd_set readfds;
struct timeval tv;
while (1) {
while (!session->thread_stop) {
FD_ZERO(&readfds);
FD_SET(session->read_fd, &readfds);
......@@ -115,6 +115,7 @@ vnc_session_t *vnc_create_session(const wssshd_config_t *config, const char *cli
session->output_size = 4096;
session->output_used = 0;
session->debug_vnc = debug_vnc;
session->thread_stop = 0;
pthread_mutex_init(&session->output_mutex, NULL);
// Create pipes for bidirectional communication
......@@ -183,6 +184,9 @@ vnc_session_t *vnc_create_session(const wssshd_config_t *config, const char *cli
void vnc_free_session(vnc_session_t *session) {
if (!session) return;
// Signal thread to stop
session->thread_stop = 1;
if (session->read_fd >= 0) {
close(session->read_fd);
session->read_fd = -1;
......@@ -192,6 +196,9 @@ void vnc_free_session(vnc_session_t *session) {
session->write_fd = -1;
}
// Wait a bit for thread to exit
usleep(200000); // 200ms
if (session->output_buffer) {
free(session->output_buffer);
session->output_buffer = NULL;
......
......@@ -33,6 +33,7 @@ typedef struct {
int write_fd; // pipe write end
bool active;
bool debug_vnc;
volatile int thread_stop;
char *output_buffer;
size_t output_size;
size_t output_used;
......
......@@ -34,6 +34,7 @@
#include "web.h"
#include "terminal.h"
#include "vnc.h"
#include "rdp.h"
#include "assets.h"
#include "websocket.h"
#include "websocket_protocol.h"
......@@ -41,6 +42,7 @@
#include "html_pages/login_page.h"
#include "html_pages/terminal_page.h"
#include "html_pages/vnc_page.h"
#include "html_pages/rdp_page.h"
#include "html_pages/users_page.h"
// Embedded web assets are defined in assets.c
......@@ -85,6 +87,12 @@ static vnc_session_t *active_vncs[MAX_ACTIVE_VNCS];
static int active_vncs_count = 0;
static pthread_mutex_t vncs_mutex = PTHREAD_MUTEX_INITIALIZER;
// Active RDP sessions
#define MAX_ACTIVE_RDPS 100
static rdp_session_t *active_rdps[MAX_ACTIVE_RDPS];
static int active_rdps_count = 0;
static pthread_mutex_t rdps_mutex = PTHREAD_MUTEX_INITIALIZER;
// WebSocket connections for terminals
#define MAX_WEBSOCKET_CONNECTIONS 100
typedef struct {
......@@ -115,9 +123,26 @@ static websocket_vnc_conn_t websocket_vnc_connections[MAX_VNC_WEBSOCKET_CONNECTI
static int websocket_vnc_connections_count = 0;
static pthread_mutex_t websocket_vnc_mutex = PTHREAD_MUTEX_INITIALIZER;
// WebSocket connections for RDP
#define MAX_RDP_WEBSOCKET_CONNECTIONS 100
typedef struct {
ws_connection_t *ws_conn;
char request_id[37];
char client_id[256];
char username[50];
bool active;
pthread_t thread;
} websocket_rdp_conn_t;
static websocket_rdp_conn_t websocket_rdp_connections[MAX_RDP_WEBSOCKET_CONNECTIONS];
static int websocket_rdp_connections_count = 0;
static pthread_mutex_t websocket_rdp_mutex = PTHREAD_MUTEX_INITIALIZER;
// JSON response for ended session
static const char ended_json[] = "{\"ended\":true}";
// Base64 encoding for WebSocket text frames is declared in websocket_protocol.h
// WebSocket terminal connection management
static websocket_terminal_conn_t *add_websocket_connection(ws_connection_t *ws_conn, const char *request_id, const char *client_id, const char *username) {
pthread_mutex_lock(&websocket_mutex);
......@@ -226,6 +251,59 @@ static void remove_websocket_vnc_connection(const char *request_id) {
pthread_mutex_unlock(&websocket_vnc_mutex);
}
// WebSocket RDP connection management
static websocket_rdp_conn_t *add_websocket_rdp_connection(ws_connection_t *ws_conn, const char *request_id, const char *client_id, const char *username) {
pthread_mutex_lock(&websocket_rdp_mutex);
if (websocket_rdp_connections_count >= MAX_RDP_WEBSOCKET_CONNECTIONS) {
pthread_mutex_unlock(&websocket_rdp_mutex);
return NULL;
}
websocket_rdp_conn_t *conn = &websocket_rdp_connections[websocket_rdp_connections_count++];
conn->ws_conn = ws_conn;
if (request_id) {
strncpy(conn->request_id, request_id, sizeof(conn->request_id) - 1);
conn->request_id[sizeof(conn->request_id) - 1] = '\0';
} else {
conn->request_id[0] = '\0';
}
if (client_id) {
size_t len = strlen(client_id);
if (len >= sizeof(conn->client_id)) {
len = sizeof(conn->client_id) - 1;
}
memcpy(conn->client_id, client_id, len);
conn->client_id[len] = '\0';
} else {
conn->client_id[0] = '\0';
}
if (username) {
strncpy(conn->username, username, sizeof(conn->username) - 1);
conn->username[sizeof(conn->username) - 1] = '\0';
} else {
conn->username[0] = '\0';
}
conn->active = true;
pthread_mutex_unlock(&websocket_rdp_mutex);
return conn;
}
static void remove_websocket_rdp_connection(const char *request_id) {
pthread_mutex_lock(&websocket_rdp_mutex);
for (int i = 0; i < websocket_rdp_connections_count; i++) {
if (strcmp(websocket_rdp_connections[i].request_id, request_id) == 0) {
websocket_rdp_connections[i].active = false;
// Shift remaining connections
memmove(&websocket_rdp_connections[i], &websocket_rdp_connections[i + 1],
sizeof(websocket_rdp_conn_t) * (websocket_rdp_connections_count - i - 1));
websocket_rdp_connections_count--;
break;
}
}
pthread_mutex_unlock(&websocket_rdp_mutex);
}
// WebSocket terminal handler thread
static void *websocket_terminal_handler(void *arg) {
websocket_terminal_conn_t *ws_conn = (websocket_terminal_conn_t *)arg;
......@@ -432,9 +510,10 @@ static void *websocket_vnc_handler(void *arg) {
// Send VNC data as a single WebSocket frame - don't chunk it
// VNC is a streaming protocol and chunking breaks the decoder
if (!ws_send_binary_frame(ws_conn->ws_conn, output, output_len, global_config->debug_vnc)) {
printf("Failed to send VNC data frame (temporary SSL issue?), continuing...\n");
// Don't abort - SSL buffer issues are often temporary
// The data will be sent in the next iteration when buffer clears
printf("Failed to send VNC data frame, disconnecting...\n");
free(output);
remove_websocket_vnc_connection(ws_conn->request_id);
return NULL;
}
free(output);
}
......@@ -471,11 +550,183 @@ static void *websocket_vnc_handler(void *arg) {
}
}
// Clean up the VNC session
pthread_mutex_lock(&vncs_mutex);
for (int i = 0; i < active_vncs_count; i++) {
if (strcmp(active_vncs[i]->request_id, ws_conn->request_id) == 0) {
vnc_disconnect(active_vncs[i]);
vnc_free_session(active_vncs[i]);
memmove(&active_vncs[i], &active_vncs[i + 1], sizeof(vnc_session_t *) * (active_vncs_count - i - 1));
active_vncs_count--;
break;
}
}
pthread_mutex_unlock(&vncs_mutex);
printf("WebSocket VNC handler ended for request_id: %s\n", ws_conn->request_id);
remove_websocket_vnc_connection(ws_conn->request_id);
return NULL;
}
// WebSocket RDP handler thread
static void *websocket_rdp_handler(void *arg) {
websocket_rdp_conn_t *ws_conn = (websocket_rdp_conn_t *)arg;
printf("WebSocket RDP handler started for client: %s\n", ws_conn->client_id);
// Launch wsssht for RDP immediately when WebSocket opens
if (global_config && global_config->debug_rdp) {
printf("[DEBUG] Launching wsssht for RDP client %s\n", ws_conn->client_id);
}
rdp_session_t *session = rdp_create_session(global_config, ws_conn->client_id, ws_conn->username, global_config->debug_rdp);
if (!session) {
printf("Failed to create RDP session for client: %s\n", ws_conn->client_id);
ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, "{\"error\":\"Failed to launch RDP session\"}", 40);
remove_websocket_rdp_connection(ws_conn->request_id);
return NULL;
}
// Update the WebSocket connection with the request_id
size_t len = strlen(session->request_id);
if (len >= sizeof(ws_conn->request_id)) {
len = sizeof(ws_conn->request_id) - 1;
}
memcpy(ws_conn->request_id, session->request_id, len);
ws_conn->request_id[len] = '\0';
// Add session to active list
pthread_mutex_lock(&rdps_mutex);
if (active_rdps_count < MAX_ACTIVE_RDPS) {
active_rdps[active_rdps_count++] = session;
printf("WebSocket RDP handler running for request_id: %s\n", ws_conn->request_id);
} else {
rdp_free_session(session);
printf("Too many active RDP sessions, connection rejected for client: %s\n", ws_conn->client_id);
ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, "{\"error\":\"Too many active RDP sessions\"}", 38);
remove_websocket_rdp_connection(ws_conn->request_id);
pthread_mutex_unlock(&rdps_mutex);
return NULL;
}
pthread_mutex_unlock(&rdps_mutex);
// Wait for RDP connection to be established and RDP handshake to complete
sleep(1);
// Add a delay to allow the RDP client to complete the handshake before sending RDP data
sleep(2);
// Wait for the RDP connection to establish (up to 20 seconds)
int wait_count = 0;
const int max_wait = 400; // 20 seconds at 50ms intervals
bool connection_ready = false;
while (ws_conn->active && ws_conn->ws_conn->state == WS_STATE_OPEN && wait_count < max_wait && !connection_ready) {
// Check if RDP session is still active
if (!rdp_is_running(session)) {
printf("WebSocket RDP session ended during connection establishment for request_id: %s\n", ws_conn->request_id);
ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, "{\"error\":\"RDP session failed to start\"}", 39);
break;
}
// Check for initial output from RDP server
size_t output_len = 0;
char *output = rdp_get_output(session, &output_len);
if (output && output_len > 0) {
printf("WebSocket RDP connection established, received %zu bytes of initial data for request_id: %s\n", output_len, ws_conn->request_id);
if (global_config->debug_rdp) {
printf("RDP initial data (first 20 bytes): ");
for (size_t i = 0; i < output_len && i < 20; i++) {
printf("%02x ", (unsigned char)output[i]);
}
printf("\n");
}
// Send initial data to WebSocket
char *b64 = base64_encode((unsigned char *)output, output_len);
if (b64) {
ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, b64, strlen(b64));
free(b64);
}
free(output);
connection_ready = true;
break;
} else if (output) {
free(output);
}
usleep(50000); // 50ms delay
wait_count++;
}
if (!connection_ready) {
printf("WebSocket RDP connection timed out waiting for establishment for request_id: %s\n", ws_conn->request_id);
ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, "{\"error\":\"RDP connection timeout\"}", 33);
remove_websocket_rdp_connection(ws_conn->request_id);
return NULL;
}
while (ws_conn->active && ws_conn->ws_conn->state == WS_STATE_OPEN) {
// Check if RDP session is still active
if (!rdp_is_running(session)) {
// RDP session ended
printf("WebSocket RDP session ended for request_id: %s\n", ws_conn->request_id);
ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, ended_json, sizeof(ended_json) - 1);
break;
}
// Send any available output immediately
size_t output_len = 0;
char *output = rdp_get_output(session, &output_len);
if (output && output_len > 0) {
printf("WebSocket sending %zu bytes of RDP output for request_id: %s\n", output_len, ws_conn->request_id);
if (global_config->debug_rdp) {
printf("RDP output data (first 20 bytes): ");
for (size_t i = 0; i < output_len && i < 20; i++) {
printf("%02x ", (unsigned char)output[i]);
}
printf("\n");
}
char *b64 = base64_encode((unsigned char *)output, output_len);
if (b64) {
ws_send_frame(ws_conn->ws_conn, WS_OPCODE_TEXT, b64, strlen(b64));
free(b64);
}
free(output);
}
// Check for input from WebSocket (non-blocking)
uint8_t opcode = 0;
void *data = NULL;
size_t len = 0;
if (ws_receive_frame(ws_conn->ws_conn, &opcode, &data, &len)) {
if (opcode == WS_OPCODE_BINARY && len > 0) {
// RDP input data - this is raw binary data from mstsc.js
printf("WebSocket received %zu bytes of RDP input for request_id: %s\n", len, ws_conn->request_id);
if (global_config->debug_rdp) {
printf("RDP input data (first 20 bytes): ");
for (size_t i = 0; i < len && i < 20; i++) {
printf("%02x ", (unsigned char)((const char *)data)[i]);
}
printf("\n");
}
rdp_send_data(session, (const char *)data, len);
} else if (opcode == WS_OPCODE_CLOSE) {
// Connection closed
printf("WebSocket RDP connection closed for request_id: %s\n", ws_conn->request_id);
break;
}
if (data) free(data);
} else {
// No data available, small delay
usleep(10000); // 10ms
}
}
printf("WebSocket RDP handler ended for request_id: %s\n", ws_conn->request_id);
remove_websocket_rdp_connection(ws_conn->request_id);
return NULL;
}
// Simple hash function for passwords (not secure, but for demo)
static void simple_hash(const char *input, char *output, size_t output_size) {
unsigned long hash = 5381;
......@@ -1299,7 +1550,10 @@ static char *generate_index_html(const char *username, int is_admin, const char
strcat(actions_html, "\" class=\"btn btn-info btn-sm me-1\">"
"<i class=\"fas fa-desktop\"></i> VNC</a>");
} else if (strcmp(service, "rdp") == 0) {
// Future: RDP support
strcat(actions_html, "<a href=\"/rdp/");
strcat(actions_html, global_state->clients[i].client_id);
strcat(actions_html, "\" class=\"btn btn-primary btn-sm me-1\">"
"<i class=\"fas fa-windows\"></i> RDP</a>");
}
if (strlen(services_html) > 0) strcat(services_html, ", ");
......@@ -1437,21 +1691,26 @@ static int handle_request(int client_fd, const http_request_t *req) {
}
// Route handling
// Handle WebSocket terminal and VNC connections first
// Handle WebSocket terminal, VNC, and RDP connections first
char *headers_copy = NULL;
printf("[DEBUG] Checking path: %s\n", req->path);
if ((strncmp(req->path, "/terminal/", 9) == 0 && strstr(req->path, "/ws")) ||
(strncmp(req->path, "/vnc/", 5) == 0 && strstr(req->path, "/ws"))) {
(strncmp(req->path, "/vnc/", 5) == 0 && strstr(req->path, "/ws")) ||
(strncmp(req->path, "/rdp/", 5) == 0 && strstr(req->path, "/ws"))) {
printf("[WEBSOCKET] WebSocket upgrade request detected for path: %s\n", req->path);
// WebSocket terminal or VNC connection
// WebSocket terminal, VNC, or RDP connection
char path_copy[1024];
strcpy(path_copy, req->path);
char *client_id;
bool is_vnc = false;
bool is_rdp = false;
if (strncmp(req->path, "/vnc/", 5) == 0) {
client_id = path_copy + 5;
is_vnc = true;
} else if (strncmp(req->path, "/rdp/", 5) == 0) {
client_id = path_copy + 5;
is_rdp = true;
} else {
client_id = path_copy + 9;
}
......@@ -1512,7 +1771,7 @@ static int handle_request(int client_fd, const http_request_t *req) {
}
if (is_valid_upgrade) {
// Web interface WebSockets can use non-TLS for noVNC connections
// Web interface WebSockets can use non-TLS for noVNC/mstsc.js connections
// Set socket to non-blocking mode for WebSocket
int flags = fcntl(client_fd, F_GETFL, 0);
......@@ -1540,7 +1799,7 @@ static int handle_request(int client_fd, const http_request_t *req) {
// Send WebSocket handshake response
char response[512];
int response_len;
if (is_vnc) {
if (is_vnc || is_rdp) {
response_len = snprintf(response, sizeof(response),
"HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade: websocket\r\n"
......@@ -1588,6 +1847,24 @@ static int handle_request(int client_fd, const http_request_t *req) {
}
pthread_detach(ws_vnc_conn->thread);
} else if (is_rdp) {
// Create WebSocket RDP connection
websocket_rdp_conn_t *ws_rdp_conn = add_websocket_rdp_connection(ws_conn, "", client_id, username);
if (!ws_rdp_conn) {
ws_connection_free(ws_conn);
send_response(client_fd, 500, "Internal Server Error", "text/plain", "Too many WebSocket connections", 30, NULL, NULL);
return 0;
}
// Start WebSocket RDP handler thread
if (pthread_create(&ws_rdp_conn->thread, NULL, websocket_rdp_handler, ws_rdp_conn) != 0) {
remove_websocket_rdp_connection("");
ws_connection_free(ws_conn);
send_response(client_fd, 500, "Internal Server Error", "text/plain", "Failed to start WebSocket handler", 32, NULL, NULL);
return 0;
}
pthread_detach(ws_rdp_conn->thread);
} else {
// Create WebSocket terminal connection
websocket_terminal_conn_t *ws_term_conn = add_websocket_connection(ws_conn, "", client_id, username);
......@@ -2099,6 +2376,137 @@ static int handle_request(int client_fd, const http_request_t *req) {
return 0;
}
// Handle RDP routes (both GET and POST)
if (strncmp(req->path, "/rdp/", 5) == 0) {
// Extract client_id and action from path
char path_copy[1024];
strcpy(path_copy, req->path);
char *client_id = path_copy + 5;
// Skip any leading slashes
while (*client_id == '/') client_id++;
char *action = strchr(client_id, '/');
if (action) {
*action = '\0';
action++;
}
if (global_config && global_config->debug_web && (!action || strcmp(action, "data") != 0)) {
printf("[WEB-DEBUG] RDP request: path=%s, client_id=%s, action=%s\n", req->path, client_id, action ? action : "(null)");
}
if (!username) {
send_response(client_fd, 302, "Found", "text/html", NULL, 0, NULL, "Location: /");
return 0;
}
if (!action || *action == '\0') {
// GET /rdp/{client_id} - show RDP page
// Check if client exists and has RDP service
int client_has_rdp = 0;
int client_found = 0;
for (size_t i = 0; i < global_state->clients_count; i++) {
if (strcmp(global_state->clients[i].client_id, client_id) == 0) {
client_found = 1;
if (global_config && global_config->debug_web) {
printf("[WEB-DEBUG] Found client %s, active=%d, services='%s'\n", client_id, global_state->clients[i].active, global_state->clients[i].services);
}
// Check if RDP is in services
char services_copy[256];
strcpy(services_copy, global_state->clients[i].services);
char *service = strtok(services_copy, ",");
while (service) {
while (*service == ' ') service++;
char *end = service + strlen(service) - 1;
while (end > service && *end == ' ') *end-- = '\0';
if (strcmp(service, "rdp") == 0) {
client_has_rdp = 1;
break;
}
service = strtok(NULL, ",");
}
break;
}
}
if (global_config && global_config->debug_web) {
printf("[WEB-DEBUG] Client %s found=%d, has_rdp=%d\n", client_id, client_found, client_has_rdp);
}
if (!client_has_rdp) {
// Redirect to dashboard with error notification
char location_header[2048];
snprintf(location_header, sizeof(location_header), "Location: /?error=RDP service not available for client %s", client_id);
send_response(client_fd, 302, "Found", "text/html", NULL, 0, NULL, location_header);
return 0;
}
// Generate RDP HTML with client_id
char html[32768];
int len = snprintf(html, sizeof(html), rdp_page_html,
client_id, client_id, client_id, client_id, client_id);
send_response(client_fd, 200, "OK", "text/html", html, len, NULL, NULL);
} else {
// Handle RDP actions (connect, disconnect)
// Check if client exists
int client_exists = 0;
for (size_t i = 0; i < global_state->clients_count; i++) {
if (global_state->clients[i].active &&
strcmp(global_state->clients[i].client_id, client_id) == 0) {
client_exists = 1;
break;
}
}
if (global_config && global_config->debug_web && strcmp(action, "data") != 0) {
printf("[WEB-DEBUG] RDP action '%s' for client %s, client_exists=%d\n", action, client_id, client_exists);
}
if (!client_exists) {
send_response(client_fd, 404, "Not Found", "application/json", "{\"error\":\"Client not connected\"}", 31, NULL, NULL);
return 0;
}
if (strcmp(req->method, "POST") == 0) {
if (strcmp(action, "disconnect") == 0) {
char request_id[37] = "";
// Parse form data
char temp_body[4096];
strcpy(temp_body, req->body);
char *pair = strtok(temp_body, "&");
while (pair) {
char *eq = strchr(pair, '=');
if (eq) {
*eq = '\0';
char *key = pair;
char *value = eq + 1;
url_decode(value);
if (strcmp(key, "request_id") == 0) {
strncpy(request_id, value, sizeof(request_id) - 1);
}
}
pair = strtok(NULL, "&");
}
pthread_mutex_lock(&rdps_mutex);
for (int i = 0; i < active_rdps_count; i++) {
if (strcmp(active_rdps[i]->request_id, request_id) == 0) {
rdp_disconnect(active_rdps[i]);
rdp_free_session(active_rdps[i]);
memmove(&active_rdps[i], &active_rdps[i + 1],
sizeof(rdp_session_t *) * (active_rdps_count - i - 1));
active_rdps_count--;
break;
}
}
pthread_mutex_unlock(&rdps_mutex);
send_response(client_fd, 200, "OK", "text/plain", "OK", 2, NULL, NULL);
} else {
send_response(client_fd, 404, "Not Found", "text/plain", "Not found", 9, NULL, NULL);
}
} else {
send_response(client_fd, 405, "Method Not Allowed", "text/plain", "Method not allowed", 18, NULL, NULL);
}
}
return 0;
}
if (strcmp(req->method, "GET") == 0) {
if (strcmp(req->path, "/") == 0 || strcmp(req->path, "/index.html") == 0) {
if (!username) {
......
......@@ -1491,7 +1491,11 @@ void handle_tunnel_close(SSL *ssl __attribute__((unused)), const char *request_i
tunnel_t *tunnel = find_tunnel_by_request_id(request_id);
if (tunnel) {
tunnel->active = 0;
int was_active = (active_tunnel == tunnel);
remove_tunnel(request_id);
if (was_active) {
active_tunnel = NULL;
}
if (debug) {
printf("[DEBUG - Tunnel] Tunnel %s closed\n", request_id);
fflush(stdout);
......
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